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 7ee1919ddb4fc20f07d033b84ee4e98da7c6d9db
Author: Oleksii Novikov <[email protected]>
AuthorDate: Mon Sep 8 17:48:17 2025 +0300

    FINERACT-2354: Allow re-age today if the previous re-age was reversed
---
 .../domain/LoanTransactionRepository.java          | 82 ++--------------------
 .../service/reaging/LoanReAgingValidator.java      | 11 ++-
 .../service/reaging/LoanReAgingValidatorTest.java  | 26 +++++--
 3 files changed, 34 insertions(+), 85 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
index 77165a10f3..41de177fe1 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
@@ -314,36 +314,6 @@ public interface LoanTransactionRepository extends 
JpaRepository<LoanTransaction
     Optional<LocalDate> 
findLastNonReversedTransactionDateByLoanAndTypes(@Param("loan") Loan loan,
             @Param("types") Set<LoanTransactionType> types);
 
-    @Query("""
-            SELECT lt FROM LoanTransaction lt
-            WHERE lt.loan = :loan
-                AND (
-                    (lt.reversed = true AND lt.id IN :existingTransactionIds 
AND lt.id NOT IN :existingReversedTransactionIds)
-                    OR (lt.id NOT IN :existingTransactionIds)
-                )
-            """)
-    List<LoanTransaction> findTransactionsForAccountingBridge(@Param("loan") 
Loan loan,
-            @Param("existingTransactionIds") List<Long> existingTransactionIds,
-            @Param("existingReversedTransactionIds") List<Long> 
existingReversedTransactionIds);
-
-    @Query("""
-            SELECT lt FROM LoanTransaction lt
-            WHERE lt.loan = :loan
-                AND (
-                    (lt.reversed = true AND lt.id IN :existingTransactionIds)
-                    OR (lt.id NOT IN :existingTransactionIds)
-                )
-            """)
-    List<LoanTransaction> findTransactionsForAccountingBridge(@Param("loan") 
Loan loan,
-            @Param("existingTransactionIds") List<Long> 
existingTransactionIds);
-
-    @Query("""
-            SELECT lt FROM LoanTransaction lt
-            WHERE lt.loan = :loan
-                AND lt.reversed = false
-            """)
-    List<LoanTransaction> findNonReversedByLoan(@Param("loan") Loan loan);
-
     @Query("""
             SELECT lt FROM LoanTransaction lt
             WHERE lt.loan = :loan
@@ -482,54 +452,14 @@ public interface LoanTransactionRepository extends 
JpaRepository<LoanTransaction
     BigDecimal calculateTotalRecoveryPaymentAmount(@Param("loan") Loan loan);
 
     @Query("""
-            SELECT lt FROM LoanTransaction lt
-            WHERE lt.loan = :loan
-                AND (
-                    (:dateComparison = 'BEFORE' AND lt.dateOf < 
:chargeOffDate) OR
-                    (:dateComparison = 'EQUAL' AND lt.dateOf = :chargeOffDate) 
OR
-                    (:dateComparison = 'AFTER' AND lt.dateOf > :chargeOffDate)
-                )
-                AND (
-                    (lt.reversed = true AND lt.id IN :existingTransactionIds 
AND lt.id NOT IN :existingReversedTransactionIds)
-                    OR (lt.id NOT IN :existingTransactionIds)
-                )
-            ORDER BY lt.dateOf, lt.createdDate, lt.id
-            """)
-    List<LoanTransaction> 
findTransactionsForChargeOffClassification(@Param("loan") Loan loan,
-            @Param("chargeOffDate") LocalDate chargeOffDate, 
@Param("dateComparison") String dateComparison,
-            @Param("existingTransactionIds") List<Long> existingTransactionIds,
-            @Param("existingReversedTransactionIds") List<Long> 
existingReversedTransactionIds);
-
-    @Query("""
-            SELECT lt FROM LoanTransaction lt
-            WHERE lt.loan = :loan
-                AND (
-                    (:dateComparison = 'BEFORE' AND lt.dateOf < 
:chargeOffDate) OR
-                    (:dateComparison = 'EQUAL' AND lt.dateOf = :chargeOffDate) 
OR
-                    (:dateComparison = 'AFTER' AND lt.dateOf > :chargeOffDate)
-                )
-                AND (
-                    (lt.reversed = true AND lt.id IN :existingTransactionIds)
-                    OR (lt.id NOT IN :existingTransactionIds)
-                )
-            ORDER BY lt.dateOf, lt.createdDate, lt.id
-            """)
-    List<LoanTransaction> 
findTransactionsForChargeOffClassification(@Param("loan") Loan loan,
-            @Param("chargeOffDate") LocalDate chargeOffDate, 
@Param("dateComparison") String dateComparison,
-            @Param("existingTransactionIds") List<Long> 
existingTransactionIds);
-
-    @Query("""
-            SELECT lt FROM LoanTransaction lt
+            SELECT CASE WHEN COUNT(lt) > 0 THEN true ELSE false END
+            FROM LoanTransaction lt
             WHERE lt.loan = :loan
-                AND (
-                    (:dateComparison = 'BEFORE' AND lt.dateOf < 
:chargeOffDate) OR
-                    (:dateComparison = 'EQUAL' AND lt.dateOf = :chargeOffDate) 
OR
-                    (:dateComparison = 'AFTER' AND lt.dateOf > :chargeOffDate)
-                )
                 AND lt.reversed = false
-            ORDER BY lt.dateOf, lt.createdDate, lt.id
+                AND lt.typeOf = :type
+                AND lt.dateOf = :transactionDate
             """)
-    List<LoanTransaction> 
findTransactionsForChargeOffClassification(@Param("loan") Loan loan,
-            @Param("chargeOffDate") LocalDate chargeOffDate, 
@Param("dateComparison") String dateComparison);
+    boolean existsNonReversedByLoanAndTypeAndDate(@Param("loan") Loan loan, 
@Param("type") LoanTransactionType type,
+            @Param("transactionDate") LocalDate transactionDate);
 
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidator.java
index 196a386d78..74fa9ac4ab 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidator.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
+import lombok.RequiredArgsConstructor;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.ApiParameterError;
 import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
@@ -34,14 +35,19 @@ import 
org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.portfolio.loanaccount.api.LoanReAgingApiConstants;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ChangeOperation;
 import 
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
 import org.springframework.stereotype.Component;
 
 @Component
+@RequiredArgsConstructor
 public class LoanReAgingValidator {
 
+    private final LoanTransactionRepository loanTransactionRepository;
+
     public void validateReAge(Loan loan, JsonCommand command) {
         validateReAgeRequest(loan, command);
         validateReAgeBusinessRules(loan);
@@ -107,8 +113,9 @@ public class LoanReAgingValidator {
         }
 
         // validate if there's already a re-aging transaction for today
-        boolean isReAgingTransactionForTodayPresent = 
loan.getLoanTransactions().stream()
-                .anyMatch(tx -> tx.getTypeOf().isReAge() && 
tx.getTransactionDate().equals(getBusinessLocalDate()));
+        final boolean isReAgingTransactionForTodayPresent = 
loanTransactionRepository.existsNonReversedByLoanAndTypeAndDate(loan,
+                LoanTransactionType.REAGE, getBusinessLocalDate());
+
         if (isReAgingTransactionForTodayPresent) {
             throw new 
GeneralPlatformDomainRuleException("error.msg.loan.reage.reage.transaction.already.present.for.today",
                     "Loan reaging can only be done once a day. There has 
already been a reaging done for today", loan.getId());
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidatorTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidatorTest.java
index 57d92c78d1..f1f0514378 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidatorTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingValidatorTest.java
@@ -34,7 +34,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.domain.ActionContext;
@@ -46,6 +45,7 @@ import 
org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
 import 
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor;
@@ -54,18 +54,30 @@ import 
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
 
 @SuppressFBWarnings({ "VA_FORMAT_STRING_USES_NEWLINE" })
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
 class LoanReAgingValidatorTest {
 
+    @Mock
+    private LoanTransactionRepository loanTransactionRepository;
+
+    @InjectMocks
+    private LoanReAgingValidator underTest;
+
     public static final String DATE_FORMAT = "dd MMMM yyyy";
     private final LocalDate actualDate = LocalDate.now(Clock.systemUTC());
     private final LocalDate maturityDate = actualDate.plusDays(30);
     private final LocalDate businessDate = maturityDate.plusDays(1);
     private final LocalDate afterMaturity = maturityDate.plusDays(7);
 
-    private LoanReAgingValidator underTest = new LoanReAgingValidator();
-
     @BeforeEach
     public void setUp() {
         ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, 
"default", "Default", "Asia/Kolkata", null));
@@ -92,7 +104,8 @@ class LoanReAgingValidatorTest {
     public void 
testValidateReAge_ShouldThrowException_WhenExternalIdIsLongerThan100() {
         // given
         Loan loan = loan();
-        JsonCommand command = 
jsonCommand(RandomStringUtils.randomAlphabetic(120));
+        String longExternalId = "A".repeat(120);
+        JsonCommand command = jsonCommand(longExternalId);
         // when
         PlatformApiDataValidationException result = 
assertThrows(PlatformApiDataValidationException.class,
                 () -> underTest.validateReAge(loan, command));
@@ -367,10 +380,9 @@ class LoanReAgingValidatorTest {
     @Test
     public void 
testValidateReAge_ShouldThrowException_WhenLoanAlreadyHasReAgeForToday() {
         // given
-        List<LoanTransaction> transactions = 
List.of(loanTransaction(LoanTransactionType.DISBURSEMENT, 
maturityDate.minusDays(2)),
-                loanTransaction(LoanTransactionType.REAGE, businessDate));
         Loan loan = loan();
-        given(loan.getLoanTransactions()).willReturn(transactions);
+        
given(loanTransactionRepository.existsNonReversedByLoanAndTypeAndDate(loan, 
LoanTransactionType.REAGE, businessDate))
+                .willReturn(true);
         JsonCommand command = jsonCommand();
         // when
         GeneralPlatformDomainRuleException result = 
assertThrows(GeneralPlatformDomainRuleException.class,

Reply via email to