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


The following commit(s) were added to refs/heads/develop by this push:
     new afc09b533 FINERACT-1971: Fix Accrual Activity reversal on Progressive 
interest bearing loans during loan reopening
afc09b533 is described below

commit afc09b5333487d286fce878fc9be96c4671a395f
Author: Soma Sörös <[email protected]>
AuthorDate: Tue Jan 7 19:38:08 2025 +0100

    FINERACT-1971: Fix Accrual Activity reversal on Progressive interest 
bearing loans during loan reopening
---
 ...tLoanRepaymentScheduleTransactionProcessor.java |  22 ++-
 .../LoanTransactionAccrualActivityPostingTest.java | 149 +++++++++++++++++++++
 2 files changed, 159 insertions(+), 12 deletions(-)

diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index dbe127bfe..bae506da0 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -218,24 +218,22 @@ public abstract class 
AbstractLoanRepaymentScheduleTransactionProcessor implemen
 
     protected void calculateAccrualActivity(LoanTransaction loanTransaction, 
MonetaryCurrency currency,
             List<LoanRepaymentScheduleInstallment> installments) {
-        loanTransaction.resetDerivedComponents();
-        // determine how much is outstanding total and breakdown for 
principal, interest and charges
-        final Money principalPortion = Money.zero(currency);
-        Money interestPortion = Money.zero(currency);
-        Money feeChargesPortion = Money.zero(currency);
-        Money penaltychargesPortion = Money.zero(currency);
+
         final int firstNormalInstallmentNumber = 
LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
 
         final LoanRepaymentScheduleInstallment currentInstallment = 
installments.stream()
                 .filter(installment -> 
LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(),
 installment,
                         
installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)))
                 .findFirst().orElseThrow();
-
-        interestPortion = 
interestPortion.plus(currentInstallment.getInterestCharged(currency));
-        feeChargesPortion = 
feeChargesPortion.plus(currentInstallment.getFeeChargesCharged(currency));
-        penaltychargesPortion = 
penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency));
-
-        loanTransaction.updateComponentsAndTotal(principalPortion, 
interestPortion, feeChargesPortion, penaltychargesPortion);
+        if 
(loanTransaction.getDateOf().isEqual(currentInstallment.getDueDate()) || 
installments.stream()
+                .filter(i -> !i.isAdditional() && 
!i.isDownPayment()).noneMatch(LoanRepaymentScheduleInstallment::isNotFullyPaidOff))
 {
+            loanTransaction.resetDerivedComponents();
+            final Money principalPortion = Money.zero(currency);
+            Money interestPortion = 
currentInstallment.getInterestCharged(currency);
+            Money feeChargesPortion = 
currentInstallment.getFeeChargesCharged(currency);
+            Money penaltyChargesPortion = 
currentInstallment.getPenaltyChargesCharged(currency);
+            loanTransaction.updateComponentsAndTotal(principalPortion, 
interestPortion, feeChargesPortion, penaltyChargesPortion);
+        }
     }
 
     private void 
recalculateAccrualActivityTransaction(ChangedTransactionDetail 
changedTransactionDetail, LoanTransaction loanTransaction,
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
index 622394ea8..66301d4ac 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
@@ -42,15 +42,18 @@ import org.apache.fineract.client.models.ChargeData;
 import org.apache.fineract.client.models.ChargeToGLAccountMapper;
 import org.apache.fineract.client.models.GetLoanFeeToIncomeAccountMappings;
 import 
org.apache.fineract.client.models.GetLoanPaymentChannelToFundSourceMappings;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.PaymentAllocationOrder;
 import org.apache.fineract.client.models.PostChargesRequest;
 import org.apache.fineract.client.models.PostChargesResponse;
 import org.apache.fineract.client.models.PostClientsResponse;
 import org.apache.fineract.client.models.PostLoanProductsRequest;
+import org.apache.fineract.client.models.PostLoanProductsResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdChargesRequest;
 import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdRequest;
 import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
 import org.apache.fineract.integrationtests.common.BusinessStepHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
@@ -910,6 +913,152 @@ public class LoanTransactionAccrualActivityPostingTest 
extends BaseLoanIntegrati
         });
     }
 
