FINERACT-2152: Fix interest pause on the 1st day of the 1st period

This commit is contained in:
Oleksii Novikov
2025-06-02 21:24:15 +02:00
committed by Arnold Gálovics
parent c44972043e
commit 42f5de937d
2 changed files with 44 additions and 9 deletions
@@ -161,13 +161,13 @@ public class ProgressiveLoanInterestScheduleModel {
if (repaymentPeriods.isEmpty()) {
return 0;
}
final RepaymentPeriod firstPeriod = repaymentPeriods.get(0);
final RepaymentPeriod firstPeriod = repaymentPeriods.getFirst();
final RepaymentPeriod lastPeriod = repaymentPeriods.size() > 1 ? getLastRepaymentPeriod() : firstPeriod;
return DateUtils.getExactDifferenceInDays(firstPeriod.getFromDate(), lastPeriod.getDueDate());
}
public LocalDate getStartDate() {
return !repaymentPeriods.isEmpty() ? repaymentPeriods.get(0).getFromDate() : null;
return !repaymentPeriods.isEmpty() ? repaymentPeriods.getFirst().getFromDate() : null;
}
public LocalDate getMaturityDate() {
@@ -264,7 +264,9 @@ public class ProgressiveLoanInterestScheduleModel {
}
private void insertInterestPausePeriods(final RepaymentPeriod repaymentPeriod, final LocalDate pauseStart, final LocalDate pauseEnd) {
final LocalDate effectivePauseStart = pauseStart.minusDays(1);
final boolean isPauseStartOnFirstDayOfPeriod = pauseStart.isEqual(repaymentPeriod.getFromDate().plusDays(1));
final LocalDate effectivePauseStart = isPauseStartOnFirstDayOfPeriod ? pauseStart : pauseStart.minusDays(1);
final LocalDate finalPauseStart = effectivePauseStart.isBefore(repaymentPeriod.getFromDate()) ? repaymentPeriod.getFromDate()
: effectivePauseStart;
final LocalDate finalPauseEnd = pauseEnd.isAfter(repaymentPeriod.getDueDate()) ? repaymentPeriod.getDueDate() : pauseEnd;
@@ -302,7 +304,7 @@ public class ProgressiveLoanInterestScheduleModel {
} else {
return repaymentPeriod.getInterestPeriods().stream()
.filter(ip -> date.isAfter(ip.getFromDate()) && !date.isAfter(ip.getDueDate())).reduce((first, second) -> second)
.orElse(repaymentPeriod.getInterestPeriods().get(0));
.orElse(repaymentPeriod.getInterestPeriods().getFirst());
}
}
@@ -357,10 +359,6 @@ public class ProgressiveLoanInterestScheduleModel {
return MathUtil.negativeToZero(getTotalDuePrincipal().minus(getTotalPaidPrincipal()));
}
public Money getTotalOutstandingInterest() {
return MathUtil.negativeToZero(getTotalDueInterest().minus(getTotalPaidInterest()));
}
public Optional<RepaymentPeriod> findRepaymentPeriod(@NotNull LocalDate transactionDate) {
return repaymentPeriods.stream() //
.filter(period -> isInPeriod(transactionDate, period.getFromDate(), period.getDueDate(), period.isFirstRepaymentPeriod()))//
@@ -381,7 +379,7 @@ public class ProgressiveLoanInterestScheduleModel {
@NotNull
public RepaymentPeriod getLastRepaymentPeriod() {
return repaymentPeriods.get(repaymentPeriods.size() - 1);
return repaymentPeriods.getLast();
}
public boolean isLastRepaymentPeriod(@NotNull RepaymentPeriod repaymentPeriod) {
@@ -1809,6 +1809,43 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 5, 0, 16.48, 0.005833333333, 0.0955499999946, 0.1, 16.38, 0.0);
}
@Test
public void test_interestPauseOnTheFirstDayOfFirstPeriodAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
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)));
final BigDecimal interestRate = BigDecimal.valueOf(7.0);
final Integer installmentAmountInMultiplesOf = null;
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024, 1, 2), LocalDate.of(2024, 1, 10));
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.43, 16.58, 83.42);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.000188172043, 0.0188172043, 0.43, 16.58, 83.42);
checkPeriod(interestSchedule, 0, 2, 17.01, 0.001505376344, 0.0, 0.43, 16.58, 83.42);
checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.486616666638, 0.49, 16.52, 66.90);
checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.390249999978, 0.39, 16.62, 50.28);
checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.293299999983, 0.29, 16.72, 33.56);
checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.195766666655, 0.20, 16.81, 16.75);
checkPeriod(interestSchedule, 5, 0, 16.85, 0.005833333333, 0.0977083333278, 0.10, 16.75, 0.0);
}
@Test
public void test_reschedule_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(