FINERACT-2181: Prepayment flow test fixes
This commit is contained in:
committed by
Arnold Gálovics
parent
cb82af119b
commit
a158c930bc
+2
-2
@@ -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"
|
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 successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount
|
||||||
When Admin sets the business date to "02 January 2023"
|
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 status will be "CLOSED_OBLIGATIONS_MET"
|
||||||
Then Loan Transactions tab has a transaction with date: "02 January 2023", and with the following data:
|
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 |
|
| 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"
|
Then LoanAccrualTransactionCreatedBusinessEvent is raised on "02 January 2023"
|
||||||
|
|
||||||
@TestRailId:C2683
|
@TestRailId:C2683
|
||||||
|
|||||||
+4
-10
@@ -164,6 +164,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
||||||
private final LoanBalanceService loanBalanceService;
|
private final LoanBalanceService loanBalanceService;
|
||||||
private final LoanTransactionService loanTransactionService;
|
private final LoanTransactionService loanTransactionService;
|
||||||
|
private final LoanAccountDomainServiceJpaHelper loanAccountDomainServiceJpaHelper;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -242,21 +243,14 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LocalDate recalculateFrom = null;
|
LocalDate recalculateFrom = null;
|
||||||
if (loan.isInterestBearingAndInterestRecalculationEnabled() && loan.getLoanProduct().getProductInterestRecalculationDetails()
|
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||||
.getPreCloseInterestCalculationStrategy().calculateTillPreClosureDateEnabled()) {
|
|
||||||
recalculateFrom = transactionDate;
|
recalculateFrom = transactionDate;
|
||||||
}
|
}
|
||||||
final ScheduleGeneratorDTO scheduleGeneratorDTOForPrepay = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom,
|
final ScheduleGeneratorDTO scheduleGeneratorDTOForPrepay = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom,
|
||||||
transactionDate, holidayDetailDto);
|
transactionDate, holidayDetailDto);
|
||||||
|
|
||||||
Money outstanding = loanTransactionProcessingService.fetchPrepaymentDetail(scheduleGeneratorDTOForPrepay, transactionDate, loan)
|
LocalDate recalculateTill = loanAccountDomainServiceJpaHelper.calculateRecalculateTillDate(loan, transactionDate,
|
||||||
.getTotalOutstanding();
|
scheduleGeneratorDTOForPrepay, repaymentAmount);
|
||||||
LocalDate recalculateTill = null;
|
|
||||||
if (repaymentAmount.isGreaterThanOrEqualTo(outstanding) && loan.isInterestBearingAndInterestRecalculationEnabled()
|
|
||||||
&& loan.getLoanProduct().getProductInterestRecalculationDetails().getPreCloseInterestCalculationStrategy()
|
|
||||||
.calculateTillPreClosureDateEnabled()) {
|
|
||||||
recalculateTill = transactionDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom,
|
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom,
|
||||||
recalculateTill, holidayDetailDto);
|
recalculateTill, holidayDetailDto);
|
||||||
|
|||||||
+83
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-2
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.FlushModeType;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
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.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.transaction.interceptor.TransactionInterceptor;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Transactional(readOnly = true)
|
@Transactional
|
||||||
public class LoanPointInTimeServiceImpl implements LoanPointInTimeService {
|
public class LoanPointInTimeServiceImpl implements LoanPointInTimeService {
|
||||||
|
|
||||||
private final LoanUtilService loanUtilService;
|
private final LoanUtilService loanUtilService;
|
||||||
private final LoanScheduleService loanScheduleService;
|
private final LoanScheduleService loanScheduleService;
|
||||||
private final LoanAssembler loanAssembler;
|
private final LoanAssembler loanAssembler;
|
||||||
private final LoanPointInTimeData.Mapper dataMapper;
|
private final LoanPointInTimeData.Mapper dataMapper;
|
||||||
|
private final EntityManager entityManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanPointInTimeData retrieveAt(Long loanId, LocalDate date) {
|
public LoanPointInTimeData retrieveAt(Long loanId, LocalDate date) {
|
||||||
|
entityManager.setFlushMode(FlushModeType.COMMIT);
|
||||||
validateSingularRetrieval(loanId, date);
|
validateSingularRetrieval(loanId, date);
|
||||||
|
|
||||||
// Note: since everything is running in a readOnly transaction
|
// Note: since everything is running in a readOnly transaction
|
||||||
@@ -66,6 +71,8 @@ public class LoanPointInTimeServiceImpl implements LoanPointInTimeService {
|
|||||||
|
|
||||||
return dataMapper.map(loan);
|
return dataMapper.map(loan);
|
||||||
} finally {
|
} finally {
|
||||||
|
entityManager.clear();
|
||||||
|
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
|
||||||
ThreadLocalContextUtil.setBusinessDates(originalBDs);
|
ThreadLocalContextUtil.setBusinessDates(originalBDs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +96,9 @@ public class LoanPointInTimeServiceImpl implements LoanPointInTimeService {
|
|||||||
@Override
|
@Override
|
||||||
public List<LoanPointInTimeData> retrieveAt(List<Long> loanIds, LocalDate date) {
|
public List<LoanPointInTimeData> retrieveAt(List<Long> loanIds, LocalDate date) {
|
||||||
validateBulkRetrieval(loanIds, 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) {
|
private void validateBulkRetrieval(List<Long> loanIds, LocalDate date) {
|
||||||
|
|||||||
+1
-1
@@ -342,7 +342,7 @@ public class LoanPointInTimeTest extends BaseLoanIntegrationTest {
|
|||||||
transaction(500.0, "Repayment", "09 February 2023"), //
|
transaction(500.0, "Repayment", "09 February 2023"), //
|
||||||
transaction(500.0, "Repayment", "01 March 2023"), //
|
transaction(500.0, "Repayment", "01 March 2023"), //
|
||||||
transaction(5032.52, "Repayment", "05 March 2023"), //
|
transaction(5032.52, "Repayment", "05 March 2023"), //
|
||||||
transaction(1282.52, "Accrual", "05 March 2023") //
|
transaction(1110.08, "Accrual", "05 March 2023") //
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user