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 6e8024a00 FINERACT-1981: fix progressive accruals on early repayment
6e8024a00 is described below

commit 6e8024a004b9a247f12c6fc9c8347cd658a91108
Author: adam.magyari <[email protected]>
AuthorDate: Thu Jan 23 14:34:05 2025 +0100

    FINERACT-1981: fix progressive accruals on early repayment
---
 .../service/LoanAccrualsProcessingServiceImpl.java |  8 +--
 .../LoanCOBCreateAccrualsTest.java                 | 59 ++++++++++++++++++++++
 2 files changed, 63 insertions(+), 4 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
index fb53d65ba..b33a17f11 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
@@ -427,9 +427,9 @@ public class LoanAccrualsProcessingServiceImpl implements 
LoanAccrualsProcessing
         AccrualPeriodData period = 
accrualPeriods.getPeriodByInstallmentNumber(installment.getInstallmentNumber());
         MonetaryCurrency currency = accrualPeriods.getCurrency();
         Money interest = null;
-        boolean isFullPeriod = isFullPeriod(tillDate, installment);
+        boolean isPastPeriod = isAfterPeriod(tillDate, installment);
         boolean isInPeriod = isInPeriod(tillDate, installment, false);
-        if (isFullPeriod) {
+        if (isPastPeriod || loan.isClosed() || loan.isOverPaid()) {
             interest = installment.getInterestCharged(currency);
         } else {
             if (isInPeriod) { // first period first day is not accrued
@@ -447,7 +447,7 @@ public class LoanAccrualsProcessingServiceImpl implements 
LoanAccrualsProcessing
             unrecognizedWaived = MathUtil.min(unrecognizedWaived,
                     
MathUtil.minusToZero(installment.getInterestWaived(currency), 
transactionWaived), false);
             period.setUnrecognizedWaive(unrecognizedWaived);
-            Money waived = isFullPeriod ? 
installment.getInterestWaived(currency) : MathUtil.plus(transactionWaived, 
unrecognizedWaived);
+            Money waived = isPastPeriod ? 
installment.getInterestWaived(currency) : MathUtil.plus(transactionWaived, 
unrecognizedWaived);
             accruable = MathUtil.minusToZero(period.getInterestAmount(), 
waived);
         }
         period.setInterestAccruable(accruable);
@@ -499,7 +499,7 @@ public class LoanAccrualsProcessingServiceImpl implements 
LoanAccrualsProcessing
                     .map(p -> 
MathUtil.toBigDecimal(p.getTransactionAccrued())).reduce(BigDecimal.ZERO, 
MathUtil::add);
             BigDecimal accrued = MathUtil.subtractToZero(totalAccrued, 
prevAccrued);
             // if this is the current-last period, all the remaining accrued 
amount is added
-            return isInPeriod(tillDate, installment, false) ? accrued : 
MathUtil.min(installment.getInterestCharged(), accrued, false);
+            return isInPeriod(tillDate, installment, false) ? accrued : 
MathUtil.min(installment.getInterestAccrued(), accrued, false);
         } else {
             return isFullPeriod(tillDate, installment) ? 
installment.getInterestAccrued()
                     : loan.getLoanTransactions().stream()
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java
index 59476b965..713eba7e8 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java
@@ -26,8 +26,10 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 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.integrationtests.common.ClientHelper;
 import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
 
 @Slf4j
 public class LoanCOBCreateAccrualsTest extends BaseLoanIntegrationTest {
@@ -423,4 +425,61 @@ public class LoanCOBCreateAccrualsTest extends 
BaseLoanIntegrationTest {
 
         });
     }
+
+    @Test
+    public void testEarlyRepaymentAccruals() {
+        AtomicReference<Long> loanIdRef = new AtomicReference<>();
+        setup();
+        final PostLoanProductsResponse loanProductsResponse = 
loanProductHelper.createLoanProduct(create4IProgressive());
+
+        runAt("20 December 2024", () -> {
+            Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), 
loanProductsResponse.getResourceId(), "20 December 2024",
+                    430.0, 26.0, 6, null);
+
+            loanIdRef.set(loanId);
+
+            disburseLoan(loanId, BigDecimal.valueOf(430), "20 December 2024");
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 1, "20 January 
2025", 67.88, 0, 0, 9.32);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 2, "20 February 
2025", 69.35, 0, 0, 7.85);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 3, "20 March 
2025", 70.86, 0, 0, 6.34);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 4, "20 April 
2025", 72.39, 0, 0, 4.81);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 5, "20 May 2025", 
73.96, 0, 0, 3.24);
+            validateFullyUnpaidRepaymentPeriod(loanDetails, 6, "20 June 2025", 
75.56, 0, 0, 1.64);
+
+            verifyTransactions(loanId, transaction(430.0d, "Disbursement", "20 
December 2024"));
+            executeInlineCOB(loanId);
+        });
+        runAt("30 December 2024", () -> {
+            Long loanId = loanIdRef.get();
+            loanTransactionHelper.makeLoanRepayment(loanId, new 
PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN)
+                    .transactionDate("30 December 
2024").locale("en").transactionAmount(200.0));
+        });
+        runAt("22 March 2025", () -> {
+            Long loanId = loanIdRef.get();
+            executeInlineCOB(loanId);
+
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+
+            // No unexpected big accruals or any accrual adjustments
+            
Assertions.assertTrue(loanDetails.getTransactions().stream().noneMatch(t -> 
(t.getType().getAccrual() && t.getAmount() > 0.31)
+                    || 
"loanTransactionType.accrualAdjustment".equals(t.getType().getCode())));
+
+            // Accruals around installment due dates are as expected
+            
Assertions.assertTrue(loanDetails.getTransactions().stream().anyMatch(
+                    t -> t.getDate().equals(LocalDate.of(2025, 1, 20)) && 
t.getType().getAccrual() && t.getAmount().equals(0.16D)));
+            
Assertions.assertTrue(loanDetails.getTransactions().stream().anyMatch(
+                    t -> t.getDate().equals(LocalDate.of(2025, 1, 21)) && 
t.getType().getAccrual() && t.getAmount().equals(0.16D)));
+            
Assertions.assertTrue(loanDetails.getTransactions().stream().anyMatch(
+                    t -> t.getDate().equals(LocalDate.of(2025, 2, 20)) && 
t.getType().getAccrual() && t.getAmount().equals(0.16D)));
+            
Assertions.assertTrue(loanDetails.getTransactions().stream().anyMatch(
+                    t -> t.getDate().equals(LocalDate.of(2025, 2, 21)) && 
t.getType().getAccrual() && t.getAmount().equals(0.18D)));
+            
Assertions.assertTrue(loanDetails.getTransactions().stream().anyMatch(
+                    t -> t.getDate().equals(LocalDate.of(2025, 3, 20)) && 
t.getType().getAccrual() && t.getAmount().equals(0.18D)));
+            
Assertions.assertTrue(loanDetails.getTransactions().stream().anyMatch(
+                    t -> t.getDate().equals(LocalDate.of(2025, 3, 21)) && 
t.getType().getAccrual() && t.getAmount().equals(0.16D)));
+        });
+
+    }
 }

Reply via email to