This is an automated email from the ASF dual-hosted git repository.

arnold 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 0a2db35f8 FINERACT-1839: Reversal on the loan account after CBR / 
Chargeback
0a2db35f8 is described below

commit 0a2db35f8d791cf3d31b20a53862c99b82a4fbde
Author: Adam Saghy <[email protected]>
AuthorDate: Thu Feb 2 20:34:03 2023 +0100

    FINERACT-1839: Reversal on the loan account after CBR / Chargeback
---
 .../service/LoanDelinquencyDomainServiceImpl.java  |  68 +++---
 .../portfolio/loanaccount/domain/Loan.java         |  22 +-
 .../domain/LoanRepaymentScheduleInstallment.java   |   6 +-
 .../loanaccount/domain/LoanTransaction.java        |   5 +-
 ...tLoanRepaymentScheduleTransactionProcessor.java | 267 +++++++++++++++------
 .../LoanWritePlatformServiceJpaRepositoryImpl.java |   3 +-
 .../LoanDelinquencyDomainServiceTest.java          |   4 +
 .../ClientLoanIntegrationTest.java                 | 229 ++++++++++++++++++
 8 files changed, 492 insertions(+), 112 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java
index 1d569c90b..72086c72f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/LoanDelinquencyDomainServiceImpl.java
@@ -20,6 +20,8 @@ package org.apache.fineract.portfolio.delinquency.service;
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.util.List;
+import java.util.Objects;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -27,7 +29,6 @@ import 
org.apache.fineract.portfolio.loanaccount.data.CollectionData;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
-import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -43,17 +44,19 @@ public class LoanDelinquencyDomainServiceImpl implements 
LoanDelinquencyDomainSe
         final MonetaryCurrency loanCurrency = loan.getCurrency();
         LocalDate overdueSinceDate = null;
         CollectionData collectionData = CollectionData.template();
-        BigDecimal amountAvailable = BigDecimal.ZERO;
+        BigDecimal amountAvailable;
         BigDecimal outstandingAmount = BigDecimal.ZERO;
         boolean oldestOverdueInstallment = false;
         boolean overdueSinceDateWasSet = false;
         boolean firstNotYetDueInstallment = false;
