This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new f5d9a3a02 FINERACT-1981: Update Payable Interest Calculation For
LoanSummaryData
f5d9a3a02 is described below
commit f5d9a3a025628eb8b3654a4676fc212a90629fb4
Author: Soma Sörös <[email protected]>
AuthorDate: Wed Nov 20 17:18:46 2024 +0100
FINERACT-1981: Update Payable Interest Calculation For LoanSummaryData
---
.../loanaccount/data/LoanSummaryData.java | 174 --------------------
.../loan/LoanBusinessEventSerializer.java | 15 +-
.../api/InternalLoanInformationApiResource.java | 21 ---
.../loanaccount/api/LoansApiResource.java | 13 +-
.../service/CommonLoanSummaryDataProvider.java | 173 ++++++++++++++++++++
.../service/CumulativeLoanSummaryDataProvider.java | 90 +++++++++++
.../service/LoanSummaryDataProvider.java | 47 ++++++
.../service/LoanSummaryProviderDelegate.java | 35 ++++
.../ProgressiveLoanSummaryDataProvider.java | 110 +++++++++++++
...PaymentAllocationLoanRepaymentScheduleTest.java | 22 +++
.../integrationtests/LoanPrepayAmountTest.java | 61 +++++++
.../LoanRefundTransactionTest.java | 177 ---------------------
12 files changed, 558 insertions(+), 380 deletions(-)
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 de2f68cb0..b1e147a5d 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
@@ -20,19 +20,10 @@ package org.apache.fineract.portfolio.loanaccount.data;
import java.math.BigDecimal;
import java.time.LocalDate;
-import java.util.Collection;
-import java.util.Optional;
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.organisation.monetary.domain.Money;
-import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
-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;
/**
* Immutable data object representing loan summary information.
@@ -105,169 +96,4 @@ public class LoanSummaryData {
private BigDecimal totalUnpaidPayableDueInterest;
private BigDecimal totalUnpaidPayableNotDueInterest;
- public static LoanSummaryData withTransactionAmountsSummary(final
LoanSummaryData defaultSummaryData,
- final LoanScheduleData repaymentSchedule, final
Collection<LoanTransactionBalance> loanTransactionBalances) {
- final LocalDate businessDate = DateUtils.getBusinessLocalDate();
-
- BigDecimal totalMerchantRefund = BigDecimal.ZERO;
- BigDecimal totalMerchantRefundReversed = BigDecimal.ZERO;
- BigDecimal totalPayoutRefund = BigDecimal.ZERO;
- BigDecimal totalPayoutRefundReversed = BigDecimal.ZERO;
- BigDecimal totalGoodwillCredit = BigDecimal.ZERO;
- BigDecimal totalGoodwillCreditReversed = BigDecimal.ZERO;
- BigDecimal totalChargeAdjustment = BigDecimal.ZERO;
- BigDecimal totalChargeAdjustmentReversed = BigDecimal.ZERO;
- BigDecimal totalChargeback = BigDecimal.ZERO;
- BigDecimal totalCreditBalanceRefund = BigDecimal.ZERO;
- BigDecimal totalCreditBalanceRefundReversed = BigDecimal.ZERO;
- BigDecimal totalRepaymentTransaction = BigDecimal.ZERO;
- BigDecimal totalRepaymentTransactionReversed = BigDecimal.ZERO;
- BigDecimal totalInterestPaymentWaiver = BigDecimal.ZERO;
- BigDecimal totalInterestRefund = BigDecimal.ZERO;
- BigDecimal totalUnpaidPayableDueInterest = BigDecimal.ZERO;
- BigDecimal totalUnpaidPayableNotDueInterest = BigDecimal.ZERO;
-
- totalChargeAdjustment =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
- LoanTransactionType.CHARGE_ADJUSTMENT.getValue());
- totalChargeAdjustmentReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
- LoanTransactionType.CHARGE_ADJUSTMENT.getValue());
-
- totalChargeback =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.CHARGEBACK.getValue());
-
- totalCreditBalanceRefund =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
- LoanTransactionType.CREDIT_BALANCE_REFUND.getValue());
- totalCreditBalanceRefundReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
- LoanTransactionType.CREDIT_BALANCE_REFUND.getValue());
-
- totalGoodwillCredit =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.GOODWILL_CREDIT.getValue());
- totalGoodwillCreditReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
- LoanTransactionType.GOODWILL_CREDIT.getValue());
-
- totalInterestRefund =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.INTEREST_REFUND.getValue());
-
- totalInterestPaymentWaiver =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
- LoanTransactionType.INTEREST_PAYMENT_WAIVER.getValue());
-
- totalMerchantRefund =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
- LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue());
- totalMerchantRefundReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
- LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue());
-
- totalPayoutRefund =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.PAYOUT_REFUND.getValue());
- totalPayoutRefundReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
- LoanTransactionType.PAYOUT_REFUND.getValue());
-
- totalRepaymentTransaction =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.REPAYMENT.getValue())
-
.add(fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.DOWN_PAYMENT.getValue()));
- totalRepaymentTransactionReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
- LoanTransactionType.REPAYMENT.getValue());
-
- if (repaymentSchedule != null) {
- // Outstanding Interest on Past due installments
- totalUnpaidPayableDueInterest =
computeTotalUnpaidPayableDueInterestAmount(repaymentSchedule.getPeriods(),
businessDate);
-
- // Accumulated daily interest of the current Installment period
- totalUnpaidPayableNotDueInterest =
computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(repaymentSchedule.getPeriods(),
- businessDate, defaultSummaryData.currency);
- }
-
- return
LoanSummaryData.builder().currency(defaultSummaryData.currency).principalDisbursed(defaultSummaryData.principalDisbursed)
-
.principalAdjustments(defaultSummaryData.principalAdjustments).principalPaid(defaultSummaryData.principalPaid)
-
.principalWrittenOff(defaultSummaryData.principalWrittenOff).principalOutstanding(defaultSummaryData.principalOutstanding)
-
.principalOverdue(defaultSummaryData.principalOverdue).interestCharged(defaultSummaryData.interestCharged)
-
.interestPaid(defaultSummaryData.interestPaid).interestWaived(defaultSummaryData.interestWaived)
-
.interestWrittenOff(defaultSummaryData.interestWrittenOff).interestOutstanding(defaultSummaryData.interestOutstanding)
-
.interestOverdue(defaultSummaryData.interestOverdue).feeChargesCharged(defaultSummaryData.feeChargesCharged)
- .feeAdjustments(defaultSummaryData.feeAdjustments)
-
.feeChargesDueAtDisbursementCharged(defaultSummaryData.feeChargesDueAtDisbursementCharged)
-
.feeChargesPaid(defaultSummaryData.feeChargesPaid).feeChargesWaived(defaultSummaryData.feeChargesWaived)
- .feeChargesWrittenOff(defaultSummaryData.feeChargesWrittenOff)
-
.feeChargesOutstanding(defaultSummaryData.feeChargesOutstanding).feeChargesOverdue(defaultSummaryData.feeChargesOverdue)
-
.penaltyChargesCharged(defaultSummaryData.penaltyChargesCharged).penaltyAdjustments(defaultSummaryData.penaltyAdjustments)
-
.penaltyChargesPaid(defaultSummaryData.penaltyChargesPaid).penaltyChargesWaived(defaultSummaryData.penaltyChargesWaived)
-
.penaltyChargesWrittenOff(defaultSummaryData.penaltyChargesWrittenOff)
-
.penaltyChargesOutstanding(defaultSummaryData.penaltyChargesOutstanding)
-
.penaltyChargesOverdue(defaultSummaryData.penaltyChargesOverdue)
-
.totalExpectedRepayment(defaultSummaryData.totalExpectedRepayment).totalRepayment(defaultSummaryData.totalRepayment)
-
.totalExpectedCostOfLoan(defaultSummaryData.totalExpectedCostOfLoan).totalCostOfLoan(defaultSummaryData.totalCostOfLoan)
-
.totalWaived(defaultSummaryData.totalWaived).totalWrittenOff(defaultSummaryData.totalWrittenOff)
-
.totalOutstanding(defaultSummaryData.totalOutstanding).totalOverdue(defaultSummaryData.totalOverdue)
-
.overdueSinceDate(defaultSummaryData.overdueSinceDate).writeoffReasonId(defaultSummaryData.writeoffReasonId)
-
.writeoffReason(defaultSummaryData.writeoffReason).totalRecovered(defaultSummaryData.totalRecovered)
-
.chargeOffReasonId(defaultSummaryData.chargeOffReasonId).chargeOffReason(defaultSummaryData.chargeOffReason)
-
.totalMerchantRefund(totalMerchantRefund).totalMerchantRefundReversed(totalMerchantRefundReversed)
-
.totalPayoutRefund(totalPayoutRefund).totalPayoutRefundReversed(totalPayoutRefundReversed)
-
.totalGoodwillCredit(totalGoodwillCredit).totalGoodwillCreditReversed(totalGoodwillCreditReversed)
-
.totalChargeAdjustment(totalChargeAdjustment).totalChargeAdjustmentReversed(totalChargeAdjustmentReversed)
-
.totalChargeback(totalChargeback).totalCreditBalanceRefund(totalCreditBalanceRefund)
-
.totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed).totalRepaymentTransaction(totalRepaymentTransaction)
-
.totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).totalInterestPaymentWaiver(totalInterestPaymentWaiver)
- .totalUnpaidPayableDueInterest(totalUnpaidPayableDueInterest)
-
.totalUnpaidPayableNotDueInterest(totalUnpaidPayableNotDueInterest).totalInterestRefund(totalInterestRefund).build();
- }
-
- private static BigDecimal fetchLoanTransactionBalanceByType(final
Collection<LoanTransactionBalance> loanTransactionBalances,
- final Integer transactionType) {
- final Optional<LoanTransactionBalance> optLoanTransactionBalance =
loanTransactionBalances.stream()
- .filter(balance ->
balance.getTransactionType().equals(transactionType) &&
!balance.isReversed()).findFirst();
- return optLoanTransactionBalance.isPresent() ?
optLoanTransactionBalance.get().getAmount() : BigDecimal.ZERO;
- }
-
- private static BigDecimal fetchLoanTransactionBalanceReversedByType(final
Collection<LoanTransactionBalance> loanTransactionBalances,
- final Integer transactionType) {
- final Optional<LoanTransactionBalance> optLoanTransactionBalance =
loanTransactionBalances.stream()
- .filter(balance ->
balance.getTransactionType().equals(transactionType) && balance.isReversed()
- && balance.isManuallyAdjustedOrReversed())
- .findFirst();
- return optLoanTransactionBalance.isPresent() ?
optLoanTransactionBalance.get().getAmount() : BigDecimal.ZERO;
- }
-
- public static LoanSummaryData withOnlyCurrencyData(CurrencyData
currencyData) {
- return LoanSummaryData.builder().currency(currencyData).build();
- }
-
- private static BigDecimal
computeTotalUnpaidPayableDueInterestAmount(Collection<LoanSchedulePeriodData>
periods,
- final LocalDate businessDate) {
- return periods.stream().filter(period ->
!period.getDownPaymentPeriod() && businessDate.compareTo(period.getDueDate())
>= 0)
- .map(period ->
period.getInterestOutstanding()).reduce(BigDecimal.ZERO, BigDecimal::add);
- }
-
- private static BigDecimal
computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final
Collection<LoanSchedulePeriodData> periods,
- final LocalDate businessDate, final CurrencyData currency) {
- // Find the current Period (If exists one) based on the Business date
- final Optional<LoanSchedulePeriodData> optCurrentPeriod =
periods.stream()
- .filter(period -> !period.getDownPaymentPeriod() &&
period.isActualPeriodForNotDuePayableCalculation(businessDate))
- .findFirst();
-
- if (optCurrentPeriod.isPresent()) {
- final LoanSchedulePeriodData currentPeriod =
optCurrentPeriod.get();
- final long remainingDays = currentPeriod.getDaysInPeriod()
- -
DateUtils.getDifferenceInDays(currentPeriod.getFromDate(), businessDate);
-
- return computeAccruedInterestTillDay(currentPeriod, remainingDays,
currency);
- }
- // Default value equal to Zero
- return BigDecimal.ZERO;
- }
-
- public static BigDecimal computeAccruedInterestTillDay(final
LoanSchedulePeriodData period, final long untilDay,
- final CurrencyData currency) {
- Integer remainingDays = period.getDaysInPeriod();
- BigDecimal totalAccruedInterest = BigDecimal.ZERO;
- while (remainingDays > untilDay) {
- final BigDecimal accruedInterest =
period.getInterestDue().subtract(totalAccruedInterest)
- .divide(BigDecimal.valueOf(remainingDays),
MoneyHelper.getMathContext());
- totalAccruedInterest = totalAccruedInterest.add(accruedInterest);
- remainingDays--;
- }
-
- totalAccruedInterest =
totalAccruedInterest.subtract(period.getInterestPaid()).subtract(period.getInterestWaived());
- if (MathUtil.isLessThanZero(totalAccruedInterest)) {
- // Set Zero If the Interest Paid + Waived is greather than
Interest Accrued
- totalAccruedInterest = BigDecimal.ZERO;
- }
-
- return Money.of(currency, totalAccruedInterest).getAmount();
- }
-
}
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 36cfe8637..ef8588c6c 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
@@ -35,11 +35,13 @@ import
org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
-import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryBalancesRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import
org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import
org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import
org.apache.fineract.portfolio.loanaccount.service.LoanSummaryDataProvider;
+import
org.apache.fineract.portfolio.loanaccount.service.LoanSummaryProviderDelegate;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
@@ -52,6 +54,8 @@ public class LoanBusinessEventSerializer implements
BusinessEventSerializer {
private final DelinquencyReadPlatformService
delinquencyReadPlatformService;
private final LoanInstallmentLevelDelinquencyEventProducer
installmentLevelDelinquencyEventProducer;
private final LoanSummaryBalancesRepository loanSummaryBalancesRepository;
+ @Lazy
+ private final LoanSummaryProviderDelegate loanSummaryProviderDelegate;
@Override
public <T> boolean canSerialize(BusinessEvent<T> event) {
@@ -74,12 +78,15 @@ public class LoanBusinessEventSerializer implements
BusinessEventSerializer {
CollectionData delinquentData =
delinquencyReadPlatformService.calculateLoanCollectionData(loanId);
data.setDelinquent(delinquentData);
+ LoanSummaryDataProvider loanSummaryDataProvider =
loanSummaryProviderDelegate
+
.resolveLoanSummaryDataProvider(data.getTransactionProcessingStrategyCode());
+
if (data.getSummary() != null) {
-
data.setSummary(LoanSummaryData.withTransactionAmountsSummary(data.getSummary(),
data.getRepaymentSchedule(),
-
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(loanId,
+
data.setSummary(loanSummaryDataProvider.withTransactionAmountsSummary(event.get(),
data.getSummary(),
+ data.getRepaymentSchedule(),
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(loanId,
LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
} else {
-
data.setSummary(LoanSummaryData.withOnlyCurrencyData(data.getCurrency()));
+
data.setSummary(loanSummaryDataProvider.withOnlyCurrencyData(data.getCurrency()));
}
List<LoanInstallmentDelinquencyBucketDataV1>
installmentsDelinquencyData = installmentLevelDelinquencyEventProducer
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
index 84785072d..ee37e4972 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/InternalLoanInformationApiResource.java
@@ -27,7 +27,6 @@ import com.google.gson.Gson;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
-import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
@@ -43,7 +42,6 @@ import
org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
import
org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import
org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
-import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
@@ -158,23 +156,4 @@ public class InternalLoanInformationApiResource implements
InitializingBean {
final Loan loan =
loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
return
advancedPaymentDataMapper.mapLoanPaymentAllocationRule(loan.getPaymentAllocationRules());
}
-
- @POST
- @Path("{loanId}/apply-interest-refund")
- @Consumes({ MediaType.APPLICATION_JSON })
- @Produces({ MediaType.APPLICATION_JSON })
- @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
- public Long applyInterestRefundToLoan(@Context final UriInfo uriInfo,
@PathParam("loanId") Long loanId,
- final String apiRequestBodyAsJson) {
-
log.warn("------------------------------------------------------------");
- log.warn("
");
- log.warn(" Apply Loan Transaction to Interest Refund loanId {} ",
loanId);
- log.warn("
");
-
log.warn("------------------------------------------------------------");
- LoanRefundRequestData loanRefundRequestData =
gson.fromJson(apiRequestBodyAsJson, LoanRefundRequestData.class);
- final Loan loan =
loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
- final LoanTransaction loanTransaction =
loanAccountDomainService.applyInterestRefund(loan, loanRefundRequestData);
- return loanTransaction.getId();
- }
-
}
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 c4d494910..9e909840d 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
@@ -127,7 +127,6 @@ import
org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import
org.apache.fineract.portfolio.loanaccount.data.LoanCollateralManagementData;
-import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
@@ -150,6 +149,8 @@ import
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermV
import
org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformService;
import
org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import
org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import
org.apache.fineract.portfolio.loanaccount.service.LoanSummaryDataProvider;
+import
org.apache.fineract.portfolio.loanaccount.service.LoanSummaryProviderDelegate;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import
org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
@@ -289,6 +290,7 @@ public class LoansApiResource {
private final LoanSummaryBalancesRepository loanSummaryBalancesRepository;
private final ClientReadPlatformService clientReadPlatformService;
private final LoanTermVariationsRepository loanTermVariationsRepository;
+ private final LoanSummaryProviderDelegate loanSummaryProviderDelegate;
/*
* This template API is used for loan approval, ideally this should be
invoked on loan that are pending for
@@ -1115,9 +1117,12 @@ public class LoansApiResource {
// updating summary with transaction amounts summary
if (loanBasicDetails.getSummary() != null) {
-
loanBasicDetails.setSummary(LoanSummaryData.withTransactionAmountsSummary(loanBasicDetails.getSummary(),
repaymentSchedule,
-
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(loanBasicDetails.getId(),
- LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
+ LoanSummaryDataProvider loanSummaryDataProvider =
loanSummaryProviderDelegate
+
.resolveLoanSummaryDataProvider(loanBasicDetails.getTransactionProcessingStrategyCode());
+ loanBasicDetails.setSummary(
+
loanSummaryDataProvider.withTransactionAmountsSummary(loanBasicDetails.getId(),
loanBasicDetails.getSummary(),
+ repaymentSchedule,
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(
+ loanBasicDetails.getId(),
LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
}
final LoanAccountData loanAccount =
loanBasicDetails.associationsAndTemplate(repaymentSchedule, loanRepayments,
charges,
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CommonLoanSummaryDataProvider.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CommonLoanSummaryDataProvider.java
new file mode 100644
index 000000000..6e1ce2dab
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CommonLoanSummaryDataProvider.java
@@ -0,0 +1,173 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.Optional;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionBalance;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+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;
+
+public abstract class CommonLoanSummaryDataProvider implements
LoanSummaryDataProvider {
+
+ @Override
+ public LoanSummaryData withTransactionAmountsSummary(Loan loan,
LoanSummaryData defaultSummaryData, LoanScheduleData repaymentSchedule,
+ Collection<LoanTransactionBalance> loanTransactionBalances) {
+ final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+
+ BigDecimal totalMerchantRefund = BigDecimal.ZERO;
+ BigDecimal totalMerchantRefundReversed = BigDecimal.ZERO;
+ BigDecimal totalPayoutRefund = BigDecimal.ZERO;
+ BigDecimal totalPayoutRefundReversed = BigDecimal.ZERO;
+ BigDecimal totalGoodwillCredit = BigDecimal.ZERO;
+ BigDecimal totalGoodwillCreditReversed = BigDecimal.ZERO;
+ BigDecimal totalChargeAdjustment = BigDecimal.ZERO;
+ BigDecimal totalChargeAdjustmentReversed = BigDecimal.ZERO;
+ BigDecimal totalChargeback = BigDecimal.ZERO;
+ BigDecimal totalCreditBalanceRefund = BigDecimal.ZERO;
+ BigDecimal totalCreditBalanceRefundReversed = BigDecimal.ZERO;
+ BigDecimal totalRepaymentTransaction = BigDecimal.ZERO;
+ BigDecimal totalRepaymentTransactionReversed = BigDecimal.ZERO;
+ BigDecimal totalInterestPaymentWaiver = BigDecimal.ZERO;
+ BigDecimal totalInterestRefund = BigDecimal.ZERO;
+ BigDecimal totalUnpaidPayableDueInterest = BigDecimal.ZERO;
+ BigDecimal totalUnpaidPayableNotDueInterest = BigDecimal.ZERO;
+
+ totalChargeAdjustment =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
+ LoanTransactionType.CHARGE_ADJUSTMENT.getValue());
+ totalChargeAdjustmentReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
+ LoanTransactionType.CHARGE_ADJUSTMENT.getValue());
+
+ totalChargeback =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.CHARGEBACK.getValue());
+
+ totalCreditBalanceRefund =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
+ LoanTransactionType.CREDIT_BALANCE_REFUND.getValue());
+ totalCreditBalanceRefundReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
+ LoanTransactionType.CREDIT_BALANCE_REFUND.getValue());
+
+ totalGoodwillCredit =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.GOODWILL_CREDIT.getValue());
+ totalGoodwillCreditReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
+ LoanTransactionType.GOODWILL_CREDIT.getValue());
+
+ totalInterestRefund =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.INTEREST_REFUND.getValue());
+
+ totalInterestPaymentWaiver =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
+ LoanTransactionType.INTEREST_PAYMENT_WAIVER.getValue());
+
+ totalMerchantRefund =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
+ LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue());
+ totalMerchantRefundReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
+ LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue());
+
+ totalPayoutRefund =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.PAYOUT_REFUND.getValue());
+ totalPayoutRefundReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
+ LoanTransactionType.PAYOUT_REFUND.getValue());
+
+ totalRepaymentTransaction =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.REPAYMENT.getValue())
+
.add(fetchLoanTransactionBalanceByType(loanTransactionBalances,
LoanTransactionType.DOWN_PAYMENT.getValue()));
+ totalRepaymentTransactionReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
+ LoanTransactionType.REPAYMENT.getValue());
+
+ if (repaymentSchedule != null) {
+ // Outstanding Interest on Past due installments
+ totalUnpaidPayableDueInterest =
computeTotalUnpaidPayableDueInterestAmount(repaymentSchedule.getPeriods(),
businessDate);
+
+ // Accumulated daily interest of the current Installment period
+ totalUnpaidPayableNotDueInterest =
computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(loan,
+ repaymentSchedule.getPeriods(), businessDate,
defaultSummaryData.getCurrency());
+ }
+
+ return
LoanSummaryData.builder().currency(defaultSummaryData.getCurrency())
+ .principalDisbursed(defaultSummaryData.getPrincipalDisbursed())
+
.principalAdjustments(defaultSummaryData.getPrincipalAdjustments()).principalPaid(defaultSummaryData.getPrincipalPaid())
+
.principalWrittenOff(defaultSummaryData.getPrincipalWrittenOff())
+
.principalOutstanding(defaultSummaryData.getPrincipalOutstanding())
+
.principalOverdue(defaultSummaryData.getPrincipalOverdue()).interestCharged(defaultSummaryData.getInterestCharged())
+
.interestPaid(defaultSummaryData.getInterestPaid()).interestWaived(defaultSummaryData.getInterestWaived())
+ .interestWrittenOff(defaultSummaryData.getInterestWrittenOff())
+
.interestOutstanding(defaultSummaryData.getInterestOutstanding()).interestOverdue(defaultSummaryData.getInterestOverdue())
+
.feeChargesCharged(defaultSummaryData.getFeeChargesCharged()).feeAdjustments(defaultSummaryData.getFeeAdjustments())
+
.feeChargesDueAtDisbursementCharged(defaultSummaryData.getFeeChargesDueAtDisbursementCharged())
+
.feeChargesPaid(defaultSummaryData.getFeeChargesPaid()).feeChargesWaived(defaultSummaryData.getFeeChargesWaived())
+
.feeChargesWrittenOff(defaultSummaryData.getFeeChargesWrittenOff())
+
.feeChargesOutstanding(defaultSummaryData.getFeeChargesOutstanding())
+ .feeChargesOverdue(defaultSummaryData.getFeeChargesOverdue())
+
.penaltyChargesCharged(defaultSummaryData.getPenaltyChargesCharged())
+ .penaltyAdjustments(defaultSummaryData.getPenaltyAdjustments())
+ .penaltyChargesPaid(defaultSummaryData.getPenaltyChargesPaid())
+
.penaltyChargesWaived(defaultSummaryData.getPenaltyChargesWaived())
+
.penaltyChargesWrittenOff(defaultSummaryData.getPenaltyChargesWrittenOff())
+
.penaltyChargesOutstanding(defaultSummaryData.getPenaltyChargesOutstanding())
+
.penaltyChargesOverdue(defaultSummaryData.getPenaltyChargesOverdue())
+
.totalExpectedRepayment(defaultSummaryData.getTotalExpectedRepayment())
+ .totalRepayment(defaultSummaryData.getTotalRepayment())
+
.totalExpectedCostOfLoan(defaultSummaryData.getTotalExpectedCostOfLoan())
+
.totalCostOfLoan(defaultSummaryData.getTotalCostOfLoan()).totalWaived(defaultSummaryData.getTotalWaived())
+
.totalWrittenOff(defaultSummaryData.getTotalWrittenOff()).totalOutstanding(defaultSummaryData.getTotalOutstanding())
+
.totalOverdue(defaultSummaryData.getTotalOverdue()).overdueSinceDate(defaultSummaryData.getOverdueSinceDate())
+
.writeoffReasonId(defaultSummaryData.getWriteoffReasonId()).writeoffReason(defaultSummaryData.getWriteoffReason())
+
.totalRecovered(defaultSummaryData.getTotalRecovered()).chargeOffReasonId(defaultSummaryData.getChargeOffReasonId())
+
.chargeOffReason(defaultSummaryData.getChargeOffReason()).totalMerchantRefund(totalMerchantRefund)
+
.totalMerchantRefundReversed(totalMerchantRefundReversed).totalPayoutRefund(totalPayoutRefund)
+
.totalPayoutRefundReversed(totalPayoutRefundReversed).totalGoodwillCredit(totalGoodwillCredit)
+
.totalGoodwillCreditReversed(totalGoodwillCreditReversed).totalChargeAdjustment(totalChargeAdjustment)
+
.totalChargeAdjustmentReversed(totalChargeAdjustmentReversed).totalChargeback(totalChargeback)
+
.totalCreditBalanceRefund(totalCreditBalanceRefund).totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed)
+
.totalRepaymentTransaction(totalRepaymentTransaction).totalRepaymentTransactionReversed(totalRepaymentTransactionReversed)
+
.totalInterestPaymentWaiver(totalInterestPaymentWaiver).totalUnpaidPayableDueInterest(totalUnpaidPayableDueInterest)
+
.totalUnpaidPayableNotDueInterest(totalUnpaidPayableNotDueInterest).totalInterestRefund(totalInterestRefund).build();
+ }
+
+ private static BigDecimal fetchLoanTransactionBalanceByType(final
Collection<LoanTransactionBalance> loanTransactionBalances,
+ final Integer transactionType) {
+ final Optional<LoanTransactionBalance> optLoanTransactionBalance =
loanTransactionBalances.stream()
+ .filter(balance ->
balance.getTransactionType().equals(transactionType) &&
!balance.isReversed()).findFirst();
+ return optLoanTransactionBalance.isPresent() ?
optLoanTransactionBalance.get().getAmount() : BigDecimal.ZERO;
+ }
+
+ private static BigDecimal fetchLoanTransactionBalanceReversedByType(final
Collection<LoanTransactionBalance> loanTransactionBalances,
+ final Integer transactionType) {
+ final Optional<LoanTransactionBalance> optLoanTransactionBalance =
loanTransactionBalances.stream()
+ .filter(balance ->
balance.getTransactionType().equals(transactionType) && balance.isReversed()
+ && balance.isManuallyAdjustedOrReversed())
+ .findFirst();
+ return optLoanTransactionBalance.isPresent() ?
optLoanTransactionBalance.get().getAmount() : BigDecimal.ZERO;
+ }
+
+ @Override
+ public BigDecimal
computeTotalUnpaidPayableDueInterestAmount(Collection<LoanSchedulePeriodData>
periods, final LocalDate businessDate) {
+ return periods.stream().filter(period ->
!period.getDownPaymentPeriod() && businessDate.compareTo(period.getDueDate())
>= 0)
+
.map(LoanSchedulePeriodData::getInterestOutstanding).reduce(BigDecimal.ZERO,
BigDecimal::add);
+ }
+
+ @Override
+ public LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData) {
+ {
+ return LoanSummaryData.builder().currency(currencyData).build();
+ }
+ }
+
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CumulativeLoanSummaryDataProvider.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CumulativeLoanSummaryDataProvider.java
new file mode 100644
index 000000000..2d4e06d9d
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CumulativeLoanSummaryDataProvider.java
@@ -0,0 +1,90 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.Optional;
+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.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionBalance;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CumulativeLoanSummaryDataProvider extends
CommonLoanSummaryDataProvider {
+
+ @Override
+ public boolean accept(String loanProcessingStrategyCode) {
+ return
!loanProcessingStrategyCode.equalsIgnoreCase("advanced-payment-allocation-strategy");
+ }
+
+ @Override
+ public BigDecimal
computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan,
+ final Collection<LoanSchedulePeriodData> periods, final LocalDate
businessDate, final CurrencyData currency) {
+ // Find the current Period (If exists one) based on the Business date
+ final Optional<LoanSchedulePeriodData> optCurrentPeriod =
periods.stream()
+ .filter(period -> !period.getDownPaymentPeriod() &&
period.isActualPeriodForNotDuePayableCalculation(businessDate))
+ .findFirst();
+
+ if (optCurrentPeriod.isPresent()) {
+ final LoanSchedulePeriodData currentPeriod =
optCurrentPeriod.get();
+ final long remainingDays = currentPeriod.getDaysInPeriod()
+ -
DateUtils.getDifferenceInDays(currentPeriod.getFromDate(), businessDate);
+
+ return computeAccruedInterestTillDay(currentPeriod, remainingDays,
currency);
+ }
+ // Default value equal to Zero
+ return BigDecimal.ZERO;
+ }
+
+ @Override
+ public LoanSummaryData withTransactionAmountsSummary(Long loanId,
LoanSummaryData defaultSummaryData,
+ LoanScheduleData repaymentSchedule,
Collection<LoanTransactionBalance> loanTransactionBalances) {
+ Loan loan = null;
+ return super.withTransactionAmountsSummary(loan, defaultSummaryData,
repaymentSchedule, loanTransactionBalances);
+ }
+
+ private static BigDecimal computeAccruedInterestTillDay(final
LoanSchedulePeriodData period, final long untilDay,
+ final CurrencyData currency) {
+ Integer remainingDays = period.getDaysInPeriod();
+ BigDecimal totalAccruedInterest = BigDecimal.ZERO;
+ while (remainingDays > untilDay) {
+ final BigDecimal accruedInterest =
period.getInterestDue().subtract(totalAccruedInterest)
+ .divide(BigDecimal.valueOf(remainingDays),
MoneyHelper.getMathContext());
+ totalAccruedInterest = totalAccruedInterest.add(accruedInterest);
+ remainingDays--;
+ }
+
+ totalAccruedInterest =
totalAccruedInterest.subtract(period.getInterestPaid()).subtract(period.getInterestWaived());
+ if (MathUtil.isLessThanZero(totalAccruedInterest)) {
+ // Set Zero If the Interest Paid + Waived is greather than
Interest Accrued
+ totalAccruedInterest = BigDecimal.ZERO;
+ }
+
+ return Money.of(currency, totalAccruedInterest).getAmount();
+ }
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSummaryDataProvider.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSummaryDataProvider.java
new file mode 100644
index 000000000..b10f86f74
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSummaryDataProvider.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collection;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionBalance;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+
+public interface LoanSummaryDataProvider {
+
+ BigDecimal
computeTotalUnpaidPayableDueInterestAmount(Collection<LoanSchedulePeriodData>
periods, LocalDate businessDate);
+
+ BigDecimal
computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(Loan loan,
Collection<LoanSchedulePeriodData> periods,
+ LocalDate businessDate, CurrencyData currency);
+
+ LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData);
+
+ LoanSummaryData withTransactionAmountsSummary(Long loanId, LoanSummaryData
defaultSummaryData, LoanScheduleData repaymentSchedule,
+ Collection<LoanTransactionBalance> loanTransactionBalances);
+
+ LoanSummaryData withTransactionAmountsSummary(Loan loan, LoanSummaryData
defaultSummaryData, LoanScheduleData repaymentSchedule,
+ Collection<LoanTransactionBalance> loanTransactionBalances);
+
+ boolean accept(String loanProcessingStrategyCode);
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSummaryProviderDelegate.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSummaryProviderDelegate.java
new file mode 100644
index 000000000..20f9663fc
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSummaryProviderDelegate.java
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+
+@Component
+@AllArgsConstructor
+public class LoanSummaryProviderDelegate {
+
+ private final List<LoanSummaryDataProvider> loanSummaryDataProviders;
+
+ public LoanSummaryDataProvider resolveLoanSummaryDataProvider(String
loanProcessingStrategyCode) {
+ return loanSummaryDataProviders.stream().filter(provider ->
provider.accept(loanProcessingStrategyCode)).findAny()
+ .orElseThrow(() -> new IllegalArgumentException("No provider
found for :" + loanProcessingStrategyCode));
+ }
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java
new file mode 100644
index 000000000..6bddb5e95
--- /dev/null
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.fineract.organisation.monetary.data.CurrencyData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionBalance;
+import
org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDetails;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
+import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
+import org.springframework.stereotype.Component;
+
+@Component
+@AllArgsConstructor
+public class ProgressiveLoanSummaryDataProvider extends
CommonLoanSummaryDataProvider {
+
+ private final AdvancedPaymentScheduleTransactionProcessor
advancedPaymentScheduleTransactionProcessor;
+ private final EMICalculator emiCalculator;
+ private final LoanRepositoryWrapper loanRepository;
+
+ @Override
+ public boolean accept(String loanProcessingStrategyCode) {
+ return
loanProcessingStrategyCode.equalsIgnoreCase("advanced-payment-allocation-strategy");
+ }
+
+ @Override
+ public LoanSummaryData withTransactionAmountsSummary(Long loanId,
LoanSummaryData defaultSummaryData,
+ LoanScheduleData repaymentSchedule,
Collection<LoanTransactionBalance> loanTransactionBalances) {
+ final Loan loan = loanRepository.findOneWithNotFoundDetection(loanId,
true);
+ return super.withTransactionAmountsSummary(loan, defaultSummaryData,
repaymentSchedule, loanTransactionBalances);
+ }
+
+ @Override
+ public LoanSummaryData withTransactionAmountsSummary(Loan loan,
LoanSummaryData defaultSummaryData, LoanScheduleData repaymentSchedule,
+ Collection<LoanTransactionBalance> loanTransactionBalances) {
+ return super.withTransactionAmountsSummary(loan, defaultSummaryData,
repaymentSchedule, loanTransactionBalances);
+ }
+
+ private LoanRepaymentScheduleInstallment
getRelatedRepaymentScheduleInstallment(Loan loan, LocalDate businessDate) {
+ return loan.getRepaymentScheduleInstallments().stream().filter(i ->
!i.isDownPayment() && !i.isAdditional()
+ && !businessDate.isBefore(i.getFromDate()) &&
businessDate.isBefore(i.getDueDate())).findFirst().orElseGet(() -> {
+ List<LoanRepaymentScheduleInstallment> list =
loan.getRepaymentScheduleInstallments().stream()
+ .filter(i -> !i.isDownPayment() &&
!i.isAdditional()).toList();
+ return !list.isEmpty() ? list.get(list.size() - 1) : null;
+ });
+ }
+
+ @Override
+ public BigDecimal
computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan,
+ final Collection<LoanSchedulePeriodData> periods, final LocalDate
businessDate, final CurrencyData currency) {
+
+ LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment =
getRelatedRepaymentScheduleInstallment(loan, businessDate);
+ if (loan.isInterestBearing() && loanRepaymentScheduleInstallment !=
null) {
+ List<LoanTransaction> transactionsToReprocess =
loan.retrieveListOfTransactionsForReprocessing().stream()
+ .filter(t -> !t.isAccrualActivity()).toList();
+ Pair<ChangedTransactionDetail,
ProgressiveLoanInterestScheduleModel>
changedTransactionDetailProgressiveLoanInterestScheduleModelPair =
advancedPaymentScheduleTransactionProcessor
+
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate,
transactionsToReprocess,
+ loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
+ ProgressiveLoanInterestScheduleModel model =
changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight();
+ if
(!changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft().getCurrentTransactionToOldId().isEmpty()
+ ||
!changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft().getNewTransactionMappings().isEmpty())
{
+ throw new RuntimeException("Transactions should not be reverse
replayed!");
+ }
+ if (model != null) {
+ PeriodDueDetails dueAmounts =
emiCalculator.getDueAmounts(model,
loanRepaymentScheduleInstallment.getDueDate(),
+ businessDate);
+ if (dueAmounts != null) {
+ BigDecimal interestPaid =
loanRepaymentScheduleInstallment.getInterestPaid();
+ BigDecimal dueInterest =
dueAmounts.getDueInterest().getAmount();
+ if (interestPaid == null) {
+ return dueInterest;
+ }
+ return dueInterest.subtract(interestPaid);
+ }
+ }
+ }
+
+ return BigDecimal.ZERO;
+ }
+}
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 07bf0a4c7..bfe96eb77 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
@@ -4674,6 +4674,28 @@ public class
AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
assertEquals(new BigDecimal("0.05"),
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
});
+ runAt("31 January 2024", () -> {
+ // Generate the Accruals
+ final PeriodicAccrualAccountingHelper
periodicAccrualAccountingHelper = new
PeriodicAccrualAccountingHelper(requestSpec,
+ responseSpec);
+ periodicAccrualAccountingHelper.runPeriodicAccrualAccounting("31
January 2024");
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+ assertEquals(BigDecimal.ZERO,
loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
+ assertEquals(new BigDecimal("0.09"),
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
+ });
+
+ runAt("1 February 2024", () -> {
+ // Generate the Accruals
+ final PeriodicAccrualAccountingHelper
periodicAccrualAccountingHelper = new
PeriodicAccrualAccountingHelper(requestSpec,
+ responseSpec);
+ periodicAccrualAccountingHelper.runPeriodicAccrualAccounting("1
February 2024");
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+ assertEquals(new BigDecimal("0.12"),
loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
+ assertEquals(BigDecimal.ZERO,
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
+ });
+
// Not Due and Due Interest
runAt("20 February 2024", () -> {
final PeriodicAccrualAccountingHelper
periodicAccrualAccountingHelper = new
PeriodicAccrualAccountingHelper(requestSpec,
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPrepayAmountTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPrepayAmountTest.java
new file mode 100644
index 000000000..bde69d064
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanPrepayAmountTest.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+
+@Slf4j
+public class LoanPrepayAmountTest extends BaseLoanIntegrationTest {
+
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+ Long loanId;
+
+ @Test
+ public void testLoanPrepayAmountProgressive() {
+ runAt("1 January 2024", () -> {
+ final PostLoanProductsResponse loanProductsResponse =
loanProductHelper.createLoanProduct(create4IProgressive());
+ PostLoansResponse postLoansResponse =
loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
+ loanProductsResponse.getResourceId(), "01 January 2024",
1000.0, 9.99, 6, null));
+ loanId = postLoansResponse.getLoanId();
+ loanTransactionHelper.approveLoan(loanId,
approveLoanRequest(1000.0, "01 January 2024"));
+ disburseLoan(loanId, BigDecimal.valueOf(250.0), "01 January 2024");
+ });
+ runAt("7 january 2024", () -> {
+ disburseLoan(loanId, BigDecimal.valueOf(350.0), "04 January 2024");
+ disburseLoan(loanId, BigDecimal.valueOf(400.0), "05 January 2024");
+ });
+ for (int i = 7; i <= 31; i++) {
+ runAt(i + " January 2024", () -> {
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId);
+ HashMap prepayAmount =
loanTransactionHelper.getPrepayAmount(requestSpec, responseSpec,
loanId.intValue());
+ Assertions.assertEquals((float)
prepayAmount.get("interestPortion"),
+
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().floatValue());
+ });
+ }
+ }
+
+}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java
index cc9b93e6a..b40068e3b 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java
@@ -18,11 +18,6 @@
*/
package org.apache.fineract.integrationtests;
-import static
org.apache.fineract.integrationtests.BaseLoanIntegrationTest.InterestRateFrequencyType.YEARS;
-import static
org.apache.fineract.integrationtests.BaseLoanIntegrationTest.RepaymentFrequencyType.DAYS;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import com.google.gson.Gson;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
@@ -30,22 +25,16 @@ import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.math.BigDecimal;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.concurrent.atomic.AtomicLong;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
-import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
-import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
-import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
-import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
@@ -78,158 +67,6 @@ public class LoanRefundTransactionTest extends
BaseLoanIntegrationTest {
loanRescheduleRequestHelper = new
LoanRescheduleRequestHelper(requestSpec, responseSpec);
}
- // UC1: (Internal case) Generate a totalInterestRefund using Advanced
payment allocation with Interest Refund
- // options and Apply
- // Interest Refund transaction
- // 1. Create a Loan product with Adv. Pment. Alloc. and with Accrual and
Interest Refund
- // 2. Submit, Approve and Disburse a Loan account
- // 3. Apply the INTEREST_REFUND transaction to validate the Journal
Entries generated
- @Test
- public void uc1() {
- final String operationDate = "1 January 2024";
- AtomicLong createdLoanId = new AtomicLong();
- runAt(operationDate, () -> {
- Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
- PostLoanProductsRequest product =
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
-
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
-
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.DAYS_360)
- .numberOfRepayments(4)//
- .maxInterestRatePerPeriod((double) 110)//
- .repaymentEvery(1)//
- .repaymentFrequencyType(1L)//
- .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, 1000.0, 4)
- .interestRatePerPeriod(BigDecimal.valueOf(108.0));
-
- applicationRequest =
applicationRequest.interestCalculationPeriodType(DAYS)
-
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
-
- PostLoansResponse loanResponse =
loanTransactionHelper.applyLoan(applicationRequest);
- createdLoanId.set(loanResponse.getLoanId());
-
- loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
- new
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
- .approvedOnDate("1 January 2024").locale("en"));
-
- loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
- new PostLoansLoanIdRequest().actualDisbursementDate("1
January 2024").dateFormat(DATETIME_PATTERN)
-
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
-
- // After Disbursement we are expecting zero interest refund
- GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
- assertEquals(BigDecimal.ZERO,
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
- });
-
- runAt("10 February 2024", () -> {
- // After Interest refund transaction we are expecting non zero
interest refund
- GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
- LOG.info("value {}",
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
- assertEquals(BigDecimal.ZERO,
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
-
- final BigDecimal inteterestRefund = BigDecimal.valueOf(11);
- final Long loanTransactionId =
loanTransactionHelper.applyInterestRefundLoanTransaction(requestSpec,
responseSpec,
- createdLoanId.get(),
buildJsonBody(BigDecimal.valueOf(5.0), BigDecimal.valueOf(3.0),
BigDecimal.valueOf(2.0),
- BigDecimal.valueOf(1.0), null));
-
- loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
- assertEquals(inteterestRefund,
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
-
- verifyTRJournalEntries(loanTransactionId, journalEntry(5.0,
loansReceivableAccount, "CREDIT"),
- journalEntry(3.0, interestReceivableAccount, "CREDIT"),
journalEntry(2.0, feeReceivableAccount, "CREDIT"),
- journalEntry(1.0, penaltyReceivableAccount, "CREDIT"),
journalEntry(11.0, overpaymentAccount, "CREDIT"),
- journalEntry(22.0, interestIncomeAccount, "DEBIT"));
- });
- }
-
- // UC2: (Internal case) Generate a totalInterestRefund using Advanced
payment allocation with Interest Refund
- // options and Apply
- // Interest Refund transaction when the Loan Account is ChargeBack
- // 1. Create a Loan product with Adv. Pment. Alloc. and with Accrual and
Interest Refund
- // 2. Submit, Approve and Disburse a Loan account
- // 3. Apply the MERCHANT_ISSUED_REFUND transaction to validate
totalInterestRefund different than Zero
- // 4- Apply the INTEREST_REFUND transaction to validate the Journal
Entries generated
- @Test
- public void uc2() {
- final String operationDate = "1 January 2024";
- AtomicLong createdLoanId = new AtomicLong();
- runAt(operationDate, () -> {
- Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
- PostLoanProductsRequest product =
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
-
.interestRatePerPeriod(108.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS)
-
.interestRateFrequencyType(YEARS).daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.DAYS_360)
- .numberOfRepayments(4)//
- .maxInterestRatePerPeriod((double) 110)//
- .repaymentEvery(1)//
- .repaymentFrequencyType(1L)//
- .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, 1000.0, 4)
- .interestRatePerPeriod(BigDecimal.valueOf(108.0));
-
- applicationRequest =
applicationRequest.interestCalculationPeriodType(DAYS)
-
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
-
- PostLoansResponse loanResponse =
loanTransactionHelper.applyLoan(applicationRequest);
- createdLoanId.set(loanResponse.getLoanId());
-
- loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
- new
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
- .approvedOnDate("1 January 2024").locale("en"));
-
- loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
- new PostLoansLoanIdRequest().actualDisbursementDate("1
January 2024").dateFormat(DATETIME_PATTERN)
-
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
-
- // After Disbursement we are expecting zero interest refund
- GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
- assertEquals(BigDecimal.ZERO,
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
- });
-
- runAt("1 February 2024", () -> {
- // After Interest refund transaction we are expecting non zero
interest refund
- GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
- LOG.info("value {}",
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
- assertEquals(BigDecimal.ZERO,
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
-
- Long repayment1TransactionId =
addRepaymentForLoan(createdLoanId.get(), 250.0, "1 February 2024");
- chargeOffLoan(createdLoanId.get(), "1 February 2024");
- });
-
- runAt("10 February 2024", () -> {
- // After Interest refund transaction we are expecting non zero
interest refund
- GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
- LOG.info("value {}",
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
- LOG.info("chargedOffOnDate {}",
loanDetails.getTimeline().getChargedOffOnDate());
- assertEquals(BigDecimal.ZERO,
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
-
- final BigDecimal inteterestRefund = BigDecimal.valueOf(11);
- final Long loanTransactionId =
loanTransactionHelper.applyInterestRefundLoanTransaction(requestSpec,
responseSpec,
- createdLoanId.get(),
buildJsonBody(BigDecimal.valueOf(5.0), BigDecimal.valueOf(3.0),
BigDecimal.valueOf(2.0),
- BigDecimal.valueOf(1.0), null));
-
- loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
- assertEquals(inteterestRefund,
loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
-
- verifyTRJournalEntries(loanTransactionId, journalEntry(11.0,
interestIncomeChargeOffAccount, "CREDIT"),
- journalEntry(11.0, overpaymentAccount, "CREDIT"),
journalEntry(22.0, interestIncomeAccount, "DEBIT"));
- });
- }
-
@Test
public void testMerchantIssuedRefundCreatesAndReversesInterestRefund() {
runAt("01 July 2024", () -> {
@@ -292,20 +129,6 @@ public class LoanRefundTransactionTest extends
BaseLoanIntegrationTest {
});
}
- private String buildJsonBody(final BigDecimal principal, final BigDecimal
interest, final BigDecimal feeCharges,
- final BigDecimal penaltyCharges, final BigDecimal overpayment) {
- final HashMap<String, BigDecimal> map = new HashMap<>();
- map.put("principal", principal);
- map.put("interest", interest);
- map.put("feeCharges", feeCharges);
- map.put("penaltyCharges", penaltyCharges);
- if (overpayment != null) {
- map.put("overpayment", overpayment);
- }
-
- return new Gson().toJson(map);
- }
-
private Long createLoanForMerchantIssuedRefundWithInterestRefund(Long
clientId) {
return createLoanForRefundWithInterestRefund(clientId,
"MERCHANT_ISSUED_REFUND");
}