FINERACT-2181: Rework "createMissingAccrualTransactionDuringChargeOffIfNeeded" to avoid flushing and triggering business event as part of Transaction processor

This commit is contained in:
mariiaKraievska
2025-03-18 12:03:27 +01:00
committed by Adam Saghy
parent 166c1981fb
commit 22282ded00
16 changed files with 286 additions and 127 deletions
@@ -2917,14 +2917,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
}
final List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsForReprocessing();
loanTransactions.add(chargeOffTransaction);
reprocessLoanTransactionsService.reprocessTransactions(loan, loanTransactions);
loan.addLoanTransaction(chargeOffTransaction);
reprocessLoanTransactionsService.reprocessTransactions(loan);
} else {
reprocessLoanTransactionsService.processLatestTransaction(chargeOffTransaction, loan);
loan.addLoanTransaction(chargeOffTransaction);
loan.getTransactionProcessor().processLatestTransaction(chargeOffTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
loan.updateLoanSummaryDerivedFields();
}
loanTransactionRepository.saveAndFlush(chargeOffTransaction);
@@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
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.LoanRepaymentScheduleInstallment;
@@ -92,7 +93,9 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessResult = processor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), relatedRefundTransactionDate, transactionsToReprocess,
loan.getCurrency(), installmentsToReprocess, loan.getActiveCharges());
loan.getLoanTransactions().addAll(reprocessResult.getLeft().getCurrentTransactionToOldId().keySet());
final List<LoanTransaction> newTransactions = reprocessResult.getLeft().getTransactionChanges().stream()
.map(TransactionChangeData::getNewTransaction).toList();
loan.getLoanTransactions().addAll(newTransactions);
ProgressiveLoanInterestScheduleModel modelAfter = reprocessResult.getRight();
return emiCalculator.getSumOfDueInterestsOnDate(modelAfter, relatedRefundTransactionDate);
@@ -23,6 +23,7 @@ import java.time.LocalDate;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
@@ -97,11 +98,11 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess,
loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
ProgressiveLoanInterestScheduleModel model = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getRight();
if (!changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft().getCurrentTransactionToOldId().isEmpty()
|| !changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft().getNewTransactionMappings()
.isEmpty()) {
List<Long> replayedTransactions = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
.getNewTransactionMappings().keySet().stream().toList();
final List<Long> replayedTransactions = changedTransactionDetailProgressiveLoanInterestScheduleModelPair.getLeft()
.getTransactionChanges().stream().filter(change -> change.getOldTransaction() != null)
.map(change -> change.getNewTransaction().getId()).filter(Objects::nonNull).toList();
if (!replayedTransactions.isEmpty()) {
log.warn("Reprocessed transactions show differences: There are unsaved changes of the following transactions: {}",
replayedTransactions);
}
@@ -20,10 +20,8 @@ package org.apache.fineract.portfolio.loanaccount.starter;
import java.util.List;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.CreocoreLoanRepaymentScheduleTransactionProcessor;
@@ -112,10 +110,8 @@ public class LoanAccountAutoStarter {
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator,
LoanRepositoryWrapper loanRepositoryWrapper,
@Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService,
LoanTransactionRepository loanTransactionRepository, ExternalIdFactory externalIdFactory,
@Lazy BusinessEventNotifierService businessEventNotifierService) {
@Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService, ExternalIdFactory externalIdFactory) {
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService,
loanTransactionRepository, externalIdFactory, businessEventNotifierService);
externalIdFactory);
}
}
@@ -27,6 +27,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
@@ -88,8 +89,9 @@ class ReplayedTransactionBusinessEventServiceIntegrationTest {
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
lenient().when(oldLoanTransaction.getId()).thenReturn(1L);
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
changedTransactionDetail.getNewTransactionMappings().put(1L, newLoanTransaction);
changedTransactionDetail.addTransactionChange(new TransactionChangeData(oldLoanTransaction, newLoanTransaction));
// when
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
// then
@@ -103,13 +105,16 @@ class ReplayedTransactionBusinessEventServiceIntegrationTest {
@Test
public void testWhenParamHasTwoNewTransaction() {
// given
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
LoanTransaction oldLoanTransaction1 = Mockito.mock(LoanTransaction.class);
LoanTransaction oldLoanTransaction2 = Mockito.mock(LoanTransaction.class);
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
lenient().when(loanTransactionRepository.findById(2L)).thenReturn(Optional.of(oldLoanTransaction));
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction1));
lenient().when(loanTransactionRepository.findById(2L)).thenReturn(Optional.of(oldLoanTransaction2));
lenient().when(oldLoanTransaction1.getId()).thenReturn(1L);
lenient().when(oldLoanTransaction2.getId()).thenReturn(2L);
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
changedTransactionDetail.getNewTransactionMappings().put(1L, newLoanTransaction);
changedTransactionDetail.getNewTransactionMappings().put(2L, newLoanTransaction);
changedTransactionDetail.addTransactionChange(new TransactionChangeData(oldLoanTransaction1, newLoanTransaction));
changedTransactionDetail.addTransactionChange(new TransactionChangeData(oldLoanTransaction2, newLoanTransaction));
// when
underTest.raiseTransactionReplayedEvents(changedTransactionDetail);
// then
@@ -128,8 +133,9 @@ class ReplayedTransactionBusinessEventServiceIntegrationTest {
LoanTransaction oldLoanTransaction = Mockito.mock(LoanTransaction.class);
LoanTransaction newLoanTransaction = Mockito.mock(LoanTransaction.class);
lenient().when(loanTransactionRepository.findById(1L)).thenReturn(Optional.of(oldLoanTransaction));
lenient().when(oldLoanTransaction.getId()).thenReturn(1L);
ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
changedTransactionDetail.getNewTransactionMappings().put(1L, newLoanTransaction);
changedTransactionDetail.addTransactionChange(new TransactionChangeData(oldLoanTransaction, newLoanTransaction));
// when
assertThrows(RuntimeException.class, () -> underTest.raiseTransactionReplayedEvents(changedTransactionDetail));
// then