From 4a102e0e1b7d37181188ce1ef075ed050b1daffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Soma=20S=C3=B6r=C3=B6s?= Date: Wed, 11 Feb 2026 14:26:57 +0100 Subject: [PATCH] FINERACT-2421: Fix Progressive Loan - Missing Model Issues During COB Runs * COB Periodic Accruals Create model if model is not saved before calculate new accrual * Force Reprocess All Transactions in case there is no Saved Model * Create Internal API Endpoint and LoanStepDef to remove ProgressiveLoanInterestScheduleModel by loanId --- .../test/stepdef/loan/LoanStepDef.java | 7 +++ .../features/LoanAccrualTransaction.feature | 7 +++ .../service/LoanScheduleService.java | 6 +- .../ReprocessLoanTransactionsService.java | 2 + ...dableProgressiveLoanScheduleGenerator.java | 49 +-------------- .../ProgressiveLoanScheduleGenerator.java | 11 ++-- .../ProgressiveLoanModelRepository.java | 2 + ...talizedIncomeWritePlatformServiceImpl.java | 3 +- ...nterestScheduleModelRepositoryWrapper.java | 2 + ...estScheduleModelRepositoryWrapperImpl.java | 5 ++ .../InternalProgressiveLoanApiResource.java | 11 ++++ ...ProgressiveLoanModelProcessingService.java | 61 +++++++++++++++++++ ...siveLoanModelProcessingServiceWrapper.java | 44 +++++++++++++ .../domain/LoanScheduleGeneratorTest.java | 8 +-- .../LoanAccrualsProcessingServiceImpl.java | 9 +++ .../LoanChargeWritePlatformServiceImpl.java | 2 +- ...WritePlatformServiceJpaRepositoryImpl.java | 26 ++++---- .../ReprocessLoanTransactionsServiceImpl.java | 5 ++ .../LoanContractTerminationServiceImpl.java | 2 +- .../service/reaging/LoanReAgingService.java | 3 +- .../LoanReAmortizationService.java | 2 +- 21 files changed, 193 insertions(+), 74 deletions(-) create mode 100644 fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanModelProcessingService.java create mode 100644 fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanModelProcessingServiceWrapper.java diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index 9d7d26e098c..23dcb365d3a 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -5926,6 +5926,13 @@ public void inLoanTransactionsTheThTransactionOfOnHasRelationshipWithTypeREPLAYE assertEquals(Integer.valueOf(numberOfRelations), relationshipOptional.size(), "Missed relationship for transaction"); } + @When("Call Internal API to remove progressive loan model by loan Id") + public void callInternalAPIToRemoveProgressiveLoanModelByLoanId() { + final PostLoansResponse loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + final long loanId = loanCreateResponse.getLoanId(); + ok(() -> fineractClient.progressiveLoan().deleteModel(loanId)); + } + public static AdvancedPaymentData editPaymentAllocationFutureInstallment(String transactionType, String futureInstallmentAllocationRule, List paymentAllocationOrder) { AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData(); diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature index 3c831869ddd..5937c979fb5 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature @@ -1619,6 +1619,7 @@ Feature: LoanAccrualTransaction | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | # --- Early repayment with 17.01 EUR on 15 Jan --- When Admin sets the business date to "15 January 2024" + When Call Internal API to remove progressive loan model by loan Id When Admin makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 January 2024" with 17.01 EUR transaction amount Then Loan Repayment schedule has 6 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | @@ -1660,6 +1661,7 @@ Feature: LoanAccrualTransaction | 01 May 2024 | Accrual Activity | 0.48 | 0.0 | 0.48 | 0.0 | 0.0 | 0.0 | false | false | | 31 May 2024 | Accrual | 1.87 | 0.0 | 1.87 | 0.0 | 0.0 | 0.0 | false | false | When Admin sets the business date to "02 June 2024" + When Call Internal API to remove progressive loan model by loan Id And Admin runs inline COB job for Loan Then Loan Repayment schedule has 6 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | @@ -1685,6 +1687,7 @@ Feature: LoanAccrualTransaction | 01 June 2024 | Accrual | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | | 01 June 2024 | Accrual Activity | 0.48 | 0.0 | 0.48 | 0.0 | 0.0 | 0.0 | false | false | When Admin sets the business date to "01 July 2024" + When Call Internal API to remove progressive loan model by loan Id And Admin runs inline COB job for Loan Then Loan Repayment schedule has 6 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | @@ -1739,6 +1742,7 @@ Feature: LoanAccrualTransaction | 29 June 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | | 30 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | When Admin sets the business date to "02 July 2024" + When Call Internal API to remove progressive loan model by loan Id And Admin runs inline COB job for Loan Then Loan Repayment schedule has 6 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | @@ -1794,6 +1798,7 @@ Feature: LoanAccrualTransaction | 30 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | | 01 July 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | When Admin sets the business date to "03 July 2024" + When Call Internal API to remove progressive loan model by loan Id And Admin runs inline COB job for Loan Then Loan Repayment schedule has 6 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | @@ -1849,6 +1854,7 @@ Feature: LoanAccrualTransaction | 30 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | | 01 July 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | When Admin sets the business date to "01 August 2024" + When Call Internal API to remove progressive loan model by loan Id And Admin runs inline COB job for Loan Then Loan Repayment schedule has 6 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | @@ -1904,6 +1910,7 @@ Feature: LoanAccrualTransaction | 30 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | | 01 July 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | When Admin sets the business date to "02 August 2024" + When Call Internal API to remove progressive loan model by loan Id And Admin runs inline COB job for Loan Then Loan Repayment schedule has 6 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java index 55dba665987..9420211848d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java @@ -44,6 +44,10 @@ public class LoanScheduleService { private final LoanTransactionRepository loanTransactionRepository; private final ILoanUtilService loanUtilService; + public boolean shouldReprocessLoan(final Loan loan) { + return reprocessLoanTransactionsService.shouldReprocessLoan(loan); + } + /** * Ability to regenerate the repayment schedule based on the loans current details/state. */ @@ -81,7 +85,7 @@ public void recalculateScheduleFromLastTransaction(final Loan loan, final Schedu existingTransactionIds.addAll(loanTransactionRepository.findTransactionIdsByLoan(loan)); existingReversedTransactionIds.addAll(loanTransactionRepository.findReversedTransactionIdsByLoan(loan)); } - if (!loan.isProgressiveSchedule()) { + if (!loan.isProgressiveSchedule() || reprocessLoanTransactionsService.shouldReprocessLoan(loan)) { if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isChargedOff()) { regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO); } else { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java index 5db714ebdcf..b4931aeac6d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsService.java @@ -30,6 +30,8 @@ public interface ReprocessLoanTransactionsService { void reprocessTransactionsWithoutChecks(Loan loan, List newTransactions); + boolean shouldReprocessLoan(Loan loan); + void processLatestTransaction(LoanTransaction loanTransaction, Loan loan); void updateModel(Loan loan); 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 864a20c3e17..35fcd36893d 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 @@ -19,16 +19,9 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.MathContext; -import java.time.LocalDate; -import java.util.Optional; -import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlan; -import org.apache.fineract.portfolio.loanaccount.service.InterestScheduleModelRepositoryWrapper; import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator; import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator; -import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; -import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails; @SuppressWarnings("unused") public class EmbeddableProgressiveLoanScheduleGenerator { @@ -38,50 +31,10 @@ public class EmbeddableProgressiveLoanScheduleGenerator { public EmbeddableProgressiveLoanScheduleGenerator() { final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); final EMICalculator emiCalculator = new ProgressiveEMICalculator(scheduledDateGenerator); - this.scheduleGenerator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator, - new NoopInterestScheduleModelRepositoryWrapper()); + this.scheduleGenerator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator); } public LoanSchedulePlan generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) { return scheduleGenerator.generate(mc, modelData); } - - private static final class NoopInterestScheduleModelRepositoryWrapper implements InterestScheduleModelRepositoryWrapper { - - @Override - public Optional findOneByLoanId(Long loanId) { - return Optional.empty(); - } - - @Override - public Optional findOneByLoan(Loan loan) { - return Optional.empty(); - } - - @Override - public Optional extractModel(Optional progressiveLoanModel) { - return Optional.empty(); - } - - @Override - public ProgressiveLoanInterestScheduleModel writeInterestScheduleModel(Loan loan, ProgressiveLoanInterestScheduleModel model) { - return null; - } - - @Override - public Optional readProgressiveLoanInterestScheduleModel(Long loanId, - ILoanConfigurationDetails detail, Integer installmentAmountInMultipliesOf) { - 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/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index 6345264c325..47ec3c25996 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; @@ -58,18 +59,17 @@ import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { private final ScheduledDateGenerator scheduledDateGenerator; private final EMICalculator emiCalculator; - private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper; + private InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper; private LoanTransactionProcessingService loanTransactionProcessingService; - public ProgressiveLoanScheduleGenerator(ScheduledDateGenerator scheduledDateGenerator, EMICalculator emiCalculator, - InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper) { - this.scheduledDateGenerator = scheduledDateGenerator; - this.emiCalculator = emiCalculator; + @Autowired(required = false) + public void setInterestScheduleModelRepositoryWrapper(InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper) { this.interestScheduleModelRepositoryWrapper = interestScheduleModelRepositoryWrapper; } @@ -251,6 +251,7 @@ public Money getPeriodInterestTillDate(@NotNull LoanRepaymentScheduleInstallment return Money.zero(loan.getCurrency()); } Optional savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, targetDate); + ProgressiveLoanInterestScheduleModel model = savedModel.orElseThrow(); return emiCalculator.getPeriodInterestTillDate(model, installment.getFromDate(), installment.getDueDate(), targetDate, false, true); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/ProgressiveLoanModelRepository.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/ProgressiveLoanModelRepository.java index be6af1aac27..5b40e6c7cca 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/ProgressiveLoanModelRepository.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/ProgressiveLoanModelRepository.java @@ -30,4 +30,6 @@ public interface ProgressiveLoanModelRepository Optional findOneByLoanId(Long loanId); Optional findOneByLoan(Loan loan); + + Long removeByLoanId(Long loanId); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java index 2c804898ca7..accd2d794e6 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java @@ -189,7 +189,8 @@ public CommandProcessingResult capitalizedIncomeAdjustment(final Long loanId, fi } private void recalculateLoanTransactions(Loan loan, LoanTransaction transaction) { - if (loan.isInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transaction.getTransactionDate())) { + if (loan.isInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transaction.getTransactionDate()) + || reprocessLoanTransactionsService.shouldReprocessLoan(loan)) { loanScheduleService.regenerateRepaymentSchedule(loan); reprocessLoanTransactionsService.reprocessTransactions(loan, List.of(transaction)); } else { 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 1ce74910b7c..31f64be465b 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 @@ -31,6 +31,8 @@ public interface InterestScheduleModelRepositoryWrapper { Optional findOneByLoan(Loan loan); + Long removeByLoanId(Long loanId); + Optional extractModel(Optional progressiveLoanModel); ProgressiveLoanInterestScheduleModel writeInterestScheduleModel(Loan loan, ProgressiveLoanInterestScheduleModel model); 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 654af32509f..e0c0e2c1f7f 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 @@ -89,6 +89,11 @@ public Optional findOneByLoan(Loan loan) { return progressiveLoanModelRef.get(); } + @Override + public Long removeByLoanId(Long loanId) { + return loanModelRepository.removeByLoanId(loanId); + } + @Override public Optional extractModel(Optional progressiveLoanModel) { return progressiveLoanModel.map(ProgressiveLoanModel::getJsonModel) // diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java index 4fca8a38f87..4562e6665d7 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java @@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; @@ -99,4 +100,14 @@ public ProgressiveLoanInterestScheduleModel updateModel(@PathParam("loanId") @Pa return writePlatformService.writeInterestScheduleModel(loan, model); } + + @DELETE + @Path("{loanId}/model") + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Delete ProgressiveLoanInterestScheduleModel By Loan ID", description = "DO NOT USE THIS IN PRODUCTION!") + @Transactional + public Long deleteModel(@PathParam("loanId") @Parameter(description = "loanId") long loanId) { + return writePlatformService.removeByLoanId(loanId); + } + } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanModelProcessingService.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanModelProcessingService.java new file mode 100644 index 00000000000..a5ac9cc777b --- /dev/null +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanModelProcessingService.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.service; + +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; +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.LoanRepaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +public class ProgressiveLoanModelProcessingService { + + private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory; + private final LoanTransactionRepository loanTransactionRepository; + private final LoanRepositoryWrapper loanRepositoryWrapper; + + @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) + public ProgressiveLoanInterestScheduleModel getRecalculatedModel(Long loanId, LocalDate tillDate) { + Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId); + LoanRepaymentScheduleTransactionProcessor transactionProcessor = transactionProcessorFactory + .determineProcessor(loan.getTransactionProcessingStrategyCode()); + if (transactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor) { + List loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan); + Pair result = advancedPaymentScheduleTransactionProcessor + .reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), tillDate, loanTransactions, loan.getCurrency(), + loan.getRepaymentScheduleInstallments(), loan.getActiveCharges()); + return result.getRight(); + } + return null; + } + +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanModelProcessingServiceWrapper.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanModelProcessingServiceWrapper.java new file mode 100644 index 00000000000..19f72b0c281 --- /dev/null +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanModelProcessingServiceWrapper.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.service; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +public class ProgressiveLoanModelProcessingServiceWrapper { + + private final ProgressiveLoanModelProcessingService modelProcessingService; + private final InterestScheduleModelRepositoryWrapper modelRepositoryWrapper; + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void recalculateModelAndSave(Loan loan) { + ProgressiveLoanInterestScheduleModel recalculatedModel = modelProcessingService.getRecalculatedModel(loan.getId(), + ThreadLocalContextUtil.getBusinessDate()); + if (recalculatedModel != null) { + modelRepositoryWrapper.writeInterestScheduleModel(loan, recalculatedModel); + } + } +} diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java index 06d86fe6ad1..f40b53cca5d 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java @@ -68,9 +68,9 @@ void testGenerateLoanSchedule() { InterestMethod.DECLINING_BALANCE, true, false); ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); - ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator, - interestScheduleModelRepositoryWrapperMock); + ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator); generator.setLoanTransactionProcessingService(loanTransactionProcessingService); + generator.setInterestScheduleModelRepositoryWrapper(interestScheduleModelRepositoryWrapperMock); LoanSchedulePlan loanSchedule = generator.generate(mc, modelData); @@ -106,9 +106,9 @@ void testGenerateLoanScheduleWithDownPayment() { null, InterestMethod.DECLINING_BALANCE, true, false); ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); - ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator, - interestScheduleModelRepositoryWrapperMock); + ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator); generator.setLoanTransactionProcessingService(loanTransactionProcessingService); + generator.setInterestScheduleModelRepositoryWrapper(interestScheduleModelRepositoryWrapperMock); LoanSchedulePlan loanSchedule = generator.generate(mc, modelData); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java index b319daecc16..77507cfbb88 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java @@ -108,6 +108,8 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing private final LoanRepositoryWrapper loanRepositoryWrapper; private final LoanTransactionRepository loanTransactionRepository; private final LoanScheduleGeneratorFactory loanScheduleFactory; + private final LoanScheduleService loanScheduleService; + private final ProgressiveLoanModelProcessingServiceWrapper modelProcessingServiceWrapper; @Qualifier(TaskExecutorConstant.CONFIGURABLE_TASK_EXECUTOR_BEAN_NAME) private final ThreadPoolTaskExecutor taskExecutor; @@ -437,6 +439,12 @@ private boolean hasNoActiveChargeOnDate(Loan loan, LocalDate accrualDate) { return loan.getLoanCharges(t -> t.isActive() && DateUtils.isEqual(t.getDueDate(), accrualDate)).isEmpty(); } + private void reprocessProgressiveModelIfItsMissing(Loan loan, LocalDate tillDate) { + if (loanScheduleService.shouldReprocessLoan(loan)) { + modelProcessingServiceWrapper.recalculateModelAndSave(loan); + } + } + private AccrualPeriodsData calculateAccrualAmounts(@NonNull final Loan loan, @NonNull final LocalDate tillDate, final boolean periodic, final boolean isFinal, final boolean chargeOnDueDate) { final LoanProductRelatedDetail productDetail = loan.getLoanProductRelatedDetail(); @@ -493,6 +501,7 @@ private void addInterestAccrual(@NonNull final Loan loan, @NonNull final LocalDa interest = installment.getInterestCharged(currency).minus(installment.getCreditedInterest()); } else { if (isInPeriod) { // first period first day is not accrued + reprocessProgressiveModelIfItsMissing(loan, tillDate); interest = scheduleGenerator.getPeriodInterestTillDate(installment, tillDate); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index 47304853ebd..fba6ca50710 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -870,7 +870,7 @@ private LoanTransaction applyChargeAdjustment(final Loan loan, final LoanCharge LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT); loanChargeAdjustmentTransaction.getLoanTransactionRelations().add(loanTransactionRelation); - if (loan.isInterestBearingAndInterestRecalculationEnabled()) { + if (loan.isInterestBearingAndInterestRecalculationEnabled() || reprocessLoanTransactionsService.shouldReprocessLoan(loan)) { if (loan.isProgressiveSchedule()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null); loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 49bc7e2a0fc..b0d27547755 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -962,8 +962,10 @@ private void recalculateLoanWithInterestPaymentWaiverTxn(Loan loan, LoanTransact reprocess = false; } - if (isTransactionChronologicallyLatest && (!reprocess || !loan.isInterestBearingAndInterestRecalculationEnabled()) - && !loan.isForeclosure()) { + boolean missingModel = reprocessLoanTransactionsService.shouldReprocessLoan(loan); + + if (missingModel || (isTransactionChronologicallyLatest && (!reprocess || !loan.isInterestBearingAndInterestRecalculationEnabled()) + && !loan.isForeclosure())) { loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), newInterestPaymentWaiverTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), @@ -985,7 +987,7 @@ private void recalculateLoanWithInterestPaymentWaiverTxn(Loan loan, LoanTransact loan.addLoanTransaction(newInterestPaymentWaiverTransaction); } } - if (reprocess) { + if (missingModel || reprocess) { reprocessChangedLoanTransactions(loan, newInterestPaymentWaiverTransaction, scheduleGeneratorDTO); } @@ -2344,10 +2346,10 @@ public Loan recalculateInterest(Loan loan) { businessEventNotifierService.notifyPreBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan)); final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); + LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate(); + ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); if (loan.isCumulativeSchedule()) { - LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate(); - ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); loanScheduleService.recalculateScheduleFromLastTransaction(loan, generatorDTO, existingTransactionIds, existingReversedTransactionIds); loanAccrualsProcessingService.reprocessExistingAccruals(loan, true); @@ -2360,8 +2362,8 @@ public Loan recalculateInterest(Loan loan) { loan.isInterestBearingAndInterestRecalculationEnabled(), true); businessEventNotifierService.notifyPostBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan)); } else { - loanScheduleService.recalculateScheduleFromLastTransaction(loan, null, existingTransactionIds, existingReversedTransactionIds, - true); + loanScheduleService.recalculateScheduleFromLastTransaction(loan, generatorDTO, existingTransactionIds, + existingReversedTransactionIds, true); loanBalanceService.updateLoanSummaryDerivedFields(loan); loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan); businessEventNotifierService.notifyPostBusinessEvent(new LoanInterestRecalculationBusinessEvent(loan)); @@ -2746,7 +2748,7 @@ public CommandProcessingResult chargeOff(JsonCommand command) { final LoanTransaction chargeOffTransaction; - if (loan.isInterestBearingAndInterestRecalculationEnabled()) { + if (loan.isInterestBearingAndInterestRecalculationEnabled() || reprocessLoanTransactionsService.shouldReprocessLoan(loan)) { final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); chargeOffTransaction = LoanTransaction.chargeOff(loan, transactionDate, txnExternalId); @@ -3053,7 +3055,8 @@ public CommandProcessingResult makeManualInterestRefund(final Long loanId, final public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) { loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction); - if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) { + if ((loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) + || reprocessLoanTransactionsService.shouldReprocessLoan(loan)) { loan.addLoanTransaction(chargebackTransaction); reprocessLoanTransactionsService.reprocessTransactions(loan); } else { @@ -3249,8 +3252,9 @@ private Optional closeAsWrittenOff(final Loan loan, final JsonC writtenOffOnLocalDate); } - if (loan.isInterestBearingAndInterestRecalculationEnabled() - && DateUtils.isBeforeBusinessDate(loanTransaction.getTransactionDate())) { + if ((loan.isInterestBearingAndInterestRecalculationEnabled() + && DateUtils.isBeforeBusinessDate(loanTransaction.getTransactionDate())) + || reprocessLoanTransactionsService.shouldReprocessLoan(loan)) { if (loan.isProgressiveSchedule()) { loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java index 07e1b34aef7..478af4c20c3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java @@ -85,6 +85,11 @@ public void reprocessTransactionsWithoutChecks(final Loan loan, final List