+        LoanRepaymentScheduleInstallment latestInstallment = 
loan.getLastLoanRepaymentScheduleInstallment();
+
+        List<LoanTransaction> chargebackTransactions = 
loan.getLoanTransactions(LoanTransaction::isChargeback);
 
         log.debug("Loan id {} with {} installments", loan.getId(), 
loan.getRepaymentScheduleInstallments().size());
         // Get the oldest overdue installment if exists one
         for (LoanRepaymentScheduleInstallment installment : 
loan.getRepaymentScheduleInstallments()) {
             if (!installment.isObligationsMet()) {
-
                 if (installment.getDueDate().isBefore(businessDate)) {
                     log.debug("Loan Id: {} with installment {} due date {}", 
loan.getId(), installment.getInstallmentNumber(),
                             installment.getDueDate());
@@ -66,10 +69,17 @@ public class LoanDelinquencyDomainServiceImpl implements 
LoanDelinquencyDomainSe
 
                         amountAvailable = 
installment.getTotalPaid(loanCurrency).getAmount();
 
-                        for (LoanTransactionToRepaymentScheduleMapping 
mappingInstallment : installment
-                                
.getLoanTransactionToRepaymentScheduleMappings()) {
-                            final LoanTransaction loanTransaction = 
mappingInstallment.getLoanTransaction();
-                            if (loanTransaction.isChargeback()) {
+                        boolean isLatestInstallment = 
Objects.equals(installment.getId(), latestInstallment.getId());
+                        for (LoanTransaction loanTransaction : 
chargebackTransactions) {
+                            boolean 
isLoanTransactionIsOnOrAfterInstallmentFromDate = 
loanTransaction.getTransactionDate().isEqual(
+                                    installment.getFromDate()) || 
loanTransaction.getTransactionDate().isAfter(installment.getFromDate());
+                            boolean 
isLoanTransactionIsBeforeNotLastInstallmentDueDate = !isLatestInstallment
+                                    && 
loanTransaction.getTransactionDate().isBefore(installment.getDueDate());
+                            boolean 
isLoanTransactionIsOnOrBeforeLastInstallmentDueDate = isLatestInstallment
+                                    && 
(loanTransaction.getTransactionDate().isEqual(installment.getDueDate())
+                                            || 
loanTransaction.getTransactionDate().isBefore(installment.getDueDate()));
+                            if 
(isLoanTransactionIsOnOrAfterInstallmentFromDate && 
(isLoanTransactionIsBeforeNotLastInstallmentDueDate
+                                    || 
isLoanTransactionIsOnOrBeforeLastInstallmentDueDate)) {
                                 amountAvailable = 
amountAvailable.subtract(loanTransaction.getAmount());
                                 if (amountAvailable.compareTo(BigDecimal.ZERO) 
< 0) {
                                     overdueSinceDate = 
loanTransaction.getTransactionDate();
@@ -78,29 +88,33 @@ public class LoanDelinquencyDomainServiceImpl implements 
LoanDelinquencyDomainSe
                             }
                         }
                     }
-                }
-            } else if (!firstNotYetDueInstallment) {
-                log.debug("Loan Id: {} with installment {} due date {}", 
loan.getId(), installment.getInstallmentNumber(),
-                        installment.getDueDate());
-                firstNotYetDueInstallment = true;
-                amountAvailable = 
installment.getTotalPaid(loanCurrency).getAmount();
-                log.debug("Amount available {}", amountAvailable);
-                for (LoanTransactionToRepaymentScheduleMapping 
mappingInstallment : installment
-                        .getLoanTransactionToRepaymentScheduleMappings()) {
-                    final LoanTransaction loanTransaction = 
mappingInstallment.getLoanTransaction();
-                    if (loanTransaction.isChargeback() && 
loanTransaction.getTransactionDate().isBefore(businessDate)) {
-                        log.debug("Loan CB Transaction: {} {} {}", 
loanTransaction.getId(), loanTransaction.getTransactionDate(),
-                                loanTransaction.getAmount());
-                        amountAvailable = 
amountAvailable.subtract(loanTransaction.getAmount());
-                        if (amountAvailable.compareTo(BigDecimal.ZERO) < 0 && 
!overdueSinceDateWasSet) {
-                            overdueSinceDate = 
loanTransaction.getTransactionDate();
-                            overdueSinceDateWasSet = true;
+                } else if (!firstNotYetDueInstallment) {
+                    log.debug("Loan Id: {} with installment {} due date {}", 
loan.getId(), installment.getInstallmentNumber(),
+                            installment.getDueDate());
+                    firstNotYetDueInstallment = true;
+                    amountAvailable = 
installment.getTotalPaid(loanCurrency).getAmount();
+                    log.debug("Amount available {}", amountAvailable);
+                    for (LoanTransaction loanTransaction : 
chargebackTransactions) {
+                        boolean 
isLoanTransactionIsOnOrAfterInstallmentFromDate = 
loanTransaction.getTransactionDate().isEqual(
+                                installment.getFromDate()) || 
loanTransaction.getTransactionDate().isAfter(installment.getFromDate());
+                        boolean isLoanTransactionIsBeforeInstallmentDueDate = 
loanTransaction.getTransactionDate()
+                                .isBefore(installment.getDueDate());
+                        boolean isLoanTransactionIsBeforeBusinessDate = 
loanTransaction.getTransactionDate().isBefore(businessDate);
+                        if (isLoanTransactionIsOnOrAfterInstallmentFromDate && 
isLoanTransactionIsBeforeInstallmentDueDate
+                                && isLoanTransactionIsBeforeBusinessDate) {
+                            log.debug("Loan CB Transaction: {} {} {}", 
loanTransaction.getId(), loanTransaction.getTransactionDate(),
+                                    loanTransaction.getAmount());
+                            amountAvailable = 
amountAvailable.subtract(loanTransaction.getAmount());
+                            if (amountAvailable.compareTo(BigDecimal.ZERO) < 0 
&& !overdueSinceDateWasSet) {
+                                overdueSinceDate = 
loanTransaction.getTransactionDate();
+                                overdueSinceDateWasSet = true;
+                            }
                         }
                     }
-                }
 
-                if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) {
-                    outstandingAmount = 
outstandingAmount.add(amountAvailable.abs());
+                    if (amountAvailable.compareTo(BigDecimal.ZERO) < 0) {
+                        outstandingAmount = 
outstandingAmount.add(amountAvailable.abs());
+                    }
                 }
             }
         }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index e9ccab2fd..a1307afc2 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -44,6 +44,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Predicate;
 import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Embedded;
@@ -1346,8 +1347,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             }
             installment.updateAccrualPortion(interest, fee, penality);
         }
-        LoanRepaymentScheduleInstallment lastInstallment = 
getRepaymentScheduleInstallments()
-                .get(getRepaymentScheduleInstallments().size() - 1);
+        LoanRepaymentScheduleInstallment lastInstallment = 
getLastLoanRepaymentScheduleInstallment();
         for (LoanTransaction loanTransaction : accruals) {
             if 
(loanTransaction.getTransactionDate().isAfter(lastInstallment.getDueDate()) && 
!loanTransaction.isReversed()) {
                 loanTransaction.reverse();
@@ -3798,9 +3798,10 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             if (loanTransaction.isReversed()) {
                 continue;
             }
-            if ((loanTransaction.isRefund() || 
loanTransaction.isRefundForActiveLoan() || 
loanTransaction.isCreditBalanceRefund()
-                    || loanTransaction.isChargeback())) {
+            if (loanTransaction.isRefund() || 
loanTransaction.isRefundForActiveLoan()) {
                 totalPaidInRepayments = 
totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
+            } else if (loanTransaction.isCreditBalanceRefund() || 
loanTransaction.isChargeback()) {
+                totalPaidInRepayments = 
totalPaidInRepayments.minus(loanTransaction.getOverPaymentPortion(currency));
             }
         }
 
@@ -5804,7 +5805,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
             if (loanTransaction.isDisbursement() || 
loanTransaction.isIncomePosting()) {
                 outstanding = 
outstanding.plus(loanTransaction.getAmount(getCurrency()));
                 
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
-            } else if (loanTransaction.isChargeback()) {
+            } else if (loanTransaction.isChargeback() || 
loanTransaction.isCreditBalanceRefund()) {
                 Money transactionOutstanding = 
loanTransaction.getAmount(getCurrency());
                 if 
(!loanTransaction.getOverPaymentPortion(getCurrency()).isZero()) {
                     transactionOutstanding = 
loanTransaction.getAmount(getCurrency())
@@ -6277,9 +6278,6 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         final LoanRepaymentScheduleTransactionProcessor 
loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
                 .determineProcessor(this.transactionProcessingStrategyCode);
         final Money overpaidAmount = calculateTotalOverpayment(); // Before 
Transaction
-        if (overpaidAmount.isGreaterThanZero()) {
-            chargebackTransaction.setOverPayments(overpaidAmount);
-        }
 
         if (chargebackTransaction.isNotZero(loanCurrency())) {
             addLoanTransaction(chargebackTransaction);
@@ -7018,4 +7016,12 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         return this.chargedOff;
     }
 
+    public LoanRepaymentScheduleInstallment 
getLastLoanRepaymentScheduleInstallment() {
+        return 
getRepaymentScheduleInstallments().get(getRepaymentScheduleInstallments().size()
 - 1);
+    }
+
+    public List<LoanTransaction> 
getLoanTransactions(Predicate<LoanTransaction> predicate) {
+        return getLoanTransactions().stream().filter(predicate).toList();
+    }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 9e864bb51..ab2b6326d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -393,6 +393,10 @@ public class LoanRepaymentScheduleInstallment extends 
AbstractAuditableWithUTCDa
 
         this.obligationsMet = false;
         this.obligationsMetOnDate = null;
+        if (this.credits != null) {
+            this.principal = this.principal.subtract(this.credits);
+            this.credits = null;
+        }
     }
 
     public void resetAccrualComponents() {
@@ -836,7 +840,7 @@ public class LoanRepaymentScheduleInstallment extends 
AbstractAuditableWithUTCDa
         }
     }
 
-    public void updateDueChargeback(final LocalDate transactionDate, final 
Money transactionAmount) {
+    public void updateDueAndCredits(final LocalDate transactionDate, final 
Money transactionAmount) {
         updateDueDate(transactionDate);
         addToCredits(transactionAmount.getAmount());
         addToPrincipal(transactionDate, transactionAmount);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 9a0fbcc60..2ae376090 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -291,9 +291,8 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
 
     public static LoanTransaction creditBalanceRefund(final Loan loan, final 
Office office, final Money amount, final LocalDate paymentDate,
             final ExternalId externalId) {
-        final PaymentDetail paymentDetail = null;
-        return new LoanTransaction(loan, office, 
LoanTransactionType.CREDIT_BALANCE_REFUND, paymentDetail, amount.getAmount(), 
paymentDate,
-                externalId);
+        return new LoanTransaction(loan, office, 
LoanTransactionType.CREDIT_BALANCE_REFUND.getValue(), paymentDate, 
amount.getAmount(),
+                null, null, null, null, amount.getAmount(), false, null, 
externalId);
     }
 
     public static LoanTransaction refundForActiveLoan(final Office office, 
final Money amount, final PaymentDetail paymentDetail,
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index ecb992846..46d79637a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -25,7 +25,9 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import org.apache.fineract.interoperation.util.MathUtil;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
@@ -34,7 +36,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
-import 
org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -93,7 +94,7 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
         wrapper.reprocess(currency, disbursementDate, installments, charges);
 
         final ChangedTransactionDetail changedTransactionDetail = new 
ChangedTransactionDetail();
-        final List<LoanTransaction> transactionstoBeProcessed = new 
ArrayList<>();
+        final List<LoanTransaction> transactionsToBeProcessed = new 
ArrayList<>();
         for (final LoanTransaction loanTransaction : 
transactionsPostDisbursement) {
             if (loanTransaction.isChargePayment()) {
                 List<LoanChargePaidDetail> chargePaidDetails = new 
ArrayList<>();
@@ -147,11 +148,11 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
                 }
 
             } else {
-                transactionstoBeProcessed.add(loanTransaction);
+                transactionsToBeProcessed.add(loanTransaction);
             }
         }
 
-        for (final LoanTransaction loanTransaction : 
transactionstoBeProcessed) {
+        for (final LoanTransaction loanTransaction : 
transactionsToBeProcessed) {
             // TODO: analyze and remove this
             if 
(!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN))
 {
                 final Comparator<LoanRepaymentScheduleInstallment> byDate = 
new Comparator<LoanRepaymentScheduleInstallment>() {
@@ -188,13 +189,7 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
                         
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
                                 
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
                     } else {
-                        loanTransaction.reverse();
-                        loanTransaction.updateExternalId(null);
-                        
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
-                        // Adding Replayed relation from newly created 
transaction to reversed transaction
-                        
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
-                                loanTransaction, 
LoanTransactionRelationTypeEnum.REPLAYED));
-                        
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(),
 newLoanTransaction);
+                        createNewTransactionIfNecessary(loanTransaction, 
newLoanTransaction, currency, changedTransactionDetail);
                     }
                 }
 
@@ -203,13 +198,200 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
                 handleWriteOff(loanTransaction, currency, installments);
             } else if (loanTransaction.isRefundForActiveLoan()) {
                 loanTransaction.resetDerivedComponents();
-
                 handleRefund(loanTransaction, currency, installments, charges);
+            } else if (loanTransaction.isCreditBalanceRefund()) {
+                recalculateCreditTransaction(changedTransactionDetail, 
loanTransaction, currency, installments, transactionsToBeProcessed);
+            } else if (loanTransaction.isChargeback()) {
+                recalculateCreditTransaction(changedTransactionDetail, 
loanTransaction, currency, installments, transactionsToBeProcessed);
+                
reprocessChargebackTransactionRelation(changedTransactionDetail, 
transactionsToBeProcessed);
             }
         }
+        reprocessInstallments(installments, currency);
+
         return changedTransactionDetail;
     }
 
