Skip to content

Commit

Permalink
FINERACT-2114: Fix EMI Calculator get payment details
Browse files Browse the repository at this point in the history
  • Loading branch information
janez89 committed Sep 6, 2024
1 parent 9bb7e37 commit 456db2f
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void changeInterestRate(ProgressiveLoanInterestScheduleModel scheduleModel, Loca
void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate,
Money balanceCorrectionAmount);

Optional<ProgressiveLoanInterestRepaymentModel> getPayableDetails(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate date);
Optional<ProgressiveLoanInterestRepaymentModel> getPayableDetails(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate periodDueDate, LocalDate payDate);

ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(ProgressiveLoanInterestScheduleModel scheduleModel);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.DateUtils;
Expand Down Expand Up @@ -137,23 +138,31 @@ public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleM
Optional<ProgressiveLoanInterestRepaymentModel> changeOutstandingBalanceAndUpdateInterestPeriods(
final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate balanceChangeDate, final Money disbursedAmount,
final Money correctionAmount) {
return findInterestRepaymentPeriodForBalanceChange(scheduleModel, balanceChangeDate).stream().peek(repaymentPeriod -> {
return findInterestRepaymentPeriodForBalanceChange(scheduleModel, balanceChangeDate)
.stream()//
.peek(updateInterestPeriodOnRepaymentPeriod(balanceChangeDate, disbursedAmount, correctionAmount))//
.findFirst();//
}

@NotNull
private Consumer<ProgressiveLoanInterestRepaymentModel> updateInterestPeriodOnRepaymentPeriod(final LocalDate balanceChangeDate, final Money disbursedAmount, final Money correctionAmount) {
return repaymentPeriod -> {
var interestPeriodOptional = findInterestPeriodForBalanceChange(repaymentPeriod, balanceChangeDate);
if (interestPeriodOptional.isPresent()) {
interestPeriodOptional.get().addDisbursedAmount(disbursedAmount);
interestPeriodOptional.get().addCorrectionAmount(correctionAmount);
} else {
insertInterestPeriod(repaymentPeriod, balanceChangeDate, disbursedAmount, correctionAmount);
}
}).findFirst();
};
}

void insertInterestPeriod(final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final LocalDate balanceChangeDate,
final Money disbursedAmount, final Money correctionAmount) {
// balanceChangeDate is after disb.date because this case when disbursement date is different then interest
// period start date
final ProgressiveLoanInterestRepaymentInterestPeriod previousInterestPeriod = repaymentPeriod.getInterestPeriods().stream()
.filter(balanceChangeRelatedPreviousInterestPeriod(repaymentPeriod, balanceChangeDate))//
.filter(operationRelatedPreviousInterestPeriod(repaymentPeriod, balanceChangeDate))//
.findFirst()//
.get();//

Expand All @@ -170,11 +179,11 @@ void insertInterestPeriod(final ProgressiveLoanInterestRepaymentModel repaymentP
Collections.sort(repaymentPeriod.getInterestPeriods());
}

private static @NotNull Predicate<ProgressiveLoanInterestRepaymentInterestPeriod> balanceChangeRelatedPreviousInterestPeriod(
ProgressiveLoanInterestRepaymentModel repaymentPeriod, LocalDate balanceChangeDate) {
return interestPeriod -> balanceChangeDate.isAfter(interestPeriod.getFromDate())
&& (balanceChangeDate.isBefore(interestPeriod.getDueDate())
|| (repaymentPeriod.isLastPeriod() && !balanceChangeDate.isBefore(repaymentPeriod.getDueDate())));
private static @NotNull Predicate<ProgressiveLoanInterestRepaymentInterestPeriod> operationRelatedPreviousInterestPeriod(
ProgressiveLoanInterestRepaymentModel repaymentPeriod, LocalDate operationDate) {
return interestPeriod -> operationDate.isAfter(interestPeriod.getFromDate())
&& (operationDate.isBefore(interestPeriod.getDueDate())
|| (repaymentPeriod.getDueDate().equals(interestPeriod.getDueDate()) && !operationDate.isBefore(repaymentPeriod.getDueDate())));
}

@Override
Expand Down Expand Up @@ -241,15 +250,16 @@ public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleMo
}

@Override
public Optional<ProgressiveLoanInterestRepaymentModel> getPayableDetails(ProgressiveLoanInterestScheduleModel scheduleModel,
LocalDate date) {
public Optional<ProgressiveLoanInterestRepaymentModel> getPayableDetails(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate periodDueDate, final LocalDate payDate) {
final var newScheduleModel = makeScheduleModelDeepCopy(scheduleModel);
final var zeroAmount = Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());

return changeOutstandingBalanceAndUpdateInterestPeriods(newScheduleModel, date, zeroAmount, zeroAmount).stream()
return findInterestRepaymentPeriod(newScheduleModel, periodDueDate)
.stream()
.peek(updateInterestPeriodOnRepaymentPeriod(payDate, zeroAmount, zeroAmount))//
.peek(repaymentPeriod -> {
calculateRateFactorMinus1ForRepaymentPeriod(repaymentPeriod, scheduleModel);
calculatePrincipalInterestComponentsForPeriod(repaymentPeriod, date);
calculatePrincipalInterestComponentsForPeriod(repaymentPeriod, payDate);
}).findFirst();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,13 +366,28 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repay
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);

// schedule 1st period 1st day
ProgressiveLoanInterestRepaymentModel repaymentDetails = emiCalculator
.getPayableDetails(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 1, 1)).get();
Assertions.assertEquals(100, toDouble(repaymentDetails.getOutstandingBalance().getAmount()));
Assertions.assertEquals(17.01, toDouble(repaymentDetails.getPrincipalDue().getAmount()));
Assertions.assertEquals(0.0, toDouble(repaymentDetails.getInterestDue().getAmount()));

