From 931e9202953419793595fed26d6b5ccbdeaa8197 Mon Sep 17 00:00:00 2001 From: "mark.vituska" Date: Wed, 23 Apr 2025 17:28:07 +0200 Subject: [PATCH] FINERACT-2210: fix previously refunded interest calculation during interest refund --- .../service/InterestRefundService.java | 4 -- .../domain/LoanAccountDomainServiceJpa.java | 5 +- ...gressiveLoanInterestRefundServiceImpl.java | 13 ----- .../LoanRefundTransactionTest.java | 56 ++++++++++++++----- 4 files changed, 44 insertions(+), 34 deletions(-) diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundService.java index 8d3e663e3..6178c005f 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InterestRefundService.java @@ -18,10 +18,8 @@ */ package org.apache.fineract.portfolio.loanaccount.service; -import java.math.MathContext; import java.time.LocalDate; import java.util.List; -import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; @@ -36,6 +34,4 @@ public interface InterestRefundService { @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) Money totalInterestByTransactions(LoanRepaymentScheduleTransactionProcessor processor, Long loanId, LocalDate relatedRefundTransactionDate, List newTransactions, List oldTransactionIds); - - Money getTotalInterestRefunded(List loanTransactions, MonetaryCurrency currency, MathContext mc); } 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 4003292c5..0a748f244 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 @@ -190,13 +190,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { Money totalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), refundTransaction.getTransactionDate(), List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList()); - Money previouslyRefundedInterests = interestRefundService.getTotalInterestRefunded(loan.getLoanTransactions(), loan.getCurrency(), - totalInterest.getMc()); - Money newTotalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), refundTransaction.getTransactionDate(), List.of(refundTransaction), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList()); - BigDecimal interestRefundAmount = totalInterest.minus(previouslyRefundedInterests).minus(newTotalInterest).getAmount(); + BigDecimal interestRefundAmount = totalInterest.minus(newTotalInterest).getAmount(); if (MathUtil.isZero(interestRefundAmount)) { return null; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java index 17d0d3729..a61d368d6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanInterestRefundServiceImpl.java @@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.service; import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REPAYMENT; import java.math.BigDecimal; -import java.math.MathContext; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -31,7 +30,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; import org.apache.fineract.infrastructure.core.service.MathUtil; -import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData; @@ -145,15 +143,4 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS return recalculateTotalInterest((AdvancedPaymentScheduleTransactionProcessor) processor, loan, relatedRefundTransactionDate, transactionsToReprocess); } - - @Override - public Money getTotalInterestRefunded(List loanTransactions, MonetaryCurrency currency, MathContext mc) { - final BigDecimal totalInterestRefunded = loanTransactions.stream() // - .filter(LoanTransaction::isNotReversed) // - .filter(LoanTransaction::isInterestRefund) // - .map(LoanTransaction::getAmount) // - .reduce(BigDecimal.ZERO, BigDecimal::add); // - return Money.of(currency, totalInterestRefunded, mc); - } - } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java index b40068e3b..6b97c0144 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java @@ -71,7 +71,7 @@ public class LoanRefundTransactionTest extends BaseLoanIntegrationTest { public void testMerchantIssuedRefundCreatesAndReversesInterestRefund() { runAt("01 July 2024", () -> { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); - final Long loanId = createLoanForMerchantIssuedRefundWithInterestRefund(clientId); + final Long loanId = createAndDisburseLoanForMerchantIssuedRefundWithInterestRefund(clientId); final PostLoansLoanIdTransactionsResponse merchantIssuedRefundResponse = loanTransactionHelper.makeMerchantIssuedRefund(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("01 July 2024").locale("en") .transactionAmount(100.0)); @@ -103,7 +103,7 @@ public class LoanRefundTransactionTest extends BaseLoanIntegrationTest { public void testPayoutRefundCreatesAndReversesInterestRefund() { runAt("01 July 2024", () -> { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); - final Long loanId = createLoanForPayoutRefundWithInterestRefund(clientId); + final Long loanId = createAndDisburseLoanForPayoutRefundWithInterestRefund(clientId); final PostLoansLoanIdTransactionsResponse payoutRefundResponse = loanTransactionHelper.makePayoutRefund(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("01 July 2024").locale("en") .transactionAmount(100.0)); @@ -129,22 +129,52 @@ public class LoanRefundTransactionTest extends BaseLoanIntegrationTest { }); } - private Long createLoanForMerchantIssuedRefundWithInterestRefund(Long clientId) { - return createLoanForRefundWithInterestRefund(clientId, "MERCHANT_ISSUED_REFUND"); + @Test + public void testMerchantIssuedRefundDoesNotCreateInterestRefundWithLessThanOrEqualToZeroInterest() { + runAt("20 April 2025", () -> { + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + Long loanId = createLoanForRefundWithInterestRefund(clientId, "MERCHANT_ISSUED_REFUND", "05 April 2025", 500.0, 20.99, 6); + disburseLoan(loanId, BigDecimal.valueOf(265.91), "05 April 2025"); + disburseLoan(loanId, BigDecimal.valueOf(1.99), "05 April 2025"); + disburseLoan(loanId, BigDecimal.valueOf(20.00), "05 April 2025"); + + final PostLoansLoanIdTransactionsResponse merchantIssuedRefundResponse1 = loanTransactionHelper.makeMerchantIssuedRefund(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("06 April 2025").locale("en") + .transactionAmount(6.29)); + + final PostLoansLoanIdTransactionsResponse merchantIssuedRefundResponse2 = loanTransactionHelper.makeMerchantIssuedRefund(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("07 April 2025").locale("en") + .transactionAmount(1.99)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + Assertions.assertTrue(loanDetails.getTransactions().stream() + .filter(transaction -> transaction.getType().getCode().equals("loanTransactionType.interestRefund")) + .allMatch(transaction -> transaction.getAmount().compareTo(0.0) > 0)); + }); } - private Long createLoanForPayoutRefundWithInterestRefund(Long clientId) { - return createLoanForRefundWithInterestRefund(clientId, "PAYOUT_REFUND"); + private Long createAndDisburseLoanForMerchantIssuedRefundWithInterestRefund(Long clientId) { + return createAndDisburseLoanForRefundWithInterestRefund(clientId, "MERCHANT_ISSUED_REFUND"); } - private Long createLoanForRefundWithInterestRefund(Long clientId, String refundType) { - final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct( - create4IProgressive().supportedInterestRefundTypes(new ArrayList<>()).addSupportedInterestRefundTypesItem(refundType)); - PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan( - applyLP2ProgressiveLoanRequest(clientId, loanProductsResponse.getResourceId(), "01 June 2024", 1000.0, 10.0, 4, null)); - Long loanId = postLoansResponse.getLoanId(); - loanTransactionHelper.approveLoan(loanId, approveLoanRequest(1000.0, "01 June 2024")); + private Long createAndDisburseLoanForPayoutRefundWithInterestRefund(Long clientId) { + return createAndDisburseLoanForRefundWithInterestRefund(clientId, "PAYOUT_REFUND"); + } + + private Long createAndDisburseLoanForRefundWithInterestRefund(Long clientId, String refundType) { + Long loanId = createLoanForRefundWithInterestRefund(clientId, refundType, "01 June 2024", 1000.0, 10.0, 4); disburseLoan(loanId, BigDecimal.valueOf(1000.0), "01 June 2024"); return loanId; } + + private Long createLoanForRefundWithInterestRefund(Long clientId, String refundType, String date, double amount, double interestRate, + int numRepayments) { + final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct( + create4IProgressive().supportedInterestRefundTypes(new ArrayList<>()).addSupportedInterestRefundTypesItem(refundType)); + PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId, + loanProductsResponse.getResourceId(), date, amount, interestRate, numRepayments, null)); + Long loanId = postLoansResponse.getLoanId(); + loanTransactionHelper.approveLoan(loanId, approveLoanRequest(amount, date)); + return loanId; + } }