From 456db2f0fa0de0cc4ec29fcfefd960d49703f2e7 Mon Sep 17 00:00:00 2001 From: Janos Meszaros Date: Fri, 6 Sep 2024 16:46:38 +0200 Subject: [PATCH] FINERACT-2114: Fix EMI Calculator get payment details --- .../loanproduct/calc/EMICalculator.java | 2 +- .../calc/ProgressiveEMICalculator.java | 34 ++++++++++++------- .../calc/ProgressiveEMICalculatorTest.java | 28 ++++++++++++--- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java index c932f9b277f..afcca8db75b 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java @@ -49,7 +49,7 @@ void changeInterestRate(ProgressiveLoanInterestScheduleModel scheduleModel, Loca void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate, Money balanceCorrectionAmount); - Optional getPayableDetails(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate date); + Optional getPayableDetails(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate periodDueDate, LocalDate payDate); ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(ProgressiveLoanInterestScheduleModel scheduleModel); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index 9110a3d31fa..80107f76ed4 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -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; @@ -137,7 +138,15 @@ public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleM Optional 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 updateInterestPeriodOnRepaymentPeriod(final LocalDate balanceChangeDate, final Money disbursedAmount, final Money correctionAmount) { + return repaymentPeriod -> { var interestPeriodOptional = findInterestPeriodForBalanceChange(repaymentPeriod, balanceChangeDate); if (interestPeriodOptional.isPresent()) { interestPeriodOptional.get().addDisbursedAmount(disbursedAmount); @@ -145,7 +154,7 @@ Optional changeOutstandingBalanceAndUpdat } else { insertInterestPeriod(repaymentPeriod, balanceChangeDate, disbursedAmount, correctionAmount); } - }).findFirst(); + }; } void insertInterestPeriod(final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final LocalDate balanceChangeDate, @@ -153,7 +162,7 @@ void insertInterestPeriod(final ProgressiveLoanInterestRepaymentModel repaymentP // 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();// @@ -170,11 +179,11 @@ void insertInterestPeriod(final ProgressiveLoanInterestRepaymentModel repaymentP Collections.sort(repaymentPeriod.getInterestPeriods()); } - private static @NotNull Predicate 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 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 @@ -241,15 +250,16 @@ public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleMo } @Override - public Optional getPayableDetails(ProgressiveLoanInterestScheduleModel scheduleModel, - LocalDate date) { + public Optional 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(); } diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java index cca1df98715..15995ce844f 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java @@ -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())); @@ -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())); @@ -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()));