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 ef80634b4e8513f0f3147cc40da781ec14dc4d34
Author: Adam Saghy <adamsa...@gmail.com>
AuthorDate: Thu Sep 25 21:05:18 2025 +0200

    FINERACT-2381: Fix Accrual Activity calculation
---
 ...tLoanRepaymentScheduleTransactionProcessor.java | 51 --------------
 .../LoanAccrualActivityProcessingService.java      |  3 +
 ...dvancedPaymentScheduleTransactionProcessor.java |  5 --
 .../LoanAccrualActivityProcessingServiceImpl.java  | 78 ++++++++++++++++++++++
 .../ReprocessLoanTransactionsServiceImpl.java      |  2 +
 5 files changed, 83 insertions(+), 56 deletions(-)

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 7e8adf046d..aa20e1fe40 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
@@ -223,63 +223,12 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
                 
reprocessChargebackTransactionRelation(changedTransactionDetail, 
transactionsToBeProcessed);
             } else if (loanTransaction.isChargeOff()) {
                 recalculateChargeOffTransaction(changedTransactionDetail, 
loanTransaction, currency, installments);
-            } else if (loanTransaction.isAccrualActivity()) {
-                
recalculateAccrualActivityTransaction(changedTransactionDetail, 
loanTransaction, currency, installments);
             }
         }
         reprocessInstallments(disbursementDate, transactionsToBeProcessed, 
installments, currency);
         return changedTransactionDetail;
     }
 
-    protected void calculateAccrualActivity(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
-            List<LoanRepaymentScheduleInstallment> installments) {
-
-        final int firstNormalInstallmentNumber = 
LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
-
-        final Optional<LoanRepaymentScheduleInstallment> currentInstallmentOpt 
= installments.stream()
-                .filter(installment -> 
LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(),
 installment,
-                        
installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)))
-                .findFirst();
-
-        if (currentInstallmentOpt.isEmpty()) {
-            return;
-        }
-
-        final LoanRepaymentScheduleInstallment currentInstallment = 
currentInstallmentOpt.get();
-        if (currentInstallment.isNotFullyPaidOff() && 
(currentInstallment.getDueDate().isAfter(loanTransaction.getTransactionDate())
-                || 
(currentInstallment.getDueDate().isEqual(loanTransaction.getTransactionDate())
-                        && 
loanTransaction.getTransactionDate().equals(DateUtils.getBusinessLocalDate()))))
 {
-            loanTransaction.reverse();
-        } else {
-            loanTransaction.resetDerivedComponents();
-            final Money principalPortion = Money.zero(currency);
-            Money interestPortion = 
currentInstallment.getInterestCharged(currency);
-            Money feeChargesPortion = 
currentInstallment.getFeeChargesCharged(currency);
-            Money penaltyChargesPortion = 
currentInstallment.getPenaltyChargesCharged(currency);
-            if 
(interestPortion.plus(feeChargesPortion).plus(penaltyChargesPortion).isZero()) {
-                loanTransaction.reverse();
-            } else {
-                loanTransaction.updateComponentsAndTotal(principalPortion, 
interestPortion, feeChargesPortion, penaltyChargesPortion);
-                final Loan loan = loanTransaction.getLoan();
-                if ((loan.isClosedObligationsMet() || 
loanBalanceService.isOverPaid(loan)) && currentInstallment.isObligationsMet()
-                        && 
currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate()))
 {
-                    
loanTransaction.updateTransactionDate(currentInstallment.getObligationsMetOnDate());
-                }
-            }
-        }
-    }
-
-    private void 
recalculateAccrualActivityTransaction(ChangedTransactionDetail 
changedTransactionDetail, LoanTransaction loanTransaction,
-            MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> 
installments) {
-        final LoanTransaction newLoanTransaction = 
LoanTransaction.copyTransactionProperties(loanTransaction);
-
-        calculateAccrualActivity(newLoanTransaction, currency, installments);
-
-        if (!LoanTransaction.transactionAmountsMatch(currency, 
loanTransaction, newLoanTransaction)) {
-            createNewTransaction(loanTransaction, newLoanTransaction, 
changedTransactionDetail);
-        }
-    }
-
     @Override
     public ChangedTransactionDetail processLatestTransaction(final 
LoanTransaction loanTransaction, final TransactionCtx ctx) {
         switch (loanTransaction.getTypeOf()) {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java
index a3c233915b..aa4769f5f5 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.portfolio.loanaccount.service;
 
 import java.time.LocalDate;
+import 
org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.springframework.lang.NonNull;
 import org.springframework.transaction.annotation.Transactional;
@@ -30,6 +31,8 @@ public interface LoanAccrualActivityProcessingService {
 
     void makeAccrualActivityTransaction(@NonNull Loan loan, @NonNull LocalDate 
currentDate);
 
+    void recalculateAccrualActivityTransaction(Loan loan, 
ChangedTransactionDetail changedTransactionDetail);
+
     @Transactional
     void processAccrualActivityForLoanClosure(@NonNull Loan loan);
 
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 85970a72f7..a9a9bd1cae 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
@@ -412,7 +412,6 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
             case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will 
not be processed.");
             case REAMORTIZE -> handleReAmortization(loanTransaction, ctx);
             case REAGE -> handleReAge(loanTransaction, ctx);
-            case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction, 
ctx);
             case CAPITALIZED_INCOME -> 
handleCapitalizedIncome(loanTransaction, ctx);
             case CONTRACT_TERMINATION -> 
handleContractTermination(loanTransaction, ctx);
             // TODO: Cover rest of the transaction types
@@ -2903,10 +2902,6 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
         reprocessInstallments(installments);
     }
 
-    protected void calculateAccrualActivity(LoanTransaction transaction, 
TransactionCtx ctx) {
-        super.calculateAccrualActivity(transaction, ctx.getCurrency(), 
ctx.getInstallments());
-    }
-
     private void reprocessInstallments(final 
List<LoanRepaymentScheduleInstallment> installments) {
         final AtomicInteger counter = new AtomicInteger(1);
         final AtomicReference<LocalDate> previousDueDate = new 
AtomicReference<>(null);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
index 754857d921..2d531c9a7c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
@@ -26,6 +26,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -36,9 +37,14 @@ import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustT
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
+import 
org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
@@ -101,6 +107,32 @@ public class LoanAccrualActivityProcessingServiceImpl 
implements LoanAccrualActi
         });
     }
 
