FINERACT-2354: Allow re-age today if the previous re-age was reversed
This commit is contained in:
committed by
Adam Saghy
parent
5b1d72d15b
commit
7ee1919ddb
+6
-76
@@ -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
|
||||
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 = 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
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
+9
-2
@@ -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());
|
||||
|
||||
+19
-7
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user