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 0b9687d2a FINERACT-2081: Fix end of month and partial interest
calculation for 360/30
0b9687d2a is described below
commit 0b9687d2aab6fce1097a8eeb86319879b2f3471c
Author: Adam Saghy <[email protected]>
AuthorDate: Mon Jan 27 20:04:14 2025 +0100
FINERACT-2081: Fix end of month and partial interest calculation for 360/30
---
.../loanschedule/domain/LoanApplicationTerms.java | 8 +-
.../data/LoanProductRelatedDetailMinimumData.java | 34 +-
...oductMinimumRepaymentScheduleRelatedDetail.java | 5 -
.../domain/LoanProductRelatedDetail.java | 2 -
...dvancedPaymentScheduleTransactionProcessor.java | 33 +-
.../data/ProgressiveLoanInterestScheduleModel.java | 23 +-
.../domain/ProgressiveLoanScheduleGenerator.java | 8 +-
.../portfolio/loanproduct/calc/EMICalculator.java | 6 +-
.../loanproduct/calc/ProgressiveEMICalculator.java | 152 ++++++-
.../calc/ProgressiveEMICalculatorTest.java | 500 ++++++++++++++++++---
10 files changed, 615 insertions(+), 156 deletions(-)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index aff822083..c8769c39f 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -1549,10 +1549,10 @@ public final class LoanApplicationTerms {
public LoanProductMinimumRepaymentScheduleRelatedDetail
toLoanProductRelatedDetailMinimumData() {
final CurrencyData currency = new
CurrencyData(this.currency.getCode(), this.currency.getDecimalPlaces(),
this.currency.getInMultiplesOf());
- return new LoanProductRelatedDetailMinimumData(currency, principal,
inArrearsTolerance, interestRatePerPeriod,
- annualNominalInterestRate, interestChargingGrace,
interestPaymentGrace, principalGrace,
- recurringMoratoriumOnPrincipalPeriods, interestMethod,
interestCalculationPeriodMethod, daysInYearType, daysInMonthType,
- amortizationMethod, repaymentPeriodFrequencyType,
repaymentEvery, numberOfRepayments);
+ return new LoanProductRelatedDetailMinimumData(currency,
interestRatePerPeriod, annualNominalInterestRate, interestChargingGrace,
+ interestPaymentGrace, principalGrace,
recurringMoratoriumOnPrincipalPeriods, interestMethod,
+ interestCalculationPeriodMethod, daysInYearType,
daysInMonthType, amortizationMethod, repaymentPeriodFrequencyType,
+ repaymentEvery, numberOfRepayments);
}
public Integer getLoanTermFrequency() {
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductRelatedDetailMinimumData.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductRelatedDetailMinimumData.java
index 175d7d605..5e2b80115 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductRelatedDetailMinimumData.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductRelatedDetailMinimumData.java
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanproduct.data;
import java.math.BigDecimal;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
-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;
@@ -32,39 +31,28 @@ import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaym
public class LoanProductRelatedDetailMinimumData implements
LoanProductMinimumRepaymentScheduleRelatedDetail {
private final CurrencyData currency;
-
- private final Money principal;
- private final Money inArrearsTolerance;
-
private final BigDecimal interestRatePerPeriod;
private final BigDecimal annualNominalInterestRate;
-
private final Integer interestChargingGrace;
private final Integer interestPaymentGrace;
private final Integer principalGrace;
private final Integer recurringMoratoriumOnPrincipalPeriods;
-
private final InterestMethod interestMethod;
private final InterestCalculationPeriodMethod
interestCalculationPeriodMethod;
-
private final DaysInYearType daysInYearType;
private final DaysInMonthType daysInMonthType;
-
private final AmortizationMethod amortizationMethod;
-
private final PeriodFrequencyType repaymentPeriodFrequencyType;
private final Integer repaymentEvery;
private final Integer numberOfRepayments;
- public LoanProductRelatedDetailMinimumData(CurrencyData currency, Money
principal, Money inArrearsTolerance,
- BigDecimal interestRatePerPeriod, BigDecimal
annualNominalInterestRate, Integer interestChargingGrace,
- Integer interestPaymentGrace, Integer principalGrace, Integer
recurringMoratoriumOnPrincipalPeriods,
- InterestMethod interestMethod, InterestCalculationPeriodMethod
interestCalculationPeriodMethod, DaysInYearType daysInYearType,
- DaysInMonthType daysInMonthType, AmortizationMethod
amortizationMethod, PeriodFrequencyType repaymentPeriodFrequencyType,
- Integer repaymentEvery, Integer numberOfRepayments) {
+ public LoanProductRelatedDetailMinimumData(CurrencyData currency,
BigDecimal interestRatePerPeriod,
+ BigDecimal annualNominalInterestRate, Integer
interestChargingGrace, Integer interestPaymentGrace, Integer principalGrace,
+ Integer recurringMoratoriumOnPrincipalPeriods, InterestMethod
interestMethod,
+ InterestCalculationPeriodMethod interestCalculationPeriodMethod,
DaysInYearType daysInYearType, DaysInMonthType daysInMonthType,
+ AmortizationMethod amortizationMethod, PeriodFrequencyType
repaymentPeriodFrequencyType, Integer repaymentEvery,
+ Integer numberOfRepayments) {
this.currency = currency;
- this.principal = principal;
- this.inArrearsTolerance = inArrearsTolerance;
this.interestRatePerPeriod = interestRatePerPeriod;
this.annualNominalInterestRate = annualNominalInterestRate;
this.interestChargingGrace =
defaultToNullIfZero(interestChargingGrace);
@@ -94,11 +82,6 @@ public class LoanProductRelatedDetailMinimumData implements
LoanProductMinimumRe
return currency;
}
- @Override
- public Money getPrincipal() {
- return principal;
- }
-
@Override
public Integer getGraceOnInterestCharged() {
return interestChargingGrace;
@@ -119,11 +102,6 @@ public class LoanProductRelatedDetailMinimumData
implements LoanProductMinimumRe
return recurringMoratoriumOnPrincipalPeriods;
}
- @Override
- public Money getInArrearsTolerance() {
- return inArrearsTolerance;
- }
-
@Override
public BigDecimal getNominalInterestRatePerPeriod() {
return interestRatePerPeriod;
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java
index 2e3f84089..0509405a5 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanproduct.domain;
import java.math.BigDecimal;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
-import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
/**
@@ -30,8 +29,6 @@ public interface
LoanProductMinimumRepaymentScheduleRelatedDetail {
CurrencyData getCurrencyData();
- Money getPrincipal();
-
Integer getGraceOnInterestCharged();
Integer getGraceOnInterestPayment();
@@ -40,8 +37,6 @@ public interface
LoanProductMinimumRepaymentScheduleRelatedDetail {
Integer getRecurringMoratoriumOnPrincipalPeriods();
- Money getInArrearsTolerance();
-
BigDecimal getNominalInterestRatePerPeriod();
PeriodFrequencyType getInterestPeriodFrequencyType();
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
index 5dc7ecafd..52c14d046 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java
@@ -263,12 +263,10 @@ public class LoanProductRelatedDetail implements
LoanProductMinimumRepaymentSche
return currency.toData();
}
- @Override
public Money getPrincipal() {
return Money.of(getCurrencyData(), this.principal);
}
- @Override
public Money getInArrearsTolerance() {
return Money.of(getCurrencyData(), this.inArrearsTolerance);
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index ee5dfc072..49b86a365 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -65,7 +65,6 @@ import
org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
-import
org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper;
import
org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
@@ -76,6 +75,7 @@ import
org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRul
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
@@ -183,10 +183,8 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
final Loan loan = loanTransactions.get(0).getLoan();
- LoanTermVariationsDataWrapper loanTermVariations = Optional
-
.ofNullable(loan.getActiveLoanTermVariations()).map(loanTermVariationsSet ->
loanTermVariationsSet.stream()
-
.map(LoanTermVariations::toData).collect(Collectors.toCollection(ArrayList::new)))
- .map(LoanTermVariationsDataWrapper::new).orElse(null);
+ List<LoanTermVariationsData> loanTermVariations =
loan.getActiveLoanTermVariations().stream().map(LoanTermVariations::toData)
+ .collect(Collectors.toCollection(ArrayList::new));
final Integer installmentAmountInMultiplesOf =
loan.getLoanProduct().getInstallmentAmountInMultiplesOf();
final LoanProductRelatedDetail loanProductRelatedDetail =
loan.getLoanRepaymentScheduleDetail();
ProgressiveLoanInterestScheduleModel scheduleModel =
emiCalculator.generateInstallmentInterestScheduleModel(installments,
@@ -235,8 +233,9 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
public ChangedTransactionDetail reprocessLoanTransactions(LocalDate
disbursementDate, List<LoanTransaction> loanTransactions,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment>
installments, Set<LoanCharge> charges) {
LocalDate currentDate = DateUtils.getBusinessLocalDate();
- return reprocessProgressiveLoanTransactions(disbursementDate,
currentDate, loanTransactions, currency, installments, charges)
- .getLeft();
+ Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel>
result = reprocessProgressiveLoanTransactions(disbursementDate,
+ currentDate, loanTransactions, currency, installments,
charges);
+ return result.getLeft();
}
@NotNull
@@ -823,16 +822,18 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
}
@NotNull
- private List<ChangeOperation> createSortedChangeList(final
LoanTermVariationsDataWrapper loanTermVariations,
+ private List<ChangeOperation> createSortedChangeList(final
List<LoanTermVariationsData> loanTermVariations,
final List<LoanTransaction> loanTransactions, final
Set<LoanCharge> charges) {
- final List<ChangeOperation> changeOperations = new ArrayList<>();
- if (loanTermVariations != null) {
- if (!loanTermVariations.getInterestPauseVariations().isEmpty()) {
-
changeOperations.addAll(loanTermVariations.getInterestPauseVariations().stream().map(ChangeOperation::new).toList());
- }
- if
(!loanTermVariations.getInterestRateFromInstallment().isEmpty()) {
-
changeOperations.addAll(loanTermVariations.getInterestRateFromInstallment().stream().map(ChangeOperation::new).toList());
- }
+ List<ChangeOperation> changeOperations = new ArrayList<>();
+ Map<LoanTermVariationType, List<LoanTermVariationsData>>
loanTermVariationsMap = loanTermVariations.stream()
+ .collect(Collectors.groupingBy(ltvd ->
LoanTermVariationType.fromInt(ltvd.getTermType().getId().intValue())));
+ if
(loanTermVariationsMap.get(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT)
!= null) {
+
changeOperations.addAll(loanTermVariationsMap.get(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT).stream()
+ .map(ChangeOperation::new).toList());
+ }
+ if (loanTermVariationsMap.get(LoanTermVariationType.INTEREST_PAUSE) !=
null) {
+ changeOperations
+
.addAll(loanTermVariationsMap.get(LoanTermVariationType.INTEREST_PAUSE).stream().map(ChangeOperation::new).toList());
}
if (charges != null) {
changeOperations.addAll(charges.stream().map(ChangeOperation::new).toList());
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
index 513cc6ff6..528d85725 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java
@@ -27,18 +27,22 @@ import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.Money;
-import
org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
@Data
@@ -48,18 +52,18 @@ public class ProgressiveLoanInterestScheduleModel {
private final List<RepaymentPeriod> repaymentPeriods;
private final TreeSet<InterestRate> interestRates;
private final LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail;
- private final LoanTermVariationsDataWrapper loanTermVariations;
+ private final Map<LoanTermVariationType, List<LoanTermVariationsData>>
loanTermVariations;
private final Integer installmentAmountInMultiplesOf;
private final MathContext mc;
private final Money zero;
public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod>
repaymentPeriods,
final LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail,
- final LoanTermVariationsDataWrapper loanTermVariations, final
Integer installmentAmountInMultiplesOf, final MathContext mc) {
+ final List<LoanTermVariationsData> loanTermVariations, final
Integer installmentAmountInMultiplesOf, final MathContext mc) {
this.repaymentPeriods = repaymentPeriods;
this.interestRates = new TreeSet<>(Collections.reverseOrder());
this.loanProductRelatedDetail = loanProductRelatedDetail;
- this.loanTermVariations = loanTermVariations;
+ this.loanTermVariations =
buildLoanTermVariationMap(loanTermVariations);
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.mc = mc;
this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
@@ -67,7 +71,8 @@ public class ProgressiveLoanInterestScheduleModel {
private ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod>
repaymentPeriods, final TreeSet<InterestRate> interestRates,
final LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail,
- final LoanTermVariationsDataWrapper loanTermVariations, final
Integer installmentAmountInMultiplesOf, final MathContext mc) {
+ final Map<LoanTermVariationType, List<LoanTermVariationsData>>
loanTermVariations, final Integer installmentAmountInMultiplesOf,
+ final MathContext mc) {
this.mc = mc;
this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods,
(previousPeriod, repaymentPeriod) -> new
RepaymentPeriod(previousPeriod, repaymentPeriod, mc));
@@ -343,4 +348,12 @@ public class ProgressiveLoanInterestScheduleModel {
: date.isAfter(previousInterestPeriod.getDueDate()) ?
previousInterestPeriod.getDueDate() : date;
}
+ private Map<LoanTermVariationType, List<LoanTermVariationsData>>
buildLoanTermVariationMap(
+ final List<LoanTermVariationsData> loanTermVariationsData) {
+ if (loanTermVariationsData == null) {
+ return new HashMap<>();
+ }
+ return loanTermVariationsData.stream()
+ .collect(Collectors.groupingBy(ltvd ->
LoanTermVariationType.fromInt(ltvd.getTermType().getId().intValue())));
+ }
}
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 18126360b..b71e340d7 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
@@ -37,6 +37,7 @@ import
org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
@@ -88,9 +89,12 @@ public class ProgressiveLoanScheduleGenerator implements
LoanScheduleGenerator {
// generate list of proposed schedule due dates
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= scheduledDateGenerator.generateRepaymentPeriods(mc,
periodStartDate, loanApplicationTerms, holidayDetailDTO);
+ List<LoanTermVariationsData> loanTermVariations =
loanApplicationTerms.getLoanTermVariations() != null
+ ?
loanApplicationTerms.getLoanTermVariations().getExceptionData()
+ : null;
final ProgressiveLoanInterestScheduleModel interestScheduleModel =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods,
loanApplicationTerms.toLoanProductRelatedDetailMinimumData(),
- loanApplicationTerms.getLoanTermVariations(),
loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
+ expectedRepaymentPeriods,
loanApplicationTerms.toLoanProductRelatedDetailMinimumData(),
loanTermVariations,
+ loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
final List<LoanScheduleModelPeriod> periods = new
ArrayList<>(expectedRepaymentPeriods.size());
prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms);
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 f9b206a69..900950ca6 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
@@ -25,7 +25,7 @@ import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.apache.fineract.organisation.monetary.domain.Money;
-import
org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.OutstandingDetails;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDetails;
@@ -39,13 +39,13 @@ public interface EMICalculator {
@NotNull
ProgressiveLoanInterestScheduleModel
generatePeriodInterestScheduleModel(@NotNull
List<LoanScheduleModelRepaymentPeriod> periods,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail,
- LoanTermVariationsDataWrapper loanTermVariations, Integer
installmentAmountInMultiplesOf, MathContext mc);
+ List<LoanTermVariationsData> loanTermVariations, Integer
installmentAmountInMultiplesOf, MathContext mc);
@NotNull
ProgressiveLoanInterestScheduleModel
generateInstallmentInterestScheduleModel(
@NotNull List<LoanRepaymentScheduleInstallment> installments,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail,
- LoanTermVariationsDataWrapper loanTermVariations, Integer
installmentAmountInMultiplesOf, MathContext mc);
+ List<LoanTermVariationsData> loanTermVariations, Integer
installmentAmountInMultiplesOf, MathContext mc);
Optional<RepaymentPeriod>
findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel,
LocalDate dueDate);
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 fa349cbf0..5d09c4368 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
@@ -23,6 +23,8 @@ import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate;
import java.time.Year;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
@@ -35,8 +37,9 @@ 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.data.LoanTermVariationsDataWrapper;
+import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.EmiAdjustment;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.InterestPeriod;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.OutstandingDetails;
@@ -58,7 +61,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
@NotNull
public ProgressiveLoanInterestScheduleModel
generatePeriodInterestScheduleModel(@NotNull
List<LoanScheduleModelRepaymentPeriod> periods,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail,
- LoanTermVariationsDataWrapper loanTermVariations, final Integer
installmentAmountInMultiplesOf, final MathContext mc) {
+ List<LoanTermVariationsData> loanTermVariations, final Integer
installmentAmountInMultiplesOf, final MathContext mc) {
return generateInterestScheduleModel(periods,
LoanScheduleModelRepaymentPeriod::periodFromDate,
LoanScheduleModelRepaymentPeriod::periodDueDate,
loanProductRelatedDetail, loanTermVariations,
installmentAmountInMultiplesOf, mc);
@@ -69,7 +72,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
public ProgressiveLoanInterestScheduleModel
generateInstallmentInterestScheduleModel(
@NotNull List<LoanRepaymentScheduleInstallment> installments,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail,
- LoanTermVariationsDataWrapper loanTermVariations, final Integer
installmentAmountInMultiplesOf, final MathContext mc) {
+ List<LoanTermVariationsData> loanTermVariations, final Integer
installmentAmountInMultiplesOf, final MathContext mc) {
installments = installments.stream().filter(installment ->
!installment.isDownPayment() && !installment.isAdditional()).toList();
return generateInterestScheduleModel(installments,
LoanRepaymentScheduleInstallment::getFromDate,
LoanRepaymentScheduleInstallment::getDueDate,
loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf,
@@ -79,7 +82,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
@NotNull
private <T> ProgressiveLoanInterestScheduleModel
generateInterestScheduleModel(@NotNull List<T> periods, Function<T, LocalDate>
from,
Function<T, LocalDate> to, @NotNull
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
- LoanTermVariationsDataWrapper loanTermVariations, final Integer
installmentAmountInMultiplesOf, final MathContext mc) {
+ List<LoanTermVariationsData> loanTermVariations, final Integer
installmentAmountInMultiplesOf, final MathContext mc) {
final Money zero =
Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>();
List<RepaymentPeriod> repaymentPeriods = periods.stream().map(e -> {
@@ -314,8 +317,8 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
}
calculateOutstandingBalance(scheduleModel);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
- if (onlyOnActualModelShouldApply
- && (scheduleModel.loanTermVariations() == null ||
scheduleModel.loanTermVariations().getDueDateVariation().isEmpty())) {
+ if (onlyOnActualModelShouldApply &&
(scheduleModel.loanTermVariations() == null
+ ||
scheduleModel.loanTermVariations().get(LoanTermVariationType.DUE_DATE) ==
null)) {
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel,
relatedRepaymentPeriods);
}
}
@@ -420,18 +423,124 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
final ProgressiveLoanInterestScheduleModel scheduleModel) {
repaymentPeriod.getInterestPeriods().forEach(interestPeriod -> {
interestPeriod.setRateFactor(calculateRateFactorPerPeriod(scheduleModel,
repaymentPeriod, interestPeriod.getFromDate(),
- interestPeriod.getDueDate(), false));
-
interestPeriod.setRateFactorTillPeriodDueDate(calculateRateFactorPerPeriod(scheduleModel,
repaymentPeriod,
- interestPeriod.getFromDate(),
repaymentPeriod.getDueDate(), true));
+ interestPeriod.getDueDate()));
+
interestPeriod.setRateFactorTillPeriodDueDate(calculateRateFactorPerPeriodForInterest(scheduleModel,
repaymentPeriod,
+ interestPeriod.getFromDate(),
repaymentPeriod.getDueDate()));
});
}
+ BigDecimal calculateRateFactorPerPeriodForInterest(final
ProgressiveLoanInterestScheduleModel scheduleModel,
+ final RepaymentPeriod repaymentPeriod, final LocalDate
interestPeriodFromDate, final LocalDate interestPeriodDueDate) {
+ final MathContext mc = scheduleModel.mc();
+ final LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
+ final BigDecimal interestRate =
calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate),
+ scheduleModel.mc());
+ final DaysInYearType daysInYearType =
DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
+ final DaysInMonthType daysInMonthType =
DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
+ final PeriodFrequencyType repaymentFrequency =
loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
+
+ final BigDecimal daysInYear =
BigDecimal.valueOf(daysInYearType.getNumberOfDays(interestPeriodFromDate));
+ final BigDecimal actualDaysInPeriod = BigDecimal
+ .valueOf(DateUtils.getDifferenceInDays(interestPeriodFromDate,
interestPeriodDueDate));
+ final BigDecimal calculatedDaysInPeriod = BigDecimal
+
.valueOf(DateUtils.getDifferenceInDays(repaymentPeriod.getFromDate(),
repaymentPeriod.getDueDate()));
+ final int numberOfYearsDifferenceInPeriod =
interestPeriodDueDate.getYear() - interestPeriodFromDate.getYear();
+ final boolean partialPeriodCalculationNeeded = daysInYearType ==
DaysInYearType.ACTUAL && numberOfYearsDifferenceInPeriod > 0;
+
+ // TODO check:
loanApplicationTerms.calculatePeriodsBetweenDates(startDate, endDate); //
calculate period data
+ // TODO review: (repayment frequency: days, weeks, years; validation
day is month fix 30)
+ // TODO refactor this logic to represent in interest period
+ if (partialPeriodCalculationNeeded) {
+ final BigDecimal cumulatedPeriodFractions =
calculatePeriodFractions(interestPeriodFromDate, interestPeriodDueDate, mc);
+ return rateFactorByRepaymentPartialPeriod(interestRate,
BigDecimal.ONE, cumulatedPeriodFractions, BigDecimal.ONE,
+ BigDecimal.ONE, mc);
+ }
+
+ if (daysInMonthType.equals(DaysInMonthType.ACTUAL)) {
+ return rateFactorByRepaymentPeriod(interestRate,
actualDaysInPeriod, BigDecimal.ONE, daysInYear, BigDecimal.ONE, BigDecimal.ONE,
+ mc);
+ } else if (daysInMonthType.isDaysInMonth_30()) {
+ BigDecimal periodRatio = switch (repaymentFrequency) {
+ case YEARS -> calculatePeriodRatio(scheduleModel,
repaymentPeriod, ChronoUnit.YEARS, mc);
+ case MONTHS -> calculatePeriodRatio(scheduleModel,
repaymentPeriod, ChronoUnit.MONTHS, mc);
+ case WEEKS -> calculatePeriodRatio(scheduleModel,
repaymentPeriod, ChronoUnit.WEEKS, mc);
+ case DAYS -> calculatePeriodRatio(scheduleModel,
repaymentPeriod, ChronoUnit.DAYS, mc);
+ default -> throw new
UnsupportedOperationException("Unsupported repayment frequency: " +
repaymentFrequency);
+ };
+
+ return
calculateRateFactorPerPeriodBasedOnRepaymentFrequency(interestRate,
repaymentFrequency, periodRatio,
+ BigDecimal.valueOf(30), daysInYear, actualDaysInPeriod,
calculatedDaysInPeriod, mc);
+ }
+ throw new UnsupportedOperationException(
+ "Unsupported combination: Days in year: " + daysInYearType +
", days in month: " + daysInMonthType);
+ }
+
+ private static BigDecimal
calculatePeriodRatio(ProgressiveLoanInterestScheduleModel scheduleModel,
RepaymentPeriod repaymentPeriod,
+ ChronoUnit chronoUnit, MathContext mc) {
+
+ LocalDate seedDate = calculateSeedDate(scheduleModel, repaymentPeriod);
+ int numberOfPeriodBetweenSeedDateAndActualRepaymentPeriod = switch
(chronoUnit) {
+ case DAYS, WEEKS, YEARS -> DateUtils.getExactDifference(seedDate,
repaymentPeriod.getFromDate(), chronoUnit);
+ case MONTHS -> {
+ int seedDateDay = seedDate.getDayOfMonth();
+ int targetDateDay =
repaymentPeriod.getFromDate().getDayOfMonth();
+ int targetDateLastDay = ((LocalDate)
TemporalAdjusters.lastDayOfMonth().adjustInto(repaymentPeriod.getFromDate()))
+ .getDayOfMonth();
+ // In case target date is the last day of the month and the
seed date day is later than the target date
+ // day, we need to move it by 1 days
+ if (targetDateLastDay == targetDateDay && seedDateDay >
targetDateDay) {
+ yield DateUtils.getExactDifference(seedDate,
repaymentPeriod.getFromDate().plusDays(1), chronoUnit);
+ } else {
+ yield DateUtils.getExactDifference(seedDate,
repaymentPeriod.getFromDate(), chronoUnit);
+ }
+ }
+ default -> throw new UnsupportedOperationException("Unsupported
chrono unit: " + chronoUnit);
+ };
+
+ int multiplicator =
numberOfPeriodBetweenSeedDateAndActualRepaymentPeriod + 1;
+ LocalDate fromDate = repaymentPeriod.getFromDate();
+ while (fromDate.isBefore(repaymentPeriod.getDueDate())) {
+ fromDate = seedDate.plus(multiplicator, chronoUnit);
+ if (!fromDate.isAfter(repaymentPeriod.getDueDate())) {
+ multiplicator++;
+ } else {
+ LocalDate fullPeriodDate = fromDate;
+ multiplicator = multiplicator -
numberOfPeriodBetweenSeedDateAndActualRepaymentPeriod - 1;
+ fromDate = seedDate.plus(multiplicator, chronoUnit);
+ final long differenceInDays =
DateUtils.getDifferenceInDays(fromDate, repaymentPeriod.getDueDate());
+ final long fullPeriodDifferenceInDays =
DateUtils.getDifferenceInDays(fromDate, fullPeriodDate);
+ return
BigDecimal.valueOf(differenceInDays).divide(BigDecimal.valueOf(fullPeriodDifferenceInDays),
mc)
+ .add(BigDecimal.valueOf(multiplicator));
+ }
+ }
+ multiplicator = multiplicator -
numberOfPeriodBetweenSeedDateAndActualRepaymentPeriod - 1;
+ return BigDecimal.valueOf(multiplicator);
+ }
+
+ private static LocalDate
calculateSeedDate(ProgressiveLoanInterestScheduleModel scheduleModel,
RepaymentPeriod repaymentPeriod) {
+ LocalDate seedDate = scheduleModel.getStartDate();
+ LocalDate calculatedDate;
+ int multiplicator = 1;
+ ChronoUnit chronoUnit = switch
(scheduleModel.loanProductRelatedDetail().getRepaymentPeriodFrequencyType()) {
+ case YEARS -> ChronoUnit.YEARS;
+ case MONTHS -> ChronoUnit.MONTHS;
+ case WEEKS -> ChronoUnit.WEEKS;
+ case DAYS -> ChronoUnit.DAYS;
+ default -> throw new UnsupportedOperationException(
+ "Unsupported repayment frequency: " +
scheduleModel.loanProductRelatedDetail().getRepaymentPeriodFrequencyType());
+ };
+ do {
+ calculatedDate = seedDate.plus(multiplicator, chronoUnit);
+ multiplicator++;
+ } while (calculatedDate.isBefore(repaymentPeriod.getDueDate()));
+ return calculatedDate.equals(repaymentPeriod.getDueDate()) ? seedDate
: repaymentPeriod.getFromDate();
+ }
+
/**
* Calculate Rate Factor for an exact Period
*/
private BigDecimal calculateRateFactorPerPeriod(final
ProgressiveLoanInterestScheduleModel scheduleModel,
- final RepaymentPeriod repaymentPeriod, final LocalDate
interestPeriodFromDate, final LocalDate interestPeriodDueDate,
- final boolean isTillDate) {
+ final RepaymentPeriod repaymentPeriod, final LocalDate
interestPeriodFromDate, final LocalDate interestPeriodDueDate) {
final MathContext mc = scheduleModel.mc();
final LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
final BigDecimal interestRate =
calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate),
@@ -441,7 +550,6 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
final PeriodFrequencyType repaymentFrequency =
loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
final BigDecimal repaymentEvery =
BigDecimal.valueOf(loanProductRelatedDetail.getRepayEvery());
- final BigDecimal daysInMonth =
BigDecimal.valueOf(daysInMonthType.getNumberOfDays(interestPeriodFromDate));
final BigDecimal daysInYear =
BigDecimal.valueOf(daysInYearType.getNumberOfDays(interestPeriodFromDate));
final BigDecimal actualDaysInPeriod = BigDecimal
.valueOf(DateUtils.getDifferenceInDays(interestPeriodFromDate,
interestPeriodDueDate));
@@ -449,26 +557,24 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
.valueOf(DateUtils.getDifferenceInDays(repaymentPeriod.getFromDate(),
repaymentPeriod.getDueDate()));
final int numberOfYearsDifferenceInPeriod =
interestPeriodDueDate.getYear() - interestPeriodFromDate.getYear();
final boolean partialPeriodCalculationNeeded = daysInYearType ==
DaysInYearType.ACTUAL && numberOfYearsDifferenceInPeriod > 0;
-
- long addedDays = !isTillDate || scheduleModel.loanTermVariations() ==
null ? 0L
- :
scheduleModel.loanTermVariations().getDueDateVariation().stream()
- .filter(x ->
!repaymentPeriod.getFromDate().isAfter(x.getTermVariationApplicableFrom())
- &&
!repaymentPeriod.getDueDate().isBefore(x.getTermVariationApplicableFrom())
- &&
!repaymentPeriod.getDueDate().isAfter(x.getDateValue()))
- .map(x ->
DateUtils.getDifferenceInDays(x.getTermVariationApplicableFrom(),
x.getDateValue()))
- .reduce(0L, Long::sum);
+ final BigDecimal daysInMonth = daysInMonthType.isDaysInMonth_30() ?
BigDecimal.valueOf(30) : calculatedDaysInPeriod;
// TODO check:
loanApplicationTerms.calculatePeriodsBetweenDates(startDate, endDate); //
calculate period data
// TODO review: (repayment frequency: days, weeks, years; validation
day is month fix 30)
// TODO refactor this logic to represent in interest period
if (partialPeriodCalculationNeeded) {
final BigDecimal cumulatedPeriodFractions =
calculatePeriodFractions(interestPeriodFromDate, interestPeriodDueDate, mc);
- return rateFactorByRepaymentPartialPeriod(interestRate,
repaymentEvery, cumulatedPeriodFractions, BigDecimal.ONE,
+ return rateFactorByRepaymentPartialPeriod(interestRate,
BigDecimal.ONE, cumulatedPeriodFractions, BigDecimal.ONE,
BigDecimal.ONE, mc);
}
- return
calculateRateFactorPerPeriodBasedOnRepaymentFrequency(interestRate,
repaymentFrequency, repaymentEvery, daysInMonth,
- daysInYear, actualDaysInPeriod,
calculatedDaysInPeriod.subtract(BigDecimal.valueOf(addedDays), mc), mc);
+ return switch (daysInMonthType) {
+ case ACTUAL -> rateFactorByRepaymentPeriod(interestRate,
actualDaysInPeriod, BigDecimal.ONE, daysInYear, BigDecimal.ONE,
+ BigDecimal.ONE, mc);
+ case DAYS_30 ->
calculateRateFactorPerPeriodBasedOnRepaymentFrequency(interestRate,
repaymentFrequency, repaymentEvery,
+ daysInMonth, daysInYear, actualDaysInPeriod,
calculatedDaysInPeriod, mc);
+ default -> throw new UnsupportedOperationException("Unsupported
combination: Days in month: " + daysInMonthType);
+ };
}
/**
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 c3bb6bcaf..011fe6585 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
@@ -33,7 +33,6 @@ 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.data.LoanTermVariationsData;
-import
org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.InterestPeriod;
@@ -46,7 +45,6 @@ import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -64,7 +62,6 @@ class ProgressiveEMICalculatorTest {
private static MathContext mc = new MathContext(12,
RoundingMode.HALF_EVEN);
private static LoanProductMinimumRepaymentScheduleRelatedDetail
loanProductRelatedDetail = Mockito
.mock(LoanProductMinimumRepaymentScheduleRelatedDetail.class);
- private static LoanTermVariationsDataWrapper loanTermVariations =
Mockito.mock(LoanTermVariationsDataWrapper.class);
private static final CurrencyData currency = new CurrencyData("USD",
"USD", 2, 1, "$", "USD");
@@ -87,11 +84,6 @@ class ProgressiveEMICalculatorTest {
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new
MathContext(12, RoundingMode.HALF_EVEN));
}
- @BeforeEach
- public void setup() {
- Mockito.when(loanTermVariations.getDueDateVariation()).thenReturn(new
ArrayList<>());
- }
-
@AfterAll
public static void tearDown() {
threadLocalContextUtil.close();
@@ -178,7 +170,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestScheduleModel =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
Assertions.assertTrue(interestScheduleModel != null);
Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail()
!= null);
@@ -217,7 +209,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -261,7 +253,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -297,7 +289,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -333,7 +325,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -381,7 +373,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024,
2, 14));
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -423,7 +415,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024,
2, 14));
final ProgressiveLoanInterestScheduleModel interestModel =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1),
disbursedAmount);
@@ -470,7 +462,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024,
2, 14));
final ProgressiveLoanInterestScheduleModel interestModel =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1),
disbursedAmount);
@@ -516,7 +508,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024,
2, 14));
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -562,7 +554,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024,
2, 15));
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -669,7 +661,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024,
2, 15));
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -740,7 +732,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024,
2, 15));
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -817,7 +809,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
Money disbursedAmount = toMoney(100);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -866,7 +858,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -915,7 +907,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
5), disbursedAmount);
@@ -945,10 +937,10 @@ class ProgressiveEMICalculatorTest {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
repayment(1, LocalDate.of(2023, 12, 12), LocalDate.of(2024, 1,
12)),
repayment(2, LocalDate.of(2024, 1, 12), LocalDate.of(2024, 2,
12)),
- repayment(3, LocalDate.of(2024, 2, 12), LocalDate.of(2024, 3,
1)),
- repayment(4, LocalDate.of(2024, 3, 12), LocalDate.of(2024, 4,
1)),
- repayment(5, LocalDate.of(2024, 4, 12), LocalDate.of(2024, 5,
1)),
- repayment(6, LocalDate.of(2024, 5, 12), LocalDate.of(2024, 6,
1)));
+ repayment(3, LocalDate.of(2024, 2, 12), LocalDate.of(2024, 3,
12)),
+ repayment(4, LocalDate.of(2024, 3, 12), LocalDate.of(2024, 4,
12)),
+ repayment(5, LocalDate.of(2024, 4, 12), LocalDate.of(2024, 5,
12)),
+ repayment(6, LocalDate.of(2024, 5, 12), LocalDate.of(2024, 6,
12)));
final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
final Integer installmentAmountInMultiplesOf = null;
@@ -961,7 +953,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12,
12), disbursedAmount);
@@ -997,7 +989,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), toMoney(300.0));
Assertions.assertEquals(6.15,
toDouble(interestSchedule.getTotalDueInterest()));
@@ -1039,7 +1031,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(1000.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -1072,7 +1064,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -1105,7 +1097,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -1132,13 +1124,13 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
-
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.INVALID.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.DAYS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -1153,7 +1145,7 @@ class ProgressiveEMICalculatorTest {
}
@Test
- public void
soma_test_disbursedAmt1000_dayInYears360_daysInMonthDoesntMatter_repayEvery15Days()
{
+ public void
test_disbursedAmt1000_dayInYears360_daysInMonthDoesntMatter_repayEvery15Days() {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2,
1)),
@@ -1180,7 +1172,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(1000.0);
LocalDate disbursementDate = LocalDate.of(2024, 1, 1);
@@ -1228,7 +1220,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestModel =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(1000.0);
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1),
disbursedAmount);
@@ -1290,7 +1282,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestModel =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount1st = toMoney(1000.0);
final Money disbursedAmount2nd = toMoney(1000.0);
@@ -1363,7 +1355,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -1401,7 +1393,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
@@ -1437,6 +1429,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+ List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1475,6 +1468,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+ List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1513,6 +1507,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+ List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1551,6 +1546,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+ List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1589,6 +1585,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+ List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1630,6 +1627,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+ List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1674,6 +1672,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+ List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1714,6 +1713,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+ List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1751,24 +1751,24 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
- Mockito.when(loanTermVariations.getDueDateVariation()).thenReturn(new
ArrayList<>(List.of(//
+ List<LoanTermVariationsData> loanTermVariationsData = List.of(//
new LoanTermVariationsData(1L,
LoanTermVariationType.DUE_DATE.getValue(), //
LocalDate.of(2024, 2, 1), null, //
- LocalDate.of(2024, 3, 1), false))));
+ LocalDate.of(2024, 3, 1), false));
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariationsData, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
- checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 1.13, 15.88,
84.12);
- checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 1.13,
15.88, 84.12);
- checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.49,
16.52, 67.60);
- checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39,
16.62, 50.98);
- checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.30,
16.71, 34.27);
- checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20,
16.81, 17.46);
- checkPeriod(interestSchedule, 5, 0, 17.56, 0.005833333333, 0.10,
17.46, 0.0);
+ checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 1.17, 15.84,
84.16);
+ checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 1.17,
15.84, 84.16);
+ checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.49,
16.52, 67.64);
+ checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39,
16.62, 51.02);
+ checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.30,
16.71, 34.31);
+ checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20,
16.81, 17.50);
+ checkPeriod(interestSchedule, 5, 0, 17.60, 0.005833333333, 0.10,
17.50, 0.0);
}
@Test
@@ -1790,27 +1790,27 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
- Mockito.when(loanTermVariations.getDueDateVariation()).thenReturn(new
ArrayList<>(List.of(//
+ List<LoanTermVariationsData> loanTermVariationsData = List.of(//
new LoanTermVariationsData(1L,
LoanTermVariationType.DUE_DATE.getValue(), //
LocalDate.of(2024, 2, 1), null, //
LocalDate.of(2024, 3, 1), false), //
new LoanTermVariationsData(2L,
LoanTermVariationType.DUE_DATE.getValue(), //
LocalDate.of(2024, 5, 1), null, //
- LocalDate.of(2024, 6, 1), false))));
+ LocalDate.of(2024, 6, 1), false));
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariationsData, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
- checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 1.13, 15.88,
84.12);
- checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 1.13,
15.88, 84.12);
- checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.49,
16.52, 67.60);
- checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.80,
16.21, 51.39);
- checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.30,
16.71, 34.68);
- checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20,
16.81, 17.87);
- checkPeriod(interestSchedule, 5, 0, 17.97, 0.005833333333, 0.10,
17.87, 0.0);
+ checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 1.17, 15.84,
84.16);
+ checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 1.17,
15.84, 84.16);
+ checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.49,
16.52, 67.64);
+ checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.79,
16.22, 51.42);
+ checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.30,
16.71, 34.71);
+ checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20,
16.81, 17.90);
+ checkPeriod(interestSchedule, 5, 0, 18.00, 0.005833333333, 0.10,
17.90, 0.0);
}
@Test
@@ -1832,24 +1832,388 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
- Mockito.when(loanTermVariations.getDueDateVariation()).thenReturn(new
ArrayList<>(List.of(//
+ List<LoanTermVariationsData> loanTermVariationsData = List.of(//
new LoanTermVariationsData(1L,
LoanTermVariationType.DUE_DATE.getValue(), //
LocalDate.of(2024, 3, 1), null, //
- LocalDate.of(2024, 4, 15), false))));
+ LocalDate.of(2024, 4, 15), false));
final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
- expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariations, installmentAmountInMultiplesOf, mc);
+ expectedRepaymentPeriods, loanProductRelatedDetail,
loanTermVariationsData, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1,
1), disbursedAmount);
checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43,
83.57);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58,
16.43, 83.57);
- checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 1.24,
15.77, 67.80);
- checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.40,
16.61, 51.19);
- checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.30,
16.71, 34.48);
- checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20,
16.81, 17.67);
- checkPeriod(interestSchedule, 5, 0, 17.77, 0.005833333333, 0.10,
17.67, 0.0);
+ checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 1.20,
15.81, 67.76);
+ checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.40,
16.61, 51.15);
+ checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.30,
16.71, 34.44);
+ checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20,
16.81, 17.63);
+ checkPeriod(interestSchedule, 5, 0, 17.73, 0.005833333333, 0.10,
17.63, 0.0);
+ }
+
+ @Test
+ public void test_actual_actual_repayment_schedule_across_multiple_years() {
+ MathContext mc = new MathContext(12, RoundingMode.HALF_UP);
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2023, 11, 13), LocalDate.of(2023,
12, 13)),
+ repayment(2, LocalDate.of(2023, 12, 13), LocalDate.of(2024, 1,
13)),
+ repayment(3, LocalDate.of(2024, 1, 13), LocalDate.of(2024, 2,
13)),
+ repayment(4, LocalDate.of(2024, 2, 13), LocalDate.of(2024, 3,
13)),
+ repayment(5, LocalDate.of(2024, 3, 13), LocalDate.of(2024, 4,
13)),
+ repayment(6, LocalDate.of(2024, 4, 13), LocalDate.of(2024, 5,
13)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(9.99);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(5000.0);
+ LocalDate disbursementDate = LocalDate.of(2023, 11, 13);
+
+ emiCalculator.addDisbursement(interestSchedule, disbursementDate,
disbursedAmount);
+
+ checkPeriod(interestSchedule, 0, 857.71, 41.05, 816.66, 4183.34,
false);
+ checkPeriod(interestSchedule, 1, 857.71, 35.45, 822.26, 3361.08,
false);
+ checkPeriod(interestSchedule, 2, 857.71, 28.44, 829.27, 2531.81,
false);
+ checkPeriod(interestSchedule, 3, 857.71, 20.04, 837.67, 1694.14,
false);
+ checkPeriod(interestSchedule, 4, 857.71, 14.33, 843.38, 850.76, false);
+ checkPeriod(interestSchedule, 5, 857.73, 6.97, 850.76, 0, false);
+
+ emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2023, 12,
13), LocalDate.of(2023, 12, 10),
+ Money.of(currency, BigDecimal.valueOf(820.76)));
+ emiCalculator.payInterest(interestSchedule, LocalDate.of(2023, 12,
13), LocalDate.of(2023, 12, 10),
+ Money.of(currency, BigDecimal.valueOf(36.95)));
+
+ checkPeriod(interestSchedule, 0, 857.71, 36.95, 820.76, 4179.24, true);
+ checkPeriod(interestSchedule, 1, 857.71, 38.85, 818.86, 3360.38,
false);
+ checkPeriod(interestSchedule, 2, 857.71, 28.43, 829.28, 2531.1, false);
+ checkPeriod(interestSchedule, 3, 857.71, 20.04, 837.67, 1693.43,
false);
+ checkPeriod(interestSchedule, 4, 857.71, 14.33, 843.38, 850.05, false);
+ checkPeriod(interestSchedule, 5, 857.01, 6.96, 850.05, 0, false);
+ }
+
+ @Test
+ public void
test_actual_actual_repayment_schedule_across_multiple_years_overdue() {
+ MathContext mc = new MathContext(12, RoundingMode.HALF_UP);
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2023, 11, 13), LocalDate.of(2023,
12, 13)),
+ repayment(2, LocalDate.of(2023, 12, 13), LocalDate.of(2024, 1,
13)),
+ repayment(3, LocalDate.of(2024, 1, 13), LocalDate.of(2024, 2,
13)),
+ repayment(4, LocalDate.of(2024, 2, 13), LocalDate.of(2024, 3,
13)),
+ repayment(5, LocalDate.of(2024, 3, 13), LocalDate.of(2024, 4,
13)),
+ repayment(6, LocalDate.of(2024, 4, 13), LocalDate.of(2024, 5,
13)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(9.99);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(5000.0);
+ LocalDate disbursementDate = LocalDate.of(2023, 11, 13);
+
+ emiCalculator.addDisbursement(interestSchedule, disbursementDate,
disbursedAmount);
+
+ for (int i = 0; i < 6; i++) {
+ LocalDate dueDate = expectedRepaymentPeriods.get(i).getDueDate();
+ PeriodDueDetails dueAmounts =
emiCalculator.getDueAmounts(interestSchedule, dueDate, dueDate);
+ Money duePrincipal = dueAmounts.getDuePrincipal();
+ emiCalculator.addBalanceCorrection(interestSchedule, dueDate,
duePrincipal);
+ }
+
+ checkPeriod(interestSchedule, 0, 857.71, 41.05, 816.66, 5000.0, false);
+ checkPeriod(interestSchedule, 1, 857.71, 42.37, 815.34, 5000.0, false);
+ checkPeriod(interestSchedule, 2, 857.71, 42.31, 815.4, 5000.0, false);
+ checkPeriod(interestSchedule, 3, 857.71, 39.58, 818.13, 5000.0, false);
+ checkPeriod(interestSchedule, 4, 857.71, 42.31, 815.4, 5000.0, false);
+ checkPeriod(interestSchedule, 5, 960.01, 40.94, 919.07, 5000.0, false);
+ }
+
+ @Test
+ public void
test_repayment_actual_actual_repayment_schedule_across_multiple_years_overdue()
{
+ MathContext mc = new MathContext(12, RoundingMode.HALF_UP);
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2023, 11, 13), LocalDate.of(2023,
12, 13)),
+ repayment(2, LocalDate.of(2023, 12, 13), LocalDate.of(2024, 1,
13)),
+ repayment(3, LocalDate.of(2024, 1, 13), LocalDate.of(2024, 2,
13)),
+ repayment(4, LocalDate.of(2024, 2, 13), LocalDate.of(2024, 3,
13)),
+ repayment(5, LocalDate.of(2024, 3, 13), LocalDate.of(2024, 4,
13)),
+ repayment(6, LocalDate.of(2024, 4, 13), LocalDate.of(2024, 5,
13)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(9.99);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(5000.0);
+ LocalDate disbursementDate = LocalDate.of(2023, 11, 13);
+
+ emiCalculator.addDisbursement(interestSchedule, disbursementDate,
disbursedAmount);
+
+ emiCalculator.payInterest(interestSchedule, LocalDate.of(2023, 12,
13), LocalDate.of(2023, 12, 10),
+ Money.of(currency, BigDecimal.valueOf(36.95)));
+ emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2023, 12,
13), LocalDate.of(2023, 12, 10),
+ Money.of(currency, BigDecimal.valueOf(820.76)));
+
+ for (int i = 1; i < 6; i++) {
+ LocalDate dueDate = expectedRepaymentPeriods.get(i).getDueDate();
+ PeriodDueDetails dueAmounts =
emiCalculator.getDueAmounts(interestSchedule, dueDate, dueDate);
+ Money duePrincipal = dueAmounts.getDuePrincipal();
+ emiCalculator.addBalanceCorrection(interestSchedule, dueDate,
duePrincipal);
+ }
+
+ checkPeriod(interestSchedule, 0, 857.71, 36.95, 820.76, 4179.24, true);
+ checkPeriod(interestSchedule, 1, 857.71, 38.85, 818.86, 4179.24,
false);
+ checkPeriod(interestSchedule, 2, 857.71, 35.36, 822.35, 4179.24,
false);
+ checkPeriod(interestSchedule, 3, 857.71, 33.08, 824.63, 4179.24,
false);
+ checkPeriod(interestSchedule, 4, 857.71, 35.36, 822.35, 4179.24,
false);
+ checkPeriod(interestSchedule, 5, 925.27, 34.22, 891.05, 4179.24,
false);
+ }
+
+ @Test
+ public void test_360_30_repayment_schedule_disbursement_month_end() {
+ MathContext mc = new MathContext(12, RoundingMode.HALF_UP);
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2023, 10, 31), LocalDate.of(2023,
11, 30)),
+ repayment(2, LocalDate.of(2023, 11, 30), LocalDate.of(2023,
12, 31)),
+ repayment(3, LocalDate.of(2023, 12, 31), LocalDate.of(2024, 1,
31)),
+ repayment(4, LocalDate.of(2024, 1, 31), LocalDate.of(2024, 2,
29)),
+ repayment(5, LocalDate.of(2024, 2, 29), LocalDate.of(2024, 3,
31)),
+ repayment(6, LocalDate.of(2024, 3, 31), LocalDate.of(2024, 4,
30)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(9.99);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(2450.0);
+ LocalDate disbursementDate = LocalDate.of(2023, 10, 31);
+
+ emiCalculator.addDisbursement(interestSchedule, disbursementDate,
disbursedAmount);
+
+ checkPeriod(interestSchedule, 0, 420.31, 20.40, 399.91, 2050.09,
false);
+ checkPeriod(interestSchedule, 1, 420.31, 17.07, 403.24, 1646.85,
false);
+ checkPeriod(interestSchedule, 2, 420.31, 13.71, 406.60, 1240.25,
false);
+ checkPeriod(interestSchedule, 3, 420.31, 10.33, 409.98, 830.27, false);
+ checkPeriod(interestSchedule, 4, 420.31, 6.91, 413.40, 416.87, false);
+ checkPeriod(interestSchedule, 5, 420.34, 3.47, 416.87, 0.00, false);
+ }
+
+ @Test
+ public void test_360_30_repayment_schedule_disbursement_near_month_end() {
+ MathContext mc = new MathContext(12, RoundingMode.HALF_UP);
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2023, 10, 30), LocalDate.of(2023,
11, 30)),
+ repayment(2, LocalDate.of(2023, 11, 30), LocalDate.of(2023,
12, 30)),
+ repayment(3, LocalDate.of(2023, 12, 30), LocalDate.of(2024, 1,
30)),
+ repayment(4, LocalDate.of(2024, 1, 30), LocalDate.of(2024, 2,
29)),
+ repayment(5, LocalDate.of(2024, 2, 29), LocalDate.of(2024, 3,
30)),
+ repayment(6, LocalDate.of(2024, 3, 30), LocalDate.of(2024, 4,
30)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(45.0);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(10000.0);
+ LocalDate disbursementDate = LocalDate.of(2023, 10, 30);
+
+ emiCalculator.addDisbursement(interestSchedule, disbursementDate,
disbursedAmount);
+
+ checkPeriod(interestSchedule, 0, 1892.12, 375.00, 1517.12, 8482.88,
false);
+ checkPeriod(interestSchedule, 1, 1892.12, 318.11, 1574.01, 6908.87,
false);
+ checkPeriod(interestSchedule, 2, 1892.12, 259.08, 1633.04, 5275.83,
false);
+ checkPeriod(interestSchedule, 3, 1892.12, 197.84, 1694.28, 3581.55,
false);
+ checkPeriod(interestSchedule, 4, 1892.12, 134.31, 1757.81, 1823.74,
false);
+ checkPeriod(interestSchedule, 5, 1892.13, 68.39, 1823.74, 0.00, false);
+ }
+
+ @Test
+ public void
test_360_30_repayment_schedule_disbursement_repay_every_2_months() {
+ MathContext mc = new MathContext(12, RoundingMode.HALF_UP);
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2023, 10, 29), LocalDate.of(2023,
12, 29)),
+ repayment(2, LocalDate.of(2023, 12, 29), LocalDate.of(2024, 2,
29)),
+ repayment(3, LocalDate.of(2024, 2, 29), LocalDate.of(2024, 4,
29)),
+ repayment(4, LocalDate.of(2024, 4, 29), LocalDate.of(2024, 6,
29)),
+ repayment(5, LocalDate.of(2024, 6, 29), LocalDate.of(2024, 8,
29)),
+ repayment(6, LocalDate.of(2024, 8, 29), LocalDate.of(2024, 10,
29)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(19.99);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(2);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(3333.0);
+ LocalDate disbursementDate = LocalDate.of(2023, 10, 29);
+
+ emiCalculator.addDisbursement(interestSchedule, disbursementDate,
disbursedAmount);
+
+ checkPeriod(interestSchedule, 0, 622.04, 111.04, 511.00, 2822.00,
false);
+ checkPeriod(interestSchedule, 1, 622.04, 94.02, 528.02, 2293.98,
false);
+ checkPeriod(interestSchedule, 2, 622.04, 76.43, 545.61, 1748.37,
false);
+ checkPeriod(interestSchedule, 3, 622.04, 58.25, 563.79, 1184.58,
false);
+ checkPeriod(interestSchedule, 4, 622.04, 39.47, 582.57, 602.01, false);
+ checkPeriod(interestSchedule, 5, 622.07, 20.06, 602.01, 0.00, false);
+ }
+
+ @Test
+ public void test_actual_actual_repayment_schedule_disbursement_month_end()
{
+ MathContext mc = new MathContext(12, RoundingMode.HALF_UP);
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2023, 10, 31), LocalDate.of(2023,
11, 30)),
+ repayment(2, LocalDate.of(2023, 11, 30), LocalDate.of(2023,
12, 31)),
+ repayment(3, LocalDate.of(2023, 12, 31), LocalDate.of(2024, 1,
31)),
+ repayment(4, LocalDate.of(2024, 1, 31), LocalDate.of(2024, 2,
29)),
+ repayment(5, LocalDate.of(2024, 2, 29), LocalDate.of(2024, 3,
31)),
+ repayment(6, LocalDate.of(2024, 3, 31), LocalDate.of(2024, 4,
30)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(45.0);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(245000.0);
+ LocalDate disbursementDate = LocalDate.of(2023, 10, 31);
+
+ emiCalculator.addDisbursement(interestSchedule, disbursementDate,
disbursedAmount);
+
+ checkPeriod(interestSchedule, 0, 46343.27, 9061.64, 37281.63,
207718.37, false);
+ checkPeriod(interestSchedule, 1, 46343.27, 7938.83, 38404.44,
169313.93, false);
+ checkPeriod(interestSchedule, 2, 46343.27, 6453.36, 39889.91,
129424.02, false);
+ checkPeriod(interestSchedule, 3, 46343.27, 4614.71, 41728.56,
87695.46, false);
+ checkPeriod(interestSchedule, 4, 46343.27, 3342.49, 43000.78,
44694.68, false);
+ checkPeriod(interestSchedule, 5, 46343.25, 1648.57, 44694.68, 0.00,
false);
+ }
+
+ @Test
+ public void
test_actual_actual_repayment_schedule_disbursement_near_month_end() {
+ MathContext mc = new MathContext(12, RoundingMode.HALF_UP);
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2021, 10, 30), LocalDate.of(2021,
11, 30)),
+ repayment(2, LocalDate.of(2021, 11, 30), LocalDate.of(2021,
12, 30)),
+ repayment(3, LocalDate.of(2021, 12, 30), LocalDate.of(2022, 1,
30)),
+ repayment(4, LocalDate.of(2022, 1, 30), LocalDate.of(2022, 2,
28)),
+ repayment(5, LocalDate.of(2022, 2, 28), LocalDate.of(2022, 3,
30)),
+ repayment(6, LocalDate.of(2022, 3, 30), LocalDate.of(2022, 4,
30)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(1500.0);
+ LocalDate disbursementDate = LocalDate.of(2021, 10, 30);
+
+ emiCalculator.addDisbursement(interestSchedule, disbursementDate,
disbursedAmount);
+
+ checkPeriod(interestSchedule, 0, 256.95, 12.08, 244.87, 1255.13,
false);
+ checkPeriod(interestSchedule, 1, 256.95, 9.78, 247.17, 1007.96, false);
+ checkPeriod(interestSchedule, 2, 256.95, 8.12, 248.83, 759.13, false);
+ checkPeriod(interestSchedule, 3, 256.95, 5.72, 251.23, 507.90, false);
+ checkPeriod(interestSchedule, 4, 256.95, 3.96, 252.99, 254.91, false);
+ checkPeriod(interestSchedule, 5, 256.96, 2.05, 254.91, 0.00, false);
+ }
+
+ @Test
+ public void
test_actual_actual_repayment_schedule_disbursement_near_month_end_repay_every_2_months()
{
+ MathContext mc = new MathContext(12, RoundingMode.HALF_UP);
+ final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods
= List.of(
+ repayment(1, LocalDate.of(2022, 10, 29), LocalDate.of(2022,
12, 29)),
+ repayment(2, LocalDate.of(2022, 12, 29), LocalDate.of(2023, 2,
28)),
+ repayment(3, LocalDate.of(2023, 2, 28), LocalDate.of(2023, 4,
29)),
+ repayment(4, LocalDate.of(2023, 4, 29), LocalDate.of(2023, 6,
29)),
+ repayment(5, LocalDate.of(2023, 6, 29), LocalDate.of(2023, 8,
29)),
+ repayment(6, LocalDate.of(2023, 8, 29), LocalDate.of(2023, 10,
29)));
+
+ final BigDecimal interestRate = BigDecimal.valueOf(7);
+ final Integer installmentAmountInMultiplesOf = null;
+
+
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
+
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
+
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
+ Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
+
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
+
+ final ProgressiveLoanInterestScheduleModel interestSchedule =
emiCalculator.generatePeriodInterestScheduleModel(
+ expectedRepaymentPeriods, loanProductRelatedDetail, List.of(),
installmentAmountInMultiplesOf, mc);
+
+ final Money disbursedAmount = toMoney(5000);
+ LocalDate disbursementDate = LocalDate.of(2022, 10, 29);
+
+ emiCalculator.addDisbursement(interestSchedule, disbursementDate,
disbursedAmount);
+
+ checkPeriod(interestSchedule, 0, 867.68, 58.49, 809.19, 4190.81,
false);
+ checkPeriod(interestSchedule, 1, 867.68, 49.03, 818.65, 3372.16,
false);
+ checkPeriod(interestSchedule, 2, 867.68, 38.80, 828.88, 2543.28,
false);
+ checkPeriod(interestSchedule, 3, 867.68, 29.75, 837.93, 1705.35,
false);
+ checkPeriod(interestSchedule, 4, 867.68, 19.95, 847.73, 857.62, false);
+ checkPeriod(interestSchedule, 5, 867.65, 10.03, 857.62, 0.00, false);
}
private static LoanScheduleModelRepaymentPeriod repayment(int
periodNumber, LocalDate fromDate, LocalDate dueDate) {