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,

Reply via email to