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 67440c47a FINERACT-1981: Adjust EMI if needed
67440c47a is described below
commit 67440c47abd08438851dae58bdcd13983825c0e3
Author: Adam Saghy <[email protected]>
AuthorDate: Fri Jul 5 00:01:15 2024 +0200
FINERACT-1981: Adjust EMI if needed
---
.../AbstractProgressiveLoanScheduleGenerator.java | 4 +-
.../domain/ProgressiveLoanScheduleGenerator.java | 11 +-
.../loanproduct/calc/EMICalculationResult.java | 3 +-
.../portfolio/loanproduct/calc/EMICalculator.java | 7 +-
.../loanproduct/calc/ProgressiveEMICalculator.java | 115 ++++++++++--
...MICalculator.java => RepaymentPeriodModel.java} | 12 +-
...Calculator.java => RepaymentScheduleModel.java} | 17 +-
.../calc/ProgressiveEMICalculatorTest.java | 50 +++---
...PaymentAllocationLoanRepaymentScheduleTest.java | 197 ++++++++++++++++++++-
9 files changed, 344 insertions(+), 72 deletions(-)
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java
index c98935cbc..79b33799c 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java
@@ -119,8 +119,8 @@ public abstract class
AbstractProgressiveLoanScheduleGenerator implements LoanSc
: scheduleParams.getPeriodStartDate();
List<PreGeneratedLoanSchedulePeriod> expectedRepaymentPeriods
= getScheduledDateGenerator()
.generateRepaymentPeriods(startDate,
loanApplicationTerms, holidayDetailDTO);
- emiCalculationResult =
getEMICalculator().calculateEMIValueAndRateFactors(scheduleParams,
- loanApplicationTerms.toLoanProductRelatedDetail(),
expectedRepaymentPeriods, mc);
+ emiCalculationResult =
getEMICalculator().calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams,
+ expectedRepaymentPeriods, mc);
}
// 5 determine principal,interest of repayment period
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
index 3f51b727d..a0556f396 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java
@@ -55,12 +55,11 @@ public class ProgressiveLoanScheduleGenerator extends
AbstractProgressiveLoanSch
final LoanScheduleParams loanScheduleParams, final
EMICalculationResult emiCalculationResult, final MathContext mc) {
final Money equalMonthlyInstallmentValue =
loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null
- ? Money.of(loanApplicationTerms.getCurrency(),
-
Money.roundToMultiplesOf(emiCalculationResult.getEqualMonthlyInstallmentValue(),
-
loanApplicationTerms.getInstallmentAmountInMultiplesOf()))
- : Money.of(loanApplicationTerms.getCurrency(),
emiCalculationResult.getEqualMonthlyInstallmentValue());
+ ?
Money.roundToMultiplesOf(emiCalculationResult.getEqualMonthlyInstallmentValue(),
+
loanApplicationTerms.getInstallmentAmountInMultiplesOf())
+ : emiCalculationResult.getEqualMonthlyInstallmentValue();
final BigDecimal rateFactorMinus1 =
emiCalculationResult.getNextRepaymentPeriodRateFactorMinus1();
- final Money calculatedInterest =
loanScheduleParams.getOutstandingBalance().multipliedBy(rateFactorMinus1);
+ final Money calculatedInterest =
loanScheduleParams.getOutstandingBalanceAsPerRest().multipliedBy(rateFactorMinus1);
final Money calculatedPrincipal =
equalMonthlyInstallmentValue.minus(calculatedInterest);
return new PrincipalInterest(
@@ -72,7 +71,7 @@ public class ProgressiveLoanScheduleGenerator extends
AbstractProgressiveLoanSch
final LoanApplicationTerms loanApplicationTerms, final
LoanScheduleParams loanScheduleParams) {
final boolean isLastRepaymentPeriod =
loanScheduleParams.getPeriodNumber() ==
loanApplicationTerms.getActualNoOfRepaymnets();
if (isLastRepaymentPeriod) {
- final Money remainingAmount =
loanScheduleParams.getOutstandingBalance().minus(calculatedPrincipal);
+ final Money remainingAmount =
loanScheduleParams.getOutstandingBalanceAsPerRest().minus(calculatedPrincipal);
return calculatedPrincipal.plus(remainingAmount);
}
return calculatedPrincipal;
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculationResult.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculationResult.java
index 71b9435c1..1990f6355 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculationResult.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculationResult.java
@@ -23,12 +23,13 @@ import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
+import org.apache.fineract.organisation.monetary.domain.Money;
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public class EMICalculationResult {
@Getter
- private final BigDecimal equalMonthlyInstallmentValue;
+ private final Money equalMonthlyInstallmentValue;
private final List<BigDecimal> repaymentPeriodRateFactorMinus1List;
private int counter = 0;
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 bf217b73e..6f50b9ebf 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
@@ -21,12 +21,11 @@ package org.apache.fineract.portfolio.loanproduct.calc;
import java.math.MathContext;
import java.util.List;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
-import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
public interface EMICalculator {
- EMICalculationResult calculateEMIValueAndRateFactors(LoanScheduleParams
scheduleParams,
- LoanProductRelatedDetail loanProductRelatedDetail, List<? extends
LoanScheduleModelPeriod> expectedRepaymentPeriods,
- MathContext mc);
+ EMICalculationResult calculateEMIValueAndRateFactors(LoanApplicationTerms
loanApplicationTerms, LoanScheduleParams scheduleParams,
+ List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods,
MathContext mc);
}
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 f0812a6ed..d7e830862 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
@@ -25,10 +25,12 @@ import java.time.Year;
import java.util.List;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
+import org.apache.fineract.organisation.monetary.domain.Money;
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.loanschedule.data.LoanScheduleParams;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.springframework.stereotype.Component;
@@ -42,25 +44,28 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
/**
* Calculate Equal Monthly Installment value and Rate Factor -1 values for
calculate Interest
*
+ * @param loanApplicationTerms
+ * LoanTermApplication
+ *
* @param scheduleParams
* Loan Schedule Params
*
- * @param loanProductRelatedDetail
- * Loan Product Related Detail from LoanTermApplication
- *
* @param expectedRepaymentPeriods
* Expected Repayment Periods
*
* @param mc
- * @return
+ * MathContext for rounding
+ *
+ * @return EMICalculationResult Contains rate factor for each period and
calculated EMI
*/
@Override
- public EMICalculationResult calculateEMIValueAndRateFactors(final
LoanScheduleParams scheduleParams,
- final LoanProductRelatedDetail loanProductRelatedDetail, final
List<? extends LoanScheduleModelPeriod> expectedRepaymentPeriods,
+ public EMICalculationResult calculateEMIValueAndRateFactors(final
LoanApplicationTerms loanApplicationTerms,
+ final LoanScheduleParams scheduleParams, final List<? extends
LoanScheduleModelPeriod> expectedRepaymentPeriods,
final MathContext mc) {
+ final LoanProductRelatedDetail loanProductRelatedDetail =
loanApplicationTerms.toLoanProductRelatedDetail();
final BigDecimal nominalInterestRatePerPeriod =
calcNominalInterestRatePerPeriod(
loanProductRelatedDetail.getNominalInterestRatePerPeriod(),
mc);
- final BigDecimal outstandingBalance =
scheduleParams.getOutstandingBalanceAsPerRest().getAmount();
+ final Money outstandingBalance =
scheduleParams.getOutstandingBalanceAsPerRest();
final DaysInYearType daysInYearType =
DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
final DaysInMonthType daysInMonthType =
DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
final PeriodFrequencyType repaymentFrequency =
loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
@@ -69,7 +74,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
final List<BigDecimal> rateFactorList =
getRateFactorList(expectedRepaymentPeriods, nominalInterestRatePerPeriod,
daysInYearType,
daysInMonthType, repaymentFrequency, repaymentEvery, mc);
- return calculateEMI(rateFactorList, outstandingBalance, mc);
+ return calculateEMI(loanApplicationTerms, scheduleParams,
rateFactorList, outstandingBalance, mc);
}
/**
@@ -200,18 +205,98 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
* Calculate EMI parts and return an EMI calculation result object with
repayment installment rate factors
*
* @param rateFactorList
- * @param principal
+ * @param outstandingBalanceForRest
* @param mc
* @return
*/
- EMICalculationResult calculateEMI(final List<BigDecimal> rateFactorList,
final BigDecimal principal, final MathContext mc) {
+ EMICalculationResult calculateEMI(final LoanApplicationTerms
loanApplicationTerms, final LoanScheduleParams loanScheduleParams,
+ final List<BigDecimal> rateFactorList, final Money
outstandingBalanceForRest, final MathContext mc) {
final BigDecimal rateFactorN =
MathUtil.stripTrailingZeros(calculateRateFactorN(rateFactorList, mc));
final BigDecimal fnResult =
MathUtil.stripTrailingZeros(calculateFnResult(rateFactorList, mc));
- final BigDecimal emiValue =
MathUtil.stripTrailingZeros(calculateEMIValue(rateFactorN, principal, fnResult,
mc));
+ final Money emiValue = Money.of(loanApplicationTerms.getCurrency(),
+ calculateEMIValue(rateFactorN,
outstandingBalanceForRest.getAmount(), fnResult, mc));
final List<BigDecimal> rateFactorMinus1List =
getRateFactorMinus1List(rateFactorList, mc);
- return new EMICalculationResult(emiValue, rateFactorMinus1List);
+ final Money adjustedEqualMonthlyInstallmentValue =
adjustEMIForMoreStreamlinedRepaymentSchedule(loanApplicationTerms,
+ loanScheduleParams, emiValue, rateFactorMinus1List, mc);
+
+ return new EMICalculationResult(adjustedEqualMonthlyInstallmentValue,
rateFactorMinus1List);
+ }
+
+ /**
+ * Due to rounding or unequal installments, the first calculated EMI might
not be the best one! Reiterate with
+ * adjusted EMI to get a better streamlined repayment schedule (less
difference between calculated EMI and last
+ * installment EMI).
+ *
+ * @param loanApplicationTerms
+ * @param loanScheduleParams
+ * @param equalMonthlyInstallmentValue
+ * @param rateFactorMinus1List
+ * @param mc
+ * @return
+ */
+ Money adjustEMIForMoreStreamlinedRepaymentSchedule(final
LoanApplicationTerms loanApplicationTerms,
+ final LoanScheduleParams loanScheduleParams, final Money
equalMonthlyInstallmentValue, List<BigDecimal> rateFactorMinus1List,
+ final MathContext mc) {
+ int numberOfUpcomingPeriods =
loanApplicationTerms.getNumberOfRepayments() -
loanScheduleParams.getPeriodNumber() + 1;
+ if (numberOfUpcomingPeriods < 2) {
+ return equalMonthlyInstallmentValue;
+ }
+
+ RepaymentScheduleModel repaymentScheduleModel =
generateRepaymentScheduleModel(loanApplicationTerms, loanScheduleParams,
+ equalMonthlyInstallmentValue, rateFactorMinus1List);
+ Money calculatedLastEMI =
repaymentScheduleModel.getScheduleList().get(repaymentScheduleModel.getScheduleList().size()
- 1).emi();
+ Money originalDifference =
calculatedLastEMI.minus(equalMonthlyInstallmentValue);
+ if (originalDifference.isZero()) {
+ return equalMonthlyInstallmentValue;
+ }
+ double lowerHalfOfUpcomingPeriods = Math.floor((double)
numberOfUpcomingPeriods / 2);
+ if (lowerHalfOfUpcomingPeriods == 0.0) {
+ return equalMonthlyInstallmentValue;
+ }
+ boolean shouldBeAdjusted = originalDifference.abs().multipliedBy(100)
+
.isGreaterThan(Money.of(equalMonthlyInstallmentValue.getCurrency(),
BigDecimal.valueOf(lowerHalfOfUpcomingPeriods)));
+ // Reiterate only when needed
+ if (shouldBeAdjusted) {
+ Money adjustment =
originalDifference.dividedBy(numberOfUpcomingPeriods, mc.getRoundingMode());
+
+ Money adjustedEqualMonthlyInstallmentValue =
equalMonthlyInstallmentValue.plus(adjustment);
+ RepaymentScheduleModel repaymentScheduleModelWithAdjustedEMI =
generateRepaymentScheduleModel(loanApplicationTerms,
+ loanScheduleParams, adjustedEqualMonthlyInstallmentValue,
rateFactorMinus1List);
+ Money calculatedLastEMIAfterAdjustment =
repaymentScheduleModelWithAdjustedEMI.getScheduleList()
+
.get(repaymentScheduleModelWithAdjustedEMI.getScheduleList().size() - 1).emi();
+ Money differenceAfterEMIAdjustment =
calculatedLastEMIAfterAdjustment.minus(adjustedEqualMonthlyInstallmentValue);
+ // Use the adjusted EMI only if it is better than the original one
+ return
differenceAfterEMIAdjustment.abs().isLessThan(originalDifference.abs()) ?
adjustedEqualMonthlyInstallmentValue
+ : equalMonthlyInstallmentValue;
+ } else {
+ return equalMonthlyInstallmentValue;
+ }
+ }
+
+ RepaymentScheduleModel generateRepaymentScheduleModel(LoanApplicationTerms
loanApplicationTerms, LoanScheduleParams loanScheduleParams,
+ Money equalMonthlyInstallmentValue, List<BigDecimal>
rateFactorMinus1List) {
+ RepaymentScheduleModel repaymentScheduleModel = new
RepaymentScheduleModel();
+ Money balanceOfLoan =
loanScheduleParams.getOutstandingBalanceAsPerRest();
+ for (int i = 0; i < loanApplicationTerms.getNumberOfRepayments(); i++)
{
+ final Money calculatedInterest =
balanceOfLoan.multipliedBy(rateFactorMinus1List.get(i));
+ // WE need to calculate EMI differently for last installment
(decided by number of repayments or when
+ // schedule got shorter then planned)
+ if
(balanceOfLoan.isLessThan(equalMonthlyInstallmentValue.minus(calculatedInterest))
+ || i == loanApplicationTerms.getNumberOfRepayments() - 1) {
+ equalMonthlyInstallmentValue =
balanceOfLoan.plus(calculatedInterest);
+ }
+ final Money calculatedPrincipal =
equalMonthlyInstallmentValue.minus(calculatedInterest);
+ repaymentScheduleModel.addRepaymentPeriodModel(
+ new RepaymentPeriodModel(balanceOfLoan,
equalMonthlyInstallmentValue, calculatedInterest, calculatedPrincipal));
+ balanceOfLoan = balanceOfLoan.minus(calculatedPrincipal);
+ // We can stop processing if there is no outstanding principal
+ if (balanceOfLoan.isZero()) {
+ break;
+ }
+ }
+ return repaymentScheduleModel;
}
/**
@@ -252,14 +337,14 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
* Calculate the EMI (Equal Monthly Installment) value
*
* @param rateFactorN
- * @param principal
+ * @param outstandingBalanceForRest
* @param fnResult
* @param mc
* @return
*/
- BigDecimal calculateEMIValue(final BigDecimal rateFactorN, final
BigDecimal principal, final BigDecimal fnResult,
+ BigDecimal calculateEMIValue(final BigDecimal rateFactorN, final
BigDecimal outstandingBalanceForRest, final BigDecimal fnResult,
final MathContext mc) {
- return rateFactorN.multiply(principal, mc).divide(fnResult, mc);
+ return rateFactorN.multiply(outstandingBalanceForRest,
mc).divide(fnResult, mc);
}
/**
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/RepaymentPeriodModel.java
similarity index 58%
copy from
fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
copy to
fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentPeriodModel.java
index bf217b73e..de99d69c0 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/RepaymentPeriodModel.java
@@ -18,15 +18,7 @@
*/
package org.apache.fineract.portfolio.loanproduct.calc;
-import java.math.MathContext;
-import java.util.List;
-import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
-import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
-import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.apache.fineract.organisation.monetary.domain.Money;
-public interface EMICalculator {
-
- EMICalculationResult calculateEMIValueAndRateFactors(LoanScheduleParams
scheduleParams,
- LoanProductRelatedDetail loanProductRelatedDetail, List<? extends
LoanScheduleModelPeriod> expectedRepaymentPeriods,
- MathContext mc);
+public record RepaymentPeriodModel(Money balanceOfLoan, Money emi, Money
principal, Money interest) {
}
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/RepaymentScheduleModel.java
similarity index 60%
copy from
fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java
copy to
fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/RepaymentScheduleModel.java
index bf217b73e..14d224921 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/RepaymentScheduleModel.java
@@ -18,15 +18,16 @@
*/
package org.apache.fineract.portfolio.loanproduct.calc;
-import java.math.MathContext;
+import java.util.ArrayList;
import java.util.List;
-import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
-import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
-import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import lombok.Getter;
-public interface EMICalculator {
+@Getter
+public class RepaymentScheduleModel {
- EMICalculationResult calculateEMIValueAndRateFactors(LoanScheduleParams
scheduleParams,
- LoanProductRelatedDetail loanProductRelatedDetail, List<? extends
LoanScheduleModelPeriod> expectedRepaymentPeriods,
- MathContext mc);
+ List<RepaymentPeriodModel> scheduleList = new ArrayList<>();
+
+ public void addRepaymentPeriodModel(final RepaymentPeriodModel
repaymentPeriodModel) {
+ scheduleList.add(repaymentPeriodModel);
+ }
}
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 94cb18a26..0f7dcc663 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
@@ -35,6 +35,7 @@ import
org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelDisbursementPeriod;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.PreGeneratedLoanSchedulePeriod;
@@ -55,6 +56,7 @@ class ProgressiveEMICalculatorTest {
private static MockedStatic<MoneyHelper> moneyHelper =
Mockito.mockStatic(MoneyHelper.class);
private static LoanScheduleParams scheduleParams =
Mockito.mock(LoanScheduleParams.class);
+ private static LoanApplicationTerms loanApplicationTerms =
Mockito.mock(LoanApplicationTerms.class);
private static LoanProductRelatedDetail loanProductRelatedDetail =
Mockito.mock(LoanProductRelatedDetail.class);
private static final MonetaryCurrency monetaryCurrency = MonetaryCurrency
@@ -77,6 +79,8 @@ class ProgressiveEMICalculatorTest {
// When
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new
MathContext(12, RoundingMode.HALF_EVEN));
+
Mockito.when(loanApplicationTerms.toLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
+
Mockito.when(loanApplicationTerms.getCurrency()).thenReturn(monetaryCurrency);
}
private BigDecimal getRateFactorsByMonth(final DaysInYearType
daysInYearType, final DaysInMonthType daysInMonthType,
@@ -166,11 +170,11 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
- final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail,
+ final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams,
expectedRepaymentPeriods, mc);
// 17.13
- Assertions.assertEquals(BigDecimal.valueOf(17.1280789505),
result.getEqualMonthlyInstallmentValue());
+ Assertions.assertEquals(BigDecimal.valueOf(17.13),
result.getEqualMonthlyInstallmentValue().getAmount());
Assertions.assertEquals(BigDecimal.valueOf(0.00803137158),
result.getNextRepaymentPeriodRateFactorMinus1());
Assertions.assertEquals(BigDecimal.valueOf(0.00751321858),
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -208,11 +212,11 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
- final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail,
+ final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams,
expectedRepaymentPeriods, mc);
// 1713.12
- Assertions.assertEquals(BigDecimal.valueOf(1713.12436983),
result.getEqualMonthlyInstallmentValue());
+ Assertions.assertEquals(BigDecimal.valueOf(1713.12),
result.getEqualMonthlyInstallmentValue().getAmount());
Assertions.assertEquals(BigDecimal.valueOf(0.00804485776),
result.getNextRepaymentPeriodRateFactorMinus1());
Assertions.assertEquals(BigDecimal.valueOf(0.00803137158),
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -247,11 +251,11 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
- final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail,
+ final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams,
expectedRepaymentPeriods, mc);
// 17.13
- Assertions.assertEquals(BigDecimal.valueOf(17.1293512777),
result.getEqualMonthlyInstallmentValue());
+ Assertions.assertEquals(BigDecimal.valueOf(17.13),
result.getEqualMonthlyInstallmentValue().getAmount());
Assertions.assertEquals(BigDecimal.valueOf(0.00805337534),
result.getNextRepaymentPeriodRateFactorMinus1());
Assertions.assertEquals(BigDecimal.valueOf(0.00753380274),
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -290,11 +294,11 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
- final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail,
+ final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams,
expectedRepaymentPeriods, mc);
// 17.13
- Assertions.assertEquals(BigDecimal.valueOf(17.1306301273),
result.getEqualMonthlyInstallmentValue());
+ Assertions.assertEquals(BigDecimal.valueOf(17.13),
result.getEqualMonthlyInstallmentValue().getAmount());
Assertions.assertEquals(BigDecimal.valueOf(0.00790183333),
result.getNextRepaymentPeriodRateFactorMinus1());
Assertions.assertEquals(BigDecimal.valueOf(0.00790183333),
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -333,11 +337,11 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
- final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail,
+ final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams,
expectedRepaymentPeriods, mc);
// 16.77
- Assertions.assertEquals(BigDecimal.valueOf(16.7731989919),
result.getEqualMonthlyInstallmentValue());
+ Assertions.assertEquals(BigDecimal.valueOf(16.77),
result.getEqualMonthlyInstallmentValue().getAmount());
final BigDecimal fixValue = new BigDecimal("0.0018235");
Assertions.assertEquals(fixValue,
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -377,11 +381,11 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(2);
- final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail,
+ final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams,
expectedRepaymentPeriods, mc);
// 16.88
- Assertions.assertEquals(new BigDecimal("16.8824319133"),
result.getEqualMonthlyInstallmentValue());
+ Assertions.assertEquals(new BigDecimal("16.88"),
result.getEqualMonthlyInstallmentValue().getAmount());
final BigDecimal fixValue = new BigDecimal("0.00368752222");
Assertions.assertEquals(fixValue,
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -421,11 +425,11 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.DAYS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15);
- final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail,
+ final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams,
expectedRepaymentPeriods, mc);
// 16.90
- Assertions.assertEquals(new BigDecimal("16.8978941103"),
result.getEqualMonthlyInstallmentValue());
+ Assertions.assertEquals(new BigDecimal("16.90"),
result.getEqualMonthlyInstallmentValue().getAmount());
final BigDecimal fixValue = new BigDecimal("0.00395091667");
Assertions.assertEquals(fixValue,
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -470,11 +474,11 @@ class ProgressiveEMICalculatorTest {
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(5,
LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(6,
LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
- final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail,
+ final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams,
expectedRepaymentPeriods, mc);
- // 17.13
- Assertions.assertEquals(new BigDecimal("15.3574482569"),
result.getEqualMonthlyInstallmentValue());
+ // 15.36
+ Assertions.assertEquals(new BigDecimal("15.36"),
result.getEqualMonthlyInstallmentValue().getAmount());
Assertions.assertEquals(BigDecimal.valueOf(0.00790183333),
result.getNextRepaymentPeriodRateFactorMinus1());
Assertions.assertEquals(BigDecimal.valueOf(0.00790183333),
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -510,11 +514,11 @@ class ProgressiveEMICalculatorTest {
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(3,
LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)));
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(4,
LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
- final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail,
+ final EMICalculationResult result =
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams,
expectedRepaymentPeriods, mc);
- // 17.13
- Assertions.assertEquals(new BigDecimal("250"),
result.getEqualMonthlyInstallmentValue());
+ // 250.00
+ Assertions.assertEquals(new BigDecimal("250.00"),
result.getEqualMonthlyInstallmentValue().getAmount());
Assertions.assertEquals(BigDecimal.ZERO,
result.getNextRepaymentPeriodRateFactorMinus1());
Assertions.assertEquals(BigDecimal.ZERO,
result.getNextRepaymentPeriodRateFactorMinus1());
@@ -548,7 +552,7 @@ class ProgressiveEMICalculatorTest {
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1,
LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
try {
- emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail, expectedRepaymentPeriods, mc);
+
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams, expectedRepaymentPeriods, mc);
Assertions.fail();
} catch (Exception e) {
Assertions.assertInstanceOf(UnsupportedOperationException.class,
e);
@@ -574,7 +578,7 @@ class ProgressiveEMICalculatorTest {
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1,
LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
try {
- emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail, expectedRepaymentPeriods, mc);
+
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams, expectedRepaymentPeriods, mc);
Assertions.fail();
} catch (Exception e) {
Assertions.assertInstanceOf(UnsupportedOperationException.class,
e);
@@ -600,7 +604,7 @@ class ProgressiveEMICalculatorTest {
expectedRepaymentPeriods.add(new PreGeneratedLoanSchedulePeriod(1,
LocalDate.of(2024, 1, 1), LocalDate.of(2024, 1, 16)));
try {
- emiCalculator.calculateEMIValueAndRateFactors(scheduleParams,
loanProductRelatedDetail, expectedRepaymentPeriods, mc);
+
emiCalculator.calculateEMIValueAndRateFactors(loanApplicationTerms,
scheduleParams, expectedRepaymentPeriods, mc);
Assertions.fail();
} catch (Exception e) {
Assertions.assertInstanceOf(UnsupportedOperationException.class,
e);
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
index a85911696..5d08ad104 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
@@ -18,6 +18,8 @@
*/
package org.apache.fineract.integrationtests;
+import static
org.apache.fineract.integrationtests.BaseLoanIntegrationTest.InterestRateFrequencyType.YEARS;
+import static
org.apache.fineract.integrationtests.BaseLoanIntegrationTest.RepaymentFrequencyType.DAYS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -70,8 +72,9 @@ import
org.apache.fineract.integrationtests.common.accounting.Account;
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
-import
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
+import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.EarlyPaymentLoanRepaymentScheduleTransactionProcessor;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
@@ -81,11 +84,9 @@ import
org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-@ExtendWith(LoanTestLifecycleExtension.class)
public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends
BaseLoanIntegrationTest {
private static final Logger LOG =
LoggerFactory.getLogger(AdvancedPaymentAllocationLoanRepaymentScheduleTest.class);
@@ -4410,6 +4411,196 @@ public class
AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
});
}
+ // UC138: Advanced payment allocation with Interest, EMI is correctly
calculated, no adjustment
+ // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+ // 1. Create a Loan product with Adv. Pment. Alloc. and with 5% Interest,
360/30, 1 repayment per month
+ // 2. Submit Loan and approve
+ // 3. Disburse
+ // 4. Validate Repayment Schedule
+ @Test
+ public void uc141() {
+ final String operationDate = "1 January 2024";
+ runAt(operationDate, () -> {
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+ PostLoanProductsRequest product =
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+
.interestRatePerPeriod(5.0).interestCalculationPeriodType(DAYS).interestRateFrequencyType(YEARS)
+
.daysInMonthType(DaysInMonthType.DAYS_30.getValue()).daysInYearType(DaysInYearType.DAYS_360.getValue())
+ .numberOfRepayments(5)//
+ .repaymentEvery(1)//
+ .repaymentFrequencyType(2L)//
+ .enableDownPayment(true)//
+ .allowPartialPeriodInterestCalcualtion(false)//
+ .enableAutoRepaymentForDownPayment(false)//
+ .multiDisburseLoan(false)//
+ .disallowExpectedDisbursements(null)//
+ .allowApprovedDisbursedAmountsOverApplied(null)//
+ .overAppliedCalculationType(null)//
+ .overAppliedNumber(null)//
+ .installmentAmountInMultiplesOf(null)//
+
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25))//
+ ;//
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(product);
+ PostLoansRequest applicationRequest = applyLoanRequest(clientId,
loanProductResponse.getResourceId(), operationDate, 100.0, 5);
+
+ applicationRequest =
applicationRequest.numberOfRepayments(5).loanTermFrequency(5).loanTermFrequencyType(2)
+
.interestRatePerPeriod(BigDecimal.valueOf(5)).interestCalculationPeriodType(DAYS)
+
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(1)
+ .repaymentFrequencyType(2);
+
+ PostLoansResponse loanResponse =
loanTransactionHelper.applyLoan(applicationRequest);
+
+ loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new
PostLoansLoanIdRequest()
+
.approvedLoanAmount(BigDecimal.valueOf(100)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
+
+ loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new
PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
+
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+ validateLoanSummaryBalances(loanDetails, 100.94, 0.0, 100.0, 0.0,
null);
+ validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 1, 1),
25.0, 0.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+ 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 2, 1),
14.88, 0.0, 14.88, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.31, 0.0,
+ 0.31, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 3, 1),
14.94, 0.0, 14.94, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0,
+ 0.25, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 4, 1),
15.00, 0.0, 15.00, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.19, 0.0,
+ 0.19, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 5, 1),
15.06, 0.0, 15.06, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.13, 0.0,
+ 0.13, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 6, 1),
15.12, 0.0, 15.12, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.06, 0.0,
+ 0.06, 0.0, 0.0);
+ assertTrue(loanDetails.getStatus().getActive());
+ assertEquals(loanDetails.getNumberOfRepayments(), 5);
+ });
+ }
+
+ // UC139: Advanced payment allocation with Interest, originally calculated
EMI, need adjustment
+ // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+ // 1. Create a Loan product with Adv. Pment. Alloc. and with 12.3%
Interest, 360/30, 1 repayment per month
+ // 2. Submit Loan and approve
+ // 3. Disburse
+ // 4. Validate Repayment Schedule
+ @Test
+ public void uc142() {
+ final String operationDate = "1 January 2024";
+ runAt(operationDate, () -> {
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+ PostLoanProductsRequest product =
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
+
.daysInMonthType(DaysInMonthType.DAYS_30.getValue()).daysInYearType(DaysInYearType.DAYS_360.getValue())
+ .numberOfRepayments(5)//
+ .repaymentEvery(1)//
+ .repaymentFrequencyType(2L)//
+ .enableDownPayment(true)//
+ .allowPartialPeriodInterestCalcualtion(false)//
+ .enableAutoRepaymentForDownPayment(false)//
+ .multiDisburseLoan(false)//
+ .disallowExpectedDisbursements(null)//
+ .allowApprovedDisbursedAmountsOverApplied(null)//
+ .overAppliedCalculationType(null)//
+ .overAppliedNumber(null)//
+ .installmentAmountInMultiplesOf(null)//
+
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25))//
+ ;//
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(product);
+ PostLoansRequest applicationRequest = applyLoanRequest(clientId,
loanProductResponse.getResourceId(), operationDate, 100.0, 5);
+
+ applicationRequest =
applicationRequest.numberOfRepayments(5).loanTermFrequency(5).loanTermFrequencyType(2)
+
.interestRatePerPeriod(BigDecimal.valueOf(12.3)).interestCalculationPeriodType(DAYS)
+
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(1)
+ .repaymentFrequencyType(2);
+
+ PostLoansResponse loanResponse =
loanTransactionHelper.applyLoan(applicationRequest);
+
+ loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new
PostLoansLoanIdRequest()
+
.approvedLoanAmount(BigDecimal.valueOf(100)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
+
+ loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new
PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
+
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+ validateLoanSummaryBalances(loanDetails, 102.33, 0.0, 100.0, 0.0,
null);
+ validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 1, 1),
25.0, 0.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+ 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 2, 1),
14.70, 0.0, 14.70, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.77, 0.0,
+ 0.77, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 3, 1),
14.85, 0.0, 14.85, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.62, 0.0,
+ 0.62, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 4, 1),
15.00, 0.0, 15.00, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.47, 0.0,
+ 0.47, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 5, 1),
15.16, 0.0, 15.16, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.31, 0.0,
+ 0.31, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 6, 1),
15.29, 0.0, 15.29, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.16, 0.0,
+ 0.16, 0.0, 0.0);
+ assertTrue(loanDetails.getStatus().getActive());
+ assertEquals(loanDetails.getNumberOfRepayments(), 5);
+ });
+ }
+
+ // UC14-: Advanced payment allocation with Interest, originally calculated
EMI, need adjustment, adjusted EMI is not
+ // better than original, use original EMI
+ // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+ // 1. Create a Loan product with Adv. Pment. Alloc. and with 5% Interest,
360/30, 1 repayment per month
+ // 2. Submit Loan and approve
+ // 3. Disburse
+ // 4. Validate Repayment Schedule
+ @Test
+ public void uc143() {
+ final String operationDate = "1 January 2024";
+ runAt(operationDate, () -> {
+ Long clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+ PostLoanProductsRequest product =
createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
+
.interestRatePerPeriod(12.3).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
+
.daysInMonthType(DaysInMonthType.DAYS_30.getValue()).daysInYearType(DaysInYearType.DAYS_360.getValue())
+ .numberOfRepayments(5)//
+ .repaymentEvery(1)//
+ .repaymentFrequencyType(2L)//
+ .enableDownPayment(true)//
+ .allowPartialPeriodInterestCalcualtion(false)//
+ .enableAutoRepaymentForDownPayment(false)//
+ .multiDisburseLoan(false)//
+ .disallowExpectedDisbursements(null)//
+ .allowApprovedDisbursedAmountsOverApplied(null)//
+ .overAppliedCalculationType(null)//
+ .overAppliedNumber(null)//
+ .installmentAmountInMultiplesOf(null)//
+
.disbursedAmountPercentageForDownPayment(BigDecimal.valueOf(25))//
+ ;//
+ PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(product);
+ PostLoansRequest applicationRequest = applyLoanRequest(clientId,
loanProductResponse.getResourceId(), operationDate, 100.0, 5);
+
+ applicationRequest =
applicationRequest.numberOfRepayments(5).loanTermFrequency(5).loanTermFrequencyType(2)
+
.interestRatePerPeriod(BigDecimal.valueOf(12.3)).interestCalculationPeriodType(DAYS)
+
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).repaymentEvery(1)
+ .repaymentFrequencyType(2);
+
+ PostLoansResponse loanResponse =
loanTransactionHelper.applyLoan(applicationRequest);
+
+ loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new
PostLoansLoanIdRequest()
+
.approvedLoanAmount(BigDecimal.valueOf(100)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
+
+ loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new
PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
+
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(100.0)).locale("en"));
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+ validateLoanSummaryBalances(loanDetails, 102.33, 0.0, 100.0, 0.0,
null);
+ validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2024, 1, 1),
25.0, 0.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+ 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2024, 2, 1),
14.70, 0.0, 14.70, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.77, 0.0,
+ 0.77, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2024, 3, 1),
14.85, 0.0, 14.85, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.62, 0.0,
+ 0.62, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2024, 4, 1),
15.00, 0.0, 15.00, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.47, 0.0,
+ 0.47, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 5, 1),
15.16, 0.0, 15.16, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.31, 0.0,
+ 0.31, 0.0, 0.0);
+ validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2024, 6, 1),
15.29, 0.0, 15.29, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.16, 0.0,
+ 0.16, 0.0, 0.0);
+ assertTrue(loanDetails.getStatus().getActive());
+ assertEquals(loanDetails.getNumberOfRepayments(), 5);
+ });
+ }
+
private Long
applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long
clientId, Long loanProductId,
Integer numberOfRepayments, String loanDisbursementDate, double
amount) {
LOG.info("------------------------------APPLY AND APPROVE LOAN
---------------------------------------");