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";