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 d56cf85dc FINERACT-1968: Fix overpayment calculation
d56cf85dc is described below

commit d56cf85dcdfc32c0568d325577344b71aaf7b354
Author: Adam Saghy <[email protected]>
AuthorDate: Fri Jan 19 10:54:08 2024 +0100

    FINERACT-1968: Fix overpayment calculation
---
 .../portfolio/loanaccount/domain/Loan.java         | 13 ++--
 .../loanaccount/domain/LoanTransaction.java        |  5 --
 ...tLoanRepaymentScheduleTransactionProcessor.java | 82 +++++++---------------
 .../LoanRepaymentScheduleTransactionProcessor.java |  2 +-
 .../domain/transactionprocessor/MoneyHolder.java   | 32 +++++++++
 ...dvancedPaymentScheduleTransactionProcessor.java | 42 ++++++-----
 .../LoanChargeWritePlatformServiceImpl.java        |  3 +-
 ...cedPaymentScheduleTransactionProcessorTest.java | 10 ++-
 ...backOnPaymentTypeRepaymentTransactionsTest.java |  4 --
 9 files changed, 100 insertions(+), 93 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index dbd14e80a..0235c8b6b 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -111,6 +111,7 @@ import 
org.apache.fineract.portfolio.loanaccount.data.LoanCollateralManagementDa
 import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
 import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
 import 
org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
@@ -797,7 +798,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         final Set<LoanCharge> loanCharges = new HashSet<>(1);
         loanCharges.add(charge);
         
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargesPayment,
 getCurrency(), chargePaymentInstallments,
-                loanCharges, getTotalOverpaidAsMoney());
+                loanCharges, new MoneyHolder(getTotalOverpaidAsMoney()));
 
         updateLoanSummaryDerivedFields();
         doPostLoanTransactionChecks(chargesPayment.getTransactionDate(), 
loanLifecycleStateMachine);
@@ -3321,7 +3322,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         if (isTransactionChronologicallyLatest && adjustedTransaction == null
                 && (!reprocess || 
!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) && 
!isForeclosure()) {
             
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction,
 getCurrency(),
-                    getRepaymentScheduleInstallments(), getActiveCharges(), 
getTotalOverpaidAsMoney());
+                    getRepaymentScheduleInstallments(), getActiveCharges(), 
new MoneyHolder(getTotalOverpaidAsMoney()));
             reprocess = false;
             if 
(this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
                 if (currentInstallment == null || 
currentInstallment.isNotFullyPaidOff()) {
@@ -3914,7 +3915,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
 
             addLoanTransaction(loanTransaction);
             
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction,
 loanCurrency(),
-                    getRepaymentScheduleInstallments(), getActiveCharges(), 
getTotalOverpaidAsMoney());
+                    getRepaymentScheduleInstallments(), getActiveCharges(), 
new MoneyHolder(getTotalOverpaidAsMoney()));
 
             updateLoanSummaryDerivedFields();
         }
@@ -4019,7 +4020,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
 
                 addLoanTransaction(loanTransaction);
                 
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction,
 loanCurrency(),
-                        getRepaymentScheduleInstallments(), 
getActiveCharges(), getTotalOverpaidAsMoney());
+                        getRepaymentScheduleInstallments(), 
getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()));
 
                 updateLoanSummaryDerivedFields();
             } else if (totalOutstanding.isGreaterThanZero()) {
@@ -6373,7 +6374,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         // If is a refund
         if (adjustedTransaction == null) {
             
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction,
 getCurrency(),
-                    getRepaymentScheduleInstallments(), getActiveCharges(), 
getTotalOverpaidAsMoney());
+                    getRepaymentScheduleInstallments(), getActiveCharges(), 
new MoneyHolder(getTotalOverpaidAsMoney()));
         } else {
             final List<LoanTransaction> 
allNonContraTransactionsPostDisbursement = 
retrieveListOfTransactionsPostDisbursement();
             changedTransactionDetail = 
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
@@ -6404,7 +6405,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
 
         addLoanTransaction(chargebackTransaction);
         
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargebackTransaction,
 getCurrency(),
-                getRepaymentScheduleInstallments(), getActiveCharges(), 
getTotalOverpaidAsMoney());
+                getRepaymentScheduleInstallments(), getActiveCharges(), new 
MoneyHolder(getTotalOverpaidAsMoney()));
 
         updateLoanSummaryDerivedFields();
         if 