// schedule 2nd period last day
repaymentDetails = emiCalculator
.getPayableDetails(interestSchedule, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 3, 1)).get();
Assertions.assertEquals(83.57, toDouble(repaymentDetails.getOutstandingBalance().getAmount()));
Assertions.assertEquals(16.52, toDouble(repaymentDetails.getPrincipalDue().getAmount()));
Assertions.assertEquals(0.49, toDouble(repaymentDetails.getInterestDue().getAmount()));

// partially pay off a period with balance correction
final LocalDate op1stCorrectionPeriodDueDate = LocalDate.of(2024, 3, 1);
final LocalDate op1stCorrectionDate = LocalDate.of(2024, 2, 15);
final Money op1stCorrectionAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(-83.57));

// get remaining balance and dues for a date
final ProgressiveLoanInterestRepaymentModel repaymentDetails1st = emiCalculator
.getPayableDetails(interestSchedule, op1stCorrectionDate).get();
.getPayableDetails(interestSchedule, op1stCorrectionPeriodDueDate, op1stCorrectionDate).get();
Assertions.assertEquals(83.57, toDouble(repaymentDetails1st.getOutstandingBalance().getAmount()));
Assertions.assertEquals(16.77, toDouble(repaymentDetails1st.getPrincipalDue().getAmount()));
Assertions.assertEquals(0.24, toDouble(repaymentDetails1st.getInterestDue().getAmount()));
Expand All @@ -389,12 +404,13 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repay
checkPeriod(interestSchedule, 5, 0, 16.75, 0.005833333333, 0.10, 16.65, 0.0);

// totally pay off another period with balance correction
final LocalDate op2ndCorrectionPeriodDueDate = LocalDate.of(2024, 4, 1);
final LocalDate op2ndCorrectionDate = LocalDate.of(2024, 3, 1);
final Money op2ndCorrectionAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(-66.80));

// get remaining balance and dues for a date
final ProgressiveLoanInterestRepaymentModel repaymentDetails2st = emiCalculator
.getPayableDetails(interestSchedule, op2ndCorrectionDate).get();
.getPayableDetails(interestSchedule, op2ndCorrectionPeriodDueDate, op2ndCorrectionDate).get();
Assertions.assertEquals(66.80, toDouble(repaymentDetails2st.getOutstandingBalance().getAmount()));
Assertions.assertEquals(17.01, toDouble(repaymentDetails2st.getPrincipalDue().getAmount()));
Assertions.assertEquals(0.0, toDouble(repaymentDetails2st.getInterestDue().getAmount()));
Expand All @@ -410,15 +426,19 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repay
checkPeriod(interestSchedule, 5, 0, 16.34, 0.005833333333, 0.09, 16.25, 0.0);

// check numbers on last period due date
LocalDate periodDueDate = LocalDate.of(2024, 7, 1);
LocalDate payDate = LocalDate.of(2024, 7, 1);
final ProgressiveLoanInterestRepaymentModel repaymentDetails3rd = emiCalculator
.getPayableDetails(interestSchedule, LocalDate.of(2024, 7, 1)).get();
.getPayableDetails(interestSchedule, periodDueDate, payDate).get();
Assertions.assertEquals(16.25, toDouble(repaymentDetails3rd.getOutstandingBalance().getAmount()));
Assertions.assertEquals(16.25, toDouble(repaymentDetails3rd.getPrincipalDue().getAmount()));
Assertions.assertEquals(0.09, toDouble(repaymentDetails3rd.getInterestDue().getAmount()));

// check numbers after the last period due date
periodDueDate = LocalDate.of(2024, 7, 1);
payDate = LocalDate.of(2024, 7, 15);
final ProgressiveLoanInterestRepaymentModel repaymentDetails4th = emiCalculator
.getPayableDetails(interestSchedule, LocalDate.of(2024, 7, 15)).get();
.getPayableDetails(interestSchedule, periodDueDate, payDate).get();
Assertions.assertEquals(16.25, toDouble(repaymentDetails4th.getOutstandingBalance().getAmount()));
Assertions.assertEquals(16.25, toDouble(repaymentDetails4th.getPrincipalDue().getAmount()));
Assertions.assertEquals(0.14, toDouble(repaymentDetails4th.getInterestDue().getAmount()));
Expand Down

0 comments on commit 456db2f

Please sign in to comment.