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();

Reply via email to