+    Long interestBearingProgressiveLoanProductId = null;
+
+    public void 
createInterestBearingProgressiveNoInterestRecalculationAutoDownPayment25percentLoanProductIfNotExists()
 {
+        if (interestBearingProgressiveLoanProductId == null) {
+            final PostLoanProductsResponse loanProductsResponse = 
loanProductHelper.createLoanProduct(create4IProgressive()
+                    
.currencyCode("USD").enableAccrualActivityPosting(true).enableDownPayment(true)
+                    
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25.0)).enableAutoRepaymentForDownPayment(true)
+                    
.currencyCode("USD").daysInMonthType(DaysInMonthType.ACTUAL).daysInYearType(DaysInYearType.ACTUAL)
+                    .isInterestRecalculationEnabled(false).description(
+                            "Interest bearing Progressive Loan USD, Auto Down 
Payment 25%, Accrual Activity Posting, NO InterestRecalculation"));
+            interestBearingProgressiveLoanProductId = 
loanProductsResponse.getResourceId();
+        }
+    }
+
+    /*
+     * using Interest bearing Progressive Loan USD, Auto Down Payment 25%, 
Accrual Activity Posting, NO
+     * InterestRecalculation 9.99 yearly interest 6 repayment 400 USD 
principal apply, approve and disburse on 1 January
+     * 2024 auto down payment 100 USD on 1 January 2024 repayment 370USD on 2 
January 2024 verify Accrual and Accrual
+     * Activity transaction creation verify that the loan become overpaid 
reverse the repayment on same day verify
+     * transaction reversals
+     */
+    @Test
+    public void 
testInterestBearingProgressiveNoInterestRecalculationAutoDownPayment25percentReopenDueReverseRepayment1()
 {
+        
createInterestBearingProgressiveNoInterestRecalculationAutoDownPayment25percentLoanProductIfNotExists();
+        AtomicReference<Long> loanIdRef = new AtomicReference<>(null);
+        runAt("1 January 2024", () -> {
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
+                    interestBearingProgressiveLoanProductId, "01 January 
2024", 400.0, 9.99, 6, null));
+            Long loanId = postLoansResponse.getLoanId();
+            Assertions.assertNotNull(loanId);
+            loanIdRef.set(loanId);
+            loanTransactionHelper.approveLoan(loanId, 
approveLoanRequest(400.0, "01 January 2024"));
+            disburseLoan(loanId, BigDecimal.valueOf(400.0), "01 January 2024");
+            verifyTransactions(loanId, //
+                    transaction(400.0, "Disbursement", "01 January 2024"), //
+                    transaction(100.0, "Down Payment", "01 January 2024") //
+            );
+        });
+        runAt("2 January 2024", () -> {
+            Long loanId = loanIdRef.get();
+            Long repaymentId = loanTransactionHelper.makeLoanRepayment("02 
January 2024", 370.0f, loanId.intValue()).getResourceId();
+            Assertions.assertNotNull(repaymentId);
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+            Assertions.assertNotNull(loanDetails);
+            Assertions.assertNotNull(loanDetails.getStatus());
+            Assertions.assertNotNull(loanDetails.getStatus().getOverpaid());
+            Assertions.assertTrue(loanDetails.getStatus().getOverpaid());
+
+            verifyTransactions(loanId, transaction(400.0, "Disbursement", "01 
January 2024"),
+                    transaction(100.0, "Down Payment", "01 January 2024"), 
transaction(8.76, "Accrual", "02 January 2024"),
+                    transaction(8.76, "Accrual Activity", "02 January 2024"), 
transaction(370.0, "Repayment", "02 January 2024"));
+            loanTransactionHelper.reverseRepayment(loanId.intValue(), 
repaymentId.intValue(), "02 January 2024");
+        });
+    }
+
+    /*
+     * using Interest bearing Progressive Loan USD, Auto Down Payment 25%, 
Accrual Activity Posting, NO
+     * InterestRecalculation 9.99 yearly interest 6 repayment 400 USD 
principal apply, approve and disburse on 1 January
+     * 2024 auto down payment 100 USD on 1 January 2024 repayment 370USD on 1 
January 2024 verify Accrual and Accrual
+     * Activity transaction creation verify that the loan become overpaid 
reverse the repayment on same day verify
+     * transaction reversals
+     */
+    @Test
+    public void 
testInterestBearingProgressiveNoInterestRecalculationAutoDownPayment25percentReopenDueReverseRepayment2()
 {
+        
createInterestBearingProgressiveNoInterestRecalculationAutoDownPayment25percentLoanProductIfNotExists();
+        runAt("1 January 2024", () -> {
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
+                    interestBearingProgressiveLoanProductId, "01 January 
2024", 400.0, 9.99, 6, null));
+            Long loanId = postLoansResponse.getLoanId();
+            Assertions.assertNotNull(loanId);
+            loanTransactionHelper.approveLoan(loanId, 
approveLoanRequest(400.0, "01 January 2024"));
+            disburseLoan(loanId, BigDecimal.valueOf(400.0), "01 January 2024");
+            verifyTransactions(loanId, //
+                    transaction(400.0, "Disbursement", "01 January 2024"), //
+                    transaction(100.0, "Down Payment", "01 January 2024") //
+            );
+            Long repaymentId = loanTransactionHelper.makeLoanRepayment("01 
January 2024", 370.0f, loanId.intValue()).getResourceId();
+            Assertions.assertNotNull(repaymentId);
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+            Assertions.assertNotNull(loanDetails);
+            Assertions.assertNotNull(loanDetails.getStatus());
+            Assertions.assertNotNull(loanDetails.getStatus().getOverpaid());
+            Assertions.assertTrue(loanDetails.getStatus().getOverpaid());
+
+            verifyTransactions(loanId, transaction(400.0, "Disbursement", "01 
January 2024"),
+                    transaction(100.0, "Down Payment", "01 January 2024"), 
transaction(8.76, "Accrual", "01 January 2024"),
+                    transaction(8.76, "Accrual Activity", "01 January 2024"), 
transaction(370.0, "Repayment", "01 January 2024"));
+
+            loanTransactionHelper.reverseRepayment(loanId.intValue(), 
repaymentId.intValue(), "01 January 2024");
+            loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+            Assertions.assertNotNull(loanDetails);
+            Assertions.assertNotNull(loanDetails.getStatus());
+            Assertions.assertNotNull(loanDetails.getStatus().getActive());
+            Assertions.assertTrue(loanDetails.getStatus().getActive());
+            verifyTransactions(loanId, transaction(400.0, "Disbursement", "01 
January 2024"),
+                    transaction(100.0, "Down Payment", "01 January 2024"), 
transaction(8.76, "Accrual", "01 January 2024"),
+                    reversedTransaction(370.0, "Repayment", "01 January 
2024"));
+
+        });
+    }
+
+    /*
+     * using Interest bearing Progressive Loan USD, Auto Down Payment 25%, 
Accrual Activity Posting, NO
+     * InterestRecalculation 9.99 yearly interest 6 repayment 400 USD 
principal apply, approve and disburse on 1 January
+     * 2024 auto down payment 100 USD on 1 January 2024 charge 30USD fee on 1 
January 2024 repayment 370USD on 1 January
+     * 2024 verify Accrual and Accrual Activity transaction creation verify 
that the loan become overpaid reverse the
+     * repayment on same day verify transaction reversals
+     */
+    @Test
+    public void 
testInterestBearingProgressiveNoInterestRecalculationAutoDownPayment25percentReopenDueReverseRepayment3()
 {
+        
createInterestBearingProgressiveNoInterestRecalculationAutoDownPayment25percentLoanProductIfNotExists();
+        runAt("1 January 2024", () -> {
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
+                    interestBearingProgressiveLoanProductId, "01 January 
2024", 400.0, 9.99, 6, null));
+            Long loanId = postLoansResponse.getLoanId();
+            Assertions.assertNotNull(loanId);
+            loanTransactionHelper.approveLoan(loanId, 
approveLoanRequest(400.0, "01 January 2024"));
+            disburseLoan(loanId, BigDecimal.valueOf(400.0), "01 January 2024");
+            verifyTransactions(loanId, //
+                    transaction(400.0, "Disbursement", "01 January 2024"), //
+                    transaction(100.0, "Down Payment", "01 January 2024") //
+            );
+            addCharge(loanId, false, 30.0, "01 January 2024");
+            Long repaymentId = loanTransactionHelper.makeLoanRepayment("01 
January 2024", 370.0f, loanId.intValue()).getResourceId();
+            Assertions.assertNotNull(repaymentId);
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+            Assertions.assertNotNull(loanDetails);
+            Assertions.assertNotNull(loanDetails.getStatus());
+            Assertions.assertNotNull(loanDetails.getStatus().getOverpaid());
+            Assertions.assertTrue(loanDetails.getStatus().getOverpaid());
+
+            verifyTransactions(loanId, transaction(400.0, "Disbursement", "01 
January 2024"),
+                    transaction(100.0, "Down Payment", "01 January 2024"), 
transaction(38.76, "Accrual", "01 January 2024"),
+                    transaction(38.76, "Accrual Activity", "01 January 2024"), 
transaction(370.0, "Repayment", "01 January 2024"));
+            loanTransactionHelper.reverseRepayment(loanId.intValue(), 
repaymentId.intValue(), "01 January 2024");
+            loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+            Assertions.assertNotNull(loanDetails);
+            Assertions.assertNotNull(loanDetails.getStatus());
+            Assertions.assertNotNull(loanDetails.getStatus().getActive());
+            Assertions.assertTrue(loanDetails.getStatus().getActive());
+            verifyTransactions(loanId, transaction(400.0, "Disbursement", "01 
January 2024"),
+                    transaction(100.0, "Down Payment", "01 January 2024"), 
transaction(38.76, "Accrual", "01 January 2024"),
+                    reversedTransaction(370.0, "Repayment", "01 January 
2024"));
+        });
+    }
+
     @Test
     public void test() {
         final String disbursementDay = "01 January 2023";

Reply via email to