FINERACT-1981: Adjust EMI if needed
This commit is contained in:
+2
-2
@@ -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
|
||||
|
||||
+5
-6
@@ -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;
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+3
-4
@@ -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);
|
||||
}
|
||||
|
||||
+100
-15
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanproduct.calc;
|
||||
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
|
||||
public record RepaymentPeriodModel(Money balanceOfLoan, Money emi, Money principal, Money interest) {
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanproduct.calc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class RepaymentScheduleModel {
|
||||
|
||||
List<RepaymentPeriodModel> scheduleList = new ArrayList<>();
|
||||
|
||||
public void addRepaymentPeriodModel(final RepaymentPeriodModel repaymentPeriodModel) {
|
||||
scheduleList.add(repaymentPeriodModel);
|
||||
}
|
||||
}
|
||||
+27
-23
@@ -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);
|
||||
|
||||
+194
-3
@@ -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 ---------------------------------------");
|
||||
|
||||
Reference in New Issue
Block a user