From d34e7d7875fc90883343c45f9b7f6a9541db0f43 Mon Sep 17 00:00:00 2001 From: Janos Meszaros Date: Fri, 22 Nov 2024 13:27:37 +0100 Subject: [PATCH] FINERACT-1981: Fix interest refund transaction with zero amount --- .../service/InterestRefundService.java | 3 +- .../domain/LoanAccountDomainServiceJpa.java | 9 +++++- ...gressiveLoanInterestRefundServiceImpl.java | 11 +++++-- .../LoanInterestRefundTest.java | 29 +++++++++++++++++++ 4 files changed, 47 insertions(+), 5 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 ba27cc0783b..8d3e663e348 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,6 +18,7 @@ */ 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; @@ -36,5 +37,5 @@ public interface InterestRefundService { Money totalInterestByTransactions(LoanRepaymentScheduleTransactionProcessor processor, Long loanId, LocalDate relatedRefundTransactionDate, List newTransactions, List oldTransactionIds); - Money getTotalInterestRefunded(List loanTransactions, MonetaryCurrency currency); + 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 af9617b25d6..abe81fc1f36 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 @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.domain; +import jakarta.annotation.Nullable; import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; @@ -40,6 +41,7 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargePaymentPostBusinessEvent; @@ -166,6 +168,7 @@ public void updateLoanCollateralStatus(Set loanCollate this.loanCollateralManagementRepository.saveAll(loanCollateralManagementSet); } + @Nullable private LoanTransaction createInterestRefundLoanTransaction(Loan loan, LoanTransaction refundTransaction) { InterestRefundService interestRefundService = interestRefundServiceDelegate.lookupInterestRefundService(loan); @@ -175,13 +178,17 @@ private LoanTransaction createInterestRefundLoanTransaction(Loan loan, LoanTrans 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()); + 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(); + if (MathUtil.isZero(interestRefundAmount)) { + return null; + } final ExternalId txnExternalId = externalIdFactory.create(); businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionInterestRefundPreBusinessEvent(loan)); return LoanTransaction.interestRefund(loan, interestRefundAmount, refundTransaction.getDateOf(), txnExternalId); 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 8dc0ab31bb4..8b9490f07a1 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,6 +21,7 @@ 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; @@ -135,9 +136,13 @@ public Money totalInterestByTransactions(LoanRepaymentScheduleTransactionProcess } @Override - public Money getTotalInterestRefunded(List loanTransactions, MonetaryCurrency currency) { - return Money.of(currency, loanTransactions.stream().filter(LoanTransaction::isNotReversed).filter(LoanTransaction::isInterestRefund) - .map(LoanTransaction::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add)); + 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/LoanInterestRefundTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java index 9047fb7d50e..f3c75450827 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java @@ -1108,6 +1108,35 @@ public void verifyUC18S1() { }); } + @Test + public void verifyNoEmptyInterestRefundTransaction() { + runAt("1 January 2021", () -> { + PostLoanProductsResponse loanProduct = loanProductHelper + .createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) // + .daysInYearType(DaysInYearType.ACTUAL) // + .multiDisburseLoan(true)// + .disallowExpectedDisbursements(true)// + .maxTrancheCount(2)// + .addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.PAYOUT_REFUND) // + .addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND) // + .recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) // + ); + Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 9.9, + 12, null); + Assertions.assertNotNull(loanId); + + disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021"); + loanTransactionHelper.makeLoanRepayment("MerchantIssuedRefund", "1 January 2021", 1000F, loanId.intValue()); + logLoanTransactions(loanId); + + verifyTransactions(loanId, // + transaction(1000.0, "Disbursement", "01 January 2021"), // + transaction(1000.0, "Merchant Issued Refund", "01 January 2021") // + ); + logLoanTransactions(loanId); + }); + } + @Test public void verifyUC18S2() { AtomicReference loanIdRef = new AtomicReference<>();