diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java index 9b0a1d830..4595f6e04 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java @@ -127,34 +127,28 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler final LoanRepaymentScheduleInstallment currentInstallment = loan .fetchLoanRepaymentScheduleInstallmentByDueDate(loanTransaction.getTransactionDate()); - boolean reprocess = loan.isForeclosure() || !isTransactionChronologicallyLatest || adjustedTransaction != null - || !DateUtils.isEqualBusinessDate(loanTransaction.getTransactionDate()) || currentInstallment == null - || !currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(loanTransaction.getAmount(loan.getCurrency())); - - // TODO FINERACT-2220 fix processLatestTransaction to save model. - if (loan.isProgressiveSchedule() && loan.isInterestBearing()) { - reprocess = true; - } - - if (isTransactionChronologicallyLatest && adjustedTransaction == null - && (!reprocess || !loan.isInterestBearingAndInterestRecalculationEnabled()) && !loan.isForeclosure()) { + boolean reprocessOnPostConditions = false; + boolean processLatest = isTransactionChronologicallyLatest // + && adjustedTransaction == null // covers reversals + && !loan.isForeclosure() // + && loanTransactionProcessingService.canProcessLatestTransactionOnly(loan, loanTransaction, currentInstallment); // + if (processLatest) { loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); - reprocess = false; - if (loan.isInterestBearingAndInterestRecalculationEnabled()) { + if (!loan.isProgressiveSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) { - reprocess = true; + reprocessOnPostConditions = true; } else { final LoanRepaymentScheduleInstallment nextInstallment = loan .fetchRepaymentScheduleInstallment(currentInstallment.getInstallmentNumber() + 1); if (nextInstallment != null && nextInstallment.getTotalPaidInAdvance(loan.getCurrency()).isGreaterThanZero()) { - reprocess = true; + reprocessOnPostConditions = true; } } } } - if (reprocess) { + if (!processLatest || reprocessOnPostConditions) { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); } else if (loan.isProgressiveSchedule() && loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy()) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java index 4a191bad9..272f7fd3a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingService.java @@ -36,6 +36,9 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleD public interface LoanTransactionProcessingService { + boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction, + LoanRepaymentScheduleInstallment currentInstallment); + ChangedTransactionDetail processLatestTransaction(String transactionProcessingStrategyCode, LoanTransaction loanTransaction, TransactionCtx ctx); diff --git a/fineract-progressive-loan-embeddable-schedule-generator/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/EmbeddableProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan-embeddable-schedule-generator/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/EmbeddableProgressiveLoanScheduleGenerator.java index 1c3a19bbd..f479d20e7 100644 --- a/fineract-progressive-loan-embeddable-schedule-generator/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/EmbeddableProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan-embeddable-schedule-generator/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/EmbeddableProgressiveLoanScheduleGenerator.java @@ -71,6 +71,11 @@ public class EmbeddableProgressiveLoanScheduleGenerator { return Optional.empty(); } + @Override + public boolean hasValidModelForDate(Long loanId, LocalDate targetDate) { + return false; + } + @Override public Optional getSavedModel(Loan loan, LocalDate businessDate) { return Optional.empty(); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 2fafd65ae..fe771f812 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -1225,6 +1225,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep adjustOverduePrincipalForInstallment(targetDate, currentInstallment, overDuePrincipal, aggregatedOverDuePrincipal, ctx); } + if (aggregatedOverDuePrincipal.isGreaterThanZero() && (ctx.getModel().lastOverdueBalanceChange() == null + || ctx.getModel().lastOverdueBalanceChange().isBefore(targetDate))) { + ctx.getModel().lastOverdueBalanceChange(targetDate); + } } } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java index a86b67939..8b1ad8d98 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java @@ -47,8 +47,14 @@ public class ProgressiveTransactionCtx extends TransactionCtx { public ProgressiveTransactionCtx(MonetaryCurrency currency, List installments, Set charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail, ProgressiveLoanInterestScheduleModel model) { + this(currency, installments, charges, overpaymentHolder, changedTransactionDetail, model, Money.zero(currency)); + } + + public ProgressiveTransactionCtx(MonetaryCurrency currency, List installments, + Set charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail, + ProgressiveLoanInterestScheduleModel model, Money sumOfInterestRefundAmount) { super(currency, installments, charges, overpaymentHolder, changedTransactionDetail); - sumOfInterestRefundAmount = model.zero(); + this.sumOfInterestRefundAmount = sumOfInterestRefundAmount; this.model = model; } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapper.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapper.java index a39e202ce..bce405ca0 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapper.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapper.java @@ -36,5 +36,7 @@ public interface InterestScheduleModelRepositoryWrapper { Optional readProgressiveLoanInterestScheduleModel(Long loanId, LoanProductMinimumRepaymentScheduleRelatedDetail detail, Integer installmentAmountInMultipliesOf); + boolean hasValidModelForDate(Long loanId, LocalDate targetDate); + Optional getSavedModel(Loan loan, LocalDate businessDate); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java index 13cc71fa4..23e8bf1f4 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestScheduleModelRepositoryWrapperImpl.java @@ -58,6 +58,7 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched } String jsonModel = progressiveLoanInterestScheduleModelParserService.toJson(model); try { + // Note: FlushMode set because otherwise it could cause a concurrent writing exception entityManager.setFlushMode(FlushModeType.COMMIT); ProgressiveLoanModel progressiveLoanModel = loanModelRepository.findOneByLoanId(loan.getId()).orElseGet(() -> { ProgressiveLoanModel plm = new ProgressiveLoanModel(); @@ -88,21 +89,41 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched } @Override + public boolean hasValidModelForDate(Long loanId, LocalDate targetDate) { + try { + // Note: FlushMode set because an unwanted flush caused reverse replay new transactions + entityManager.setFlushMode(FlushModeType.COMMIT); + Optional progressiveLoanModel = findOneByLoanId(loanId); + LocalDate modelUpdatedTilDate = progressiveLoanModel.map(ProgressiveLoanModel::getBusinessDate).orElse(null); + return progressiveLoanModel.isPresent() && !targetDate.isBefore(modelUpdatedTilDate); + } finally { + entityManager.setFlushMode(FlushModeType.AUTO); + } + } + + @Override public Optional getSavedModel(Loan loan, LocalDate businessDate) { - Optional progressiveLoanModel = findOneByLoanId(loan.getId()); - Optional savedModel; - if (progressiveLoanModel.isPresent() && !progressiveLoanModel.get().getBusinessDate().isAfter(businessDate)) { - savedModel = extractModel(progressiveLoanModel); - if (progressiveLoanModel.get().getBusinessDate().isBefore(businessDate) && savedModel.isPresent()) { - ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), - Set.of(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), savedModel.get()); - ctx.setChargedOff(loan.isChargedOff()); - advancedPaymentScheduleTransactionProcessor.recalculateInterestForDate(businessDate, ctx); + try { + // Note: FlushMode set because an unwanted flush caused reverse replay new transactions + entityManager.setFlushMode(FlushModeType.COMMIT); + Optional progressiveLoanModel = findOneByLoanId(loan.getId()); + Optional savedModel; + if (progressiveLoanModel.isPresent() && !progressiveLoanModel.get().getBusinessDate().isAfter(businessDate)) { + savedModel = extractModel(progressiveLoanModel); + if (savedModel.isPresent() && progressiveLoanModel.get().getBusinessDate().isBefore(businessDate)) { + ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), + loan.getRepaymentScheduleInstallments(), Set.of(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), + new ChangedTransactionDetail(), savedModel.get()); + ctx.setChargedOff(loan.isChargedOff()); + advancedPaymentScheduleTransactionProcessor.recalculateInterestForDate(businessDate, ctx); + } + } else { + savedModel = Optional.empty(); } - } else { - savedModel = Optional.empty(); + return savedModel; + } finally { + entityManager.setFlushMode(FlushModeType.AUTO); } - return savedModel; } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 4507d5c41..83edbd7e3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -92,7 +92,6 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanRefundRequestData; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; -import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper; @@ -109,6 +108,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService; import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService; import org.apache.fineract.portfolio.loanaccount.service.LoanRefundService; import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService; +import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes; @@ -160,6 +160,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final LoanAccountService loanAccountService; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final LoanAccountingBridgeMapper loanAccountingBridgeMapper; + private final LoanTransactionProcessingService loanTransactionProcessingService; @Transactional @Override @@ -889,28 +890,21 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } } - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = transactionProcessorFactory - .determineProcessor(loan.getTransactionProcessingStrategyCode()); - final LoanRepaymentScheduleInstallment currentInstallment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(transactionDate); - boolean reprocess = loan.isInterestBearingAndInterestRecalculationEnabled() // - || loan.isForeclosure() // - || !isTransactionChronologicallyLatest // - || !DateUtils.isEqualBusinessDate(transactionDate) // - || currentInstallment == null // - || !currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(refundTransaction.getAmount(loan.getCurrency())); // - - if (!reprocess) { - loan.getLoanTransactions().add(refundTransaction); - loanRepaymentScheduleTransactionProcessor.processLatestTransaction(refundTransaction, + boolean processLatest = isTransactionChronologicallyLatest // + && !loan.isForeclosure() // + && loanTransactionProcessingService.canProcessLatestTransactionOnly(loan, refundTransaction, currentInstallment); // + if (processLatest) { + loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), refundTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + loan.getLoanTransactions().add(refundTransaction); if (interestRefundTransaction != null) { + loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), + interestRefundTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), + loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); loan.addLoanTransaction(interestRefundTransaction); - loanRepaymentScheduleTransactionProcessor.processLatestTransaction(interestRefundTransaction, - new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), - new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); } } else { if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) { @@ -924,12 +918,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } reprocessLoanTransactionsService.reprocessTransactions(loan); + } - // Store and flush newly created transaction to generate PK - loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(refundTransaction); - if (interestRefundTransaction != null) { - loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction); - } + // Store and flush newly created transaction to generate PK + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(refundTransaction); + if (interestRefundTransaction != null) { + loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(interestRefundTransaction); } loanLifecycleStateMachine.determineAndTransition(loan, transactionDate); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java index 909f7b28f..ed1eedc90 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java @@ -38,9 +38,12 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ProgressiveTransactionCtx; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; @@ -59,11 +62,57 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc private final LoanTermVariationsMapper loanMapper; private final InterestScheduleModelRepositoryWrapper modelRepository; + @Override + public boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction, + LoanRepaymentScheduleInstallment currentInstallment) { + if (!loan.isInterestBearingAndInterestRecalculationEnabled()) { + return true; + } + if (!DateUtils.isEqualBusinessDate(loanTransaction.getTransactionDate())) { + return false; + } + if (loan.isProgressiveSchedule()) { + return modelRepository.hasValidModelForDate(loan.getId(), loanTransaction.getTransactionDate()); + } + return currentInstallment != null + && currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(loanTransaction.getAmount(loan.getCurrency())); + } + + private ChangedTransactionDetail processLatestTransactionProgressiveInterestRecalculation( + AdvancedPaymentScheduleTransactionProcessor advancedProcessor, Loan loan, LoanTransaction loanTransaction) { + Optional savedModel = modelRepository.getSavedModel(loan, + loanTransaction.getTransactionDate()); + + ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(), + loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), + new ChangedTransactionDetail(), savedModel.orElse(null), getTotalRefundInterestAmount(loan)); + progressiveContext.getAlreadyProcessedTransactions().addAll(loan.retrieveListOfTransactionsForReprocessing()); + progressiveContext.setChargedOff(loan.isChargedOff()); + ChangedTransactionDetail result = advancedProcessor.processLatestTransaction(loanTransaction, progressiveContext); + if (savedModel.isPresent() && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { + modelRepository.writeInterestScheduleModel(loan, savedModel.get()); + } + return result; + } + + private Money getTotalRefundInterestAmount(Loan loan) { + List supportedInterestRefundTransactionTypes = loan.getSupportedInterestRefundTransactionTypes(); + if (supportedInterestRefundTransactionTypes != null && supportedInterestRefundTransactionTypes.isEmpty()) { + return Money.zero(loan.getCurrency()); + } + return loan.getLoanTransactions().stream().filter(LoanTransaction::isNotReversed).filter(LoanTransaction::isInterestRefund) + .map(t -> t.getAmount(loan.getCurrency())).reduce(Money.zero(loan.getCurrency()), Money::add); + } + @Override public ChangedTransactionDetail processLatestTransaction(String transactionProcessingStrategyCode, LoanTransaction loanTransaction, TransactionCtx ctx) { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor( transactionProcessingStrategyCode); + if (loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor advancedProcessor + && loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) { + return processLatestTransactionProgressiveInterestRecalculation(advancedProcessor, loanTransaction.getLoan(), loanTransaction); + } return loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, ctx); }