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)));
+ });
+
+ }
}