+    @Override
+    public void recalculateAccrualActivityTransaction(Loan loan, 
ChangedTransactionDetail changedTransactionDetail) {
+        List<LoanTransaction> accrualActivities = 
loanTransactionRepository.findNonReversedByLoanAndType(loan,
+                LoanTransactionType.ACCRUAL_ACTIVITY);
+        accrualActivities.forEach(accrualActivity -> {
+            final LoanTransaction newLoanTransaction = 
LoanTransaction.copyTransactionProperties(accrualActivity);
+
+            calculateAccrualActivity(newLoanTransaction, loan.getCurrency(), 
loan.getRepaymentScheduleInstallments());
+
+            if (!LoanTransaction.transactionAmountsMatch(loan.getCurrency(), 
accrualActivity, newLoanTransaction)) {
+                createNewTransaction(accrualActivity, newLoanTransaction, 
changedTransactionDetail);
+            }
+        });
+    }
+
+    protected void createNewTransaction(LoanTransaction loanTransaction, 
LoanTransaction newLoanTransaction,
+            ChangedTransactionDetail changedTransactionDetail) {
+        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.addTransactionChange(new 
TransactionChangeData(loanTransaction, newLoanTransaction));
+    }
+
     @Override
     @Transactional
     public void processAccrualActivityForLoanClosure(final @NonNull Loan loan) 
{
@@ -206,6 +238,52 @@ public class LoanAccrualActivityProcessingServiceImpl 
implements LoanAccrualActi
         }
     }
 
