Skip to content

Commit

Permalink
FINERACT-2081: Fix ProgressiveEMICalculator caused infinite loop on A…
Browse files Browse the repository at this point in the history
…dvancedPaymentScheduleTransactionProcessor
  • Loading branch information
somasorosdpc committed Feb 25, 2025
1 parent 8be31bd commit 6842bf5
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,22 @@ public PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleMo
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
periodDueDate, targetDate);
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
boolean multiplePeriodIsUnpaid = recalculatedScheduleModelTillDate.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
.count() > 1L;
if (multiplePeriodIsUnpaid && !targetDate.isAfter(repaymentPeriod.getFromDate())) {
repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi());
List<RepaymentPeriod> list = recalculatedScheduleModelTillDate.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()).toList();
long count = list.size();
boolean multiplePeriodIsUnpaid = count > 1L;
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
if (multiplePeriodIsUnpaid) {
repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi());
} else if (repaymentPeriod.isFullyPaid() && count == 1L) {
repaymentPeriod.setEmi(MathUtil.min(repaymentPeriod.getOriginalEmi(), //
recalculatedScheduleModelTillDate.getTotalDuePrincipal() //
.minus(recalculatedScheduleModelTillDate.getTotalPaidPrincipal()) //
.add(recalculatedScheduleModelTillDate.getTotalDueInterest()) //
.minus(recalculatedScheduleModelTillDate.getTotalPaidInterest()) //
.add(repaymentPeriod.getPaidPrincipal()) //
.add(repaymentPeriod.getPaidInterest()),
false)); //
}
}

return new PeriodDueDetails(repaymentPeriod.getEmi(), //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -1574,4 +1575,62 @@ public void verifyMerchantIssuedRefundPostingForBackdatedLoan() {
); //
});
}

/**
* Goal: test Merchant issued Refund does not cause infinite loop in special case of 2 transaction. * interest
* recalculation should be on. * merchant issued refund payment allocation should set to Last installment * default
* payment allocation should set to Next Installment Make a repayment to repay first instalment on its due date Make
* MerchantIssuedRefund to fully repay almost all the installments. 2nd installment should be fully unpaid and 3rd
* installment should have less outstanding principal portion than the total outstanding interest on the loan ( 2nd
* installment ). Make a 2nd MerchantIssuedRefund equal to remaining principal. Verify Repayment schedules and
* transactions. Verify that the loan become overpaid by the amount of 2nd interest refund.
*/
@Test
public void verifyMerchantIssuedRefundInTwoPortion() {
runAt("1 February 2025", () -> {
Long loanProductId = getOrCreateLoanProduct();
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductId, "1 January 2025", 100.0, 26.0, 6, null);
Assertions.assertNotNull(loanId);
disburseLoan(loanId, BigDecimal.valueOf(100.0), "1 January 2025");
loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 February 2025", 17.94);
loanTransactionHelper.makeLoanRepayment(loanId, "MerchantIssuedRefund", "1 February 2025", 66.41);
verifyTransactions(loanId, //
transaction(100.0, "Disbursement", "01 January 2025"), //
transaction(17.94, "Repayment", "01 February 2025"), //
transaction(66.41, "Merchant Issued Refund", "01 February 2025"), //
transaction(1.47, "Interest Refund", "01 February 2025") //
);
verifyRepaymentSchedule(loanId, //
installment(100.0, null, "01 January 2025"), //
installment(15.73, 2.21, 0.0, true, "01 February 2025"), //
installment(17.61, 0.33, 16.47, false, "01 March 2025"), //
installment(12.84, 0.01, 0.26, false, "01 April 2025"), //
installment(17.94, 0.0, 0.0, true, "01 May 2025"), //
installment(17.94, 0.0, 0.0, true, "01 June 2025"), //
installment(17.94, 0.0, 0.0, true, "01 July 2025") //
);
loanTransactionHelper.makeLoanRepayment(loanId, "MerchantIssuedRefund", "1 February 2025", 16.39);
verifyTransactions(loanId, //
transaction(100.0, "Disbursement", "01 January 2025"), //
transaction(17.94, "Repayment", "01 February 2025"), //
transaction(66.41, "Merchant Issued Refund", "01 February 2025"), //
transaction(1.47, "Interest Refund", "01 February 2025"), //
transaction(16.39, "Merchant Issued Refund", "01 February 2025"), //
transaction(0.36, "Interest Refund", "01 February 2025"), //
transaction(2.21, "Accrual", "01 February 2025") //
);
verifyRepaymentSchedule(loanId, //
installment(100.0, null, "01 January 2025"), //
installment(15.73, 2.21, 0.0, true, "01 February 2025"), //
installment(12.51, 0.0, 0.0, true, "01 March 2025"), //
installment(17.94, 0.0, 0.0, true, "01 April 2025"), //
installment(17.94, 0.0, 0.0, true, "01 May 2025"), //
installment(17.94, 0.0, 0.0, true, "01 June 2025"), //
installment(17.94, 0.0, 0.0, true, "01 July 2025") //
);
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
verifyLoanStatus(loanDetails, LoanStatus.OVERPAID);
Assertions.assertEquals(0.36, loanDetails.getTotalOverpaid());
});
}
}

0 comments on commit 6842bf5

Please sign in to comment.