This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 803a575b0 FINERACT-2114: EMI Calculator outstanding balance correction
803a575b0 is described below
commit 803a575b04de949f24c83b2d211adb8303d8a4b8
Author: Janos Meszaros <[email protected]>
AuthorDate: Wed Sep 4 10:09:58 2024 +0200
FINERACT-2114: EMI Calculator outstanding balance correction
---
...ressiveLoanInterestRepaymentInterestPeriod.java | 13 +-
.../ProgressiveLoanInterestRepaymentModel.java | 20 ++-
.../portfolio/loanproduct/calc/EMICalculator.java | 5 +
.../loanproduct/calc/ProgressiveEMICalculator.java | 160 ++++++++++++++-------
.../calc/ProgressiveEMICalculatorTest.java | 92 ++++++++++++
5 files changed, 233 insertions(+), 57 deletions(-)
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentInterestPeriod.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentInterestPeriod.java
index 2a5a04ecb..42235c756 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentInterestPeriod.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentInterestPeriod.java
@@ -35,10 +35,11 @@ public class ProgressiveLoanInterestRepaymentInterestPeriod
implements Comparabl
private BigDecimal rateFactorMinus1;
private Money disbursedAmount;
+ private Money correctionAmount;
private Money interestDue;
public ProgressiveLoanInterestRepaymentInterestPeriod(final
ProgressiveLoanInterestRepaymentInterestPeriod period) {
- this(period.fromDate, period.dueDate, period.rateFactorMinus1,
period.disbursedAmount, period.interestDue);
+ this(period.fromDate, period.dueDate, period.rateFactorMinus1,
period.disbursedAmount, period.correctionAmount, period.interestDue);
}
@Override
@@ -47,6 +48,14 @@ public class ProgressiveLoanInterestRepaymentInterestPeriod
implements Comparabl
}
public void addDisbursedAmount(final Money outstandingBalance) {
- this.disbursedAmount = this.disbursedAmount.add(outstandingBalance);
+ if (outstandingBalance != null && !outstandingBalance.isZero()) {
+ this.disbursedAmount =
this.disbursedAmount.add(outstandingBalance);
+ }
+ }
+
+ public void addCorrectionAmount(final Money correctionAmount) {
+ if (correctionAmount != null && !correctionAmount.isZero()) {
+ this.correctionAmount =
this.correctionAmount.add(correctionAmount);
+ }
}
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentModel.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentModel.java
index f39f808f3..f79e0bcdc 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentModel.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestRepaymentModel.java
@@ -32,27 +32,34 @@ public class ProgressiveLoanInterestRepaymentModel {
private LinkedList<ProgressiveLoanInterestRepaymentInterestPeriod>
interestPeriods;
+ private boolean isLastPeriod;
+
private Money equalMonthlyInstallment;
private Money principalDue;
private Money remainingBalance;
+ private Money outstandingBalance;
public ProgressiveLoanInterestRepaymentModel(final LocalDate fromDate,
final LocalDate dueDate, final Money equalMonthlyInstallment) {
this.fromDate = fromDate;
this.dueDate = dueDate;
this.equalMonthlyInstallment = equalMonthlyInstallment;
+ this.isLastPeriod = false;
final Money zeroAmount =
Money.zero(equalMonthlyInstallment.getCurrency());
+ this.outstandingBalance = zeroAmount;
this.remainingBalance = zeroAmount;
this.principalDue = zeroAmount;
this.interestPeriods = new LinkedList<>();
- this.interestPeriods
- .add(new
ProgressiveLoanInterestRepaymentInterestPeriod(fromDate, dueDate,
BigDecimal.ZERO, zeroAmount, zeroAmount));
+ this.interestPeriods.add(
+ new ProgressiveLoanInterestRepaymentInterestPeriod(fromDate,
dueDate, BigDecimal.ZERO, zeroAmount, zeroAmount, zeroAmount));
}
public
ProgressiveLoanInterestRepaymentModel(ProgressiveLoanInterestRepaymentModel
repaymentModel) {
this.fromDate = repaymentModel.fromDate;
this.dueDate = repaymentModel.dueDate;
+ this.isLastPeriod = repaymentModel.isLastPeriod;
this.equalMonthlyInstallment = repaymentModel.equalMonthlyInstallment;
+ this.outstandingBalance = repaymentModel.outstandingBalance;
this.remainingBalance = repaymentModel.remainingBalance;
this.principalDue = repaymentModel.principalDue;
this.interestPeriods = new LinkedList<>();
@@ -76,7 +83,12 @@ public class ProgressiveLoanInterestRepaymentModel {
.reduce(Money.zero(equalMonthlyInstallment.getCurrency()),
Money::plus);
}
- public Money getOutstandingBalance() {
- return remainingBalance.plus(principalDue);
+ public Money getCorrectionAmount() {
+ return
interestPeriods.stream().map(ProgressiveLoanInterestRepaymentInterestPeriod::getCorrectionAmount)
+ .reduce(Money.zero(equalMonthlyInstallment.getCurrency()),
Money::plus);
+ }
+
+ public Money getCorrectedOutstandingBalance() {
+ return outstandingBalance.plus(getCorrectionAmount());
}
}
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 352f6bf70..c932f9b27 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
@@ -46,6 +46,11 @@ public interface EMICalculator {
void changeInterestRate(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate newInterestEffectiveDate,
BigDecimal newInterestRate);
+ void addBalanceCorrection(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate balanceCorrectionDate,
+ Money balanceCorrectionAmount);
+
+ Optional<ProgressiveLoanInterestRepaymentModel>
getPayableDetails(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate
date);
+
ProgressiveLoanInterestScheduleModel
makeScheduleModelDeepCopy(ProgressiveLoanInterestScheduleModel scheduleModel);
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 290cbab13..9110a3d31 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.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
+import java.util.function.Predicate;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
@@ -41,6 +42,7 @@ import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLo
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import
org.apache.fineract.portfolio.loanproduct.mapper.ProgressiveLoanInterestRepaymentModelMapper;
+import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
@Component
@@ -61,6 +63,9 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
interestRepaymentModelList
.add(new
ProgressiveLoanInterestRepaymentModel(period.periodFromDate(),
period.periodDueDate(), zeroAmount));
}
+ if (!interestRepaymentModelList.isEmpty()) {
+ interestRepaymentModelList.get(interestRepaymentModelList.size() -
1).setLastPeriod(true);
+ }
return new
ProgressiveLoanInterestScheduleModel(interestRepaymentModelList,
loanProductRelatedDetail,
installmentAmountInMultiplesOf, mc);
}
@@ -76,24 +81,24 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
.findFirst();
}
- Optional<ProgressiveLoanInterestRepaymentModel>
findInterestRepaymentPeriodForDisbursement(
- final ProgressiveLoanInterestScheduleModel scheduleModel, final
LocalDate disbursementDueDate) {
- if (scheduleModel == null || disbursementDueDate == null) {
+ Optional<ProgressiveLoanInterestRepaymentModel>
findInterestRepaymentPeriodForBalanceChange(
+ final ProgressiveLoanInterestScheduleModel scheduleModel, final
LocalDate balanceChangeDate) {
+ if (scheduleModel == null || balanceChangeDate == null) {
return Optional.empty();
}
return scheduleModel.repayments().stream()//
- .filter(repaymentPeriod ->
!disbursementDueDate.isBefore(repaymentPeriod.getFromDate())
- &&
disbursementDueDate.isBefore(repaymentPeriod.getDueDate()))//
+ .filter(repaymentPeriod ->
!balanceChangeDate.isBefore(repaymentPeriod.getFromDate())
+ && (repaymentPeriod.isLastPeriod() ||
balanceChangeDate.isBefore(repaymentPeriod.getDueDate())))//
.findFirst();
}
- Optional<ProgressiveLoanInterestRepaymentInterestPeriod>
findInterestPeriodForDisbursement(
- final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final
LocalDate disbursementDueDate) {
- if (repaymentPeriod == null || disbursementDueDate == null) {
+ Optional<ProgressiveLoanInterestRepaymentInterestPeriod>
findInterestPeriodForBalanceChange(
+ final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final
LocalDate balanceChangeDate) {
+ if (repaymentPeriod == null || balanceChangeDate == null) {
return Optional.empty();
}
return repaymentPeriod.getInterestPeriods().stream()//
- .filter(interestPeriod ->
disbursementDueDate.isEqual(interestPeriod.getFromDate()))//
+ .filter(interestPeriod ->
balanceChangeDate.isEqual(interestPeriod.getFromDate()))//
.findFirst();
}
@@ -124,41 +129,54 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
@Override
public void addDisbursement(final ProgressiveLoanInterestScheduleModel
scheduleModel, final LocalDate disbursementDueDate,
final Money disbursedAmount) {
- final ProgressiveLoanInterestRepaymentModel repaymentPeriod =
findInterestRepaymentPeriodForDisbursement(scheduleModel,
- disbursementDueDate).orElse(null);
- if (repaymentPeriod == null) {
- return;
- }
-
- var interestPeriodOptional =
findInterestPeriodForDisbursement(repaymentPeriod, disbursementDueDate);
- if (interestPeriodOptional.isPresent()) {
- interestPeriodOptional.get().addDisbursedAmount(disbursedAmount);
- } else {
- insertInterestPeriod(repaymentPeriod, disbursementDueDate,
disbursedAmount);
- }
-
- calculateEMIValueAndRateFactors(repaymentPeriod.getDueDate(),
scheduleModel);
+ changeOutstandingBalanceAndUpdateInterestPeriods(scheduleModel,
disbursementDueDate, disbursedAmount,
+ Money.zero(disbursedAmount.getCurrency()))
+ .ifPresent((repaymentPeriod) ->
calculateEMIValueAndRateFactors(repaymentPeriod.getDueDate(), scheduleModel));
+ }
+
+ Optional<ProgressiveLoanInterestRepaymentModel>
changeOutstandingBalanceAndUpdateInterestPeriods(
+ final ProgressiveLoanInterestScheduleModel scheduleModel, final
LocalDate balanceChangeDate, final Money disbursedAmount,
+ final Money correctionAmount) {
+ return findInterestRepaymentPeriodForBalanceChange(scheduleModel,
balanceChangeDate).stream().peek(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 disbursementDueDate,
- final Money disbursedAmount) {
- // disbursementDueDate is after disb.date because this case when
disbursement date is different than interest
+ 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(interestPeriod ->
disbursementDueDate.isAfter(interestPeriod.getFromDate())
- &&
disbursementDueDate.isBefore(interestPeriod.getDueDate()))//
+
.filter(balanceChangeRelatedPreviousInterestPeriod(repaymentPeriod,
balanceChangeDate))//
.findFirst()//
.get();//
- final var interestPeriod = new
ProgressiveLoanInterestRepaymentInterestPeriod(disbursementDueDate,
- previousInterestPeriod.getDueDate(), BigDecimal.ZERO,
disbursedAmount, Money.zero(disbursedAmount.getCurrency()));
+ final boolean changeAfterLastRepaymentPeriod =
repaymentPeriod.isLastPeriod()
+ &&
previousInterestPeriod.getDueDate().isEqual(repaymentPeriod.getDueDate());
+ final LocalDate interestPeriodDueDate = changeAfterLastRepaymentPeriod
? balanceChangeDate.plusDays(1)
+ : previousInterestPeriod.getDueDate();
+ final var interestPeriod = new
ProgressiveLoanInterestRepaymentInterestPeriod(balanceChangeDate,
interestPeriodDueDate,
+ BigDecimal.ZERO, disbursedAmount, correctionAmount,
Money.zero(disbursedAmount.getCurrency()));
- previousInterestPeriod.setDueDate(disbursementDueDate);
+ previousInterestPeriod.setDueDate(balanceChangeDate);
repaymentPeriod.getInterestPeriods().add(interestPeriod);
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())));
+ }
+
@Override
public ProgressiveLoanInterestScheduleModel
makeScheduleModelDeepCopy(final ProgressiveLoanInterestScheduleModel
scheduleModel) {
return makeScheduleModelDeepCopy(scheduleModel,
scheduleModel.loanProductRelatedDetail(),
@@ -203,7 +221,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
final Money zeroAmount =
Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());
final var interestPeriod = new
ProgressiveLoanInterestRepaymentInterestPeriod(interestChangeDueDate,
- previousInterestPeriod.getDueDate(), BigDecimal.ZERO,
zeroAmount, zeroAmount);
+ previousInterestPeriod.getDueDate(), BigDecimal.ZERO,
zeroAmount, zeroAmount, zeroAmount);
previousInterestPeriod.setDueDate(interestChangeDueDate);
@@ -211,6 +229,30 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
Collections.sort(repaymentPeriod.getInterestPeriods());
}
+ @Override
+ public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate balanceCorrectionDate,
+ Money balanceCorrectionAmount) {
+ final Money zeroAmount =
Money.zero(balanceCorrectionAmount.getCurrency());
+ changeOutstandingBalanceAndUpdateInterestPeriods(scheduleModel,
balanceCorrectionDate, zeroAmount, balanceCorrectionAmount)
+ .ifPresent(repaymentPeriod -> {
+
calculateRateFactorMinus1ForRepaymentPeriod(repaymentPeriod, scheduleModel);
+
calculatePrincipalInterestComponentsForPeriods(scheduleModel);
+ });
+ }
+
+ @Override
+ public Optional<ProgressiveLoanInterestRepaymentModel>
getPayableDetails(ProgressiveLoanInterestScheduleModel scheduleModel,
+ LocalDate date) {
+ final var newScheduleModel = makeScheduleModelDeepCopy(scheduleModel);
+ final var zeroAmount =
Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());
+
+ return
changeOutstandingBalanceAndUpdateInterestPeriods(newScheduleModel, date,
zeroAmount, zeroAmount).stream()
+ .peek(repaymentPeriod -> {
+
calculateRateFactorMinus1ForRepaymentPeriod(repaymentPeriod, scheduleModel);
+
calculatePrincipalInterestComponentsForPeriod(repaymentPeriod, date);
+ }).findFirst();
+ }
+
/**
* Calculate Equal Monthly Installment value and Rate Factor -1 values for
calculate Interest
*/
@@ -302,11 +344,13 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
*/
void calculateRateFactorMinus1ForPeriods(final
List<ProgressiveLoanInterestRepaymentModel> repaymentPeriods,
final ProgressiveLoanInterestScheduleModel scheduleModel) {
- for (var repaymentPeriod : repaymentPeriods) {
- for (var interestPeriod : repaymentPeriod.getInterestPeriods()) {
-
interestPeriod.setRateFactorMinus1(calculateRateFactorMinus1PerPeriod(repaymentPeriod,
interestPeriod, scheduleModel));
- }
- }
+ repaymentPeriods.forEach(repaymentPeriod ->
calculateRateFactorMinus1ForRepaymentPeriod(repaymentPeriod, scheduleModel));
+ }
+
+ void calculateRateFactorMinus1ForRepaymentPeriod(final
ProgressiveLoanInterestRepaymentModel repaymentPeriod,
+ final ProgressiveLoanInterestScheduleModel scheduleModel) {
+ repaymentPeriod.getInterestPeriods().forEach(interestPeriod ->
interestPeriod
+
.setRateFactorMinus1(calculateRateFactorMinus1PerPeriod(repaymentPeriod,
interestPeriod, scheduleModel)));
}
/**
@@ -636,35 +680,45 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
void calculatePrincipalInterestComponentsForPeriods(final
ProgressiveLoanInterestScheduleModel scheduleModel) {
Money outstandingBalance =
Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());
- int numberOfPeriods = scheduleModel.repayments().size();
- int period = 0;
for (var repaymentPeriod : scheduleModel.repayments()) {
- final boolean isLastPeriod = ++period == numberOfPeriods;
- outstandingBalance =
calculatePrincipalInterestComponentsForPeriod(repaymentPeriod,
outstandingBalance, isLastPeriod);
+ repaymentPeriod.setOutstandingBalance(outstandingBalance);
+ calculatePrincipalInterestComponentsForPeriod(repaymentPeriod,
null);
+ outstandingBalance = repaymentPeriod.getRemainingBalance();
}
}
- Money calculatePrincipalInterestComponentsForPeriod(final
ProgressiveLoanInterestRepaymentModel repaymentPeriod,
- final Money initBalance, final boolean isLastPeriod) {
- Money outstandingBalance = initBalance;
+ void calculatePrincipalInterestComponentsForPeriod(final
ProgressiveLoanInterestRepaymentModel repaymentPeriod,
+ final LocalDate calculateTill) {
+ final Money zeroAmount =
Money.zero(repaymentPeriod.getOutstandingBalance().getCurrency());
+ Money outstandingBalance = repaymentPeriod.getOutstandingBalance();
+ Money balanceCorrection = zeroAmount;
+ Money cumulatedInterest = zeroAmount;
+
for (ProgressiveLoanInterestRepaymentInterestPeriod interestPeriod :
repaymentPeriod.getInterestPeriods()) {
+ final boolean shouldInvalidateInterestPeriod = calculateTill !=
null && interestPeriod.getDueDate().isAfter(calculateTill);
+ if (shouldInvalidateInterestPeriod) {
+ interestPeriod.setInterestDue(zeroAmount);
+ interestPeriod.setDisbursedAmount(zeroAmount);
+ interestPeriod.setCorrectionAmount(zeroAmount);
+ continue;
+ }
outstandingBalance =
outstandingBalance.plus(interestPeriod.getDisbursedAmount());
- final Money calculatedInterest =
outstandingBalance.multipliedBy(interestPeriod.getRateFactorMinus1());
+ balanceCorrection =
balanceCorrection.plus(interestPeriod.getCorrectionAmount());
+ final Money calculatedInterest =
outstandingBalance.plus(balanceCorrection).multipliedBy(interestPeriod.getRateFactorMinus1());
interestPeriod.setInterestDue(calculatedInterest);
+ cumulatedInterest = cumulatedInterest.plus(calculatedInterest);
}
- final Money cumulatedInterest = repaymentPeriod.getInterestDue();
- final Money calculatedPrincipal = isLastPeriod ? outstandingBalance
+ final Money calculatedPrincipal = repaymentPeriod.isLastPeriod() ?
outstandingBalance
:
repaymentPeriod.getEqualMonthlyInstallment().minus(cumulatedInterest);
- if (isLastPeriod) {
+ if (repaymentPeriod.isLastPeriod()) {
repaymentPeriod.setEqualMonthlyInstallment(calculatedPrincipal.add(cumulatedInterest));
}
+ final Money remainingBalance =
outstandingBalance.minus(calculatedPrincipal);
repaymentPeriod.setPrincipalDue(calculatedPrincipal);
-
repaymentPeriod.setRemainingBalance(outstandingBalance.minus(calculatedPrincipal));
-
- return repaymentPeriod.getRemainingBalance();
+ repaymentPeriod.setRemainingBalance(remainingBalance);
}
@Override
@@ -674,6 +728,10 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
.filter(period -> !period.isDownPayment() &&
!period.isAdditional()).toList();
List<ProgressiveLoanInterestRepaymentModel> repaymentModels =
progressiveLoanInterestRepaymentModelMapper
.map(repaymentModelsWithoutDownPayment);
+
+ if (!repaymentModels.isEmpty()) {
+ repaymentModels.get(repaymentModels.size() -
1).setLastPeriod(true);
+ }
return new ProgressiveLoanInterestScheduleModel(repaymentModels,
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
}
}
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 109bd4870..cca1df987 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
@@ -34,6 +34,7 @@ import
org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestRepaymentModel;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
@@ -332,6 +333,97 @@ class ProgressiveEMICalculatorTest {
checkPeriod(interestSchedule, 5, 0, 16.90, 0.003333333333, 0.06,
16.84, 0.0);
}
+ /**
+ * This test case tests a period early and late repayment with balance
correction
+ */
+ @Test
+ public void
testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month_add_balance_on0215()
{
+ final MathContext mc = MoneyHelper.getMathContext();
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= new ArrayList<>();
+
+ expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1),
LocalDate.of(2024, 2, 1)));
+ expectedRepaymentPeriods.add(repayment(2, LocalDate.of(2024, 2, 1),
LocalDate.of(2024, 3, 1)));
+ expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 1),
LocalDate.of(2024, 4, 1)));
+ expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 1),
LocalDate.of(2024, 5, 1)));
+ expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1),
LocalDate.of(2024, 6, 1)));
+ expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1),
LocalDate.of(2024, 7, 1)));
+
+ final BigDecimal interestRate = new BigDecimal("7");
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getNominalInterestRatePerPeriod()).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.getCurrency()).thenReturn(monetaryCurrency);
+
+
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024,
2, 15));
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
+ loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = Money.of(monetaryCurrency,
BigDecimal.valueOf(100));
+ emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
+
+ // partially pay off a period with balance correction
+ 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();
+ Assertions.assertEquals(83.57,
toDouble(repaymentDetails1st.getOutstandingBalance().getAmount()));
+ Assertions.assertEquals(16.77,
toDouble(repaymentDetails1st.getPrincipalDue().getAmount()));
+ Assertions.assertEquals(0.24,
toDouble(repaymentDetails1st.getInterestDue().getAmount()));
+
+ emiCalculator.addBalanceCorrection(interestSchedule,
op1stCorrectionDate, op1stCorrectionAmount);
+
+ checkDisbursementOnPeriod(interestSchedule, 0, disbursedAmount);
+ checkPeriod(interestSchedule, 0, 0, 17.01, 0.005833333333, 0.58,
16.43, 83.57);
+ checkPeriod(interestSchedule, 1, 0, 17.01, 0.002816091954, 0.24, 0.24,
16.77, 66.80);
+ checkPeriod(interestSchedule, 1, 1, 17.01, 0.003017241379, 0.0, 0.24,
16.77, 66.80);
+ checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39,
16.62, 50.18);
+ checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29,
16.72, 33.46);
+ checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20,
16.81, 16.65);
+ checkPeriod(interestSchedule, 5, 0, 16.75, 0.005833333333, 0.10,
16.65, 0.0);
+
+ // totally pay off another period with balance correction
+ 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();
+ Assertions.assertEquals(66.80,
toDouble(repaymentDetails2st.getOutstandingBalance().getAmount()));
+ Assertions.assertEquals(17.01,
toDouble(repaymentDetails2st.getPrincipalDue().getAmount()));
+ Assertions.assertEquals(0.0,
toDouble(repaymentDetails2st.getInterestDue().getAmount()));
+
+ emiCalculator.addBalanceCorrection(interestSchedule,
op2ndCorrectionDate, op2ndCorrectionAmount);
+
+ checkPeriod(interestSchedule, 0, 0, 17.01, 0.005833333333, 0.58,
16.43, 83.57);
+ checkPeriod(interestSchedule, 1, 0, 17.01, 0.002816091954, 0.24, 0.24,
16.77, 66.80);
+ checkPeriod(interestSchedule, 1, 1, 17.01, 0.003017241379, 0.0, 0.24,
16.77, 66.80);
+ checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0, 17.01,
49.79);
+ checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29,
16.72, 33.07);
+ checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.19,
16.82, 16.25);
+ checkPeriod(interestSchedule, 5, 0, 16.34, 0.005833333333, 0.09,
16.25, 0.0);
+
+ // check numbers on last period due date
+ final ProgressiveLoanInterestRepaymentModel repaymentDetails3rd =
emiCalculator
+ .getPayableDetails(interestSchedule, LocalDate.of(2024, 7,
1)).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
+ final ProgressiveLoanInterestRepaymentModel repaymentDetails4th =
emiCalculator
+ .getPayableDetails(interestSchedule, LocalDate.of(2024, 7,
15)).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()));
+ }
+
// @Test
// public void
testEMICalculation_disbursedAmt100_dayInYearsActual_daysInMonthActual_repayEvery1Month_reschedule()
{
// final MathContext mc = MoneyHelper.getMathContext();