FINERACT-1981: Remove hardcoded MoneyHelper dependency during progressive loan
This commit is contained in:
committed by
Adam Saghy
parent
806ee69798
commit
a9ecdf97a4
+1
-1
@@ -176,7 +176,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf();
|
||||
final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
|
||||
ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateModel(loanProductRelatedDetail,
|
||||
installmentAmountInMultiplesOf, installments);
|
||||
installmentAmountInMultiplesOf, installments, overpaymentHolder.getMoneyObject().getMc());
|
||||
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
|
||||
changedTransactionDetail, scheduleModel);
|
||||
|
||||
|
||||
+14
-11
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Optional;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -44,9 +45,10 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
private Money disbursementAmount;
|
||||
private Money balanceCorrectionAmount;
|
||||
private Money outstandingLoanBalance;
|
||||
private final MathContext mc;
|
||||
|
||||
public InterestPeriod(RepaymentPeriod repaymentPeriod, LocalDate fromDate, LocalDate dueDate, BigDecimal rateFactor,
|
||||
Money disbursementAmount, Money balanceCorrectionAmount, Money outstandingLoanBalance) {
|
||||
Money disbursementAmount, Money balanceCorrectionAmount, Money outstandingLoanBalance, MathContext mc) {
|
||||
this.repaymentPeriod = repaymentPeriod;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
@@ -54,12 +56,13 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
this.disbursementAmount = disbursementAmount;
|
||||
this.balanceCorrectionAmount = balanceCorrectionAmount;
|
||||
this.outstandingLoanBalance = outstandingLoanBalance;
|
||||
this.mc = mc;
|
||||
}
|
||||
|
||||
public InterestPeriod(RepaymentPeriod repaymentPeriod, InterestPeriod interestPeriod) {
|
||||
public InterestPeriod(RepaymentPeriod repaymentPeriod, InterestPeriod interestPeriod, MathContext mc) {
|
||||
this(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(), interestPeriod.getRateFactor(),
|
||||
interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(),
|
||||
interestPeriod.getOutstandingLoanBalance());
|
||||
interestPeriod.getOutstandingLoanBalance(), mc);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,11 +75,11 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
}
|
||||
|
||||
public void addDisbursementAmount(final Money disbursementAmount) {
|
||||
this.disbursementAmount = MathUtil.plus(this.disbursementAmount, disbursementAmount);
|
||||
this.disbursementAmount = MathUtil.plus(this.disbursementAmount, disbursementAmount, mc);
|
||||
}
|
||||
|
||||
public Money getCalculatedDueInterest() {
|
||||
return getOutstandingLoanBalance().multipliedBy(getRateFactor());
|
||||
return getOutstandingLoanBalance().multipliedBy(getRateFactor(), mc);
|
||||
}
|
||||
|
||||
public void updateOutstandingLoanBalance() {
|
||||
@@ -86,17 +89,17 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
InterestPeriod previousInterestPeriod = previousRepaymentPeriod.get().getInterestPeriods()
|
||||
.get(previousRepaymentPeriod.get().getInterestPeriods().size() - 1);
|
||||
this.outstandingLoanBalance = previousInterestPeriod.getOutstandingLoanBalance()//
|
||||
.plus(previousInterestPeriod.getDisbursementAmount())//
|
||||
.plus(previousInterestPeriod.getBalanceCorrectionAmount())//
|
||||
.minus(previousRepaymentPeriod.get().getDuePrincipal())//
|
||||
.plus(previousRepaymentPeriod.get().getPaidPrincipal());//
|
||||
.plus(previousInterestPeriod.getDisbursementAmount(), mc)//
|
||||
.plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc)//
|
||||
.minus(previousRepaymentPeriod.get().getDuePrincipal(), mc)//
|
||||
.plus(previousRepaymentPeriod.get().getPaidPrincipal(), mc);//
|
||||
}
|
||||
} else {
|
||||
int index = getRepaymentPeriod().getInterestPeriods().indexOf(this);
|
||||
InterestPeriod previousInterestPeriod = getRepaymentPeriod().getInterestPeriods().get(index - 1);
|
||||
this.outstandingLoanBalance = previousInterestPeriod.getOutstandingLoanBalance() //
|
||||
.plus(previousInterestPeriod.getBalanceCorrectionAmount()) //
|
||||
.plus(previousInterestPeriod.getDisbursementAmount()); //
|
||||
.plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc) //
|
||||
.plus(previousInterestPeriod.getDisbursementAmount(), mc); //
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-8
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
@@ -38,33 +39,36 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
private final List<InterestRate> interestRates;
|
||||
private final LoanProductRelatedDetail loanProductRelatedDetail;
|
||||
private final Integer installmentAmountInMultiplesOf;
|
||||
private MathContext mc;
|
||||
|
||||
public ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, LoanProductRelatedDetail loanProductRelatedDetail,
|
||||
Integer installmentAmountInMultiplesOf) {
|
||||
Integer installmentAmountInMultiplesOf, MathContext mc) {
|
||||
this.repaymentPeriods = repaymentPeriods;
|
||||
this.interestRates = new ArrayList<>();
|
||||
this.loanProductRelatedDetail = loanProductRelatedDetail;
|
||||
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
|
||||
this.mc = mc;
|
||||
}
|
||||
|
||||
private ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, final List<InterestRate> interestRates,
|
||||
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf) {
|
||||
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc) {
|
||||
this.mc = mc;
|
||||
this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods);
|
||||
this.interestRates = new ArrayList<>(interestRates);
|
||||
this.loanProductRelatedDetail = loanProductRelatedDetail;
|
||||
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
|
||||
}
|
||||
|
||||
public ProgressiveLoanInterestScheduleModel deepCopy() {
|
||||
public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) {
|
||||
return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, interestRates, loanProductRelatedDetail,
|
||||
installmentAmountInMultiplesOf);
|
||||
installmentAmountInMultiplesOf, mc);
|
||||
}
|
||||
|
||||
private List<RepaymentPeriod> copyRepaymentPeriods(final List<RepaymentPeriod> repaymentPeriods) {
|
||||
final List<RepaymentPeriod> repaymentCopies = new ArrayList<>(repaymentPeriods.size());
|
||||
RepaymentPeriod previousPeriod = null;
|
||||
for (RepaymentPeriod repaymentPeriod : repaymentPeriods) {
|
||||
RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentPeriod);
|
||||
RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentPeriod, mc);
|
||||
previousPeriod = currentPeriod;
|
||||
repaymentCopies.add(currentPeriod);
|
||||
}
|
||||
@@ -175,11 +179,11 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
previousInterestPeriod.addDisbursementAmount(disbursedAmount);
|
||||
previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount);
|
||||
final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate,
|
||||
BigDecimal.ZERO, getZero(), getZero(), getZero());
|
||||
BigDecimal.ZERO, getZero(mc), getZero(mc), getZero(mc), mc);
|
||||
repaymentPeriod.getInterestPeriods().add(interestPeriod);
|
||||
}
|
||||
|
||||
private Money getZero() {
|
||||
return Money.zero(loanProductRelatedDetail.getCurrency());
|
||||
private Money getZero(MathContext mc) {
|
||||
return Money.zero(loanProductRelatedDetail.getCurrency(), mc);
|
||||
}
|
||||
}
|
||||
|
||||
+30
-25
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -54,20 +55,23 @@ public class RepaymentPeriod {
|
||||
private Memo<Money> calculatedDueInterestCalculation;
|
||||
private Memo<Money> dueInterestCalculation;
|
||||
private Memo<Money> outstandingBalanceCalculation;
|
||||
private final MathContext mc;
|
||||
|
||||
public RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi) {
|
||||
public RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc) {
|
||||
this.previous = previous;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.emi = emi;
|
||||
this.mc = mc;
|
||||
this.interestPeriods = new ArrayList<>();
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
getInterestPeriods().add(new InterestPeriod(this, getFromDate(), getDueDate(), BigDecimal.ZERO, getZero(), getZero(), getZero()));
|
||||
this.paidInterest = getZero();
|
||||
this.paidPrincipal = getZero();
|
||||
getInterestPeriods()
|
||||
.add(new InterestPeriod(this, getFromDate(), getDueDate(), BigDecimal.ZERO, getZero(mc), getZero(mc), getZero(mc), mc));
|
||||
this.paidInterest = getZero(mc);
|
||||
this.paidPrincipal = getZero(mc);
|
||||
}
|
||||
|
||||
public RepaymentPeriod(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod) {
|
||||
public RepaymentPeriod(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||
this.previous = previous;
|
||||
this.fromDate = repaymentPeriod.fromDate;
|
||||
this.dueDate = repaymentPeriod.dueDate;
|
||||
@@ -75,9 +79,10 @@ public class RepaymentPeriod {
|
||||
this.interestPeriods = new ArrayList<>();
|
||||
this.paidPrincipal = repaymentPeriod.paidPrincipal;
|
||||
this.paidInterest = repaymentPeriod.paidInterest;
|
||||
this.mc = mc;
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
|
||||
interestPeriods.add(new InterestPeriod(this, interestPeriod));
|
||||
interestPeriods.add(new InterestPeriod(this, interestPeriod, mc));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,21 +110,21 @@ public class RepaymentPeriod {
|
||||
}
|
||||
|
||||
private Money calculateCalculatedDueInterest() {
|
||||
Money calculatedDueInterest = getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(getZero(),
|
||||
Money::plus);
|
||||
Money calculatedDueInterest = getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(getZero(mc),
|
||||
(m1, m2) -> m1.plus(m2, mc));
|
||||
if (getPrevious().isPresent()) {
|
||||
calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest());
|
||||
calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest(), mc);
|
||||
}
|
||||
return calculatedDueInterest;
|
||||
}
|
||||
|
||||
private Money getZero() {
|
||||
private Money getZero(MathContext mc) {
|
||||
// EMI is always initiated
|
||||
return this.emi.zero();
|
||||
return this.emi.zero(mc);
|
||||
}
|
||||
|
||||
public Money getCalculatedDuePrincipal() {
|
||||
return getEmi().minus(getCalculatedDueInterest());
|
||||
return getEmi().minus(getCalculatedDueInterest(), mc);
|
||||
}
|
||||
|
||||
public boolean isFullyPaid() {
|
||||
@@ -138,11 +143,11 @@ public class RepaymentPeriod {
|
||||
|
||||
public Money getDuePrincipal() {
|
||||
// Due principal might be the maximum paid if there is pay-off or early repayment
|
||||
return MathUtil.max(getEmi().minus(getDueInterest()), getPaidPrincipal(), false);
|
||||
return MathUtil.max(getEmi().minus(getDueInterest(), mc), getPaidPrincipal(), false);
|
||||
}
|
||||
|
||||
public Money getUnrecognizedInterest() {
|
||||
return getCalculatedDueInterest().minus(getDueInterest());
|
||||
return getCalculatedDueInterest().minus(getDueInterest(), mc);
|
||||
}
|
||||
|
||||
public Money getOutstandingLoanBalance() {
|
||||
@@ -150,22 +155,22 @@ public class RepaymentPeriod {
|
||||
outstandingBalanceCalculation = Memo.of(() -> {
|
||||
InterestPeriod lastInstallmentPeriod = getInterestPeriods().get(getInterestPeriods().size() - 1);
|
||||
Money calculatedOutStandingLoanBalance = lastInstallmentPeriod.getOutstandingLoanBalance() //
|
||||
.plus(lastInstallmentPeriod.getBalanceCorrectionAmount()) //
|
||||
.plus(lastInstallmentPeriod.getDisbursementAmount()) //
|
||||
.minus(getDuePrincipal())//
|
||||
.plus(getPaidPrincipal());//
|
||||
return MathUtil.negativeToZero(calculatedOutStandingLoanBalance);
|
||||
.plus(lastInstallmentPeriod.getBalanceCorrectionAmount(), mc) //
|
||||
.plus(lastInstallmentPeriod.getDisbursementAmount(), mc) //
|
||||
.minus(getDuePrincipal(), mc)//
|
||||
.plus(getPaidPrincipal(), mc);//
|
||||
return MathUtil.negativeToZero(calculatedOutStandingLoanBalance, mc);
|
||||
}, () -> new Object[] { paidPrincipal, paidInterest, interestPeriods });
|
||||
}
|
||||
return outstandingBalanceCalculation.get();
|
||||
}
|
||||
|
||||
public void addPaidPrincipalAmount(Money paidPrincipal) {
|
||||
this.paidPrincipal = MathUtil.plus(this.paidPrincipal, paidPrincipal);
|
||||
this.paidPrincipal = MathUtil.plus(this.paidPrincipal, paidPrincipal, mc);
|
||||
}
|
||||
|
||||
public void addPaidInterestAmount(Money paidInterest) {
|
||||
this.paidInterest = MathUtil.plus(this.paidInterest, paidInterest);
|
||||
this.paidInterest = MathUtil.plus(this.paidInterest, paidInterest, mc);
|
||||
}
|
||||
|
||||
public Money getInitialBalanceForEmiRecalculation() {
|
||||
@@ -173,10 +178,10 @@ public class RepaymentPeriod {
|
||||
if (getPrevious().isPresent()) {
|
||||
initialBalance = getPrevious().get().getOutstandingLoanBalance();
|
||||
} else {
|
||||
initialBalance = getZero();
|
||||
initialBalance = getZero(mc);
|
||||
}
|
||||
Money totalDisbursedAmount = getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount).reduce(getZero(),
|
||||
Money::plus);
|
||||
return initialBalance.add(totalDisbursedAmount);
|
||||
Money totalDisbursedAmount = getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount).reduce(getZero(mc),
|
||||
(m1, m2) -> m1.plus(m2, mc));
|
||||
return initialBalance.add(totalDisbursedAmount, mc);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-17
@@ -87,17 +87,17 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
: loanApplicationTerms.getSubmittedOnDate();
|
||||
|
||||
final LoanScheduleParams scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
|
||||
Money.of(currency, chargesDueAtTimeOfDisbursement), periodStartDate, Money.zero(currency));
|
||||
Money.of(currency, chargesDueAtTimeOfDisbursement, mc), periodStartDate, Money.zero(currency, mc), mc);
|
||||
|
||||
// charges which depends on total loan interest will be added to this
|
||||
// set and handled separately after all installments generated
|
||||
final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges);
|
||||
|
||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator
|
||||
.generateRepaymentPeriods(periodStartDate, loanApplicationTerms, holidayDetailDTO);
|
||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc,
|
||||
periodStartDate, loanApplicationTerms, holidayDetailDTO);
|
||||
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generateInterestScheduleModel(
|
||||
expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetail(),
|
||||
loanApplicationTerms.getInstallmentAmountInMultiplesOf());
|
||||
loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
|
||||
final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size());
|
||||
|
||||
prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms);
|
||||
@@ -110,7 +110,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
scheduleParams.setActualRepaymentDate(repaymentPeriod.getDueDate());
|
||||
|
||||
processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods,
|
||||
chargesDueAtTimeOfDisbursement, false);
|
||||
chargesDueAtTimeOfDisbursement, false, mc);
|
||||
repaymentPeriod.setPeriodNumber(scheduleParams.getInstalmentNumber());
|
||||
|
||||
if (loanApplicationTerms.getLoanTermVariations() != null) {
|
||||
@@ -135,7 +135,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
scheduleParams.addTotalCumulativePrincipal(principalDue);
|
||||
scheduleParams.addTotalCumulativeInterest(interestDue);
|
||||
// add everything
|
||||
scheduleParams.addTotalRepaymentExpected(principalDue.plus(interestDue));
|
||||
scheduleParams.addTotalRepaymentExpected(principalDue.plus(interestDue, mc));
|
||||
});
|
||||
|
||||
applyChargesForCurrentPeriod(repaymentPeriod, loanCharges, scheduleParams, currency, mc);
|
||||
@@ -147,7 +147,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods,
|
||||
chargesDueAtTimeOfDisbursement, true);
|
||||
chargesDueAtTimeOfDisbursement, true, mc);
|
||||
}
|
||||
|
||||
// determine fees and penalties for charges which depends on total
|
||||
@@ -158,8 +158,8 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
final BigDecimal totalOutstanding = BigDecimal.ZERO;
|
||||
|
||||
return LoanScheduleModel.from(periods, applicationCurrency, interestScheduleModel.getLoanTermInDays(),
|
||||
scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()),
|
||||
scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(),
|
||||
scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount(), mc),
|
||||
scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount(), mc).getAmount(),
|
||||
totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(),
|
||||
scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(),
|
||||
scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding);
|
||||
@@ -167,7 +167,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
public LoanScheduleModel generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) {
|
||||
|
||||
LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(modelData);
|
||||
LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(modelData, mc);
|
||||
|
||||
return generate(mc, loanApplicationTerms, null, null);
|
||||
}
|
||||
@@ -184,7 +184,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
private void processDisbursements(final LoanApplicationTerms loanApplicationTerms,
|
||||
final ArrayList<DisbursementData> disbursementDataList, final LoanScheduleParams scheduleParams,
|
||||
final ProgressiveLoanInterestScheduleModel interestScheduleModel, final List<LoanScheduleModelPeriod> periods,
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement, final boolean includeDisbursementsAfterMaturityDate) {
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement, final boolean includeDisbursementsAfterMaturityDate, final MathContext mc) {
|
||||
|
||||
for (DisbursementData disbursementData : disbursementDataList) {
|
||||
final LocalDate disbursementDate = disbursementData.disbursementDate();
|
||||
@@ -202,7 +202,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
Money outstandingBalance = emiCalculator.getOutstandingLoanBalance(interestScheduleModel, periodDueDate, disbursementDate);
|
||||
|
||||
final Money disbursedAmount = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal());
|
||||
final Money disbursedAmount = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal(), mc);
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||
.disbursement(disbursementData.disbursementDate(), disbursedAmount, chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
@@ -218,7 +218,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
Money downPaymentAmount = Money.zero(loanApplicationTerms.getCurrency());
|
||||
Money downPaymentAmount = Money.zero(loanApplicationTerms.getCurrency(), mc);
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(), MathUtil.percentageOf(disbursedAmount.getAmount(),
|
||||
loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19));
|
||||
@@ -321,7 +321,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
|
||||
final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
|
||||
final MathContext mc) {
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
Money cumulative = Money.zero(monetaryCurrency, mc);
|
||||
if (loanCharges != null) {
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
|
||||
@@ -356,7 +356,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
|
||||
final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
|
||||
final MathContext mc) {
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
Money cumulative = Money.zero(monetaryCurrency, mc);
|
||||
if (loanCharges != null) {
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isPenaltyCharge()) {
|
||||
@@ -407,8 +407,8 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges, MathContext mc) {
|
||||
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
|
||||
if (loanScheduleModelPeriod.isRepaymentPeriod()) {
|
||||
PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()),
|
||||
Money.of(currency, loanScheduleModelPeriod.interestDue()), null);
|
||||
PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue(), mc),
|
||||
Money.of(currency, loanScheduleModelPeriod.interestDue(), mc), null);
|
||||
Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
|
||||
loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
|
||||
scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
|
||||
|
||||
+3
-2
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.portfolio.loanproduct.calc;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -33,10 +34,10 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail
|
||||
public interface EMICalculator {
|
||||
|
||||
ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(List<LoanScheduleModelRepaymentPeriod> periods,
|
||||
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf);
|
||||
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc);
|
||||
|
||||
ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail,
|
||||
Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods);
|
||||
Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods, MathContext mc);
|
||||
|
||||
Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate);
|
||||
|
||||
|
||||
+82
-74
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.portfolio.loanproduct.calc;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Year;
|
||||
import java.util.ArrayList;
|
||||
@@ -29,7 +30,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
|
||||
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
@@ -40,12 +40,10 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLo
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.RepaymentPeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@DependsOn("moneyHelper")
|
||||
public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
private static final BigDecimal DIVISOR_100 = new BigDecimal("100");
|
||||
@@ -53,19 +51,19 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
@Override
|
||||
public ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(final List<LoanScheduleModelRepaymentPeriod> periods,
|
||||
final LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf) {
|
||||
final Money zeroAmount = Money.zero(loanProductRelatedDetail.getCurrency());
|
||||
final LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, final MathContext mc) {
|
||||
final Money zeroAmount = Money.zero(loanProductRelatedDetail.getCurrency(), mc);
|
||||
final ArrayList<RepaymentPeriod> interestRepaymentModelList = new ArrayList<>(periods.size());
|
||||
RepaymentPeriod previousPeriod = null;
|
||||
for (final LoanScheduleModelRepaymentPeriod period : periods) {
|
||||
RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, period.periodFromDate(), period.periodDueDate(),
|
||||
zeroAmount);
|
||||
RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, period.periodFromDate(), period.periodDueDate(), zeroAmount,
|
||||
mc);
|
||||
previousPeriod = currentPeriod;
|
||||
interestRepaymentModelList.add(currentPeriod);
|
||||
|
||||
}
|
||||
return new ProgressiveLoanInterestScheduleModel(interestRepaymentModelList, loanProductRelatedDetail,
|
||||
installmentAmountInMultiplesOf);
|
||||
installmentAmountInMultiplesOf, mc);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -85,7 +83,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
final Money disbursedAmount) {
|
||||
scheduleModel
|
||||
.changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount,
|
||||
Money.zero(disbursedAmount.getCurrency()))
|
||||
Money.zero(disbursedAmount.getCurrency(), scheduleModel.mc()))
|
||||
.ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors(repaymentPeriod.getDueDate(), scheduleModel));
|
||||
}
|
||||
|
||||
@@ -130,7 +128,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
@Override
|
||||
public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
final LocalDate repaymentPeriodDueDate, final LocalDate targetDate) {
|
||||
RepaymentPeriod repaymentPeriod = scheduleModel.deepCopy().repaymentPeriods().stream()
|
||||
MathContext mc = scheduleModel.mc();
|
||||
RepaymentPeriod repaymentPeriod = scheduleModel.deepCopy(mc).repaymentPeriods().stream()
|
||||
.filter(rp -> rp.getDueDate().equals(repaymentPeriodDueDate)).findFirst().orElseThrow();
|
||||
LocalDate adjustedTargetDate = targetDate;
|
||||
InterestPeriod interestPeriod;
|
||||
@@ -151,19 +150,19 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
// TODO: gather all the unrecognized interest from previous periods based on target date
|
||||
Money payableInterest = targetDate.isBefore(repaymentPeriod.getFromDate())
|
||||
? Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency())
|
||||
? Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc)
|
||||
: repaymentPeriod.getDueInterest();
|
||||
Money outstandingLoanBalance = interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount());
|
||||
|
||||
Money calculatedEmi = outstandingLoanBalance.plus(payableInterest);
|
||||
Money calculatedEmi = outstandingLoanBalance.plus(payableInterest, mc);
|
||||
if (calculatedEmi.isLessThan(repaymentPeriod.getEmi())) {
|
||||
// Review this logic
|
||||
repaymentPeriod.setEmi(outstandingLoanBalance.plus(payableInterest).plus(repaymentPeriod.getPaidInterest())
|
||||
.plus(repaymentPeriod.getPaidPrincipal()));
|
||||
repaymentPeriod.setEmi(outstandingLoanBalance.plus(payableInterest).plus(repaymentPeriod.getPaidInterest(), mc)
|
||||
.plus(repaymentPeriod.getPaidPrincipal(), mc));
|
||||
}
|
||||
Money payablePrincipal = repaymentPeriod.getEmi().minus(payableInterest);
|
||||
Money payablePrincipal = repaymentPeriod.getEmi().minus(payableInterest, mc);
|
||||
return new PayableDetails(repaymentPeriod.getEmi(), payablePrincipal, payableInterest,
|
||||
interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount()));
|
||||
interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount(), mc));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -189,18 +188,19 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
}
|
||||
|
||||
private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
MathContext mc = scheduleModel.mc();
|
||||
Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest)
|
||||
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency()), Money::plus); // 1.46
|
||||
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 1.46
|
||||
Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmi)
|
||||
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency()), Money::plus); // 101.48
|
||||
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 101.48
|
||||
Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream()
|
||||
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
|
||||
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency()), Money::plus); // 100
|
||||
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 100
|
||||
|
||||
Money diff = totalDisbursedAmount.plus(totalDueInterest).minus(totalEMI);
|
||||
Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc);
|
||||
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
|
||||
.reduce((first, second) -> second);
|
||||
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff)));
|
||||
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc)));
|
||||
}
|
||||
|
||||
private void calculateOutstandingBalance(ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
@@ -209,24 +209,26 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
final List<RepaymentPeriod> relatedRepaymentPeriods) {
|
||||
MathContext mc = scheduleModel.mc();
|
||||
final Money emiDifference = getDifferenceBetweenLastTwoPeriod(relatedRepaymentPeriods, scheduleModel);
|
||||
final int numberOfRelatedPeriods = relatedRepaymentPeriods.size();
|
||||
double lowerHalfOfRelatedPeriods = Math.floor(numberOfRelatedPeriods / 2.0);
|
||||
if (emiDifference.isZero() || lowerHalfOfRelatedPeriods == 0.0) {
|
||||
if (emiDifference.isZero(mc) || lowerHalfOfRelatedPeriods == 0.0) {
|
||||
return;
|
||||
}
|
||||
final Money originalEmi = relatedRepaymentPeriods.get(numberOfRelatedPeriods - 2).getEmi();
|
||||
boolean shouldBeAdjusted = emiDifference.abs().multipliedBy(100)
|
||||
.isGreaterThan(Money.of(originalEmi.getCurrency(), BigDecimal.valueOf(lowerHalfOfRelatedPeriods)));
|
||||
boolean shouldBeAdjusted = emiDifference.abs(mc).multipliedBy(100, mc)
|
||||
.isGreaterThan(Money.of(originalEmi.getCurrency(), BigDecimal.valueOf(lowerHalfOfRelatedPeriods), mc));
|
||||
|
||||
if (shouldBeAdjusted) {
|
||||
Money adjustment = emiDifference.dividedBy(numberOfRelatedPeriods, MoneyHelper.getMathContext().getRoundingMode());
|
||||
Money adjustedEqualMonthlyInstallmentValue = applyInstallmentAmountInMultiplesOf(scheduleModel, originalEmi.plus(adjustment));
|
||||
Money adjustment = emiDifference.dividedBy(numberOfRelatedPeriods, mc.getRoundingMode(), mc);
|
||||
Money adjustedEqualMonthlyInstallmentValue = applyInstallmentAmountInMultiplesOf(scheduleModel,
|
||||
originalEmi.plus(adjustment, mc));
|
||||
if (adjustedEqualMonthlyInstallmentValue.isEqualTo(originalEmi)) {
|
||||
return;
|
||||
}
|
||||
final LocalDate relatedPeriodsFirstDueDate = relatedRepaymentPeriods.get(0).getDueDate();
|
||||
final ProgressiveLoanInterestScheduleModel newScheduleModel = scheduleModel.deepCopy();
|
||||
final ProgressiveLoanInterestScheduleModel newScheduleModel = scheduleModel.deepCopy(mc);
|
||||
newScheduleModel.repaymentPeriods().forEach(period -> {
|
||||
if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)) {
|
||||
period.setEmi(adjustedEqualMonthlyInstallmentValue);
|
||||
@@ -235,7 +237,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
calculateOutstandingBalance(newScheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(newScheduleModel);
|
||||
final Money newEmiDifference = getDifferenceBetweenLastTwoPeriod(newScheduleModel.repaymentPeriods(), scheduleModel);
|
||||
final boolean newEmiHasLessDifference = newEmiDifference.abs().isLessThan(emiDifference.abs());
|
||||
final boolean newEmiHasLessDifference = newEmiDifference.abs(mc).isLessThan(emiDifference.abs(mc));
|
||||
if (!newEmiHasLessDifference) {
|
||||
return;
|
||||
}
|
||||
@@ -263,8 +265,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
*
|
||||
* @return Rate Interest Rate in fraction format
|
||||
*/
|
||||
BigDecimal calcNominalInterestRatePercentage(final BigDecimal interestRate) {
|
||||
return MathUtil.nullToZero(interestRate).divide(DIVISOR_100, MoneyHelper.getMathContext());
|
||||
BigDecimal calcNominalInterestRatePercentage(final BigDecimal interestRate, MathContext mc) {
|
||||
return MathUtil.nullToZero(interestRate).divide(DIVISOR_100, mc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,8 +288,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
*/
|
||||
BigDecimal calculateRateFactorPerPeriod(final RepaymentPeriod repaymentPeriod, final InterestPeriod interestPeriod,
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
final MathContext mc = scheduleModel.mc();
|
||||
final LoanProductRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
|
||||
final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriod.getFromDate()));
|
||||
final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriod.getFromDate()),
|
||||
scheduleModel.mc());
|
||||
final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
|
||||
final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
|
||||
final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
|
||||
@@ -306,13 +310,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
// 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(interestPeriod);
|
||||
final BigDecimal cumulatedPeriodFractions = calculatePeriodFractions(interestPeriod, mc);
|
||||
return rateFactorByRepaymentPartialPeriod(interestRate, repaymentEvery, cumulatedPeriodFractions, BigDecimal.ONE,
|
||||
BigDecimal.ONE);
|
||||
BigDecimal.ONE, mc);
|
||||
}
|
||||
|
||||
return calculateRateFactorPerPeriodBasedOnRepaymentFrequency(interestRate, repaymentFrequency, repaymentEvery, daysInMonth,
|
||||
daysInYear, actualDaysInPeriod, calculatedDaysInPeriod);
|
||||
daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,7 +325,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
* @param interestPeriod
|
||||
* @return
|
||||
*/
|
||||
BigDecimal calculatePeriodFractions(InterestPeriod interestPeriod) {
|
||||
BigDecimal calculatePeriodFractions(InterestPeriod interestPeriod, MathContext mc) {
|
||||
BigDecimal cumulatedRateFactor = BigDecimal.ZERO;
|
||||
int actualYear = interestPeriod.getFromDate().getYear();
|
||||
int endYear = interestPeriod.getDueDate().getYear();
|
||||
@@ -332,8 +336,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
endOfActualYear = actualYear == endYear ? interestPeriod.getDueDate() : LocalDate.of(actualYear, 12, 31);
|
||||
BigDecimal numberOfDaysInYear = BigDecimal.valueOf(Year.of(actualYear).length());
|
||||
BigDecimal calculatedDaysInActualYear = BigDecimal.valueOf(DateUtils.getDifferenceInDays(actualDate, endOfActualYear));
|
||||
cumulatedRateFactor = cumulatedRateFactor
|
||||
.add(calculatedDaysInActualYear.divide(numberOfDaysInYear, MoneyHelper.getMathContext()), MoneyHelper.getMathContext());
|
||||
cumulatedRateFactor = cumulatedRateFactor.add(calculatedDaysInActualYear.divide(numberOfDaysInYear, mc), mc);
|
||||
actualDate = endOfActualYear;
|
||||
actualYear++;
|
||||
}
|
||||
@@ -354,14 +357,15 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
*/
|
||||
BigDecimal calculateRateFactorPerPeriodBasedOnRepaymentFrequency(final BigDecimal interestRate,
|
||||
final PeriodFrequencyType repaymentFrequency, final BigDecimal repaymentEvery, final BigDecimal daysInMonth,
|
||||
final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod) {
|
||||
final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
|
||||
final MathContext mc) {
|
||||
return switch (repaymentFrequency) {
|
||||
case DAYS ->
|
||||
rateFactorByRepaymentEveryDay(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod);
|
||||
rateFactorByRepaymentEveryDay(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc);
|
||||
case WEEKS ->
|
||||
rateFactorByRepaymentEveryWeek(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod);
|
||||
rateFactorByRepaymentEveryWeek(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc);
|
||||
case MONTHS -> rateFactorByRepaymentEveryMonth(interestRate, repaymentEvery, daysInMonth, daysInYear, actualDaysInPeriod,
|
||||
calculatedDaysInPeriod);
|
||||
calculatedDaysInPeriod, mc);
|
||||
default -> throw new UnsupportedOperationException("Invalid repayment frequency"); // not supported yet
|
||||
};
|
||||
}
|
||||
@@ -370,14 +374,15 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
if (repaymentPeriods.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods));
|
||||
final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods));
|
||||
final MathContext mc = scheduleModel.mc();
|
||||
final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods, mc));
|
||||
final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods, mc));
|
||||
final RepaymentPeriod startPeriod = repaymentPeriods.get(0);
|
||||
// TODO: double check
|
||||
final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation();
|
||||
|
||||
final Money equalMonthlyInstallment = Money.of(outstandingBalance.getCurrency(),
|
||||
calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult));
|
||||
calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult, mc), mc);
|
||||
final Money finalEqualMonthlyInstallment = applyInstallmentAmountInMultiplesOf(scheduleModel, equalMonthlyInstallment);
|
||||
|
||||
repaymentPeriods.forEach(period -> period.setEmi(finalEqualMonthlyInstallment));
|
||||
@@ -392,39 +397,40 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
Money getDifferenceBetweenLastTwoPeriod(final List<RepaymentPeriod> repaymentPeriods,
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
MathContext mc = scheduleModel.mc();
|
||||
int numberOfUpcomingPeriods = repaymentPeriods.size();
|
||||
if (numberOfUpcomingPeriods < 2) {
|
||||
return Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());
|
||||
return Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc);
|
||||
}
|
||||
final RepaymentPeriod lastPeriod = repaymentPeriods.get(numberOfUpcomingPeriods - 1);
|
||||
final RepaymentPeriod penultimatePeriod = repaymentPeriods.get(numberOfUpcomingPeriods - 2);
|
||||
return lastPeriod.getEmi().minus(penultimatePeriod.getEmi());
|
||||
return lastPeriod.getEmi().minus(penultimatePeriod.getEmi(), mc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Rate Factor Product from rate factors
|
||||
*/
|
||||
BigDecimal calculateRateFactorPlus1N(final List<RepaymentPeriod> periods) {
|
||||
BigDecimal calculateRateFactorPlus1N(final List<RepaymentPeriod> periods, MathContext mc) {
|
||||
return periods.stream().map(RepaymentPeriod::getRateFactorPlus1).reduce(BigDecimal.ONE,
|
||||
(BigDecimal acc, BigDecimal value) -> acc.multiply(value, MoneyHelper.getMathContext()));
|
||||
(BigDecimal acc, BigDecimal value) -> acc.multiply(value, mc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize Fn values
|
||||
*/
|
||||
BigDecimal calculateFnResult(final List<RepaymentPeriod> periods) {
|
||||
BigDecimal calculateFnResult(final List<RepaymentPeriod> periods, final MathContext mc) {
|
||||
return periods.stream()//
|
||||
.skip(1)//
|
||||
.map(RepaymentPeriod::getRateFactorPlus1)//
|
||||
.reduce(BigDecimal.ONE, this::fnValue);//
|
||||
.reduce(BigDecimal.ONE, (previousFnValue, currentRateFactor) -> fnValue(previousFnValue, currentRateFactor, mc));//
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the EMI (Equal Monthly Installment) value
|
||||
*/
|
||||
BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final BigDecimal outstandingBalanceForRest, final BigDecimal fnResult) {
|
||||
return rateFactorPlus1N.multiply(outstandingBalanceForRest, MoneyHelper.getMathContext()).divide(fnResult,
|
||||
MoneyHelper.getMathContext());
|
||||
BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final BigDecimal outstandingBalanceForRest, final BigDecimal fnResult,
|
||||
MathContext mc) {
|
||||
return rateFactorPlus1N.multiply(outstandingBalanceForRest, mc).divide(fnResult, mc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -452,9 +458,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
* @return Rate Factor for period
|
||||
*/
|
||||
BigDecimal rateFactorByRepaymentEveryDay(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInYear,
|
||||
final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod) {
|
||||
final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, MathContext mc) {
|
||||
return rateFactorByRepaymentPeriod(interestRate, BigDecimal.ONE, repaymentEvery, daysInYear, actualDaysInPeriod,
|
||||
calculatedDaysInPeriod);
|
||||
calculatedDaysInPeriod, mc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -482,9 +488,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
* @return Rate Factor for period
|
||||
*/
|
||||
BigDecimal rateFactorByRepaymentEveryWeek(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInYear,
|
||||
final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod) {
|
||||
final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, MathContext mc) {
|
||||
return rateFactorByRepaymentPeriod(interestRate, ONE_WEEK_IN_DAYS, repaymentEvery, daysInYear, actualDaysInPeriod,
|
||||
calculatedDaysInPeriod);
|
||||
calculatedDaysInPeriod, mc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -515,9 +521,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
* @return Rate Factor for period
|
||||
*/
|
||||
BigDecimal rateFactorByRepaymentEveryMonth(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInMonth,
|
||||
final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod) {
|
||||
final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
|
||||
final MathContext mc) {
|
||||
return rateFactorByRepaymentPeriod(interestRate, daysInMonth, repaymentEvery, daysInYear, actualDaysInPeriod,
|
||||
calculatedDaysInPeriod);
|
||||
calculatedDaysInPeriod, mc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -549,14 +556,14 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
*/
|
||||
BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigDecimal repaymentPeriodMultiplierInDays,
|
||||
final BigDecimal repaymentEvery, final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod,
|
||||
final BigDecimal calculatedDaysInPeriod) {
|
||||
final BigDecimal calculatedDaysInPeriod, final MathContext mc) {
|
||||
final BigDecimal interestFractionPerPeriod = repaymentPeriodMultiplierInDays//
|
||||
.multiply(repaymentEvery, MoneyHelper.getMathContext())//
|
||||
.divide(daysInYear, MoneyHelper.getMathContext());//
|
||||
.multiply(repaymentEvery, mc)//
|
||||
.divide(daysInYear, mc);//
|
||||
return interestRate//
|
||||
.multiply(interestFractionPerPeriod, MoneyHelper.getMathContext())//
|
||||
.multiply(actualDaysInPeriod, MoneyHelper.getMathContext())//
|
||||
.divide(calculatedDaysInPeriod, MoneyHelper.getMathContext());//
|
||||
.multiply(interestFractionPerPeriod, mc)//
|
||||
.multiply(actualDaysInPeriod, mc)//
|
||||
.divide(calculatedDaysInPeriod, mc);//
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -564,12 +571,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
*
|
||||
*/
|
||||
BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, final BigDecimal repaymentEvery,
|
||||
final BigDecimal cumulatedPeriodRatio, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod) {
|
||||
final BigDecimal cumulatedPeriodRatio, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
|
||||
final MathContext mc) {
|
||||
final BigDecimal interestFractionPerPeriod = repaymentEvery.multiply(cumulatedPeriodRatio);
|
||||
return interestRate//
|
||||
.multiply(interestFractionPerPeriod, MoneyHelper.getMathContext())//
|
||||
.multiply(actualDaysInPeriod, MoneyHelper.getMathContext())//
|
||||
.divide(calculatedDaysInPeriod, MoneyHelper.getMathContext());//
|
||||
.multiply(interestFractionPerPeriod, mc)//
|
||||
.multiply(actualDaysInPeriod, mc)//
|
||||
.divide(calculatedDaysInPeriod, mc);//
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -582,13 +590,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
* @param currentRateFactor
|
||||
*
|
||||
*/
|
||||
BigDecimal fnValue(final BigDecimal previousFnValue, final BigDecimal currentRateFactor) {
|
||||
return BigDecimal.ONE.add(previousFnValue.multiply(currentRateFactor, MoneyHelper.getMathContext()), MoneyHelper.getMathContext());
|
||||
BigDecimal fnValue(final BigDecimal previousFnValue, final BigDecimal currentRateFactor, final MathContext mc) {
|
||||
return BigDecimal.ONE.add(previousFnValue.multiply(currentRateFactor, mc), mc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail,
|
||||
Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods) {
|
||||
Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods, MathContext mc) {
|
||||
List<LoanRepaymentScheduleInstallment> repaymentModelsWithoutDownPayment = repaymentPeriods.stream()
|
||||
.filter(period -> !period.isDownPayment() && !period.isAdditional()).toList();
|
||||
|
||||
@@ -596,11 +604,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
RepaymentPeriod previousPeriod = null;
|
||||
for (LoanRepaymentScheduleInstallment repaymentModel : repaymentModelsWithoutDownPayment) {
|
||||
RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentModel.getFromDate(), repaymentModel.getDueDate(),
|
||||
Money.zero(repaymentModel.getLoan().getCurrency()));
|
||||
Money.zero(repaymentModel.getLoan().getCurrency(), mc), mc);
|
||||
previousPeriod = currentPeriod;
|
||||
repaymentModels.add(currentPeriod);
|
||||
}
|
||||
|
||||
return new ProgressiveLoanInterestScheduleModel(repaymentModels, loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
return new ProgressiveLoanInterestScheduleModel(repaymentModels, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
@@ -90,11 +91,12 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
private static final MonetaryCurrency MONETARY_CURRENCY = new MonetaryCurrency("USD", 2, 1);
|
||||
private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
|
||||
private AdvancedPaymentScheduleTransactionProcessor underTest;
|
||||
private final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class);
|
||||
private static final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class);
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
|
||||
+14
-49
@@ -20,77 +20,36 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
|
||||
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LoanScheduleGeneratorTest {
|
||||
|
||||
private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator();
|
||||
private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
|
||||
private static final ApplicationCurrency APPLICATION_CURRENCY = new ApplicationCurrency("USD", "USD", 2, 1, "USD", "$");
|
||||
private static final MonetaryCurrency MONETARY_CURRENCY = MonetaryCurrency.fromApplicationCurrency(APPLICATION_CURRENCY);
|
||||
private static final BigDecimal DISBURSEMENT_AMOUNT = BigDecimal.valueOf(192.22);
|
||||
private static final BigDecimal NOMINAL_INTEREST_RATE = BigDecimal.valueOf(9.99);
|
||||
private static final int NUMBER_OF_REPAYMENTS = 6;
|
||||
private static final int REPAYMENT_FREQUENCY = 1;
|
||||
private static final String REPAYMENT_FREQUENCY_TYPE = "MONTHS";
|
||||
private static final LocalDate DISBURSEMENT_DATE = LocalDate.of(2024, 1, 15);
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void destruct() {
|
||||
moneyHelper.close();
|
||||
}
|
||||
private static final MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN);
|
||||
|
||||
@Test
|
||||
void testGenerateLoanSchedule() {
|
||||
LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), APPLICATION_CURRENCY,
|
||||
DISBURSEMENT_AMOUNT, DISBURSEMENT_DATE, NUMBER_OF_REPAYMENTS, REPAYMENT_FREQUENCY, REPAYMENT_FREQUENCY_TYPE,
|
||||
NOMINAL_INTEREST_RATE, true, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, null, null, null);
|
||||
|
||||
final MathContext mc = MoneyHelper.getMathContext();
|
||||
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
|
||||
|
||||
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
|
||||
expectedRepaymentPeriods.add(repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)));
|
||||
expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)));
|
||||
expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
|
||||
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
|
||||
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
|
||||
|
||||
ScheduledDateGenerator mockScheduledDateGenerator = Mockito.mock(ScheduledDateGenerator.class);
|
||||
ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(mockScheduledDateGenerator, emiCalculator);
|
||||
when(mockScheduledDateGenerator.generateRepaymentPeriods(any(), any(), any())).thenReturn(expectedRepaymentPeriods);
|
||||
|
||||
LoanScheduleModel loanSchedule = generator.generate(mc, modelData);
|
||||
List<LoanScheduleModelPeriod> periods = loanSchedule.getPeriods();
|
||||
List<LoanScheduleModelPeriod> periods = getLoanScheduleModelPeriods();
|
||||
|
||||
assertEquals(7, periods.size(), "Expected 7 periods including the downpayment period.");
|
||||
|
||||
@@ -111,6 +70,18 @@ class LoanScheduleGeneratorTest {
|
||||
BigDecimal.valueOf(0.27), BigDecimal.valueOf(32.87), BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
private static List<LoanScheduleModelPeriod> getLoanScheduleModelPeriods() {
|
||||
LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), APPLICATION_CURRENCY,
|
||||
DISBURSEMENT_AMOUNT, DISBURSEMENT_DATE, NUMBER_OF_REPAYMENTS, REPAYMENT_FREQUENCY, REPAYMENT_FREQUENCY_TYPE,
|
||||
NOMINAL_INTEREST_RATE, true, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, null, null, null);
|
||||
|
||||
ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||
ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator);
|
||||
|
||||
LoanScheduleModel loanSchedule = generator.generate(mc, modelData);
|
||||
return loanSchedule.getPeriods();
|
||||
}
|
||||
|
||||
private void checkPeriod(LoanScheduleModelPeriod period, int expectedPeriodNumber, LocalDate expectedFromDate,
|
||||
LocalDate expectedDueDate, BigDecimal expectedPrincipalDue, BigDecimal expectedInterestDue, BigDecimal expectedTotalDue,
|
||||
BigDecimal expectedOutstandingLoanBalance) {
|
||||
@@ -123,10 +94,4 @@ class LoanScheduleGeneratorTest {
|
||||
assertEquals(0, expectedTotalDue.compareTo(repaymentPeriod.getTotalDue().getAmount()));
|
||||
assertEquals(0, expectedOutstandingLoanBalance.compareTo(repaymentPeriod.getOutstandingLoanBalance().getAmount()));
|
||||
}
|
||||
|
||||
private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) {
|
||||
final Money zeroAmount = Money.zero(MONETARY_CURRENCY);
|
||||
return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount,
|
||||
zeroAmount, zeroAmount, false);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-21
@@ -59,6 +59,7 @@ class ProgressiveEMICalculatorTest {
|
||||
|
||||
private static MockedStatic<ThreadLocalContextUtil> threadLocalContextUtil = Mockito.mockStatic(ThreadLocalContextUtil.class);
|
||||
private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
|
||||
private static MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN);
|
||||
private static LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class);
|
||||
|
||||
private static final MonetaryCurrency monetaryCurrency = MonetaryCurrency
|
||||
@@ -95,7 +96,7 @@ class ProgressiveEMICalculatorTest {
|
||||
final BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(period.getFromDate()));
|
||||
final BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(period.getFromDate()));
|
||||
final BigDecimal rateFactor = emiCalculator.rateFactorByRepaymentEveryMonth(interestRate, BigDecimal.ONE, daysInMonth, daysInYear,
|
||||
daysInPeriod, daysInPeriod);
|
||||
daysInPeriod, daysInPeriod, mc);
|
||||
return rateFactor.setScale(12, MoneyHelper.getRoundingMode());
|
||||
}
|
||||
|
||||
@@ -145,7 +146,7 @@ class ProgressiveEMICalculatorTest {
|
||||
BigDecimal rateFactorPlus1 = getRateFactorsByMonth(daysInYearType, daysInMonthType, interestRate, period).add(BigDecimal.ONE,
|
||||
MoneyHelper.getMathContext());
|
||||
|
||||
final BigDecimal currentFnValue = emiCalculator.fnValue(previousFnValue, rateFactorPlus1);
|
||||
final BigDecimal currentFnValue = emiCalculator.fnValue(previousFnValue, rateFactorPlus1, mc);
|
||||
fnValuesCalculated.add(currentFnValue);
|
||||
|
||||
previousFnValue = currentFnValue;
|
||||
@@ -171,7 +172,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator
|
||||
.generateInterestScheduleModel(expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
.generateInterestScheduleModel(expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
Assertions.assertTrue(interestScheduleModel != null);
|
||||
Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail() != null);
|
||||
@@ -211,7 +212,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = toMoney(100.0);
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -256,7 +257,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = toMoney(100.0);
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -293,7 +294,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -330,7 +331,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -370,7 +371,7 @@ class ProgressiveEMICalculatorTest {
|
||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -414,7 +415,7 @@ class ProgressiveEMICalculatorTest {
|
||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -460,7 +461,7 @@ class ProgressiveEMICalculatorTest {
|
||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -577,7 +578,7 @@ class ProgressiveEMICalculatorTest {
|
||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -661,7 +662,7 @@ class ProgressiveEMICalculatorTest {
|
||||
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = toMoney(100.0);
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -739,7 +740,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -788,7 +789,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -837,7 +838,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount);
|
||||
@@ -881,7 +882,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount);
|
||||
@@ -915,7 +916,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(1000));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -948,7 +949,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -981,7 +982,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -1014,7 +1015,7 @@ class ProgressiveEMICalculatorTest {
|
||||
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
|
||||
|
||||
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf);
|
||||
loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
|
||||
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
|
||||
@@ -1031,7 +1032,7 @@ class ProgressiveEMICalculatorTest {
|
||||
private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) {
|
||||
final Money zeroAmount = Money.zero(monetaryCurrency);
|
||||
return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount,
|
||||
zeroAmount, zeroAmount, false);
|
||||
zeroAmount, zeroAmount, false, mc);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
Reference in New Issue
Block a user