FINERACT-2081: Fix end of month and partial interest calculation for 360/30

This commit is contained in:
Adam Saghy
2025-02-03 16:29:55 +01:00
parent 69317d62c8
commit 0b9687d2aa
10 changed files with 615 additions and 156 deletions
@@ -1549,10 +1549,10 @@ public final class LoanApplicationTerms {
public LoanProductMinimumRepaymentScheduleRelatedDetail toLoanProductRelatedDetailMinimumData() { public LoanProductMinimumRepaymentScheduleRelatedDetail toLoanProductRelatedDetailMinimumData() {
final CurrencyData currency = new CurrencyData(this.currency.getCode(), this.currency.getDecimalPlaces(), final CurrencyData currency = new CurrencyData(this.currency.getCode(), this.currency.getDecimalPlaces(),
this.currency.getInMultiplesOf()); this.currency.getInMultiplesOf());
return new LoanProductRelatedDetailMinimumData(currency, principal, inArrearsTolerance, interestRatePerPeriod, return new LoanProductRelatedDetailMinimumData(currency, interestRatePerPeriod, annualNominalInterestRate, interestChargingGrace,
annualNominalInterestRate, interestChargingGrace, interestPaymentGrace, principalGrace, interestPaymentGrace, principalGrace, recurringMoratoriumOnPrincipalPeriods, interestMethod,
recurringMoratoriumOnPrincipalPeriods, interestMethod, interestCalculationPeriodMethod, daysInYearType, daysInMonthType, interestCalculationPeriodMethod, daysInYearType, daysInMonthType, amortizationMethod, repaymentPeriodFrequencyType,
amortizationMethod, repaymentPeriodFrequencyType, repaymentEvery, numberOfRepayments); repaymentEvery, numberOfRepayments);
} }
public Integer getLoanTermFrequency() { public Integer getLoanTermFrequency() {
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanproduct.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import org.apache.fineract.organisation.monetary.data.CurrencyData; 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.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; 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 { public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRepaymentScheduleRelatedDetail {
private final CurrencyData currency; private final CurrencyData currency;
private final Money principal;
private final Money inArrearsTolerance;
private final BigDecimal interestRatePerPeriod; private final BigDecimal interestRatePerPeriod;
private final BigDecimal annualNominalInterestRate; private final BigDecimal annualNominalInterestRate;
private final Integer interestChargingGrace; private final Integer interestChargingGrace;
private final Integer interestPaymentGrace; private final Integer interestPaymentGrace;
private final Integer principalGrace; private final Integer principalGrace;
private final Integer recurringMoratoriumOnPrincipalPeriods; private final Integer recurringMoratoriumOnPrincipalPeriods;
private final InterestMethod interestMethod; private final InterestMethod interestMethod;
private final InterestCalculationPeriodMethod interestCalculationPeriodMethod; private final InterestCalculationPeriodMethod interestCalculationPeriodMethod;
private final DaysInYearType daysInYearType; private final DaysInYearType daysInYearType;
private final DaysInMonthType daysInMonthType; private final DaysInMonthType daysInMonthType;
private final AmortizationMethod amortizationMethod; private final AmortizationMethod amortizationMethod;
private final PeriodFrequencyType repaymentPeriodFrequencyType; private final PeriodFrequencyType repaymentPeriodFrequencyType;
private final Integer repaymentEvery; private final Integer repaymentEvery;
private final Integer numberOfRepayments; private final Integer numberOfRepayments;
public LoanProductRelatedDetailMinimumData(CurrencyData currency, Money principal, Money inArrearsTolerance, public LoanProductRelatedDetailMinimumData(CurrencyData currency, BigDecimal interestRatePerPeriod,
BigDecimal interestRatePerPeriod, BigDecimal annualNominalInterestRate, Integer interestChargingGrace, BigDecimal annualNominalInterestRate, Integer interestChargingGrace, Integer interestPaymentGrace, Integer principalGrace,
Integer interestPaymentGrace, Integer principalGrace, Integer recurringMoratoriumOnPrincipalPeriods, Integer recurringMoratoriumOnPrincipalPeriods, InterestMethod interestMethod,
InterestMethod interestMethod, InterestCalculationPeriodMethod interestCalculationPeriodMethod, DaysInYearType daysInYearType, InterestCalculationPeriodMethod interestCalculationPeriodMethod, DaysInYearType daysInYearType, DaysInMonthType daysInMonthType,
DaysInMonthType daysInMonthType, AmortizationMethod amortizationMethod, PeriodFrequencyType repaymentPeriodFrequencyType, AmortizationMethod amortizationMethod, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaymentEvery,
Integer repaymentEvery, Integer numberOfRepayments) { Integer numberOfRepayments) {
this.currency = currency; this.currency = currency;
this.principal = principal;
this.inArrearsTolerance = inArrearsTolerance;
this.interestRatePerPeriod = interestRatePerPeriod; this.interestRatePerPeriod = interestRatePerPeriod;
this.annualNominalInterestRate = annualNominalInterestRate; this.annualNominalInterestRate = annualNominalInterestRate;
this.interestChargingGrace = defaultToNullIfZero(interestChargingGrace); this.interestChargingGrace = defaultToNullIfZero(interestChargingGrace);
@@ -94,11 +82,6 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
return currency; return currency;
} }
@Override
public Money getPrincipal() {
return principal;
}
@Override @Override
public Integer getGraceOnInterestCharged() { public Integer getGraceOnInterestCharged() {
return interestChargingGrace; return interestChargingGrace;
@@ -119,11 +102,6 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
return recurringMoratoriumOnPrincipalPeriods; return recurringMoratoriumOnPrincipalPeriods;
} }
@Override
public Money getInArrearsTolerance() {
return inArrearsTolerance;
}
@Override @Override
public BigDecimal getNominalInterestRatePerPeriod() { public BigDecimal getNominalInterestRatePerPeriod() {
return interestRatePerPeriod; return interestRatePerPeriod;
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanproduct.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
/** /**
@@ -30,8 +29,6 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
CurrencyData getCurrencyData(); CurrencyData getCurrencyData();
Money getPrincipal();
Integer getGraceOnInterestCharged(); Integer getGraceOnInterestCharged();
Integer getGraceOnInterestPayment(); Integer getGraceOnInterestPayment();
@@ -40,8 +37,6 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
Integer getRecurringMoratoriumOnPrincipalPeriods(); Integer getRecurringMoratoriumOnPrincipalPeriods();
Money getInArrearsTolerance();
BigDecimal getNominalInterestRatePerPeriod(); BigDecimal getNominalInterestRatePerPeriod();
PeriodFrequencyType getInterestPeriodFrequencyType(); PeriodFrequencyType getInterestPeriodFrequencyType();
@@ -263,12 +263,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return currency.toData(); return currency.toData();
} }
@Override
public Money getPrincipal() { public Money getPrincipal() {
return Money.of(getCurrencyData(), this.principal); return Money.of(getCurrencyData(), this.principal);
} }
@Override
public Money getInArrearsTolerance() { public Money getInArrearsTolerance() {
return Money.of(getCurrencyData(), this.inArrearsTolerance); return Money.of(getCurrencyData(), this.inArrearsTolerance);
} }
@@ -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.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; 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.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; 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.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; 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.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
@@ -183,10 +183,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency)); MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
final Loan loan = loanTransactions.get(0).getLoan(); final Loan loan = loanTransactions.get(0).getLoan();
LoanTermVariationsDataWrapper loanTermVariations = Optional List<LoanTermVariationsData> loanTermVariations = loan.getActiveLoanTermVariations().stream().map(LoanTermVariations::toData)
.ofNullable(loan.getActiveLoanTermVariations()).map(loanTermVariationsSet -> loanTermVariationsSet.stream() .collect(Collectors.toCollection(ArrayList::new));
.map(LoanTermVariations::toData).collect(Collectors.toCollection(ArrayList::new)))
.map(LoanTermVariationsDataWrapper::new).orElse(null);
final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf(); final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf();
final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments, ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments,
@@ -235,8 +233,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List<LoanTransaction> loanTransactions, public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List<LoanTransaction> loanTransactions,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) { MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
LocalDate currentDate = DateUtils.getBusinessLocalDate(); LocalDate currentDate = DateUtils.getBusinessLocalDate();
return reprocessProgressiveLoanTransactions(disbursementDate, currentDate, loanTransactions, currency, installments, charges) Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> result = reprocessProgressiveLoanTransactions(disbursementDate,
.getLeft(); currentDate, loanTransactions, currency, installments, charges);
return result.getLeft();
} }
@NotNull @NotNull
@@ -823,16 +822,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
} }
@NotNull @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<LoanTransaction> loanTransactions, final Set<LoanCharge> charges) {
final List<ChangeOperation> changeOperations = new ArrayList<>(); List<ChangeOperation> changeOperations = new ArrayList<>();
if (loanTermVariations != null) { Map<LoanTermVariationType, List<LoanTermVariationsData>> loanTermVariationsMap = loanTermVariations.stream()
if (!loanTermVariations.getInterestPauseVariations().isEmpty()) { .collect(Collectors.groupingBy(ltvd -> LoanTermVariationType.fromInt(ltvd.getTermType().getId().intValue())));
changeOperations.addAll(loanTermVariations.getInterestPauseVariations().stream().map(ChangeOperation::new).toList()); if (loanTermVariationsMap.get(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT) != null) {
} changeOperations.addAll(loanTermVariationsMap.get(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT).stream()
if (!loanTermVariations.getInterestRateFromInstallment().isEmpty()) { .map(ChangeOperation::new).toList());
changeOperations.addAll(loanTermVariations.getInterestRateFromInstallment().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) { if (charges != null) {
changeOperations.addAll(charges.stream().map(ChangeOperation::new).toList()); changeOperations.addAll(charges.stream().map(ChangeOperation::new).toList());
@@ -27,18 +27,22 @@ import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.Money; 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; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
@Data @Data
@@ -48,18 +52,18 @@ public class ProgressiveLoanInterestScheduleModel {
private final List<RepaymentPeriod> repaymentPeriods; private final List<RepaymentPeriod> repaymentPeriods;
private final TreeSet<InterestRate> interestRates; private final TreeSet<InterestRate> interestRates;
private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail; private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail;
private final LoanTermVariationsDataWrapper loanTermVariations; private final Map<LoanTermVariationType, List<LoanTermVariationsData>> loanTermVariations;
private final Integer installmentAmountInMultiplesOf; private final Integer installmentAmountInMultiplesOf;
private final MathContext mc; private final MathContext mc;
private final Money zero; private final Money zero;
public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods, public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods,
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, 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.repaymentPeriods = repaymentPeriods;
this.interestRates = new TreeSet<>(Collections.reverseOrder()); this.interestRates = new TreeSet<>(Collections.reverseOrder());
this.loanProductRelatedDetail = loanProductRelatedDetail; this.loanProductRelatedDetail = loanProductRelatedDetail;
this.loanTermVariations = loanTermVariations; this.loanTermVariations = buildLoanTermVariationMap(loanTermVariations);
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.mc = mc; this.mc = mc;
this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), 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, private ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods, final TreeSet<InterestRate> interestRates,
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, 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.mc = mc;
this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods, this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods,
(previousPeriod, repaymentPeriod) -> new RepaymentPeriod(previousPeriod, repaymentPeriod, mc)); (previousPeriod, repaymentPeriod) -> new RepaymentPeriod(previousPeriod, repaymentPeriod, mc));
@@ -343,4 +348,12 @@ public class ProgressiveLoanInterestScheduleModel {
: date.isAfter(previousInterestPeriod.getDueDate()) ? previousInterestPeriod.getDueDate() : date; : 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())));
}
} }
@@ -37,6 +37,7 @@ import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; 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.data.OutstandingAmountsDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
@@ -88,9 +89,12 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
// generate list of proposed schedule due dates // generate list of proposed schedule due dates
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc, final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc,
periodStartDate, loanApplicationTerms, holidayDetailDTO); periodStartDate, loanApplicationTerms, holidayDetailDTO);
List<LoanTermVariationsData> loanTermVariations = loanApplicationTerms.getLoanTermVariations() != null
? loanApplicationTerms.getLoanTermVariations().getExceptionData()
: null;
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetailMinimumData(), expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetailMinimumData(), loanTermVariations,
loanApplicationTerms.getLoanTermVariations(), loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc); loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size()); final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size());
prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms); prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms);
@@ -25,7 +25,7 @@ import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.apache.fineract.organisation.monetary.domain.Money; 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.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OutstandingDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OutstandingDetails;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDetails;
@@ -39,13 +39,13 @@ public interface EMICalculator {
@NotNull @NotNull
ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods, ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
LoanTermVariationsDataWrapper loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc); List<LoanTermVariationsData> loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc);
@NotNull @NotNull
ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel( ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel(
@NotNull List<LoanRepaymentScheduleInstallment> installments, @NotNull List<LoanRepaymentScheduleInstallment> installments,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
LoanTermVariationsDataWrapper loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc); List<LoanTermVariationsData> loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc);
Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate); Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate);
@@ -23,6 +23,8 @@ import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.Year; import java.time.Year;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Optional; 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.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; 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.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.EmiAdjustment;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.InterestPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.InterestPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OutstandingDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OutstandingDetails;
@@ -58,7 +61,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@NotNull @NotNull
public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods, public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, @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, return generateInterestScheduleModel(periods, LoanScheduleModelRepaymentPeriod::periodFromDate,
LoanScheduleModelRepaymentPeriod::periodDueDate, loanProductRelatedDetail, loanTermVariations, LoanScheduleModelRepaymentPeriod::periodDueDate, loanProductRelatedDetail, loanTermVariations,
installmentAmountInMultiplesOf, mc); installmentAmountInMultiplesOf, mc);
@@ -69,7 +72,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
public ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel( public ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel(
@NotNull List<LoanRepaymentScheduleInstallment> installments, @NotNull List<LoanRepaymentScheduleInstallment> installments,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, @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(); installments = installments.stream().filter(installment -> !installment.isDownPayment() && !installment.isAdditional()).toList();
return generateInterestScheduleModel(installments, LoanRepaymentScheduleInstallment::getFromDate, return generateInterestScheduleModel(installments, LoanRepaymentScheduleInstallment::getFromDate,
LoanRepaymentScheduleInstallment::getDueDate, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, LoanRepaymentScheduleInstallment::getDueDate, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf,
@@ -79,7 +82,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@NotNull @NotNull
private <T> ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(@NotNull List<T> periods, Function<T, LocalDate> from, private <T> ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(@NotNull List<T> periods, Function<T, LocalDate> from,
Function<T, LocalDate> to, @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, 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 Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>(); final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>();
List<RepaymentPeriod> repaymentPeriods = periods.stream().map(e -> { List<RepaymentPeriod> repaymentPeriods = periods.stream().map(e -> {
@@ -314,8 +317,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
} }
calculateOutstandingBalance(scheduleModel); calculateOutstandingBalance(scheduleModel);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel); calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
if (onlyOnActualModelShouldApply if (onlyOnActualModelShouldApply && (scheduleModel.loanTermVariations() == null
&& (scheduleModel.loanTermVariations() == null || scheduleModel.loanTermVariations().getDueDateVariation().isEmpty())) { || scheduleModel.loanTermVariations().get(LoanTermVariationType.DUE_DATE) == null)) {
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, relatedRepaymentPeriods); checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, relatedRepaymentPeriods);
} }
} }
@@ -420,18 +423,124 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final ProgressiveLoanInterestScheduleModel scheduleModel) { final ProgressiveLoanInterestScheduleModel scheduleModel) {
repaymentPeriod.getInterestPeriods().forEach(interestPeriod -> { repaymentPeriod.getInterestPeriods().forEach(interestPeriod -> {
interestPeriod.setRateFactor(calculateRateFactorPerPeriod(scheduleModel, repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.setRateFactor(calculateRateFactorPerPeriod(scheduleModel, repaymentPeriod, interestPeriod.getFromDate(),
interestPeriod.getDueDate(), false)); interestPeriod.getDueDate()));
interestPeriod.setRateFactorTillPeriodDueDate(calculateRateFactorPerPeriod(scheduleModel, repaymentPeriod, interestPeriod.setRateFactorTillPeriodDueDate(calculateRateFactorPerPeriodForInterest(scheduleModel, repaymentPeriod,
interestPeriod.getFromDate(), repaymentPeriod.getDueDate(), true)); 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 * Calculate Rate Factor for an exact Period
*/ */
private BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel, private BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel,
final RepaymentPeriod repaymentPeriod, final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate, final RepaymentPeriod repaymentPeriod, final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) {
final boolean isTillDate) {
final MathContext mc = scheduleModel.mc(); final MathContext mc = scheduleModel.mc();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate), final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate),
@@ -441,7 +550,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType(); final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
final BigDecimal repaymentEvery = BigDecimal.valueOf(loanProductRelatedDetail.getRepayEvery()); 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 daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(interestPeriodFromDate));
final BigDecimal actualDaysInPeriod = BigDecimal final BigDecimal actualDaysInPeriod = BigDecimal
.valueOf(DateUtils.getDifferenceInDays(interestPeriodFromDate, interestPeriodDueDate)); .valueOf(DateUtils.getDifferenceInDays(interestPeriodFromDate, interestPeriodDueDate));
@@ -449,26 +557,24 @@ public final class ProgressiveEMICalculator implements EMICalculator {
.valueOf(DateUtils.getDifferenceInDays(repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate())); .valueOf(DateUtils.getDifferenceInDays(repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate()));
final int numberOfYearsDifferenceInPeriod = interestPeriodDueDate.getYear() - interestPeriodFromDate.getYear(); final int numberOfYearsDifferenceInPeriod = interestPeriodDueDate.getYear() - interestPeriodFromDate.getYear();
final boolean partialPeriodCalculationNeeded = daysInYearType == DaysInYearType.ACTUAL && numberOfYearsDifferenceInPeriod > 0; final boolean partialPeriodCalculationNeeded = daysInYearType == DaysInYearType.ACTUAL && numberOfYearsDifferenceInPeriod > 0;
final BigDecimal daysInMonth = daysInMonthType.isDaysInMonth_30() ? BigDecimal.valueOf(30) : calculatedDaysInPeriod;
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);
// TODO check: loanApplicationTerms.calculatePeriodsBetweenDates(startDate, endDate); // calculate period data // TODO check: loanApplicationTerms.calculatePeriodsBetweenDates(startDate, endDate); // calculate period data
// TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30) // TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30)
// TODO refactor this logic to represent in interest period // TODO refactor this logic to represent in interest period
if (partialPeriodCalculationNeeded) { if (partialPeriodCalculationNeeded) {
final BigDecimal cumulatedPeriodFractions = calculatePeriodFractions(interestPeriodFromDate, interestPeriodDueDate, mc); 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); BigDecimal.ONE, mc);
} }
return calculateRateFactorPerPeriodBasedOnRepaymentFrequency(interestRate, repaymentFrequency, repaymentEvery, daysInMonth, return switch (daysInMonthType) {
daysInYear, actualDaysInPeriod, calculatedDaysInPeriod.subtract(BigDecimal.valueOf(addedDays), mc), mc); 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);
};
} }
/** /**
@@ -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.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; 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.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.InterestPeriod; 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.AfterAll;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith; 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 MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN);
private static LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = Mockito private static LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = Mockito
.mock(LoanProductMinimumRepaymentScheduleRelatedDetail.class); .mock(LoanProductMinimumRepaymentScheduleRelatedDetail.class);
private static LoanTermVariationsDataWrapper loanTermVariations = Mockito.mock(LoanTermVariationsDataWrapper.class);
private static final CurrencyData currency = new CurrencyData("USD", "USD", 2, 1, "$", "USD"); 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)); moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
} }
@BeforeEach
public void setup() {
Mockito.when(loanTermVariations.getDueDateVariation()).thenReturn(new ArrayList<>());
}
@AfterAll @AfterAll
public static void tearDown() { public static void tearDown() {
threadLocalContextUtil.close(); threadLocalContextUtil.close();
@@ -178,7 +170,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
Assertions.assertTrue(interestScheduleModel != null); Assertions.assertTrue(interestScheduleModel != null);
Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail() != null); Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail() != null);
@@ -217,7 +209,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -261,7 +253,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -297,7 +289,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -333,7 +325,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -817,7 +809,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
Money disbursedAmount = toMoney(100); Money disbursedAmount = toMoney(100);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -866,7 +858,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
Money disbursedAmount = toMoney(100.0); Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -915,7 +907,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
Money disbursedAmount = toMoney(100.0); Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount);
@@ -945,10 +937,10 @@ class ProgressiveEMICalculatorTest {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of( final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
repayment(1, LocalDate.of(2023, 12, 12), LocalDate.of(2024, 1, 12)), 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(2, LocalDate.of(2024, 1, 12), LocalDate.of(2024, 2, 12)),
repayment(3, LocalDate.of(2024, 2, 12), LocalDate.of(2024, 3, 1)), repayment(3, LocalDate.of(2024, 2, 12), LocalDate.of(2024, 3, 12)),
repayment(4, LocalDate.of(2024, 3, 12), LocalDate.of(2024, 4, 1)), repayment(4, LocalDate.of(2024, 3, 12), LocalDate.of(2024, 4, 12)),
repayment(5, LocalDate.of(2024, 4, 12), LocalDate.of(2024, 5, 1)), repayment(5, LocalDate.of(2024, 4, 12), LocalDate.of(2024, 5, 12)),
repayment(6, LocalDate.of(2024, 5, 12), LocalDate.of(2024, 6, 1))); repayment(6, LocalDate.of(2024, 5, 12), LocalDate.of(2024, 6, 12)));
final BigDecimal interestRate = BigDecimal.valueOf(9.4822); final BigDecimal interestRate = BigDecimal.valueOf(9.4822);
final Integer installmentAmountInMultiplesOf = null; final Integer installmentAmountInMultiplesOf = null;
@@ -961,7 +953,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount);
@@ -997,7 +989,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( 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)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), toMoney(300.0));
Assertions.assertEquals(6.15, toDouble(interestSchedule.getTotalDueInterest())); Assertions.assertEquals(6.15, toDouble(interestSchedule.getTotalDueInterest()));
@@ -1039,7 +1031,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(1000.0); final Money disbursedAmount = toMoney(1000.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -1072,7 +1064,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -1105,7 +1097,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -1132,13 +1124,13 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate);
Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); 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.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.DAYS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -1153,7 +1145,7 @@ class ProgressiveEMICalculatorTest {
} }
@Test @Test
public void soma_test_disbursedAmt1000_dayInYears360_daysInMonthDoesntMatter_repayEvery15Days() { public void test_disbursedAmt1000_dayInYears360_daysInMonthDoesntMatter_repayEvery15Days() {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of( final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = List.of(
repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)),
@@ -1180,7 +1172,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(1000.0); final Money disbursedAmount = toMoney(1000.0);
LocalDate disbursementDate = LocalDate.of(2024, 1, 1); LocalDate disbursementDate = LocalDate.of(2024, 1, 1);
@@ -1228,7 +1220,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(1000.0); final Money disbursedAmount = toMoney(1000.0);
emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestModel, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -1290,7 +1282,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount1st = toMoney(1000.0); final Money disbursedAmount1st = toMoney(1000.0);
final Money disbursedAmount2nd = toMoney(1000.0); final Money disbursedAmount2nd = toMoney(1000.0);
@@ -1363,7 +1355,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -1401,7 +1393,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); 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.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1475,6 +1468,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1513,6 +1507,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1551,6 +1546,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1589,6 +1585,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1630,6 +1627,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1674,6 +1672,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1714,6 +1713,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc);
@@ -1751,24 +1751,24 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); 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(), // new LoanTermVariationsData(1L, LoanTermVariationType.DUE_DATE.getValue(), //
LocalDate.of(2024, 2, 1), null, // LocalDate.of(2024, 2, 1), null, //
LocalDate.of(2024, 3, 1), false)))); LocalDate.of(2024, 3, 1), false));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariationsData, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); 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, 0, 17.01, 0.0, 0.0, 1.17, 15.84, 84.16);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 1.13, 15.88, 84.12); 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.60); 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, 50.98); 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.27); 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.46); checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20, 16.81, 17.50);
checkPeriod(interestSchedule, 5, 0, 17.56, 0.005833333333, 0.10, 17.46, 0.0); checkPeriod(interestSchedule, 5, 0, 17.60, 0.005833333333, 0.10, 17.50, 0.0);
} }
@Test @Test
@@ -1790,27 +1790,27 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); 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(), // new LoanTermVariationsData(1L, LoanTermVariationType.DUE_DATE.getValue(), //
LocalDate.of(2024, 2, 1), null, // LocalDate.of(2024, 2, 1), null, //
LocalDate.of(2024, 3, 1), false), // LocalDate.of(2024, 3, 1), false), //
new LoanTermVariationsData(2L, LoanTermVariationType.DUE_DATE.getValue(), // new LoanTermVariationsData(2L, LoanTermVariationType.DUE_DATE.getValue(), //
LocalDate.of(2024, 5, 1), null, // LocalDate.of(2024, 5, 1), null, //
LocalDate.of(2024, 6, 1), false)))); LocalDate.of(2024, 6, 1), false));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariationsData, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); 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, 0, 17.01, 0.0, 0.0, 1.17, 15.84, 84.16);
checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 1.13, 15.88, 84.12); 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.60); checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.49, 16.52, 67.64);
checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.80, 16.21, 51.39); 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.68); 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.87); checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20, 16.81, 17.90);
checkPeriod(interestSchedule, 5, 0, 17.97, 0.005833333333, 0.10, 17.87, 0.0); checkPeriod(interestSchedule, 5, 0, 18.00, 0.005833333333, 0.10, 17.90, 0.0);
} }
@Test @Test
@@ -1832,24 +1832,388 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); 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(), // new LoanTermVariationsData(1L, LoanTermVariationType.DUE_DATE.getValue(), //
LocalDate.of(2024, 3, 1), null, // LocalDate.of(2024, 3, 1), null, //
LocalDate.of(2024, 4, 15), false)))); LocalDate.of(2024, 4, 15), false));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariationsData, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); 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, 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, 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, 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.19); 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.48); 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.67); checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.20, 16.81, 17.63);
checkPeriod(interestSchedule, 5, 0, 17.77, 0.005833333333, 0.10, 17.67, 0.0); 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) { private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) {