This is an automated email from the ASF dual-hosted git repository. adamsaghy pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 6df78f64c083a05876178e08f911116ef194824d Author: Jose Alberto Hernandez <[email protected]> AuthorDate: Wed Dec 10 10:42:37 2025 -0500 FINERACT-2389: Reprocess Interest Refund txn amount if no txn was changed --- ...dvancedPaymentScheduleTransactionProcessor.java | 10 ++-- .../integrationtests/LoanInterestRefundTest.java | 54 ++++++++++++++++++++++ ...nManualInterestRefundResponseStructureTest.java | 20 +------- .../common/loans/LoanTransactionHelper.java | 16 +++++++ 4 files changed, 76 insertions(+), 24 deletions(-) diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 584eec52f7..121172ee17 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -568,12 +568,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep .filter(LoanTransaction::isNotReversed).map(AbstractPersistableCustom::getId).toList(); final List<LoanTransaction> modifiedTransactions = new ArrayList<>(progCtx.getAlreadyProcessedTransactions().stream() .filter(LoanTransaction::isNotReversed).filter(tr -> tr.getId() == null).toList()); - if (!modifiedTransactions.isEmpty()) { - final Money interestAfterRefund = interestRefundService.totalInterestByTransactions(this, loan.getId(), targetDate, - modifiedTransactions, unmodifiedTransactionIds); - final Money newAmount = interestBeforeRefund.minus(progCtx.getSumOfInterestRefundAmount()).minus(interestAfterRefund); - loanTransaction.updateAmount(newAmount.getAmount()); - } + final Money interestAfterRefund = interestRefundService.totalInterestByTransactions(this, loan.getId(), targetDate, + modifiedTransactions, unmodifiedTransactionIds); + final Money newAmount = interestBeforeRefund.minus(progCtx.getSumOfInterestRefundAmount()).minus(interestAfterRefund); + loanTransaction.updateAmount(newAmount.getAmount()); progCtx.setSumOfInterestRefundAmount(progCtx.getSumOfInterestRefundAmount().add(loanTransaction.getAmount())); } } 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 fa3a9aacc8..02c8acfe8b 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 @@ -18,6 +18,7 @@ */ package org.apache.fineract.integrationtests; +import static org.apache.fineract.portfolio.loanaccount.domain.Loan.DATE_FORMAT; import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.REPLAYED; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -32,6 +33,7 @@ import java.math.BigDecimal; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.models.AdvancedPaymentData; @@ -41,6 +43,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionId import org.apache.fineract.client.models.PaymentAllocationOrder; import org.apache.fineract.client.models.PostClientsResponse; import org.apache.fineract.client.models.PostLoanProductsResponse; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest; import org.apache.fineract.client.util.CallFailedRuntimeException; @@ -1633,4 +1636,55 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest { Assertions.assertEquals(0.36, Utils.getDoubleValue(loanDetails.getTotalOverpaid())); }); } + + @Test + public void allowToReprocessInterestRefundEvenIfNoTransactionWasChanged() { + runAt("1 February 2025", () -> { + Long loanProductId = getOrCreateLoanProduct(); + Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductId, "1 January 2025", 100.0, 26.0, 6, null); + Assertions.assertNotNull(loanId); + disburseLoan(loanId, BigDecimal.valueOf(100.0), "1 January 2025"); + + final String transactionExternalId = UUID.randomUUID().toString(); + final PostLoansLoanIdTransactionsResponse refundResponse = loanTransactionHelper + .makeMerchantIssuedRefund(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN) + .transactionDate("1 February 2025").locale("en").transactionAmount(66.41).externalId(transactionExternalId).interestRefundCalculation(false)); + Assertions.assertNotNull(refundResponse.getResourceId()); + + verifyTransactions(loanId, // + transaction(100.0, "Disbursement", "01 January 2025"), // + transaction(66.41, "Merchant Issued Refund", "01 February 2025") // + ); + + // Create manual interest refund via API + PostLoansLoanIdTransactionsResponse interestRefundResponse = loanTransactionHelper.createManualInterestRefund(loanId, refundResponse.getResourceId(), + "1 February 2025", 1.47, null); + + verifyTransactions(loanId, // + transaction(100.0, "Disbursement", "01 January 2025"), // + transaction(66.41, "Merchant Issued Refund", "01 February 2025"), // + transaction(1.47, "Interest Refund", "01 February 2025") // + ); + + PostLoansLoanIdTransactionsResponse repaymentResponse = loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "20 January 2025", 17.94); + loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "25 January 2025", 10.94); + + verifyTransactions(loanId, // + transaction(100.0, "Disbursement", "01 January 2025"), // + transaction(17.94, "Repayment", "20 January 2025"), // + transaction(10.94, "Repayment", "25 January 2025"), // + transaction(66.41, "Merchant Issued Refund", "01 February 2025"), // + transaction(1.47, "Interest Refund", "01 February 2025") // + ); + + loanTransactionHelper.reverseLoanTransaction(loanId, repaymentResponse.getResourceId(), + new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN) + .transactionDate("25 January 2021").transactionAmount(0.0).locale("en")); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + verifyLoanStatus(loanDetails, LoanStatus.OVERPAID); + Assertions.assertEquals(0.36, Utils.getDoubleValue(loanDetails.getTotalOverpaid())); + }); + } + } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanManualInterestRefundResponseStructureTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanManualInterestRefundResponseStructureTest.java index fe69233498..abd5d4144e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanManualInterestRefundResponseStructureTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanManualInterestRefundResponseStructureTest.java @@ -96,7 +96,7 @@ public class LoanManualInterestRefundResponseStructureTest extends BaseLoanInteg targetTransactionIdRef.set(refundResponse.getResourceId()); // Create manual interest refund via API - PostLoansLoanIdTransactionsResponse interestRefundResponse = createManualInterestRefund(loanId, refundResponse.getResourceId(), + PostLoansLoanIdTransactionsResponse interestRefundResponse = loanTransactionHelper.createManualInterestRefund(loanId, refundResponse.getResourceId(), "15 January 2024", 5.0, null); assertNotNull(interestRefundResponse, "Interest refund response should not be null"); @@ -157,7 +157,7 @@ public class LoanManualInterestRefundResponseStructureTest extends BaseLoanInteg // Create manual interest refund with external ID String interestRefundExternalId = UUID.randomUUID().toString(); - PostLoansLoanIdTransactionsResponse interestRefundResponse = createManualInterestRefund(loanId, refundResponse.getResourceId(), + PostLoansLoanIdTransactionsResponse interestRefundResponse = loanTransactionHelper.createManualInterestRefund(loanId, refundResponse.getResourceId(), "15 February 2024", 5.0, interestRefundExternalId); assertNotNull(interestRefundResponse, "Interest refund response should not be null"); @@ -180,22 +180,6 @@ public class LoanManualInterestRefundResponseStructureTest extends BaseLoanInteg }); } - /** - * Helper method to create manual interest refund transaction - */ - private PostLoansLoanIdTransactionsResponse createManualInterestRefund(Long loanId, Long targetTransactionId, String transactionDate, - Double amount, String externalId) { - - PostLoansLoanIdTransactionsTransactionIdRequest request = new PostLoansLoanIdTransactionsTransactionIdRequest() - .transactionAmount(amount).dateFormat("dd MMMM yyyy").locale("en"); - - if (externalId != null) { - request.externalId(externalId); - } - - return loanTransactionHelper.manualInterestRefund(loanId, targetTransactionId, request); - } - /** * Helper method to make loan merchant issued refund (without automatic interest refund) */ diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index 8a55b88eb0..371c26649d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -1709,6 +1709,22 @@ public class LoanTransactionHelper { excludedTransactionTypes, page, size, sort)); } + /** + * Helper method to create manual interest refund transaction + */ + public PostLoansLoanIdTransactionsResponse createManualInterestRefund(Long loanId, Long targetTransactionId, String transactionDate, + Double amount, String externalId) { + + PostLoansLoanIdTransactionsTransactionIdRequest request = new PostLoansLoanIdTransactionsTransactionIdRequest() + .transactionAmount(amount).dateFormat("dd MMMM yyyy").locale("en"); + + if (externalId != null) { + request.externalId(externalId); + } + + return manualInterestRefund(loanId, targetTransactionId, request); + } + // TODO: Rewrite to use fineract-client instead! // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest)