+    private void calculateAccrualActivity(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
+            List<LoanRepaymentScheduleInstallment> installments) {
+
+        final int firstNormalInstallmentNumber = 
LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
+
+        final List<LoanRepaymentScheduleInstallment> targetInstallments = 
installments.stream()
+                .filter(installment -> 
LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(),
 installment,
+                        
installment.getInstallmentNumber().equals(firstNormalInstallmentNumber))
+                        || 
(DateUtils.isEqual(installment.getObligationsMetOnDate(), 
loanTransaction.getTransactionDate())
+                                && 
installment.getDueDate().isAfter(loanTransaction.getTransactionDate())))
+                .toList();
+
+        if (targetInstallments.isEmpty()) {
+            return;
+        }
+
+        AtomicBoolean isReset = new AtomicBoolean(false);
+        targetInstallments.forEach(currentInstallment -> {
+            if (currentInstallment.isNotFullyPaidOff() && 
(currentInstallment.getDueDate().isAfter(loanTransaction.getTransactionDate())
+                    || 
(currentInstallment.getDueDate().isEqual(loanTransaction.getTransactionDate())
+                            && 
loanTransaction.getTransactionDate().equals(DateUtils.getBusinessLocalDate()))))
 {
+                loanTransaction.reverse();
+            } else {
+                if (!isReset.get()) {
+                    loanTransaction.resetDerivedComponents();
+                    isReset.set(true);
+                }
+                final Money principalPortion = Money.zero(currency);
+                Money interestPortion = 
currentInstallment.getInterestCharged(currency);
+                Money feeChargesPortion = 
currentInstallment.getFeeChargesCharged(currency);
+                Money penaltyChargesPortion = 
currentInstallment.getPenaltyChargesCharged(currency);
+
+                loanTransaction.updateComponentsAndTotal(principalPortion, 
interestPortion, feeChargesPortion, penaltyChargesPortion);
+                final Loan loan = loanTransaction.getLoan();
+                if ((loan.isClosedObligationsMet() || 
loanBalanceService.isOverPaid(loan)) && currentInstallment.isObligationsMet()
+                        && 
currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate()))
 {
+                    
loanTransaction.updateTransactionDate(currentInstallment.getObligationsMetOnDate());
+                }
+            }
+        });
+        if 
(MathUtil.isZero(MathUtil.nullToZero(MathUtil.add(loanTransaction.getInterestPortion(),
 loanTransaction.getFeeChargesPortion(),
+                loanTransaction.getPenaltyChargesPortion())))) {
+            loanTransaction.reverse();
+        }
+    }
+
     private Map<LocalDate, List<LoanTransaction>> 
loadExistingAccrualActivitiesByDate(final @NonNull Loan loan,
             final List<LoanRepaymentScheduleInstallment> installments) {
         final Set<LocalDate> dueDates = 
installments.stream().map(LoanRepaymentScheduleInstallment::getDueDate).collect(Collectors.toSet());
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
index de8fe0a830..b35e1c0253 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
@@ -62,6 +62,7 @@ public class ReprocessLoanTransactionsServiceImpl implements 
ReprocessLoanTransa
     private final LoanTransactionService loanTransactionService;
     private final LoanJournalEntryPoster loanJournalEntryPoster;
     private final BusinessEventNotifierService businessEventNotifierService;
+    private final LoanAccrualActivityProcessingService 
loanAccrualActivityProcessingService;
 
     @Override
     public void reprocessTransactions(final Loan loan) {
@@ -227,6 +228,7 @@ public class ReprocessLoanTransactionsServiceImpl 
implements ReprocessLoanTransa
                 .map(TransactionChangeData::getNewTransaction).toList();
         loan.getLoanTransactions().addAll(newTransactions);
         loanBalanceService.updateLoanSummaryDerivedFields(loan);
+        
loanAccrualActivityProcessingService.recalculateAccrualActivityTransaction(loan,
 changedTransactionDetail);
         return changedTransactionDetail;
     }
 }

Reply via email to