FINERACT-1971: Fix EMI Calculator: Last Unpaid Repayment Period Handling.
call calculateLastUnpaidRepaymentPeriodEMI recursively when emi would be less than zero to adjust remaining emi correction for previous period.
This commit is contained in:
+7
-1
@@ -307,7 +307,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc);
|
||||
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
|
||||
.reduce((first, second) -> second);
|
||||
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc)));
|
||||
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> {
|
||||
repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc));
|
||||
if (repaymentPeriod.getEmi().isLessThanZero()) {
|
||||
repaymentPeriod.setEmi(repaymentPeriod.getEmi().zero());
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void calculateOutstandingBalance(ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
|
||||
+70
@@ -1132,6 +1132,64 @@ class ProgressiveEMICalculatorTest {
|
||||
checkPeriod(interestSchedule, 5, 0, 16.89, 0.003950916667, 0.07, 16.82, 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void soma_test_disbursedAmt1000_dayInYears360_daysInMonthDoesntMatter_repayEvery15Days() {
|
||||
|
||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
|
||||
repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)),
|
||||
repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)),
|
||||
repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)),
|
||||
repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)),
|
||||
repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)),
|
||||
repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)),
|
||||
repayment(7, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 8, 1)),
|
||||
repayment(8, LocalDate.of(2024, 8, 1), LocalDate.of(2024, 9, 1)),
|
||||
repayment(9, LocalDate.of(2024, 9, 1), LocalDate.of(2024, 10, 1)),
|
||||
repayment(10, LocalDate.of(2024, 10, 1), LocalDate.of(2024, 11, 1)),
|
||||
repayment(11, LocalDate.of(2024, 11, 1), LocalDate.of(2024, 12, 1)),
|
||||
repayment(12, LocalDate.of(2024, 12, 1), LocalDate.of(2025, 1, 1)));
|
||||
|
||||
final BigDecimal interestRate = BigDecimal.valueOf(25);
|
||||
final Integer installmentAmountInMultiplesOf = null;
|
||||
|
||||
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.ACTUAL.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
|
||||
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
|
||||
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
|
||||
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
|
||||
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = toMoney(1000.0);
|
||||
LocalDate disbursementDate = LocalDate.of(2024, 1, 1);
|
||||
|
||||
emiCalculator.addDisbursement(interestSchedule, disbursementDate, disbursedAmount);
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
LocalDate dueDate = expectedRepaymentPeriods.get(i).getDueDate();
|
||||
PeriodDueDetails dueAmounts = emiCalculator.getDueAmounts(interestSchedule, dueDate, disbursementDate);
|
||||
Money duePrincipal = dueAmounts.getDuePrincipal();
|
||||
if (duePrincipal.isGreaterThanZero()) {
|
||||
emiCalculator.payPrincipal(interestSchedule, dueDate, disbursementDate, duePrincipal);
|
||||
}
|
||||
}
|
||||
|
||||
checkPeriod(interestSchedule, 0, 95.02, 0.0, 95.02, 0.0, true);
|
||||
checkPeriod(interestSchedule, 1, 95.02, 0.0, 95.02, 0.0, true);
|
||||
checkPeriod(interestSchedule, 2, 95.02, 0.0, 95.02, 0.0, true);
|
||||
checkPeriod(interestSchedule, 3, 95.02, 0.0, 95.02, 0.0, true);
|
||||
checkPeriod(interestSchedule, 4, 95.02, 0.0, 95.02, 0.0, true);
|
||||
checkPeriod(interestSchedule, 5, 95.02, 0.0, 95.02, 0.0, true);
|
||||
checkPeriod(interestSchedule, 6, 95.02, 0.0, 95.02, 0.0, true);
|
||||
checkPeriod(interestSchedule, 7, 95.02, 0.0, 95.02, 0.0, true);
|
||||
checkPeriod(interestSchedule, 8, 95.02, 0.0, 95.02, 0.0, true);
|
||||
checkPeriod(interestSchedule, 9, 95.02, 0.0, 95.02, 0.0, true);
|
||||
checkPeriod(interestSchedule, 10, 49.8, 0.0, 49.8, 0.0, true);
|
||||
checkPeriod(interestSchedule, 11, 0.0, 0.0, 0.0, 0.0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_dailyInterest_disbursedAmt1000_dayInYears360_daysInMonth30_repayIn1Month() {
|
||||
|
||||
@@ -1298,6 +1356,18 @@ class ProgressiveEMICalculatorTest {
|
||||
remaingBalance);
|
||||
}
|
||||
|
||||
private static void checkPeriod(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx,
|
||||
final double emiValue, final double interestDueCumulated, final double principalDue, final double remainingBalance,
|
||||
final boolean fullyRepaid) {
|
||||
final RepaymentPeriod repaymentPeriod = interestScheduleModel.repaymentPeriods().get(repaymentIdx);
|
||||
|
||||
Assertions.assertEquals(emiValue, toDouble(repaymentPeriod.getEmi()));
|
||||
Assertions.assertEquals(interestDueCumulated, toDouble(repaymentPeriod.getDueInterest()));
|
||||
Assertions.assertEquals(principalDue, toDouble(repaymentPeriod.getDuePrincipal()));
|
||||
Assertions.assertEquals(remainingBalance, toDouble(repaymentPeriod.getOutstandingLoanBalance()));
|
||||
Assertions.assertEquals(fullyRepaid, repaymentPeriod.isFullyPaid());
|
||||
}
|
||||
|
||||
private static void checkPeriod(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx,
|
||||
final int interestIdx, final double emiValue, final double rateFactor, final double interestDue,
|
||||
final double interestDueCumulated, final double principalDue, final double remaingBalance) {
|
||||
|
||||
+4
@@ -1075,6 +1075,10 @@ public abstract class BaseLoanIntegrationTest {
|
||||
return new Installment(principalAmount, interestAmount, null, null, totalOutstandingAmount, completed, dueDate, null, null);
|
||||
}
|
||||
|
||||
protected Installment fullyRepaidInstallment(double principalAmount, double interestAmount, String dueDate) {
|
||||
return new Installment(principalAmount, interestAmount, null, null, 0.0, true, dueDate, null, null);
|
||||
}
|
||||
|
||||
protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double totalOutstandingAmount,
|
||||
Boolean completed, String dueDate) {
|
||||
return new Installment(principalAmount, interestAmount, feeAmount, null, totalOutstandingAmount, completed, dueDate, null, null);
|
||||
|
||||
+121
@@ -218,6 +218,127 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyInterestRefundCreatedForMerchantIssuedRefundDay22HighInterest12month() {
|
||||
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||
runAt("1 January 2021", () -> {
|
||||
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||
.addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND) //
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||
);
|
||||
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 26.0,
|
||||
12, null);
|
||||
Assertions.assertNotNull(loanId);
|
||||
loanIdRef.set(loanId);
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||
});
|
||||
runAt("22 January 2021", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||
.makeLoanRepayment("MerchantIssuedRefund", "22 January 2021", 1000F, loanId.intValue());
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||
|
||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
||||
transaction(14.96, "Accrual", "22 January 2021"), //
|
||||
transaction(14.96, "Interest Refund", "22 January 2021"), //
|
||||
transaction(1000.0, "Merchant Issued Refund", "22 January 2021") //
|
||||
);
|
||||
verifyRepaymentSchedule(loanId, installment(1000.0, null, "01 January 2021"), //
|
||||
fullyRepaidInstallment(80.52, 14.96, "01 February 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 March 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 April 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 May 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 June 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 July 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 August 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 September 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 October 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 November 2021"), //
|
||||
fullyRepaidInstallment(60.16, 0.0, "01 December 2021"), //
|
||||
fullyRepaidInstallment(0.0, 0.0, "01 January 2022") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyFullMerchantIssuedRefundOnDay0HighInterest12month() {
|
||||
runAt("1 January 2021", () -> {
|
||||
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||
.addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND) //
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||
);
|
||||
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 26.0,
|
||||
12, null);
|
||||
Assertions.assertNotNull(loanId);
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper
|
||||
.makeLoanRepayment("MerchantIssuedRefund", "1 January 2021", 1000F, loanId.intValue());
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||
|
||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
||||
transaction(1000.0, "Merchant Issued Refund", "01 January 2021") //
|
||||
);
|
||||
verifyRepaymentSchedule(loanId, installment(1000.0, null, "01 January 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 February 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 March 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 April 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 May 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 June 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 July 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 August 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 September 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 October 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 November 2021"), //
|
||||
fullyRepaidInstallment(45.2, 0.0, "01 December 2021"), //
|
||||
fullyRepaidInstallment(0.0, 0.0, "01 January 2022") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyRepaymentDay0HighInterest12month() {
|
||||
runAt("1 January 2021", () -> {
|
||||
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||
.createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) //
|
||||
.daysInYearType(DaysInYearType.ACTUAL) //
|
||||
.addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND) //
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) //
|
||||
);
|
||||
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 26.0,
|
||||
12, null);
|
||||
Assertions.assertNotNull(loanId);
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021");
|
||||
PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment("Repayment",
|
||||
"1 January 2021", 1000F, loanId.intValue());
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse);
|
||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||
|
||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), //
|
||||
transaction(1000.0, "Repayment", "01 January 2021") //
|
||||
);
|
||||
verifyRepaymentSchedule(loanId, installment(1000.0, null, "01 January 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 February 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 March 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 April 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 May 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 June 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 July 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 August 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 September 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 October 2021"), //
|
||||
fullyRepaidInstallment(95.48, 0.0, "01 November 2021"), //
|
||||
fullyRepaidInstallment(45.2, 0.0, "01 December 2021"), //
|
||||
fullyRepaidInstallment(0.0, 0.0, "01 January 2022") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyUC01() {
|
||||
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||
|
||||
Reference in New Issue
Block a user