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

commit 641c6da1b9b4f81b82281f4e94c8ba80d5eef784
Author: Soma Sörös <[email protected]>
AuthorDate: Wed Jan 21 10:23:25 2026 +0100

    FINERACT-2354: [BE] pay-off to close re-aged loan with charge outcomes with 
Active loan status
---
 .../domain/LoanRepaymentScheduleInstallment.java   | 42 +++++++++++++++++++++
 ...dvancedPaymentScheduleTransactionProcessor.java | 44 +++++++++++++++++++++-
 2 files changed, 84 insertions(+), 2 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 482586035b..1330394cfb 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -945,6 +945,48 @@ public class LoanRepaymentScheduleInstallment extends 
AbstractAuditableWithUTCDa
         }
     }
 
+    public void addPenaltyCharges(final Money amount) {
+        if (amount != null) {
+            addPenaltyCharges(amount.getAmount());
+        }
+    }
+
+    public void addPenaltyCharges(final BigDecimal amount) {
+        if (this.penaltyCharges == null) {
+            setPenaltyCharges(amount);
+        } else {
+            setPenaltyCharges(this.penaltyCharges.add(amount));
+        }
+    }
+
+    public void addToPenaltyPaid(final Money amount) {
+        if (amount != null) {
+            addToPenaltyPaid(amount.getAmount());
+        }
+    }
+
+    public void addToPenaltyPaid(final BigDecimal amount) {
+        if (this.penaltyChargesPaid == null) {
+            setPenaltyChargesPaid(amount);
+        } else {
+            setPenaltyChargesPaid(this.penaltyChargesPaid.add(amount));
+        }
+    }
+
+    public void addToFeeChargesPaid(final Money amount) {
+        if (amount != null) {
+            addToFeeChargesPaid(amount.getAmount());
+        }
+    }
+
+    public void addToFeeChargesPaid(final BigDecimal amount) {
+        if (this.feeChargesPaid == null) {
+            setFeeChargesPaid(amount);
+        } else {
+            setFeeChargesPaid(this.feeChargesPaid.add(amount));
+        }
+    }
+
     /********** UNPAY COMPONENTS ****/
 
     public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, 
final Money transactionAmountRemaining) {
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 4ed75b6031..018bce06a2 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -3764,10 +3764,18 @@ public class 
AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                     balances.getPrincipal().getAmount(), 
balances.getInterest().getAmount(), balances.getFee().getAmount(),
                     balances.getPenalty().getAmount(), null, null, null);
 
+            if (!settings.isEqualInstallmentForFeesAndPenalties) {
+                // If charges should NOT reamortize by reage, we should manage 
to keep them related to the correct
+                // installment.
+                LoanRepaymentScheduleInstallment target = 
earlyRepaidInstallment;
+                Set<LoanCharge> charges = ctx.getCharges();
+                moveRelatedChargesToInstallment(charges, target, installments, 
currency);
+            }
+
             
earlyRepaidInstallment.setPrincipalCompleted(balances.getPrincipal().getAmount());
             
earlyRepaidInstallment.setInterestPaid(balances.getInterest().getAmount());
-            
earlyRepaidInstallment.setFeeChargesPaid(balances.getFee().getAmount());
-            
earlyRepaidInstallment.setPenaltyChargesPaid(balances.getPenalty().getAmount());
+            earlyRepaidInstallment.addToFeeChargesPaid(balances.getFee());
+            earlyRepaidInstallment.addToPenaltyPaid(balances.getPenalty());
 
             
earlyRepaidInstallment.setTotalPaidInAdvance(balances.getPaidInAdvance().getAmount());
 
@@ -3820,6 +3828,38 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
         reprocessInstallments(installments);
     }
 
+    private void moveRelatedChargesToInstallment(Set<LoanCharge> charges, 
LoanRepaymentScheduleInstallment target,
+            List<LoanRepaymentScheduleInstallment> installments, 
MonetaryCurrency currency) {
+        int firstNormalInstallmentNumber = 
LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
+        Set<LoanCharge> chargesOfNewInstallment = 
getLoanChargesOfInstallment(charges, target, firstNormalInstallmentNumber);
+        Integer targetInstallmentNumber = target.getInstallmentNumber();
+        installments.stream().filter(i -> 
Objects.equals(i.getInstallmentNumber(), targetInstallmentNumber)).findFirst()
+                .filter(source -> source != target).ifPresent(source -> {
+                    // move fees
+                    
chargesOfNewInstallment.stream().filter(LoanCharge::isNotFullyPaid).filter(LoanCharge::isFeeCharge)
+                            .forEach(loanCharge -> moveFeeCharge(loanCharge, 
source, target, currency));
+                    // move penalties
+                    
chargesOfNewInstallment.stream().filter(LoanCharge::isNotFullyPaid).filter(LoanCharge::isPenaltyCharge)
+                            .forEach(loanCharge -> 
movePenaltyCharge(loanCharge, source, target, currency));
+                });
+    }
+
+    private void movePenaltyCharge(LoanCharge loanCharge, 
LoanRepaymentScheduleInstallment source, LoanRepaymentScheduleInstallment 
target,
+            MonetaryCurrency currency) {
+        source.setPenaltyCharges(MathUtil.subtract(source.getPenaltyCharges(), 
loanCharge.chargeAmount()));
+        
source.setPenaltyChargesPaid(MathUtil.subtract(source.getPenaltyChargesPaid(), 
loanCharge.getAmountPaid()));
+        target.addPenaltyCharges(loanCharge.getAmount(currency));
+        target.addToPenaltyPaid(loanCharge.getAmountPaid());
+    }
+
+    private void moveFeeCharge(LoanCharge loanCharge, 
LoanRepaymentScheduleInstallment source, LoanRepaymentScheduleInstallment 
target,
+            MonetaryCurrency currency) {
+        
source.setFeeChargesCharged(MathUtil.subtract(source.getFeeChargesCharged(), 
loanCharge.chargeAmount()));
+        source.setFeeChargesPaid(MathUtil.subtract(source.getFeeChargesPaid(), 
loanCharge.getAmountPaid()));
+        target.addToFeeCharges(loanCharge.getAmount(currency));
+        target.addToFeeChargesPaid(loanCharge.getAmountPaid());
+    }
+
     private void createChargeMappingsForInstallment(final 
LoanRepaymentScheduleInstallment installment,
             List<ReAgedChargeEqualAmortizationValues> 
reAgedChargeEqualAmortizationValues, Integer index) {
         reAgedChargeEqualAmortizationValues.forEach(amortizationValue -> 
installment.getInstallmentCharges().add(new LoanInstallmentCharge(

Reply via email to