(!doPostLoanTransactionChecks(chargebackTransaction.getTransactionDate(), 
loanLifecycleStateMachine)) {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index bca005632..7226e6f23 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -483,11 +483,6 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom {
                 .plus(getPenaltyChargesPortion(currency)).getAmount();
     }
 
-    public void updateOverPayments(final Money overPayment) {
-        final MonetaryCurrency currency = overPayment.getCurrency();
-        this.overPaymentPortion = 
defaultToNullIfZero(getOverPaymentPortion(currency).plus(overPayment).getAmount());
-    }
-
     public void setOverPayments(final Money overPayment) {
         if (overPayment != null) {
             this.overPaymentPortion = 
defaultToNullIfZero(overPayment.getAmount());
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index 8ca79d243..89a2ec7b4 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -144,7 +144,7 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
 
                 if (unprocessed.isGreaterThanZero()) {
                     onLoanOverpayment(loanTransaction, unprocessed);
-                    loanTransaction.updateOverPayments(unprocessed);
+                    loanTransaction.setOverPayments(unprocessed);
                 }
 
             } else {
@@ -152,6 +152,7 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
             }
         }
 
+        MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
         for (final LoanTransaction loanTransaction : 
transactionsToBeProcessed) {
             // TODO: analyze and remove this
             if 
(!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN))
 {
@@ -163,7 +164,7 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
             if (loanTransaction.isRepaymentLikeType() || 
loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
                 // pass through for new transactions
                 if (loanTransaction.getId() == null) {
-                    processLatestTransaction(loanTransaction, currency, 
installments, charges, null);
+                    processLatestTransaction(loanTransaction, currency, 
installments, charges, overpaymentHolder);
                     loanTransaction.adjustInterestComponent(currency);
                 } else {
                     /**
@@ -174,7 +175,7 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
 
                     // Reset derived component of new loan transaction and
                     // re-process transaction
-                    processLatestTransaction(newLoanTransaction, currency, 
installments, charges, null);
+                    processLatestTransaction(newLoanTransaction, currency, 
installments, charges, overpaymentHolder);
                     newLoanTransaction.adjustInterestComponent(currency);
                     /**
                      * Check if the transaction amounts have changed. If so, 
reverse the original transaction and update
@@ -195,9 +196,11 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
                 loanTransaction.resetDerivedComponents();
                 handleRefund(loanTransaction, currency, installments, charges);
             } else if (loanTransaction.isCreditBalanceRefund()) {
-                recalculateCreditTransaction(changedTransactionDetail, 
loanTransaction, currency, installments, transactionsToBeProcessed);
+                recalculateCreditTransaction(changedTransactionDetail, 
loanTransaction, currency, installments, transactionsToBeProcessed,
+                        overpaymentHolder);
             } else if (loanTransaction.isChargeback()) {
-                recalculateCreditTransaction(changedTransactionDetail, 
loanTransaction, currency, installments, transactionsToBeProcessed);
+                recalculateCreditTransaction(changedTransactionDetail, 
loanTransaction, currency, installments, transactionsToBeProcessed,
+                        overpaymentHolder);
                 
reprocessChargebackTransactionRelation(changedTransactionDetail, 
transactionsToBeProcessed);
             } else if (loanTransaction.isChargeOff()) {
                 recalculateChargeOffTransaction(changedTransactionDetail, 
loanTransaction, currency, installments);
@@ -209,11 +212,11 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
 
     @Override
     public void processLatestTransaction(final LoanTransaction 
loanTransaction, final MonetaryCurrency currency,
-            final List<LoanRepaymentScheduleInstallment> installments, final 
Set<LoanCharge> charges, Money overpaidAmount) {
+            final List<LoanRepaymentScheduleInstallment> installments, final 
Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
         switch (loanTransaction.getTypeOf()) {
             case WRITEOFF -> handleWriteOff(loanTransaction, currency, 
installments);
             case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, 
currency, installments, charges);
-            case CHARGEBACK -> handleChargeback(loanTransaction, currency, 
overpaidAmount, installments);
+            case CHARGEBACK -> handleChargeback(loanTransaction, currency, 
installments, overpaymentHolder);
             default -> {
                 Money transactionAmountUnprocessed = 
handleTransactionAndCharges(loanTransaction, currency, installments, charges, 
null,
                         false);
@@ -223,8 +226,11 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
                                 transactionAmountUnprocessed.zero(), 
transactionAmountUnprocessed.zero());
                     } else {
                         onLoanOverpayment(loanTransaction, 
transactionAmountUnprocessed);
-                        
loanTransaction.updateOverPayments(transactionAmountUnprocessed);
+                        
loanTransaction.setOverPayments(transactionAmountUnprocessed);
                     }
+                    
overpaymentHolder.setMoneyObject(transactionAmountUnprocessed);
+                } else {
+                    overpaymentHolder.setMoneyObject(Money.zero(currency));
                 }
             }
         }
@@ -435,17 +441,15 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
     }
 
     private void recalculateCreditTransaction(ChangedTransactionDetail 
changedTransactionDetail, LoanTransaction loanTransaction,
-            MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> 
installments,
-            List<LoanTransaction> transactionsToBeProcessed) {
+            MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> 
installments, List<LoanTransaction> transactionsToBeProcessed,
+            MoneyHolder overpaymentHolder) {
         // 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);
+        processCreditTransaction(newLoanTransaction, overpaymentHolder, 
currency, installments);
         if (!LoanTransaction.transactionAmountsMatch(currency, 
loanTransaction, newLoanTransaction)) {
             createNewTransaction(loanTransaction, newLoanTransaction, 
changedTransactionDetail);
         }
@@ -470,40 +474,7 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
 
     }
 
-    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.isRepaymentLikeType()) {
-                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,
+    private void processCreditTransaction(LoanTransaction loanTransaction, 
MoneyHolder overpaymentHolder, MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments) {
         loanTransaction.resetDerivedComponents();
         List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = 
new ArrayList<>();
@@ -511,9 +482,10 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
         installments.sort(byDate);
         final Money zeroMoney = Money.zero(currency);
         Money transactionAmount = loanTransaction.getAmount(currency);
-        Money principalPortion = 
MathUtil.negativeToZero(loanTransaction.getAmount(currency).minus(overpaidAmount));
+        Money principalPortion = 
MathUtil.negativeToZero(loanTransaction.getAmount(currency).minus(overpaymentHolder.getMoneyObject()));
         Money repaidAmount = 
MathUtil.negativeToZero(transactionAmount.minus(principalPortion));
-        loanTransaction.updateOverPayments(repaidAmount);
+        loanTransaction.setOverPayments(repaidAmount);
+        
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().minus(repaidAmount));
         loanTransaction.updateComponents(principalPortion, zeroMoney, 
zeroMoney, zeroMoney);
 
         if (principalPortion.isGreaterThanZero()) {
@@ -770,14 +742,14 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
         loanTransaction.updateComponentsAndTotal(principalPortion, 
interestPortion, feeChargesPortion, penaltychargesPortion);
     }
 
-    protected void handleChargeback(LoanTransaction loanTransaction, 
MonetaryCurrency currency, Money overpaidAmount,
-            List<LoanRepaymentScheduleInstallment> installments) {
-        processCreditTransaction(loanTransaction, overpaidAmount, currency, 
installments);
+    protected void handleChargeback(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments, MoneyHolder 
overpaidAmountHolder) {
+        processCreditTransaction(loanTransaction, overpaidAmountHolder, 
currency, installments);
     }
 
-    protected void handleCreditBalanceRefund(LoanTransaction loanTransaction, 
MonetaryCurrency currency, Money overpaidAmount,
-            List<LoanRepaymentScheduleInstallment> installments) {
-        processCreditTransaction(loanTransaction, overpaidAmount, currency, 
installments);
+    protected void handleCreditBalanceRefund(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments, MoneyHolder 
overpaidAmountHolder) {
+        processCreditTransaction(loanTransaction, overpaidAmountHolder, 
currency, installments);
     }
 
     protected void handleRefund(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
index efcfba19b..ebf16d6a7 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
@@ -41,7 +41,7 @@ public interface LoanRepaymentScheduleTransactionProcessor {
      * schedule.
      */
     void processLatestTransaction(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges, Money overpaidAmount);
+            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges, MoneyHolder overpaymentHolder);
 
     /**
      * Provides support for passing all {@link LoanTransaction}'s so it will 
completely re-process the entire loan
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/MoneyHolder.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/MoneyHolder.java
new file mode 100644
index 000000000..f967e8ffb
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/MoneyHolder.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.domain.transactionprocessor;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+@Getter
+@Setter
+@AllArgsConstructor
+public class MoneyHolder {
+
+    private Money moneyObject;
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 295b354d6..c63291b5a 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -55,6 +55,7 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
 import 
org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
+import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import org.apache.fineract.portfolio.loanproduct.domain.DueType;
 import 
org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
@@ -136,9 +137,10 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
         List<ChargeOrTransaction> chargeOrTransactions = 
createSortedChargesAndTransactionsList(loanTransactions, charges);
 
         final ChangedTransactionDetail changedTransactionDetail = new 
ChangedTransactionDetail();
+        MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
         for (final ChargeOrTransaction chargeOrTransaction : 
chargeOrTransactions) {
             chargeOrTransaction.getLoanTransaction().ifPresent(loanTransaction 
-> processSingleTransaction(loanTransaction, currency,
-                    installments, charges, changedTransactionDetail));
+                    installments, charges, changedTransactionDetail, 
overpaymentHolder));
             chargeOrTransaction.getLoanCharge()
                     .ifPresent(loanCharge -> processSingleCharge(loanCharge, 
currency, installments, disbursementDate));
         }
@@ -150,18 +152,18 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
 
     @Override
     public void processLatestTransaction(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges, Money overpaidAmount) {
+            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
         switch (loanTransaction.getTypeOf()) {
             case DISBURSEMENT -> handleDisbursement(loanTransaction, currency, 
installments);
             case WRITEOFF -> handleWriteOff(loanTransaction, currency, 
installments);
             case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, 
currency, installments, charges);
-            case CHARGEBACK -> handleChargeback(loanTransaction, currency, 
overpaidAmount, installments);
-            case CREDIT_BALANCE_REFUND -> 
handleCreditBalanceRefund(loanTransaction, currency, overpaidAmount, 
installments);
+            case CHARGEBACK -> handleChargeback(loanTransaction, currency, 
installments, overpaymentHolder);
+            case CREDIT_BALANCE_REFUND -> 
handleCreditBalanceRefund(loanTransaction, currency, installments, 
overpaymentHolder);
             case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND, 
GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT,
                     WAIVE_INTEREST, RECOVERY_REPAYMENT ->
-                handleRepayment(loanTransaction, currency, installments, 
charges);
+                handleRepayment(loanTransaction, currency, installments, 
charges, overpaymentHolder);
             case CHARGE_OFF -> handleChargeOff(loanTransaction, currency, 
installments);
-            case CHARGE_PAYMENT -> handleChargePayment(loanTransaction, 
currency, installments, charges);
+            case CHARGE_PAYMENT -> handleChargePayment(loanTransaction, 
currency, installments, charges, overpaymentHolder);
             case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will 
not be processed.");
             // TODO: Cover rest of the transaction types
             default -> {
@@ -226,10 +228,10 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
     }
 
     private void processSingleTransaction(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges,
-            ChangedTransactionDetail changedTransactionDetail) {
+            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges, ChangedTransactionDetail changedTransactionDetail,
+            MoneyHolder overpaymentHolder) {
         if (loanTransaction.getId() == null) {
-            processLatestTransaction(loanTransaction, currency, installments, 
charges, Money.zero(currency));
+            processLatestTransaction(loanTransaction, currency, installments, 
charges, overpaymentHolder);
             if (loanTransaction.isInterestWaiver()) {
                 loanTransaction.adjustInterestComponent(currency);
             }
@@ -242,7 +244,7 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
 
             // Reset derived component of new loan transaction and
             // re-process transaction
-            processLatestTransaction(newLoanTransaction, currency, 
installments, charges, Money.zero(currency));
+            processLatestTransaction(newLoanTransaction, currency, 
installments, charges, overpaymentHolder);
             if (loanTransaction.isInterestWaiver()) {
                 newLoanTransaction.adjustInterestComponent(currency);
             }
@@ -322,12 +324,12 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
     }
 
     private void handleRepayment(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges) {
+            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
         if (loanTransaction.isRepaymentLikeType() || 
loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
             loanTransaction.resetDerivedComponents();
         }
         Money transactionAmountUnprocessed = 
loanTransaction.getAmount(currency);
-        processTransaction(loanTransaction, currency, installments, 
transactionAmountUnprocessed, charges);
+        processTransaction(loanTransaction, currency, installments, 
transactionAmountUnprocessed, charges, overpaymentHolder);
     }
 
     private LoanTransactionToRepaymentScheduleMapping getTransactionMapping(
@@ -399,10 +401,13 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
         
loanTransactionToRepaymentScheduleMapping.setComponents(aggregatedPrincipal, 
aggregatedInterest, aggregatedFee, aggregatedPenalty);
     }
 
-    private void handleOverpayment(Money overpaymentPortion, LoanTransaction 
loanTransaction) {
+    private void handleOverpayment(Money overpaymentPortion, LoanTransaction 
loanTransaction, MoneyHolder overpaymentHolder) {
         if (overpaymentPortion.isGreaterThanZero()) {
             onLoanOverpayment(loanTransaction, overpaymentPortion);
-            loanTransaction.updateOverPayments(overpaymentPortion);
+            overpaymentHolder.setMoneyObject(overpaymentPortion);
+            loanTransaction.setOverPayments(overpaymentPortion);
+        } else {
+            overpaymentHolder.setMoneyObject(overpaymentPortion.zero());
         }
     }
 
@@ -427,7 +432,7 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
     }
 
     private void handleChargePayment(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges) {
+            List<LoanRepaymentScheduleInstallment> installments, 
Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
         Money zero = Money.zero(currency);
         Money feeChargesPortion = zero;
         Money penaltyChargesPortion = zero;
@@ -473,7 +478,7 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
         }
 
         if (unprocessed.isGreaterThanZero()) {
-            processTransaction(loanTransaction, currency, installments, 
unprocessed, charges);
+            processTransaction(loanTransaction, currency, installments, 
unprocessed, charges, overpaymentHolder);
         }
     }
 
@@ -658,7 +663,8 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
     }
 
     private void processTransaction(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments, Money 
transactionAmountUnprocessed, Set<LoanCharge> charges) {
+            List<LoanRepaymentScheduleInstallment> installments, Money 
transactionAmountUnprocessed, Set<LoanCharge> charges,
+            MoneyHolder overpaymentHolder) {
         Money zero = Money.zero(currency);
         List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = 
new ArrayList<>();
 
@@ -683,7 +689,7 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
                 balances.getAggregatedFeeChargesPortion(), 
balances.getAggregatedPenaltyChargesPortion());
         
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
 
-        handleOverpayment(transactionAmountUnprocessed, loanTransaction);
+        handleOverpayment(transactionAmountUnprocessed, loanTransaction, 
overpaymentHolder);
     }
 
     private Money processPeriodsHorizontally(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
index 5e96c43d9..d6391836f 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
@@ -114,6 +114,7 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationT
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
 import 
org.apache.fineract.portfolio.loanaccount.exception.InstallmentNotFoundException;
 import 
org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
@@ -832,7 +833,7 @@ public class LoanChargeWritePlatformServiceImpl implements 
LoanChargeWritePlatfo
         final LoanRepaymentScheduleTransactionProcessor 
loanRepaymentScheduleTransactionProcessor = 
loanRepaymentScheduleTransactionProcessorFactory
                 .determineProcessor(loan.transactionProcessingStrategy());
         
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanChargeAdjustmentTransaction,
 loan.getCurrency(),
-                loan.getRepaymentScheduleInstallments(), 
loan.getActiveCharges(), loan.getTotalOverpaidAsMoney());
+                loan.getRepaymentScheduleInstallments(), 
loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()));
 
         loan.addLoanTransaction(loanChargeAdjustmentTransaction);
         loan.updateLoanSummaryAndStatus();
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
index be3f43f5b..e2ae5fa40 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
@@ -43,6 +43,7 @@ import 
org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRul
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
 import 
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
 import 
org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
@@ -121,7 +122,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         Mockito.when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), 
eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
         Mockito.when(loanTransaction.isPenaltyPayment()).thenReturn(false);
 
-        underTest.processLatestTransaction(loanTransaction, currency, 
List.of(installment), Set.of(charge), overpaidAmount);
+        underTest.processLatestTransaction(loanTransaction, currency, 
List.of(installment), Set.of(charge),
+                new MoneyHolder(overpaidAmount));
 
         Mockito.verify(installment, 
Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), 
eq(chargeAmountMoney));
         Mockito.verify(loanTransaction, 
Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), 
refEq(chargeAmountMoney), refEq(zero));
@@ -165,7 +167,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         Mockito.when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), 
eq(1), refEq(zero))).thenReturn(transactionAmountMoney);
         Mockito.when(loanTransaction.isPenaltyPayment()).thenReturn(false);
 
-        underTest.processLatestTransaction(loanTransaction, currency, 
List.of(installment), Set.of(charge), overpaidAmount);
+        underTest.processLatestTransaction(loanTransaction, currency, 
List.of(installment), Set.of(charge),
+                new MoneyHolder(overpaidAmount));
 
         Mockito.verify(installment, 
Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), 
eq(transactionAmountMoney));
         Mockito.verify(loanTransaction, 
Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), 
refEq(transactionAmountMoney),
@@ -218,7 +221,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
         
Mockito.when(loanPaymentAllocationRule.getAllocationTypes()).thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL));
         
Mockito.when(loanTransaction.isOn(eq(transactionDate))).thenReturn(true);
 
-        underTest.processLatestTransaction(loanTransaction, currency, 
List.of(installment), Set.of(charge), overpaidAmount);
+        underTest.processLatestTransaction(loanTransaction, currency, 
List.of(installment), Set.of(charge),
+                new MoneyHolder(overpaidAmount));
 
         Mockito.verify(installment, 
Mockito.times(1)).payFeeChargesComponent(eq(transactionDate), 
eq(chargeAmountMoney));
         Mockito.verify(loanTransaction, 
Mockito.times(1)).updateComponents(refEq(zero), refEq(zero), 
refEq(chargeAmountMoney), refEq(zero));
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackOnPaymentTypeRepaymentTransactionsTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackOnPaymentTypeRepaymentTransactionsTest.java
index b92c9893a..2f6e81210 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackOnPaymentTypeRepaymentTransactionsTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackOnPaymentTypeRepaymentTransactionsTest.java
@@ -54,7 +54,6 @@ import 
org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHe
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Named;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -65,7 +64,6 @@ import org.junit.jupiter.params.provider.MethodSource;
 public class LoanChargebackOnPaymentTypeRepaymentTransactionsTest {
 
     private ResponseSpecification responseSpec;
-    private ResponseSpecification responseSpecErr400;
     private ResponseSpecification responseSpecErr503;
     private RequestSpecification requestSpec;
     private ClientHelper clientHelper;
@@ -77,13 +75,11 @@ public class 
LoanChargebackOnPaymentTypeRepaymentTransactionsTest {
         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);
     }
 
-    @Disabled("Issue with Chargeback and Adv Pment Alloc")
     @ParameterizedTest
     @MethodSource("loanProductFactory")
     public void 
loanTransactionChargebackForPaymentTypeRepaymentTransactionTest(LoanProductTestBuilder
 loanProductTestBuilder) {


Reply via email to