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,