+    private void 
reprocessChargebackTransactionRelation(ChangedTransactionDetail 
changedTransactionDetail,
+            List<LoanTransaction> transactionsToBeProcessed) {
+
+        List<LoanTransaction> mergedTransactionList = 
getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
+        for (Map.Entry<Long, LoanTransaction> entry : 
changedTransactionDetail.getNewTransactionMappings().entrySet()) {
+            if (entry.getValue().isChargeback()) {
+                for (LoanTransaction loanTransaction : mergedTransactionList) {
+                    if (loanTransaction.isReversed()) {
+                        continue;
+                    }
+                    LoanTransactionRelation newLoanTransactionRelation = null;
+                    LoanTransactionRelation oldLoanTransactionRelation = null;
+                    for (LoanTransactionRelation transactionRelation : 
loanTransaction.getLoanTransactionRelations()) {
+                        if 
(entry.getKey().equals(transactionRelation.getToTransaction().getId())
+                                && 
LoanTransactionRelationTypeEnum.CHARGEBACK.equals(transactionRelation.getRelationType()))
 {
+                            newLoanTransactionRelation = 
LoanTransactionRelation.linkToTransaction(loanTransaction, entry.getValue(),
+                                    
LoanTransactionRelationTypeEnum.CHARGEBACK);
+                            oldLoanTransactionRelation = transactionRelation;
+                            break;
+                        }
+                    }
+                    if (newLoanTransactionRelation != null) {
+                        
loanTransaction.getLoanTransactionRelations().add(newLoanTransactionRelation);
+                        
loanTransaction.getLoanTransactionRelations().remove(oldLoanTransactionRelation);
+                    }
+                }
+            }
+        }
+    }
+
+    private void reprocessInstallments(List<LoanRepaymentScheduleInstallment> 
installments, MonetaryCurrency currency) {
+        LoanRepaymentScheduleInstallment lastInstallment = 
installments.get(installments.size() - 1);
+        if (lastInstallment.isAdditional() && 
lastInstallment.getDue(currency).isZero()) {
+            installments.remove(lastInstallment);
+        }
+    }
+
+    private void recalculateCreditTransaction(ChangedTransactionDetail 
changedTransactionDetail, LoanTransaction loanTransaction,
+            MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> 
installments,
+            List<LoanTransaction> transactionsToBeProcessed) {
+        // pass through for new transactions
+        if (loanTransaction.getId() == null) {
+            return;
+        }
+        final LoanTransaction newLoanTransaction = 
LoanTransaction.copyTransactionProperties(loanTransaction);
+
+        List<LoanTransaction> mergedList = 
getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
+        Money overpaidAmount = calculateOverpaidAmount(loanTransaction, 
mergedList, installments, currency);
+        processCreditTransaction(newLoanTransaction, overpaidAmount, currency, 
installments);
+        createNewTransactionIfNecessary(loanTransaction, newLoanTransaction, 
currency, changedTransactionDetail);
+    }
+
+    private List<LoanTransaction> 
getMergedTransactionList(List<LoanTransaction> transactionList,
+            ChangedTransactionDetail changedTransactionDetail) {
+        List<LoanTransaction> mergedList = new 
ArrayList<>(changedTransactionDetail.getNewTransactionMappings().values());
+        mergedList.addAll(new ArrayList<>(transactionList));
+        return mergedList;
+    }
+
+    private void createNewTransactionIfNecessary(LoanTransaction 
loanTransaction, LoanTransaction newLoanTransaction,
+            MonetaryCurrency currency, ChangedTransactionDetail 
changedTransactionDetail) {
+        if (!LoanTransaction.transactionAmountsMatch(currency, 
loanTransaction, newLoanTransaction)) {
+            loanTransaction.reverse();
+            loanTransaction.updateExternalId(null);
+            
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
+            // Adding Replayed relation from newly created transaction to 
reversed transaction
+            
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
+                    loanTransaction, 
LoanTransactionRelationTypeEnum.REPLAYED));
+            
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(),
 newLoanTransaction);
