Skip to content

Commit

Permalink
FINERACT-1971: Fix Accrual Activity reversal on Progressive interest …
Browse files Browse the repository at this point in the history
…bearing loans during loan reopening
  • Loading branch information
somasorosdpc committed Jan 8, 2025
1 parent 2fbdbe9 commit 979b586
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -218,24 +218,22 @@ public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbur

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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,18 @@
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;
Expand Down Expand Up @@ -910,6 +913,152 @@ public void testAccrualActivityPostingForMultiDisburseLoan() {
});
}

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

0 comments on commit 979b586

Please sign in to comment.