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 8d43565b0 FINERACT-2081: Loan account summary interest refund field
8d43565b0 is described below
commit 8d43565b09dd634929d5ad49f97ec5aa1696c4a9
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Wed Jul 24 21:36:54 2024 -0600
FINERACT-2081: Loan account summary interest refund field
---
.../src/main/avro/loan/v1/LoanSummaryDataV1.avsc | 8 ++
.../loanaccount/api/LoanApiConstants.java | 17 ++++
.../loanaccount/data/LoanSummaryData.java | 97 ++++++++++++----------
.../loanaccount/data/LoanTransactionBalance.java | 32 +++++++
.../domain/LoanSummaryBalancesRepository.java | 71 ++++++++++++++++
.../loan/LoanBusinessEventSerializer.java | 10 ++-
.../loanaccount/api/LoansApiResource.java | 7 +-
.../loanaccount/api/LoansApiResourceSwagger.java | 2 +
...PaymentAllocationLoanRepaymentScheduleTest.java | 61 ++++++++++++++
.../LoanTransactionSummaryTest.java | 9 +-
10 files changed, 258 insertions(+), 56 deletions(-)
diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanSummaryDataV1.avsc
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanSummaryDataV1.avsc
index 1dfffa539..5e74118cf 100644
--- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanSummaryDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanSummaryDataV1.avsc
@@ -450,6 +450,14 @@
"null",
"bigdecimal"
]
+ },
+ {
+ "default": null,
+ "name": "totalInterestRefund",
+ "type": [
+ "null",
+ "bigdecimal"
+ ]
}
]
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
index f2cb9a7cd..22f3935ba 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
@@ -18,6 +18,9 @@
*/
package org.apache.fineract.portfolio.loanaccount.api;
+import java.util.List;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+
public interface LoanApiConstants {
String fixedEmiAmountParameterName = "fixedEmiAmount";
@@ -173,4 +176,18 @@ public interface LoanApiConstants {
// Data Validator names
String LOAN_FRAUD_DATAVALIDATOR_PREFIX = "loans.fraud";
+
+ // Loan Summary Transaction Types
+ List<Integer> LOAN_SUMMARY_TRANSACTION_TYPES =
List.of(LoanTransactionType.CHARGE_ADJUSTMENT.getValue(), //
+ LoanTransactionType.CHARGEBACK.getValue(), //
+ LoanTransactionType.CREDIT_BALANCE_REFUND.getValue(), //
+ LoanTransactionType.DOWN_PAYMENT.getValue(), //
+ LoanTransactionType.GOODWILL_CREDIT.getValue(), //
+ LoanTransactionType.INTEREST_PAYMENT_WAIVER.getValue(), //
+ LoanTransactionType.INTEREST_REFUND.getValue(), //
+ LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue(), //
+ LoanTransactionType.PAYOUT_REFUND.getValue(), //
+ LoanTransactionType.REPAYMENT.getValue() //
+ );
+
}
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 a2dccd031..af9e1b9f1 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
@@ -21,6 +21,7 @@ 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;
@@ -30,7 +31,6 @@ 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;
/**
* Immutable data object representing loan summary information.
@@ -96,6 +96,7 @@ public class LoanSummaryData {
private BigDecimal totalRepaymentTransaction;
private BigDecimal totalRepaymentTransactionReversed;
private BigDecimal totalInterestPaymentWaiver;
+ private BigDecimal totalInterestRefund;
private final Long chargeOffReasonId;
private final String chargeOffReason;
@@ -103,7 +104,7 @@ public class LoanSummaryData {
private BigDecimal totalUnpaidAccruedNotDueInterest;
public static LoanSummaryData withTransactionAmountsSummary(final
LoanSummaryData defaultSummaryData,
- final Collection<LoanTransactionData> loanTransactions, final
LoanScheduleData repaymentSchedule) {
+ final LoanScheduleData repaymentSchedule, final
Collection<LoanTransactionBalance> loanTransactionBalances) {
BigDecimal totalMerchantRefund = BigDecimal.ZERO;
BigDecimal totalMerchantRefundReversed = BigDecimal.ZERO;
@@ -119,32 +120,44 @@ public class LoanSummaryData {
BigDecimal totalRepaymentTransaction = BigDecimal.ZERO;
BigDecimal totalRepaymentTransactionReversed = BigDecimal.ZERO;
BigDecimal totalInterestPaymentWaiver = BigDecimal.ZERO;
+ BigDecimal totalInterestRefund = BigDecimal.ZERO;
BigDecimal totalUnpaidAccruedDueInterest = BigDecimal.ZERO;
BigDecimal totalUnpaidAccruedNotDueInterest = BigDecimal.ZERO;
- if (!CollectionUtils.isEmpty(loanTransactions)) {
+ totalChargeAdjustment =
fetchLoanTransactionBalanceByType(loanTransactionBalances,
+ LoanTransactionType.CHARGE_ADJUSTMENT.getValue());
+ totalChargeAdjustmentReversed =
fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
+ LoanTransactionType.CHARGE_ADJUSTMENT.getValue());
- totalMerchantRefund =
computeTotalAmountForNonReversedTransactions(LoanTransactionType.MERCHANT_ISSUED_REFUND,
- loanTransactions);
- totalMerchantRefundReversed =
computeTotalAmountForReversedTransactions(LoanTransactionType.MERCHANT_ISSUED_REFUND,
- loanTransactions);
- totalPayoutRefund =
computeTotalAmountForNonReversedTransactions(LoanTransactionType.PAYOUT_REFUND,
loanTransactions);
- totalPayoutRefundReversed =
computeTotalAmountForReversedTransactions(LoanTransactionType.PAYOUT_REFUND,
loanTransactions);
- totalGoodwillCredit =
computeTotalAmountForNonReversedTransactions(LoanTransactionType.GOODWILL_CREDIT,
loanTransactions);
- totalGoodwillCreditReversed =
computeTotalAmountForReversedTransactions(LoanTransactionType.GOODWILL_CREDIT,
loanTransactions);
- totalChargeAdjustment =
computeTotalAmountForNonReversedTransactions(LoanTransactionType.CHARGE_ADJUSTMENT,
loanTransactions);
- totalChargeAdjustmentReversed =
computeTotalAmountForReversedTransactions(LoanTransactionType.CHARGE_ADJUSTMENT,
- loanTransactions);
- totalChargeback =
computeTotalAmountForNonReversedTransactions(LoanTransactionType.CHARGEBACK,
loanTransactions);
- totalCreditBalanceRefund =
computeTotalAmountForNonReversedTransactions(LoanTransactionType.CREDIT_BALANCE_REFUND,
- loanTransactions);
- totalCreditBalanceRefundReversed =
computeTotalAmountForReversedTransactions(LoanTransactionType.CREDIT_BALANCE_REFUND,
- loanTransactions);
- totalRepaymentTransaction =
computeTotalRepaymentTransactionAmount(loanTransactions);
- totalRepaymentTransactionReversed =
computeTotalAmountForReversedTransactions(LoanTransactionType.REPAYMENT,
loanTransactions);
- totalInterestPaymentWaiver =
computeTotalAmountForNonReversedTransactions(LoanTransactionType.INTEREST_PAYMENT_WAIVER,
- loanTransactions);
- }
+ 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) {
// Accrued Due Interest on Past due installments
@@ -200,33 +213,27 @@ public class LoanSummaryData {
.totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed).totalRepaymentTransaction(totalRepaymentTransaction)
.totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).totalInterestPaymentWaiver(totalInterestPaymentWaiver)
.totalUnpaidAccruedDueInterest(totalUnpaidAccruedDueInterest)
-
.totalUnpaidAccruedNotDueInterest(totalUnpaidAccruedNotDueInterest).build();
+
.totalUnpaidAccruedNotDueInterest(totalUnpaidAccruedNotDueInterest).totalInterestRefund(totalInterestRefund).build();
}
- public static LoanSummaryData withOnlyCurrencyData(CurrencyData
currencyData) {
- return LoanSummaryData.builder().currency(currencyData).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
computeTotalAmountForReversedTransactions(LoanTransactionType transactionType,
- Collection<LoanTransactionData> loanTransactions) {
- return loanTransactions.stream().filter(
- transaction ->
transaction.getType().getCode().equals(transactionType.getCode()) &&
transaction.getReversedOnDate() != null)
- .map(txn -> txn.getAmount()).reduce(BigDecimal.ZERO,
BigDecimal::add);
+ 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;
}
- private static BigDecimal
computeTotalAmountForNonReversedTransactions(LoanTransactionType
transactionType,
- Collection<LoanTransactionData> loanTransactions) {
- return loanTransactions.stream().filter(
- transaction ->
transaction.getType().getCode().equals(transactionType.getCode()) &&
transaction.getReversedOnDate() == null)
- .map(txn -> txn.getAmount()).reduce(BigDecimal.ZERO,
BigDecimal::add);
- }
-
- private static BigDecimal
computeTotalRepaymentTransactionAmount(Collection<LoanTransactionData>
loanTransactions) {
- BigDecimal totalRepaymentTransaction =
computeTotalAmountForNonReversedTransactions(LoanTransactionType.REPAYMENT,
- loanTransactions);
- BigDecimal totalDownPaymentTransaction =
computeTotalAmountForNonReversedTransactions(LoanTransactionType.DOWN_PAYMENT,
- loanTransactions);
- return totalRepaymentTransaction.add(totalDownPaymentTransaction);
+ public static LoanSummaryData withOnlyCurrencyData(CurrencyData
currencyData) {
+ return LoanSummaryData.builder().currency(currencyData).build();
}
private static BigDecimal
computeTotalAccruedDueInterestAmount(Collection<LoanSchedulePeriodData>
periods) {
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionBalance.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionBalance.java
new file mode 100644
index 000000000..bd9b2016f
--- /dev/null
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionBalance.java
@@ -0,0 +1,32 @@
+/**
+ * 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.data;
+
+import java.math.BigDecimal;
+import lombok.Data;
+
+@Data
+public class LoanTransactionBalance {
+
+ private final Integer transactionType;
+ private final boolean reversed;
+ private final boolean manuallyAdjustedOrReversed;
+ private final BigDecimal amount;
+
+}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryBalancesRepository.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryBalancesRepository.java
new file mode 100644
index 000000000..6890aa989
--- /dev/null
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummaryBalancesRepository.java
@@ -0,0 +1,71 @@
+/**
+ * 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.domain;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.TypedQuery;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Path;
+import jakarta.persistence.criteria.Predicate;
+import jakarta.persistence.criteria.Root;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.jpa.CriteriaQueryFactory;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionBalance;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Repository;
+
+@Repository
+@RequiredArgsConstructor
+public class LoanSummaryBalancesRepository {
+
+ private final EntityManager entityManager;
+ private final CriteriaQueryFactory criteriaQueryFactory;
+
+ public Collection<LoanTransactionBalance>
retrieveLoanSummaryBalancesByTransactionType(final Long loanId,
+ final List<Integer> transactionTypes) {
+ CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+ CriteriaQuery<LoanTransactionBalance> query =
cb.createQuery(LoanTransactionBalance.class);
+
+ Root<LoanTransaction> root = query.from(LoanTransaction.class);
+
+ Specification<LoanTransaction> spec = (r, q, builder) -> {
+ Path<Loan> la = r.get("loan");
+
+ List<Predicate> predicates = new ArrayList<>();
+ predicates.add(cb.equal(la.get("id"), loanId)); // Loan
+ predicates.add(r.get("typeOf").in(transactionTypes)); //
Transaction Type
+
+ return cb.and(predicates.toArray(new Predicate[0]));
+ };
+
+ criteriaQueryFactory.applySpecificationToCriteria(root, spec, query);
+
+ query.groupBy(root.get("typeOf"), root.get("reversed"),
root.get("manuallyAdjustedOrReversed"));
+ query.select(cb.construct(LoanTransactionBalance.class,
root.get("typeOf"), root.get("reversed"),
+ root.get("manuallyAdjustedOrReversed"),
cb.sum(root.get("amount"))));
+
+ TypedQuery<LoanTransactionBalance> queryToExecute =
entityManager.createQuery(query);
+ return queryToExecute.getResultList();
+ }
+
+}
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 e74cbf3c8..72aa76396 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
@@ -31,11 +31,12 @@ import
org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusines
import
org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanAccountDataMapper;
import
org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
import
org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
+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.data.LoanTransactionData;
+import
org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryBalancesRepository;
import
org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import
org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.springframework.stereotype.Component;
@@ -49,6 +50,7 @@ public class LoanBusinessEventSerializer implements
BusinessEventSerializer {
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
private final DelinquencyReadPlatformService
delinquencyReadPlatformService;
private final LoanInstallmentLevelDelinquencyEventProducer
installmentLevelDelinquencyEventProducer;
+ private final LoanSummaryBalancesRepository loanSummaryBalancesRepository;
@Override
public <T> boolean canSerialize(BusinessEvent<T> event) {
@@ -72,9 +74,9 @@ public class LoanBusinessEventSerializer implements
BusinessEventSerializer {
data.setDelinquent(delinquentData);
if (data.getSummary() != null) {
- final Collection<LoanTransactionData> currentLoanTransactions =
service.retrieveLoanTransactions(loanId);
- data.setSummary(
-
LoanSummaryData.withTransactionAmountsSummary(data.getSummary(),
currentLoanTransactions, data.getRepaymentSchedule()));
+
data.setSummary(LoanSummaryData.withTransactionAmountsSummary(data.getSummary(),
data.getRepaymentSchedule(),
+
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(loanId,
+ LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
} 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 468086871..d6b284a42 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
@@ -130,6 +130,7 @@ import
org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
import
org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import
org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryBalancesRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import
org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import
org.apache.fineract.portfolio.loanaccount.exception.LoanTemplateTypeRequiredException;
@@ -281,6 +282,7 @@ public class LoansApiResource {
private final DefaultToApiJsonSerializer<LoanDelinquencyTagHistoryData>
jsonSerializerTagHistory;
private final DelinquencyReadPlatformService
delinquencyReadPlatformService;
private final SqlValidator sqlValidator;
+ private final LoanSummaryBalancesRepository loanSummaryBalancesRepository;
/*
* This template API is used for loan approval, ideally this should be
invoked on loan that are pending for
@@ -1114,8 +1116,9 @@ public class LoansApiResource {
// updating summary with transaction amounts summary
if (loanBasicDetails.getSummary() != null) {
- loanBasicDetails.setSummary(
-
LoanSummaryData.withTransactionAmountsSummary(loanBasicDetails.getSummary(),
currentLoanRepayments, repaymentSchedule));
+
loanBasicDetails.setSummary(LoanSummaryData.withTransactionAmountsSummary(loanBasicDetails.getSummary(),
repaymentSchedule,
+
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(loanBasicDetails.getId(),
+ LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
}
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 f8a26679d..44dbe6621 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
@@ -643,6 +643,8 @@ final class LoansApiResourceSwagger {
public BigDecimal totalUnpaidAccruedDueInterest;
@Schema(example = "0.000000")
public BigDecimal totalUnpaidAccruedNotDueInterest;
+ @Schema(example = "0.000000")
+ public BigDecimal totalInterestRefund;
public Set<GetLoansLoanIdOverdueCharges> overdueCharges;
@Schema(example = "1")
public Long chargeOffReasonId;
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 e0224ef3d..f94bca231 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
@@ -4836,6 +4836,67 @@ public class
AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
});
}
+ // UC146: Validate totalInterestRefund using Advanced payment allocation
with Interest Refund options
+ // 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 tranasaction to validate
totalInterestRefund different than Zero
+ @Test
+ public void uc146() {
+ AtomicLong createdLoanId = new AtomicLong();
+ runAt("1 January 2024", () -> {
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+ final ArrayList<String> interestRefundTypes = new
ArrayList<String>();
+ interestRefundTypes.add("PAYOUT_REFUND");
+ interestRefundTypes.add("MERCHANT_ISSUED_REFUND");
+ PostLoanProductsRequest product =
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+
.interestRatePerPeriod(12.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
+
.daysInMonthType(DaysInMonthType.ACTUAL.getValue()).daysInYearType(DaysInYearType.DAYS_365.getValue())
+ .numberOfRepayments(4)//
+ .repaymentEvery(5)//
+ .repaymentFrequencyType(0L)//
+ .allowPartialPeriodInterestCalcualtion(false)//
+ .multiDisburseLoan(false)//
+ .disallowExpectedDisbursements(null)//
+ .allowApprovedDisbursedAmountsOverApplied(null)//
+ .overAppliedCalculationType(null)//
+ .overAppliedNumber(null)//
+ .installmentAmountInMultiplesOf(null)//
+ .supportedInterestRefundTypes(interestRefundTypes) //
+ ;//
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(product);
+ PostLoansRequest applicationRequest = applyLoanRequest(clientId,
loanProductResponse.getResourceId(), "1 January 2024", 1000.0,
+ 4);
+
+ 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 January 2024", () -> {
+
loanTransactionHelper.makeMerchantIssuedRefund(createdLoanId.get(), new
PostLoansLoanIdTransactionsRequest()
+ .dateFormat(DATETIME_PATTERN).transactionDate("10 January
2024").locale("en").transactionAmount(300.0));
+
+ // 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());
+ });
+ }
+
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/LoanTransactionSummaryTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionSummaryTest.java
index 5ce326a9b..d15f5e4a3 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionSummaryTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionSummaryTest.java
@@ -31,6 +31,7 @@ import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.HashMap;
import java.util.UUID;
+import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
@@ -52,12 +53,11 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+@Slf4j
@ExtendWith(LoanTestLifecycleExtension.class)
public class LoanTransactionSummaryTest {
private ResponseSpecification responseSpec;
- private ResponseSpecification responseSpecErr400;
- private ResponseSpecification responseSpecErr503;
private RequestSpecification requestSpec;
private ClientHelper clientHelper;
private LoanTransactionHelper loanTransactionHelper;
@@ -69,8 +69,6 @@ public class LoanTransactionSummaryTest {
this.requestSpec = new
RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new
ResponseSpecBuilder().expectStatusCode(200).build();
- this.responseSpecErr400 = new
ResponseSpecBuilder().expectStatusCode(400).build();
- this.responseSpecErr503 = new
ResponseSpecBuilder().expectStatusCode(503).build();
this.loanTransactionHelper = new
LoanTransactionHelper(this.requestSpec, this.responseSpec);
this.clientHelper = new ClientHelper(this.requestSpec,
this.responseSpec);
}
@@ -86,13 +84,14 @@ public class LoanTransactionSummaryTest {
delinquencyBucketId);
// Client and Loan account creation
-
final Integer clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+ log.info("Client Id {}", clientId);
final GetLoanProductsProductIdResponse getLoanProductsProductResponse
= createLoanProduct(loanTransactionHelper,
delinquencyBucketId);
assertNotNull(getLoanProductsProductResponse);
final Integer loanId = createLoanAccount(clientId,
getLoanProductsProductResponse.getId(), loanExternalIdStr);
+ log.info("Loan Id {}", loanId);
// make Repayments
final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 =
loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,