+        }
+    }
+
+    private Money calculateOverpaidAmount(LoanTransaction loanTransaction, 
List<LoanTransaction> transactions,
+            List<LoanRepaymentScheduleInstallment> installments, 
MonetaryCurrency currency) {
+        Money totalPaidInRepayments = Money.zero(currency);
+
+        Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
+        for (final LoanRepaymentScheduleInstallment scheduledRepayment : 
installments) {
+            cumulativeTotalPaidOnInstallments = 
cumulativeTotalPaidOnInstallments
+                    
.plus(scheduledRepayment.getPrincipalCompleted(currency).plus(scheduledRepayment.getInterestPaid(currency)))
+                    
.plus(scheduledRepayment.getFeeChargesPaid(currency)).plus(scheduledRepayment.getPenaltyChargesPaid(currency));
+        }
+
+        for (final LoanTransaction transaction : transactions) {
+            if (transaction.isReversed()) {
+                continue;
+            }
+            if (transaction.equals(loanTransaction)) {
+                // We want to process only the transactions prior to the 
actual one
+                break;
+            }
+            if (transaction.isRefund() || transaction.isRefundForActiveLoan()) 
{
+                totalPaidInRepayments = 
totalPaidInRepayments.minus(transaction.getAmount(currency));
+            } else if (transaction.isCreditBalanceRefund() || 
transaction.isChargeback()) {
+                totalPaidInRepayments = 
totalPaidInRepayments.minus(transaction.getOverPaymentPortion(currency));
+            } else if (transaction.isRepaymentType()) {
+                totalPaidInRepayments = 
totalPaidInRepayments.plus(transaction.getAmount(currency));
+            }
+        }
+
+        // if total paid in transactions higher than repayment schedule then
+        // theres an overpayment.
+        return 
MathUtil.negativeToZero(totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments));
+    }
+
+    private void processCreditTransaction(LoanTransaction loanTransaction, 
Money overpaidAmount, MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments) {
+        loanTransaction.resetDerivedComponents();
+        List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = 
new ArrayList<>();
+        final Comparator<LoanRepaymentScheduleInstallment> byDate = 
Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
+        installments.sort(byDate);
+        final Money zeroMoney = Money.zero(currency);
+        Money transactionAmount = loanTransaction.getAmount(currency);
+        Money principalPortion = 
MathUtil.negativeToZero(loanTransaction.getAmount(currency).minus(overpaidAmount));
+        Money repaidAmount = 
MathUtil.negativeToZero(transactionAmount.minus(principalPortion));
+        loanTransaction.updateOverPayments(repaidAmount);
+        loanTransaction.updateComponents(principalPortion, zeroMoney, 
zeroMoney, zeroMoney);
+
+        if (principalPortion.isGreaterThanZero()) {
+            final LocalDate transactionDate = 
loanTransaction.getTransactionDate();
+            boolean loanTransactionMapped = false;
+            LocalDate pastDueDate = null;
+            for (final LoanRepaymentScheduleInstallment currentInstallment : 
installments) {
+                pastDueDate = currentInstallment.getDueDate();
+                if (!currentInstallment.isAdditional() && 
currentInstallment.getDueDate().isAfter(transactionDate)) {
+                    
currentInstallment.addToCredits(transactionAmount.getAmount());
+                    currentInstallment.addToPrincipal(transactionDate, 
transactionAmount);
+                    if (repaidAmount.isGreaterThanZero()) {
+                        
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), 
repaidAmount);
+                        
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
 currentInstallment,
+                                repaidAmount, zeroMoney, zeroMoney, 
zeroMoney));
+                    }
+                    loanTransactionMapped = true;
+                    break;
+
+                    // If already exists an additional installment just update 
the due date and
+                    // principal from the Loan charge back transaction
+                } else if (currentInstallment.isAdditional()) {
+                    currentInstallment.updateDueAndCredits(transactionDate, 
transactionAmount);
+                    if (repaidAmount.isGreaterThanZero()) {
+                        
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), 
repaidAmount);
+                        
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
 currentInstallment,
