This is an automated email from the ASF dual-hosted git repository. arnold pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit a158c930bcfb2dea2e60dd5fa2c9e91dd6feeece Author: Arnold Galovics <[email protected]> AuthorDate: Wed Jun 18 18:28:31 2025 +0200 FINERACT-2181: Prepayment flow test fixes --- .../features/LoanAccrualTransaction.feature | 4 +- .../domain/LoanAccountDomainServiceJpa.java | 14 ++-- .../domain/LoanAccountDomainServiceJpaHelper.java | 83 ++++++++++++++++++++++ .../service/LoanPointInTimeServiceImpl.java | 13 +++- .../loan/pointintime/LoanPointInTimeTest.java | 2 +- 5 files changed, 101 insertions(+), 15 deletions(-) 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 5afc0213e8..a38acdb51a 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature @@ -143,11 +143,11 @@ Feature: LoanAccrualTransaction And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount When Admin sets the business date to "02 January 2023" - And Customer makes "AUTOPAY" repayment on "02 January 2023" with 1010.19 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "02 January 2023" with 1000.33 EUR transaction amount Then Loan status will be "CLOSED_OBLIGATIONS_MET" Then Loan Transactions tab has a transaction with date: "02 January 2023", and with the following data: | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Accrual | 10.19 | 0.0 | 10.19 | 0.0 | 0.0 | 0.0 | + | Accrual | 0.33 | 0.0 | 0.33 | 0.0 | 0.0 | 0.0 | Then LoanAccrualTransactionCreatedBusinessEvent is raised on "02 January 2023" @TestRailId:C2683 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 e3e25ce8cc..08f66a0859 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 @@ -164,6 +164,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final LoanTransactionProcessingService loanTransactionProcessingService; private final LoanBalanceService loanBalanceService; private final LoanTransactionService loanTransactionService; + private final LoanAccountDomainServiceJpaHelper loanAccountDomainServiceJpaHelper; @Transactional @Override @@ -242,21 +243,14 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } LocalDate recalculateFrom = null; - if (loan.isInterestBearingAndInterestRecalculationEnabled() && loan.getLoanProduct().getProductInterestRecalculationDetails() - .getPreCloseInterestCalculationStrategy().calculateTillPreClosureDateEnabled()) { + if (loan.isInterestBearingAndInterestRecalculationEnabled()) { recalculateFrom = transactionDate; } final ScheduleGeneratorDTO scheduleGeneratorDTOForPrepay = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, transactionDate, holidayDetailDto); - Money outstanding = loanTransactionProcessingService.fetchPrepaymentDetail(scheduleGeneratorDTOForPrepay, transactionDate, loan) - .getTotalOutstanding(); - LocalDate recalculateTill = null; - if (repaymentAmount.isGreaterThanOrEqualTo(outstanding) && loan.isInterestBearingAndInterestRecalculationEnabled() - && loan.getLoanProduct().getProductInterestRecalculationDetails().getPreCloseInterestCalculationStrategy() - .calculateTillPreClosureDateEnabled()) { - recalculateTill = transactionDate; - } + LocalDate recalculateTill = loanAccountDomainServiceJpaHelper.calculateRecalculateTillDate(loan, transactionDate, + scheduleGeneratorDTOForPrepay, repaymentAmount); final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom, recalculateTill, holidayDetailDto); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpaHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpaHelper.java new file mode 100644 index 0000000000..ff1be8aaf1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpaHelper.java @@ -0,0 +1,83 @@ +/** + * 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.domain; + +import java.time.LocalDate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder; +import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; +import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; +import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Component +@Slf4j +public class LoanAccountDomainServiceJpaHelper { + + private final LoanAssembler loanAssembler; + private final LoanTransactionProcessingService loanTransactionProcessingService; + + @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true) + public LocalDate calculateRecalculateTillDate(Loan loan, LocalDate transactionDate, ScheduleGeneratorDTO scheduleGeneratorDTOForPrepay, + Money repaymentAmount) { + LocalDate recalculateTill = null; + try { + if (FineractRequestContextHolder.isBatchRequest() && BatchRequestContextHolder.isEnclosingTransaction()) { + // In case of Batch requests with enclosing transaction, the current way of calculating the prepayment + // amount (since it changes + // the state of entities which would be written back to the DB) is incorrect, so we won't allow it for + // now. + // With enclosing transactions where the loan is created and repaid within the same batch request, due + // to REQUIRES_NEW, this method + // will simply not see that a loan has been created. + // Temporarily if you wanna use the batch API for prepayment, make sure to split the requests in a way + // that loan creation and + // repayment doesn't occur in the same batch request. + // Example testcase: + // org.apache.fineract.integrationtests.BatchApiTest.shouldReturnOkStatusOnSuccessfulGetDatatableEntryWithNoQueryParam + // TODO: this can be removed if the prepayment amount calculation below this is fixed in a way that it + // doesn't change any entity + // but works with DTOs + return null; + } + loan = loanAssembler.assembleFrom(loan.getId()); + if (loan.isInterestBearingAndInterestRecalculationEnabled() && loan.getLoanProduct().getProductInterestRecalculationDetails() + .getPreCloseInterestCalculationStrategy().calculateTillPreClosureDateEnabled()) { + Money outstanding = loanTransactionProcessingService + .fetchPrepaymentDetail(scheduleGeneratorDTOForPrepay, transactionDate, loan).getTotalOutstanding(); + if (repaymentAmount.isGreaterThanOrEqualTo(outstanding)) { + recalculateTill = transactionDate; + } + } + } catch (Exception e) { + // TODO: there's a bug where the prepayment calculation fails + // the test-case is org.apache.fineract.integrationtests.LoanTransactionAccrualActivityPostingTest.test in + // the integration-tests + // seems like it occurs only on CUMULATIVE loans, not PROGRESSIVE + log.warn("Unable to calculate prepayment amount", e); + } + return recalculateTill; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanPointInTimeServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanPointInTimeServiceImpl.java index 7ebf62f4bd..345c8e1e79 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanPointInTimeServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanPointInTimeServiceImpl.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.loanaccount.service; +import jakarta.persistence.EntityManager; +import jakarta.persistence.FlushModeType; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; @@ -35,19 +37,22 @@ import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.interceptor.TransactionInterceptor; @Service @RequiredArgsConstructor -@Transactional(readOnly = true) +@Transactional public class LoanPointInTimeServiceImpl implements LoanPointInTimeService { private final LoanUtilService loanUtilService; private final LoanScheduleService loanScheduleService; private final LoanAssembler loanAssembler; private final LoanPointInTimeData.Mapper dataMapper; + private final EntityManager entityManager; @Override public LoanPointInTimeData retrieveAt(Long loanId, LocalDate date) { + entityManager.setFlushMode(FlushModeType.COMMIT); validateSingularRetrieval(loanId, date); // Note: since everything is running in a readOnly transaction @@ -66,6 +71,8 @@ public class LoanPointInTimeServiceImpl implements LoanPointInTimeService { return dataMapper.map(loan); } finally { + entityManager.clear(); + TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); ThreadLocalContextUtil.setBusinessDates(originalBDs); } } @@ -89,7 +96,9 @@ public class LoanPointInTimeServiceImpl implements LoanPointInTimeService { @Override public List<LoanPointInTimeData> retrieveAt(List<Long> loanIds, LocalDate date) { validateBulkRetrieval(loanIds, date); - return loanIds.stream().map(loanId -> retrieveAt(loanId, date)).toList(); + List<LoanPointInTimeData> result = loanIds.stream().map(loanId -> retrieveAt(loanId, date)).toList(); + TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); + return result; } private void validateBulkRetrieval(List<Long> loanIds, LocalDate date) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/pointintime/LoanPointInTimeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/pointintime/LoanPointInTimeTest.java index 4ef88a20b0..71d42240fe 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/pointintime/LoanPointInTimeTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/pointintime/LoanPointInTimeTest.java @@ -342,7 +342,7 @@ public class LoanPointInTimeTest extends BaseLoanIntegrationTest { transaction(500.0, "Repayment", "09 February 2023"), // transaction(500.0, "Repayment", "01 March 2023"), // transaction(5032.52, "Repayment", "05 March 2023"), // - transaction(1282.52, "Accrual", "05 March 2023") // + transaction(1110.08, "Accrual", "05 March 2023") // ); }); }
