FINERACT-2234: Replace in-memory transaction loops with repository queries

This commit is contained in:
Oleksii Novikov
2025-07-01 20:14:28 +02:00
committed by Adam Saghy
parent dd4ca4d3e2
commit cb6a15c3d2
29 changed files with 281 additions and 228 deletions
@@ -134,7 +134,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final NoteRepository noteRepository;
private final BusinessEventNotifierService businessEventNotifierService;
@@ -464,7 +463,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
chargesPayment.updateComponents(zero, zero, charge.getAmount(loan.getCurrency()), zero);
chargesPayment.updateLoan(loan);
loan.addLoanTransaction(chargesPayment);
loan.updateLoanOutstandingBalances();
loanBalanceService.updateLoanOutstandingBalances(loan);
charge.markAsFullyPaid();
}
@@ -931,7 +931,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
createUpdateIncomePostingTransaction(loan, compoundingDetail, interest, fee, penalties, externalId);
createUpdateAccrualTransaction(loan, compoundingDetail, interest, fee, penalties, feeDetails, externalId);
loan.updateLoanOutstandingBalances();
loanBalanceService.updateLoanOutstandingBalances(loan);
}
private void createUpdateIncomePostingTransaction(Loan loan, LoanInterestRecalcualtionAdditionalDetails compoundingDetail,
@@ -1000,7 +1000,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
createIncomePostingAndAccrualTransactionOnLoanClosure(loan, closedDate, interestToPost, feeToPost, penaltyToPost, amountToPost);
}
loan.updateLoanOutstandingBalances();
loanBalanceService.updateLoanOutstandingBalances(loan);
}
private void determineCumulativeIncomeFromInstallments(final Loan loan,
@@ -261,20 +261,20 @@ public class LoanAssemblerImpl implements LoanAssembler {
loanOfficer, loanPurpose, transactionProcessingStrategy, loanProductRelatedDetail, loanCharges,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
isEnableInstallmentLevelDelinquency, submittedOnDate);
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, isEnableInstallmentLevelDelinquency,
submittedOnDate);
} else if (group != null) {
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanAccountType, loanProduct, fund, loanOfficer, loanPurpose,
transactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms,
loanScheduleModel, isEnableInstallmentLevelDelinquency, submittedOnDate);
isEnableInstallmentLevelDelinquency, submittedOnDate);
} else if (client != null) {
loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanAccountType, loanProduct, fund, loanOfficer,
loanPurpose, transactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms,
loanScheduleModel, isEnableInstallmentLevelDelinquency, submittedOnDate);
isEnableInstallmentLevelDelinquency, submittedOnDate);
} else {
throw new IllegalStateException("No loan application exists for either a client or group (or both).");
}
@@ -63,6 +63,7 @@ public class LoanDisbursementService {
private final LoanDisbursementValidator loanDisbursementValidator;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanChargeService loanChargeService;
private final LoanBalanceService loanBalanceService;
public void updateDisbursementDetails(final Loan loan, final JsonCommand jsonCommand, final Map<String, Object> actualChanges) {
final List<Long> disbursementList = loan.fetchDisbursementIds();
@@ -217,7 +218,7 @@ public class LoanDisbursementService {
chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero);
chargesPayment.updateLoan(loan);
loan.addLoanTransaction(chargesPayment);
loan.updateLoanOutstandingBalances();
loanBalanceService.updateLoanOutstandingBalances(loan);
}
final LocalDate expectedDate = loan.getExpectedFirstRepaymentOnDate();
@@ -61,8 +61,8 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
private final LoanTermVariationsMapper loanMapper;
private final InterestScheduleModelRepositoryWrapper modelRepository;
private final LoanTransactionService loanTransactionService;
private final LoanBalanceService loanBalanceService;
private final LoanTransactionService loanTransactionService;
@Override
public boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction,
@@ -594,8 +594,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
if (loan.getLoanProduct().isMultiDisburseLoan() || loan.isProgressiveSchedule()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionService
.retrieveListOfTransactionsForReprocessing(loan);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionRepository
.findNonReversedTransactionsForReprocessingByLoan(loan);
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
reprocessLoanTransactionsService.reprocessTransactions(loan);
}
@@ -2246,7 +2246,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
} else {
if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
if (disbursementDataArray == null || disbursementDataArray.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
@@ -2821,7 +2821,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
}
final List<LoanTransaction> loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
final List<LoanTransaction> loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
loanTransactions.add(chargeOffTransaction);
reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions);
loan.addLoanTransaction(chargeOffTransaction);
@@ -3031,7 +3031,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final String errorMessage = "loan.product.does.not.support.multiple.disbursals.cannot.undo.last.disbursal";
throw new LoanMultiDisbursementException(errorMessage);
}
Integer trancheDisbursedCount = 0;
int trancheDisbursedCount = 0;
for (LoanDisbursementDetails disbursementDetails : loan.getDisbursementDetails()) {
if (disbursementDetails.actualDisbursementDate() != null) {
trancheDisbursedCount++;
@@ -3361,13 +3361,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> actualChanges = new LinkedHashMap<>();
List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsByType(LoanTransactionType.DISBURSEMENT);
loanTransactions.sort(Comparator.comparing(LoanTransaction::getId));
final LoanTransaction lastDisbursalTransaction = loanTransactions.get(loanTransactions.size() - 1);
final LoanTransaction lastDisbursalTransaction = loanTransactions.getLast();
final LocalDate lastTransactionDate = lastDisbursalTransaction.getTransactionDate();
existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan));
existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
loanTransactions = loan.retrieveListOfTransactionsExcludeAccruals();
loanTransactions = loanTransactionRepository.findNonReversedMonetaryTransactionsByLoan(loan);
Collections.reverse(loanTransactions);
for (final LoanTransaction previousTransaction : loanTransactions) {
if (DateUtils.isBefore(lastTransactionDate, previousTransaction.getTransactionDate())
@@ -36,6 +36,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
@@ -53,7 +54,7 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
private final EMICalculator emiCalculator;
private final LoanRepositoryWrapper loanRepository;
private final LoanTransactionService loanTransactionService;
private final LoanTransactionRepository loanTransactionRepository;
private final InterestScheduleModelRepositoryWrapper modelRepository;
@Override
@@ -81,8 +82,8 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
}
private ProgressiveLoanInterestScheduleModel calculateModel(Loan loan, LocalDate businessDate) {
final List<LoanTransaction> transactionsToReprocess = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan)
.stream().filter(t -> !t.isAccrualActivity()).toList();
final List<LoanTransaction> transactionsToReprocess = loanTransactionRepository
.findNonReversedTransactionsForReprocessingByLoan(loan).stream().filter(t -> !t.isAccrualActivity()).toList();
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
@@ -22,8 +22,6 @@ import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -37,7 +35,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
@@ -55,26 +52,18 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;
private final LoanBalanceService loanBalanceService;
private final LoanTransactionRepository loanTransactionRepository;
private final LoanTransactionService loanTransactionService;
@Override
public void reprocessTransactions(final Loan loan) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(loan);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionService
.retrieveListOfTransactionsForReprocessing(loan);
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
allNonContraTransactionsPostDisbursement);
handleChangedDetail(changedTransactionDetail);
}
private List<LoanTransaction> retrieveListOfTransactionsForReprocessing(final Loan loan) {
return loan.getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate())
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
}
private Predicate<LoanTransaction> loanTransactionForReprocessingPredicate() {
return transaction -> transaction.isNotReversed()
&& (transaction.isChargeOff() || transaction.isReAge() || transaction.isAccrualActivity() || transaction.isReAmortize()
|| !transaction.isNonMonetaryTransaction() || transaction.isContractTermination());
}
@Override
public void reprocessParticularTransactions(final Loan loan, final List<LoanTransaction> loanTransactions) {
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loanTransactions);
@@ -83,7 +72,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
@Override
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
final List<LoanTransaction> transactions = retrieveListOfTransactionsForReprocessing(loan);
final List<LoanTransaction> transactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, transactions);
handleChangedDetail(changedTransactionDetail);
}
@@ -70,10 +70,10 @@ public class LoanContractTerminationServiceImpl {
private final LoanUtilService loanUtilService;
private final ExternalIdFactory externalIdFactory;
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanTransactionService loanTransactionService;
private final LoanScheduleService loanScheduleService;
private final LoanChargeValidator loanChargeValidator;
private final ProgressiveLoanTransactionValidator loanTransactionValidator;
private final LoanTransactionService loanTransactionService;
public CommandProcessingResult applyContractTermination(final JsonCommand command) {
final Loan loan = loanAssembler.assembleFrom(command.getLoanId());
@@ -22,6 +22,7 @@ import java.util.List;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
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;
@@ -35,7 +36,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanInterestRefundServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
@@ -138,9 +138,9 @@ public class LoanAccountAutoStarter {
final LoanRepositoryWrapper loanRepositoryWrapper,
final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService,
final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule,
final LoanTransactionService loanTransactionService, final LoanChargeValidator loanChargeValidator,
final LoanTransactionRepository loanTransactionRepository, final LoanChargeValidator loanChargeValidator,
final LoanBalanceService loanBalanceService) {
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService,
externalIdFactory, loanSchedule, loanTransactionService, loanChargeValidator, loanBalanceService);
externalIdFactory, loanSchedule, loanTransactionRepository, loanChargeValidator, loanBalanceService);
}
}
@@ -457,9 +457,9 @@ public class LoanAccountConfiguration {
@ConditionalOnMissingBean(LoanDisbursementService.class)
public LoanDisbursementService loanDisbursementService(LoanChargeValidator loanChargeValidator,
LoanDisbursementValidator loanDisbursementValidator, ReprocessLoanTransactionsService reprocessLoanTransactionsService,
LoanChargeService loanChargeService) {
LoanChargeService loanChargeService, LoanBalanceService loanBalanceService) {
return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, reprocessLoanTransactionsService,
loanChargeService);
loanChargeService, loanBalanceService);
}
@Bean
@@ -363,7 +363,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
DelinquencyReadPlatformService delinquencyReadPlatformService = new DelinquencyReadPlatformServiceImpl(repositoryRange,
repositoryBucket, repositoryLoanDelinquencyTagHistory, mapperRange, mapperBucket, mapperLoanDelinquencyTagHistory,
loanRepository, loanDelinquencyDomainService, repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository,
delinquencyEffectivePauseHelper, configurationDomainService);
delinquencyEffectivePauseHelper, configurationDomainService, Mockito.mock(LoanTransactionRepository.class));
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
when(loanProduct.isMultiDisburseLoan()).thenReturn(false);
@@ -40,7 +40,6 @@ import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
@@ -80,7 +79,6 @@ public class LoanBuilder {
private BigDecimal fixedPrincipalPercentagePerInstallment;
private ExternalId externalId = ExternalId.empty();
private LoanApplicationTerms loanApplicationTerms = mock(LoanApplicationTerms.class);
private LoanScheduleModel loanScheduleModel = mock(LoanScheduleModel.class);
private Boolean enableInstallmentLevelDelinquency = false;
private LocalDate submittedOnDate = LocalDate.now(ZoneId.systemDefault());
private LocalDate approvedOnDate;
@@ -116,8 +114,8 @@ public class LoanBuilder {
Loan loan = Loan.newIndividualLoanApplication(accountNo, client, loanType, loanProduct, fund, loanOfficer, loanPurpose,
transactionProcessor, loanRepaymentScheduleDetail, charges, collateral, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential,
rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
enableInstallmentLevelDelinquency, submittedOnDate);
rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, enableInstallmentLevelDelinquency,
submittedOnDate);
if (id != null) {
loan.setId(id);
@@ -335,11 +333,6 @@ public class LoanBuilder {
return this;
}
public LoanBuilder withLoanScheduleModel(LoanScheduleModel loanScheduleModel) {
this.loanScheduleModel = loanScheduleModel;
return this;
}
public LoanBuilder withEnableInstallmentLevelDelinquency(Boolean enableInstallmentLevelDelinquency) {
this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency;
return this;