+                                repaidAmount, zeroMoney, zeroMoney, 
zeroMoney));
+                    }
+                    loanTransactionMapped = true;
+                    break;
+                }
+            }
+
+            // New installment will be added (N+1 scenario)
+            if (!loanTransactionMapped) {
+                if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
+                    LoanRepaymentScheduleInstallment currentInstallment = 
installments.get(installments.size() - 1);
+                    
currentInstallment.addToCredits(transactionAmount.getAmount());
+                    currentInstallment.addToPrincipal(transactionDate, 
transactionAmount);
+                    if (repaidAmount.isGreaterThanZero()) {
+                        
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), 
repaidAmount);
+                        
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
 currentInstallment,
+                                repaidAmount, zeroMoney, zeroMoney, 
zeroMoney));
+                    }
+                } else {
+                    Loan loan = loanTransaction.getLoan();
+                    LoanRepaymentScheduleInstallment installment = new 
LoanRepaymentScheduleInstallment(loan, (installments.size() + 1),
+                            pastDueDate, transactionDate, 
transactionAmount.getAmount(), zeroMoney.getAmount(), zeroMoney.getAmount(),
+                            zeroMoney.getAmount(), false, null);
+                    installment.markAsAdditional();
+                    installment.addToCredits(transactionAmount.getAmount());
+                    loan.addLoanRepaymentScheduleInstallment(installment);
+
+                    if (repaidAmount.isGreaterThanZero()) {
+                        
installment.payPrincipalComponent(loanTransaction.getTransactionDate(), 
repaidAmount);
+                        
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
 installment,
+                                repaidAmount, zeroMoney, zeroMoney, 
zeroMoney));
+                    }
+                }
+            }
+
+            
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
+        }
+    }
+
     /**
      * Provides support for processing the latest transaction (which should be 
latest transaction) against the loan
      * schedule.
@@ -524,66 +706,7 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
     @Override
     public void handleChargeback(LoanTransaction loanTransaction, 
MonetaryCurrency currency, Money overpaidAmount,
             List<LoanRepaymentScheduleInstallment> installments) {
-        List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = 
new ArrayList<>();
-        final Comparator<LoanRepaymentScheduleInstallment> byDate = new 
Comparator<LoanRepaymentScheduleInstallment>() {
-
-            @Override
-            public int compare(LoanRepaymentScheduleInstallment ord1, 
LoanRepaymentScheduleInstallment ord2) {
-                return ord1.getDueDate().compareTo(ord2.getDueDate());
-            }
-        };
-        Collections.sort(installments, byDate);
-        final Money zeroMoney = Money.zero(currency);
-        Money transactionAmountUnprocessed = 
loanTransaction.getAmount(currency);
-        if (overpaidAmount.isGreaterThanZero()) {
-            transactionAmountUnprocessed = 
loanTransaction.getAmount(currency).minus(overpaidAmount);
-            if (transactionAmountUnprocessed.isLessThanZero()) {
-                transactionAmountUnprocessed = zeroMoney;
-            }
-        }
-
-        if (transactionAmountUnprocessed.isGreaterThanZero()) {
-            final LocalDate transactionDate = 
loanTransaction.getTransactionDate();
-            boolean loanTransactionMapped = false;
-            LocalDate pastDueDate = null;
-            for (final LoanRepaymentScheduleInstallment currentInstallment : 
installments) {
-                pastDueDate = currentInstallment.getDueDate();
-                if (!currentInstallment.isAdditional() && 
currentInstallment.getDueDate().isAfter(transactionDate)) {
-                    
currentInstallment.addToCredits(transactionAmountUnprocessed.getAmount());
-                    currentInstallment.addToPrincipal(transactionDate, 
transactionAmountUnprocessed);
-                    
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
 currentInstallment,
-                            transactionAmountUnprocessed, zeroMoney, 
zeroMoney, zeroMoney));
-
-                    loanTransactionMapped = true;
-
-                    break;
-
-                    // If already exists an additional installment just update 
the due date and
-                    // principal from the Loan charge back transaction
-                } else if (currentInstallment.isAdditional()) {
-                    currentInstallment.updateDueChargeback(transactionDate, 
transactionAmountUnprocessed);
-                    loanTransactionMapped = true;
-                    break;
-                }
-            }
-
-            // New installment will be added (N+1 scenario)
-            if (!loanTransactionMapped) {
-                Loan loan = loanTransaction.getLoan();
-                final Set<LoanInterestRecalcualtionAdditionalDetails> 
compoundingDetails = null;
-                LoanRepaymentScheduleInstallment installment = new 
LoanRepaymentScheduleInstallment(loan, (installments.size() + 1),
-                        pastDueDate, transactionDate, 
transactionAmountUnprocessed.getAmount(), zeroMoney.getAmount(),
-                        zeroMoney.getAmount(), zeroMoney.getAmount(), false, 
compoundingDetails);
-                installment.markAsAdditional();
-                
installment.addToCredits(transactionAmountUnprocessed.getAmount());
-                loan.addLoanRepaymentScheduleInstallment(installment);
-
-                
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
 installment,
-                        transactionAmountUnprocessed, zeroMoney, zeroMoney, 
zeroMoney));
-            }
-
-            
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
-        }
+        processCreditTransaction(loanTransaction, overpaidAmount, currency, 
installments);
     }
 
     @Override
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index afac84139..a6a358fdb 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -1302,7 +1302,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
     private void validateLoanTransactionAmountChargeBack(LoanTransaction 
loanTransaction, LoanTransaction chargebackTransaction) {
         BigDecimal actualAmount = BigDecimal.ZERO;
         for (LoanTransactionRelation loanTransactionRelation : 
loanTransaction.getLoanTransactionRelations()) {
-            if 
(loanTransactionRelation.getRelationType().equals(LoanTransactionRelationTypeEnum.CHARGEBACK))
 {
+            if 
(loanTransactionRelation.getRelationType().equals(LoanTransactionRelationTypeEnum.CHARGEBACK)
+                    && 
loanTransactionRelation.getToTransaction().isNotReversed()) {
                 actualAmount = 
actualAmount.add(loanTransactionRelation.getToTransaction().getPrincipalPortion());
             }
         }
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java
index e462aeb67..ef48eb58a 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java
@@ -26,10 +26,12 @@ import java.math.RoundingMode;
 import java.time.LocalDate;
 import java.time.ZoneId;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Predicate;
 import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import 
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
 import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
@@ -136,6 +138,8 @@ public class LoanDelinquencyDomainServiceTest {
         
when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
         when(loan.getLoanProduct()).thenReturn(loanProduct);
         
when(loan.getRepaymentScheduleInstallments()).thenReturn(repaymentScheduleInstallments);
+        
when(loan.getLoanTransactions(Mockito.any(Predicate.class))).thenReturn(Collections.emptyList());
+        
when(loan.getLastLoanRepaymentScheduleInstallment()).thenReturn(repaymentScheduleInstallments.get(0));
         when(loan.getCurrency()).thenReturn(currency);
 
         CollectionData collectionData = 
underTest.getOverdueCollectionData(loan);
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index c69acfd3f..79adfb007 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -7137,6 +7137,235 @@ public class ClientLoanIntegrationTest {
         }
     }
 
+    @Test
+    public void 
testCreditBalanceRefundAfterMaturityWithReverseReplayOfRepayments() {
+        try {
+            
GlobalConfigurationHelper.updateIsAutomaticExternalIdGenerationEnabled(this.requestSpec,
 this.responseSpec, true);
+            
GlobalConfigurationHelper.updateIsBusinessDateEnabled(this.requestSpec, 
this.responseSpec, true);
+            businessDateHelper.updateBusinessDate(new 
BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2022.10.10").dateFormat("yyyy.MM.dd").locale("en"));
+
+            final Account assetAccount = 
this.accountHelper.createAssetAccount();
+            final Account incomeAccount = 
this.accountHelper.createIncomeAccount();
+            final Account expenseAccount = 
this.accountHelper.createExpenseAccount();
+            final Account overpaymentAccount = 
this.accountHelper.createLiabilityAccount();
+
+            final Integer loanProductID = 
createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, 
incomeAccount,
+                    expenseAccount, overpaymentAccount);
+
+            final Integer clientID = ClientHelper.createClient(requestSpec, 
responseSpec, "01 January 2011");
+
+            final Integer loanID = applyForLoanApplication(clientID, 
loanProductID);
+
+            HashMap<String, Object> loanStatusHashMap = 
LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
+            LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+            loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 
September 2022", loanID);
+            LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+            
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+            loanStatusHashMap = 
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 
2022", loanID, "1000");
+            LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+            this.loanTransactionHelper.makeRepayment("04 September 2022", 
Float.parseFloat("100"), loanID);
+            this.loanTransactionHelper.makeRepayment("05 September 2022", 
Float.parseFloat("1100"), loanID);
+
+            GetLoansLoanIdResponse loanDetails = 
this.loanTransactionHelper.getLoanDetails((long) loanID);
+            assertEquals(200.0, loanDetails.getTotalOverpaid());
+            assertTrue(loanDetails.getStatus().getOverpaid());
+
+            this.loanTransactionHelper.makeCreditBalanceRefund((long) loanID, 
new PostLoansLoanIdTransactionsRequest()
+                    .transactionAmount(200.0).transactionDate("10 October 
2022").dateFormat("dd MMMM yyyy").locale("en"));
+
+            loanDetails = this.loanTransactionHelper.getLoanDetails((long) 
loanID);
+            assertTrue(loanDetails.getStatus().getClosedObligationsMet());
+
+            assertEquals(2, 
loanDetails.getRepaymentSchedule().getPeriods().size());
+            assertEquals(1000, 
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
+
+            assertEquals(100.0, 
loanDetails.getTransactions().get(1).getAmount());
+            assertEquals(100.0, 
loanDetails.getTransactions().get(1).getPrincipalPortion());
+            assertEquals(LocalDate.of(2022, 9, 4), 
loanDetails.getTransactions().get(1).getDate());
+            assertEquals(900.0, 
loanDetails.getTransactions().get(1).getOutstandingLoanBalance());
+            assertEquals(1100.0, 
loanDetails.getTransactions().get(2).getAmount());
+            assertEquals(900.0, 
loanDetails.getTransactions().get(2).getPrincipalPortion());
+            assertEquals(200.0, 
loanDetails.getTransactions().get(2).getOverpaymentPortion());
+            assertEquals(LocalDate.of(2022, 9, 5), 
loanDetails.getTransactions().get(2).getDate());
+            assertEquals(0.0, 
loanDetails.getTransactions().get(2).getOutstandingLoanBalance());
+            assertEquals(200.0, 
loanDetails.getTransactions().get(3).getAmount());
+            assertEquals(200.0, 
loanDetails.getTransactions().get(3).getOverpaymentPortion());
+            assertEquals(LocalDate.of(2022, 10, 10), 
loanDetails.getTransactions().get(3).getDate());
+            assertEquals(0.0, 
loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
+
+            
this.loanTransactionHelper.reverseLoanTransaction(loanDetails.getId(), 
loanDetails.getTransactions().get(1).getId(),
+                    new 
PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat("dd MMMM 
yyyy").transactionAmount(0.0)
+                            .transactionDate("10 October 2022").locale("en"));
+
+            loanDetails = this.loanTransactionHelper.getLoanDetails((long) 
loanID);
+
+            assertEquals(100.0, 
loanDetails.getTransactions().get(1).getAmount());
+            assertEquals(100.0, 
loanDetails.getTransactions().get(1).getPrincipalPortion());
+            assertEquals(LocalDate.of(2022, 9, 4), 
loanDetails.getTransactions().get(1).getDate());
+            
assertTrue(loanDetails.getTransactions().get(1).getManuallyReversed());
+
+            assertEquals(1100.0, 
loanDetails.getTransactions().get(2).getAmount());
+            assertEquals(1000.0, 
loanDetails.getTransactions().get(2).getPrincipalPortion());
+            assertEquals(100.0, 
loanDetails.getTransactions().get(2).getOverpaymentPortion());
+            assertEquals(LocalDate.of(2022, 9, 5), 
loanDetails.getTransactions().get(2).getDate());
+            assertEquals(0.0, 
loanDetails.getTransactions().get(2).getOutstandingLoanBalance());
+            assertEquals(1, 
loanDetails.getTransactions().get(2).getTransactionRelations().size());
+
+            assertEquals(200.0, 
loanDetails.getTransactions().get(3).getAmount());
+            assertEquals(100.0, 
loanDetails.getTransactions().get(3).getPrincipalPortion());
+            assertEquals(100.0, 
loanDetails.getTransactions().get(3).getOverpaymentPortion());
+            assertEquals(100.0, 
loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
+            assertEquals(LocalDate.of(2022, 10, 10), 
loanDetails.getTransactions().get(3).getDate());
+            assertEquals(1, 
loanDetails.getTransactions().get(3).getTransactionRelations().size());
+
+            assertTrue(loanDetails.getStatus().getActive());
+
+            assertEquals(3, 
loanDetails.getRepaymentSchedule().getPeriods().size());
+            assertEquals(1000, 
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
+            
assertTrue(loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete());
+            assertEquals(200, 
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPrincipalDue());
+            
assertFalse(loanDetails.getRepaymentSchedule().getPeriods().get(2).getComplete());
+            assertEquals(100.0, 
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPrincipalPaid());
+            assertEquals(100.0, 
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPrincipalOutstanding());
+
+        } finally {
+            
GlobalConfigurationHelper.updateIsAutomaticExternalIdGenerationEnabled(this.requestSpec,
 this.responseSpec, false);
+            
GlobalConfigurationHelper.updateIsBusinessDateEnabled(this.requestSpec, 
this.responseSpec, false);
+        }
+    }
+
+    @Test
+    public void 
testCreditBalanceRefundBeforeMaturityWithReverseReplayOfRepaymentsAndRefund() {
+        try {
+            
GlobalConfigurationHelper.updateIsAutomaticExternalIdGenerationEnabled(this.requestSpec,
 this.responseSpec, true);
+            
GlobalConfigurationHelper.updateIsBusinessDateEnabled(this.requestSpec, 
this.responseSpec, true);
+            businessDateHelper.updateBusinessDate(new 
BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+                    .date("2022.10.10").dateFormat("yyyy.MM.dd").locale("en"));
+
+            final Account assetAccount = 
this.accountHelper.createAssetAccount();
+            final Account incomeAccount = 
this.accountHelper.createIncomeAccount();
+            final Account expenseAccount = 
this.accountHelper.createExpenseAccount();
+            final Account overpaymentAccount = 
this.accountHelper.createLiabilityAccount();
+
+            final Integer loanProductID = 
createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, 
incomeAccount,
+                    expenseAccount, overpaymentAccount);
+
+            final Integer clientID = ClientHelper.createClient(requestSpec, 
responseSpec, "01 January 2011");
+
+            final Integer loanID = applyForLoanApplication(clientID, 
loanProductID);
+
+            HashMap<String, Object> loanStatusHashMap = 
LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
+            LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+            loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 
September 2022", loanID);
+            LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+            
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+            loanStatusHashMap = 
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 
2022", loanID, "1000");
+            LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+            this.loanTransactionHelper.makeRepayment("04 September 2022", 
Float.parseFloat("500"), loanID);
+            this.loanTransactionHelper.makeRepayment("05 September 2022", 
Float.parseFloat("700"), loanID);
+
+            GetLoansLoanIdResponse loanDetails = 
this.loanTransactionHelper.getLoanDetails((long) loanID);
+            assertEquals(200.0, loanDetails.getTotalOverpaid());
+            assertTrue(loanDetails.getStatus().getOverpaid());
+
+            this.loanTransactionHelper.makeCreditBalanceRefund((long) loanID, 
new PostLoansLoanIdTransactionsRequest()
+                    .transactionAmount(200.0).transactionDate("06 September 
2022").dateFormat("dd MMMM yyyy").locale("en"));
+
+            this.loanTransactionHelper.makeMerchantIssuedRefund((long) loanID, 
new PostLoansLoanIdTransactionsRequest().locale("en")
+                    .dateFormat("dd MMMM yyyy").transactionDate("07 September 
2022").transactionAmount(500.0));
+
+            this.loanTransactionHelper.makeCreditBalanceRefund((long) loanID, 
new PostLoansLoanIdTransactionsRequest()
+                    .transactionAmount(500.0).transactionDate("08 September 
2022").dateFormat("dd MMMM yyyy").locale("en"));
+
+            loanDetails = this.loanTransactionHelper.getLoanDetails((long) 
loanID);
+            assertTrue(loanDetails.getStatus().getClosedObligationsMet());
+
+            assertEquals(2, 
loanDetails.getRepaymentSchedule().getPeriods().size());
+            assertEquals(1000, 
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
+
+            assertEquals(500.0, 
loanDetails.getTransactions().get(1).getAmount());
+            assertEquals(500.0, 
loanDetails.getTransactions().get(1).getPrincipalPortion());
+            assertEquals(LocalDate.of(2022, 9, 4), 
loanDetails.getTransactions().get(1).getDate());
+            assertEquals(500.0, 
loanDetails.getTransactions().get(1).getOutstandingLoanBalance());
+
+            assertEquals(700.0, 
loanDetails.getTransactions().get(2).getAmount());
+            assertEquals(500.0, 
loanDetails.getTransactions().get(2).getPrincipalPortion());
+            assertEquals(200.0, 
loanDetails.getTransactions().get(2).getOverpaymentPortion());
+            assertEquals(LocalDate.of(2022, 9, 5), 
loanDetails.getTransactions().get(2).getDate());
+            assertEquals(0.0, 
loanDetails.getTransactions().get(2).getOutstandingLoanBalance());
+
+            assertEquals(200.0, 
loanDetails.getTransactions().get(3).getAmount());
+            assertEquals(200.0, 
loanDetails.getTransactions().get(3).getOverpaymentPortion());
+            assertEquals(LocalDate.of(2022, 9, 6), 
loanDetails.getTransactions().get(3).getDate());
+            assertEquals(0.0, 
loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
+
+            assertEquals(500.0, 
loanDetails.getTransactions().get(4).getAmount());
+            assertEquals(500.0, 
loanDetails.getTransactions().get(4).getOverpaymentPortion());
+            assertEquals(LocalDate.of(2022, 9, 7), 
loanDetails.getTransactions().get(4).getDate());
+            assertEquals(0.0, 
loanDetails.getTransactions().get(4).getOutstandingLoanBalance());
+
+            assertEquals(500.0, 
loanDetails.getTransactions().get(5).getAmount());
+            assertEquals(500.0, 
loanDetails.getTransactions().get(5).getOverpaymentPortion());
+            assertEquals(LocalDate.of(2022, 9, 8), 
loanDetails.getTransactions().get(5).getDate());
+            assertEquals(0.0, 
loanDetails.getTransactions().get(5).getOutstandingLoanBalance());
+
+            
this.loanTransactionHelper.reverseLoanTransaction(loanDetails.getId(), 
loanDetails.getTransactions().get(2).getId(),
+                    new 
PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat("dd MMMM 
yyyy").transactionAmount(0.0)
+                            .transactionDate("07 September 
2022").locale("en"));
+
+            loanDetails = this.loanTransactionHelper.getLoanDetails((long) 
loanID);
+
+            assertEquals(500.0, 
loanDetails.getTransactions().get(1).getAmount());
+            assertEquals(500.0, 
loanDetails.getTransactions().get(1).getPrincipalPortion());
+            assertEquals(LocalDate.of(2022, 9, 4), 
loanDetails.getTransactions().get(1).getDate());
+            assertEquals(500.0, 
loanDetails.getTransactions().get(1).getOutstandingLoanBalance());
+
+            assertEquals(700.0, 
loanDetails.getTransactions().get(2).getAmount());
+            assertEquals(500.0, 
loanDetails.getTransactions().get(2).getPrincipalPortion());
+            assertEquals(200.0, 
loanDetails.getTransactions().get(2).getOverpaymentPortion());
+            assertEquals(LocalDate.of(2022, 9, 5), 
loanDetails.getTransactions().get(2).getDate());
+            assertEquals(0.0, 
loanDetails.getTransactions().get(2).getOutstandingLoanBalance());
+            
assertTrue(loanDetails.getTransactions().get(2).getManuallyReversed());
+
+            assertEquals(200.0, 
loanDetails.getTransactions().get(3).getAmount());
+            assertEquals(200.0, 
loanDetails.getTransactions().get(3).getPrincipalPortion());
+            assertEquals(LocalDate.of(2022, 9, 6), 
loanDetails.getTransactions().get(3).getDate());
+            assertEquals(700.0, 
loanDetails.getTransactions().get(3).getOutstandingLoanBalance());
+            assertEquals(1, 
loanDetails.getTransactions().get(3).getTransactionRelations().size());
+
+            assertEquals(500.0, 
loanDetails.getTransactions().get(4).getAmount());
+            assertEquals(500.0, 
loanDetails.getTransactions().get(4).getPrincipalPortion());
+            assertEquals(LocalDate.of(2022, 9, 7), 
loanDetails.getTransactions().get(4).getDate());
+            assertEquals(200.0, 
loanDetails.getTransactions().get(4).getOutstandingLoanBalance());
+            assertEquals(1, 
loanDetails.getTransactions().get(4).getTransactionRelations().size());
+
+            assertEquals(500.0, 
loanDetails.getTransactions().get(5).getAmount());
+            assertEquals(500.0, 
loanDetails.getTransactions().get(5).getPrincipalPortion());
+            assertEquals(LocalDate.of(2022, 9, 8), 
loanDetails.getTransactions().get(5).getDate());
+            assertEquals(700.0, 
loanDetails.getTransactions().get(5).getOutstandingLoanBalance());
+            assertEquals(1, 
loanDetails.getTransactions().get(5).getTransactionRelations().size());
+
+            assertTrue(loanDetails.getStatus().getActive());
+
+            assertEquals(2, 
loanDetails.getRepaymentSchedule().getPeriods().size());
+            assertEquals(1700, 
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalDue());
+            
assertFalse(loanDetails.getRepaymentSchedule().getPeriods().get(1).getComplete());
+            assertEquals(1000.0, 
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalPaid());
+            assertEquals(700.0, 
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPrincipalOutstanding());
+
+        } finally {
+            
GlobalConfigurationHelper.updateIsAutomaticExternalIdGenerationEnabled(this.requestSpec,
 this.responseSpec, false);
+            
GlobalConfigurationHelper.updateIsBusinessDateEnabled(this.requestSpec, 
this.responseSpec, false);
+        }
+    }
+
     private Integer applyForLoanApplication(final Integer clientID, final 
Integer loanProductID) {
         LOG.info("--------------------------------APPLYING FOR LOAN 
APPLICATION--------------------------------");
         final String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")

Reply via email to