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
The following commit(s) were added to refs/heads/develop by this push:
new ebb6b6c500 FINERACT-2324: Remove getLoanTransactions from accounting
ebb6b6c500 is described below
commit ebb6b6c5006c6066dc555f6eb88952892bf15213
Author: Oleksii Novikov <[email protected]>
AuthorDate: Tue Aug 12 13:28:49 2025 +0300
FINERACT-2324: Remove getLoanTransactions from accounting
---
.../domain/LoanTransactionRepository.java | 51 ++++++++
.../mapper/LoanAccountingBridgeMapper.java | 145 +++++++++++----------
2 files changed, 130 insertions(+), 66 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 73bc3aeb2b..77165a10f3 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
@@ -481,4 +481,55 @@ 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
+ 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
+ """)
+ List<LoanTransaction>
findTransactionsForChargeOffClassification(@Param("loan") Loan loan,
+ @Param("chargeOffDate") LocalDate chargeOffDate,
@Param("dateComparison") String dateComparison);
+
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java
index 4d385bb1a0..293efcf34f 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java
@@ -19,13 +19,12 @@
package org.apache.fineract.portfolio.loanaccount.mapper;
import java.math.BigDecimal;
+import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
-import java.util.function.Predicate;
import lombok.RequiredArgsConstructor;
-import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
import
org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeLoanTransactionDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByDTO;
@@ -36,6 +35,7 @@ import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.stereotype.Component;
@@ -75,15 +75,8 @@ public class LoanAccountingBridgeMapper {
public AccountingBridgeDataDTO deriveAccountingBridgeData(final String
currencyCode, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final boolean
isAccountTransfer, final Loan loan) {
- List<LoanTransaction> transactions;
- if (existingTransactionIds == null ||
existingTransactionIds.isEmpty()) {
- transactions =
loanTransactionRepository.findNonReversedByLoan(loan);
- } else if (existingReversedTransactionIds == null ||
existingReversedTransactionIds.isEmpty()) {
- transactions =
loanTransactionRepository.findTransactionsForAccountingBridge(loan,
existingTransactionIds);
- } else {
- transactions =
loanTransactionRepository.findTransactionsForAccountingBridge(loan,
existingTransactionIds,
- existingReversedTransactionIds);
- }
+ final List<LoanTransaction> transactions =
findTransactionsForAccountingBridge(existingTransactionIds,
+ existingReversedTransactionIds, loan);
final List<AccountingBridgeLoanTransactionDTO> newLoanTransactions =
transactions.stream() //
.map(transaction -> mapToLoanTransactionData(transaction,
currencyCode)) //
@@ -179,47 +172,53 @@ public class LoanAccountingBridgeMapper {
private void classifyTransactionsBasedOnChargeOffDate(final
List<AccountingBridgeLoanTransactionDTO> newLoanTransactionsBeforeChargeOff,
final List<AccountingBridgeLoanTransactionDTO>
newLoanTransactionsAfterChargeOff, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final String
currencyCode, final Loan loan) {
- // Before
- filterTransactionsByChargeOffDate(newLoanTransactionsBeforeChargeOff,
currencyCode, existingTransactionIds,
- existingReversedTransactionIds,
- transaction ->
DateUtils.isBefore(transaction.getTransactionDate(),
loan.getChargedOffOnDate()), loan);
- // On
- filterTransactionsByChargeOffDate(newLoanTransactionsBeforeChargeOff,
newLoanTransactionsAfterChargeOff, currencyCode,
- existingTransactionIds, existingReversedTransactionIds,
- transaction ->
DateUtils.isEqual(transaction.getTransactionDate(),
loan.getChargedOffOnDate()), loan);
- // After
- filterTransactionsByChargeOffDate(newLoanTransactionsAfterChargeOff,
currencyCode, existingTransactionIds,
- existingReversedTransactionIds,
- transaction ->
DateUtils.isAfter(transaction.getTransactionDate(),
loan.getChargedOffOnDate()), loan);
+
+ final LocalDate chargeOffDate = loan.getChargedOffOnDate();
+ if (chargeOffDate == null) {
+ return;
+ }
+
+ // Before charge-off
+ final List<LoanTransaction> beforeTransactions =
findTransactionsForChargeOffClassification(loan, chargeOffDate, "BEFORE",
+ existingTransactionIds, existingReversedTransactionIds);
+ processTransactionsForChargeOff(beforeTransactions,
newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff,
+ currencyCode, loan);
+
+ // On charge-off date
+ final List<LoanTransaction> onTransactions =
findTransactionsForChargeOffClassification(loan, chargeOffDate, "EQUAL",
+ existingTransactionIds, existingReversedTransactionIds);
+ processTransactionsForChargeOff(onTransactions,
newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff,
currencyCode,
+ loan);
+
+ // After charge-off
+ final List<LoanTransaction> afterTransactions =
findTransactionsForChargeOffClassification(loan, chargeOffDate, "AFTER",
+ existingTransactionIds, existingReversedTransactionIds);
+ processTransactionsForChargeOff(afterTransactions,
newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff,
+ currencyCode, loan);
}
- private void filterTransactionsByChargeOffDate(final
List<AccountingBridgeLoanTransactionDTO> filteredTransactions,
- final String currencyCode, final List<Long>
existingTransactionIds, final List<Long> existingReversedTransactionIds,
- final Predicate<LoanTransaction> chargeOffDateCriteria, final Loan
loan) {
- filteredTransactions.addAll(loan.getLoanTransactions().stream() //
- .filter(chargeOffDateCriteria) //
- .filter(transaction -> {
- boolean isExistingTransaction =
existingTransactionIds.contains(transaction.getId());
- boolean isExistingReversedTransaction =
existingReversedTransactionIds.contains(transaction.getId());
-
- if (transaction.isReversed() && isExistingTransaction &&
!isExistingReversedTransaction) {
- return true;
- } else {
- return !isExistingTransaction;
- }
- }) //
- .map(transaction -> mapToLoanTransactionData(transaction,
currencyCode)).toList());
+ private List<LoanTransaction>
findTransactionsForChargeOffClassification(final Loan loan, final LocalDate
chargeOffDate,
+ final String dateComparison, final List<Long>
existingTransactionIds, final List<Long> existingReversedTransactionIds) {
+
+ final boolean hasExistingIds = existingTransactionIds != null &&
!existingTransactionIds.isEmpty();
+ final boolean hasExistingReversedIds = existingReversedTransactionIds
!= null && !existingReversedTransactionIds.isEmpty();
+
+ if (hasExistingIds && hasExistingReversedIds) {
+ return
loanTransactionRepository.findTransactionsForChargeOffClassification(loan,
chargeOffDate, dateComparison,
+ existingTransactionIds, existingReversedTransactionIds);
+ } else if (hasExistingIds) {
+ return
loanTransactionRepository.findTransactionsForChargeOffClassification(loan,
chargeOffDate, dateComparison,
+ existingTransactionIds);
+ } else {
+ return
loanTransactionRepository.findTransactionsForChargeOffClassification(loan,
chargeOffDate, dateComparison);
+ }
}
- private void filterTransactionsByChargeOffDate(final
List<AccountingBridgeLoanTransactionDTO> newLoanTransactionsBeforeChargeOff,
- final List<AccountingBridgeLoanTransactionDTO>
newLoanTransactionsAfterChargeOff, final String currencyCode,
- final List<Long> existingTransactionIds, final List<Long>
existingReversedTransactionIds,
- final Predicate<LoanTransaction> chargeOffDateCriteria, final Loan
loan) {
- final Optional<LoanTransaction> chargeOffTransactionOptional =
loan.getLoanTransactions().stream() //
- .filter(LoanTransaction::isChargeOff) //
- .filter(LoanTransaction::isNotReversed) //
- .findFirst();
+ private void processTransactionsForChargeOff(final List<LoanTransaction>
transactions,
+ final List<AccountingBridgeLoanTransactionDTO>
newLoanTransactionsBeforeChargeOff,
+ final List<AccountingBridgeLoanTransactionDTO>
newLoanTransactionsAfterChargeOff, final String currencyCode, final Loan loan) {
+ final Optional<LoanTransaction> chargeOffTransactionOptional =
findChargeOffTransaction(loan);
if (chargeOffTransactionOptional.isEmpty()) {
return;
}
@@ -227,35 +226,49 @@ public class LoanAccountingBridgeMapper {
final LoanTransaction chargeOffTransaction =
chargeOffTransactionOptional.get();
final LoanTransaction originalChargeOffTransaction =
getOriginalTransactionIfReverseReplayed(chargeOffTransaction);
-
loan.getLoanTransactions().stream().filter(chargeOffDateCriteria).forEach(transaction
-> {
- boolean isExistingTransaction =
existingTransactionIds.contains(transaction.getId());
- boolean isExistingReversedTransaction =
existingReversedTransactionIds.contains(transaction.getId());
- List<AccountingBridgeLoanTransactionDTO> targetList = null;
- if ((transaction.isReversed() && isExistingTransaction &&
!isExistingReversedTransaction)) {
- // reversed transactions
- LoanTransaction originalTransaction =
getOriginalTransactionIfReverseReplayed(transaction);
+ transactions.forEach(transaction -> {
+ List<AccountingBridgeLoanTransactionDTO> targetList;
+
+ if (transaction.isReversed()) {
+ final LoanTransaction originalTransaction =
getOriginalTransactionIfReverseReplayed(transaction);
targetList =
originalTransaction.happenedBefore(originalChargeOffTransaction) ?
newLoanTransactionsBeforeChargeOff
: newLoanTransactionsAfterChargeOff;
-
- } else if (!isExistingTransaction) {
- // new and replayed transactions
+ } else {
targetList = transaction.happenedBefore(chargeOffTransaction)
? newLoanTransactionsBeforeChargeOff
: newLoanTransactionsAfterChargeOff;
}
- if (targetList != null) {
- targetList.add(mapToLoanTransactionData(transaction,
currencyCode));
- }
+
+ targetList.add(mapToLoanTransactionData(transaction,
currencyCode));
});
}
+ private Optional<LoanTransaction> findChargeOffTransaction(final Loan
loan) {
+ return loanTransactionRepository.findNonReversedByLoanAndType(loan,
LoanTransactionType.CHARGE_OFF).stream() //
+ .findFirst();
+ }
+
+ private List<LoanTransaction> findTransactionsForAccountingBridge(final
List<Long> existingTransactionIds,
+ final List<Long> existingReversedTransactionIds, final Loan loan) {
+ if (existingTransactionIds == null ||
existingTransactionIds.isEmpty()) {
+ return loanTransactionRepository.findNonReversedByLoan(loan);
+ } else if (existingReversedTransactionIds == null ||
existingReversedTransactionIds.isEmpty()) {
+ return
loanTransactionRepository.findTransactionsForAccountingBridge(loan,
existingTransactionIds);
+ } else {
+ return
loanTransactionRepository.findTransactionsForAccountingBridge(loan,
existingTransactionIds,
+ existingReversedTransactionIds);
+ }
+ }
+
private LoanTransaction getOriginalTransactionIfReverseReplayed(final
LoanTransaction loanTransaction) {
- if (!loanTransaction.getLoanTransactionRelations().isEmpty()) {
- return loanTransaction.getLoanTransactionRelations().stream()
- .filter(tr ->
LoanTransactionRelationTypeEnum.REPLAYED.equals(tr.getRelationType()))
-
.map(LoanTransactionRelation::getToTransaction).toList().stream().min(Comparator.comparingLong(LoanTransaction::getId))
- .orElse(loanTransaction);
+ if (loanTransaction.getLoanTransactionRelations().isEmpty()) {
+ return loanTransaction;
}
- return loanTransaction;
+
+ return loanTransaction.getLoanTransactionRelations().stream() //
+ .filter(tr ->
LoanTransactionRelationTypeEnum.REPLAYED.equals(tr.getRelationType())) //
+ .map(LoanTransactionRelation::getToTransaction) //
+ .min(Comparator.comparingLong(LoanTransaction::getId)) //
+ .orElse(loanTransaction);
}
}