FINERACT-1968: Progressive Loan schedule handling - interest handling (FLAT interest, Same as repayment period, Equal installments)
This commit is contained in:
+1
-1
@@ -42,7 +42,7 @@ public class InterestScheduleModelServiceGsonContext {
|
||||
public RepaymentPeriod createRepaymentPeriodInstance(Type type) {
|
||||
if (type == RepaymentPeriod.class) {
|
||||
|
||||
setPrev(RepaymentPeriod.empty(getPrev(), getMc()));
|
||||
setPrev(RepaymentPeriod.empty(getPrev(), getMc(), getLoanProductRelatedDetail()));
|
||||
return getPrev();
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported RepaymentPeriod type: " + type);
|
||||
|
||||
+105
-5
@@ -35,6 +35,7 @@ import java.util.function.Predicate;
|
||||
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.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.DaysInYearCustomStrategyType;
|
||||
@@ -91,7 +92,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
final Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
|
||||
final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>();
|
||||
List<RepaymentPeriod> repaymentPeriods = periods.stream().map(e -> {
|
||||
RepaymentPeriod rp = RepaymentPeriod.create(prev.get(), from.apply(e), to.apply(e), zero, mc);
|
||||
RepaymentPeriod rp = RepaymentPeriod.create(prev.get(), from.apply(e), to.apply(e), zero, mc, loanProductRelatedDetail);
|
||||
prev.set(rp);
|
||||
return rp;
|
||||
}).toList();
|
||||
@@ -114,10 +115,19 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
@Override
|
||||
public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate,
|
||||
final Money disbursedAmount) {
|
||||
addDisbursement(scheduleModel, EmiChangeOperation.disburse(disbursementDueDate, disbursedAmount));
|
||||
LocalDate effectiveDueDate = scheduleModel.loanProductRelatedDetail().getInterestCalculationPeriodMethod() != null
|
||||
&& scheduleModel.loanProductRelatedDetail().getInterestCalculationPeriodMethod().isSameAsRepaymentPeriod()
|
||||
&& !scheduleModel.loanProductRelatedDetail().isAllowPartialPeriodInterestCalculation()
|
||||
? scheduleModel.repaymentPeriods().stream().filter(rp -> rp.getDueDate().isAfter(disbursementDueDate)).findFirst()
|
||||
.map(RepaymentPeriod::getFromDate).orElse(disbursementDueDate)
|
||||
: disbursementDueDate;
|
||||
addDisbursement(scheduleModel, EmiChangeOperation.disburse(effectiveDueDate, disbursedAmount));
|
||||
}
|
||||
|
||||
private void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
||||
scheduleModel.repaymentPeriods().stream().filter(rp -> !operation.getSubmittedOnDate().isAfter(rp.getFromDate())).forEach(
|
||||
rp -> rp.setTotalDisbursedAmount(MathUtil.nullToZero(rp.getTotalDisbursedAmount()).add(operation.getAmount().getAmount())));
|
||||
|
||||
scheduleModel
|
||||
.changeOutstandingBalanceAndUpdateInterestPeriods(operation.getSubmittedOnDate(), operation.getAmount(),
|
||||
scheduleModel.zero(), scheduleModel.zero())
|
||||
@@ -444,11 +454,33 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
return scheduleModelCopy;
|
||||
}
|
||||
|
||||
private void calculateEMIValueAndRateFactorsForFlatInterestMethod(final LocalDate calculateFromRepaymentPeriodDueDate,
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
||||
final List<RepaymentPeriod> relatedRepaymentPeriods = scheduleModel.getRelatedRepaymentPeriods(calculateFromRepaymentPeriodDueDate);
|
||||
calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
|
||||
if (relatedRepaymentPeriods.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
calculateEMIOnActualModelWithFlatInterestMethod(relatedRepaymentPeriods, scheduleModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Equal Monthly Installment value and Rate Factor -1 values for calculate Interest
|
||||
*/
|
||||
private void calculateEMIValueAndRateFactors(final LocalDate calculateFromRepaymentPeriodDueDate,
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
||||
switch (scheduleModel.loanProductRelatedDetail().getInterestMethod()) {
|
||||
case FLAT ->
|
||||
calculateEMIValueAndRateFactorsForFlatInterestMethod(calculateFromRepaymentPeriodDueDate, scheduleModel, operation);
|
||||
case DECLINING_BALANCE -> calculateEMIValueAndRateFactorsForDecliningBalanceInterestMethod(calculateFromRepaymentPeriodDueDate,
|
||||
scheduleModel, operation);
|
||||
default -> throw new UnsupportedOperationException(
|
||||
"Unsupported interest method: " + scheduleModel.loanProductRelatedDetail().getInterestMethod());
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateEMIValueAndRateFactorsForDecliningBalanceInterestMethod(final LocalDate calculateFromRepaymentPeriodDueDate,
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
||||
final List<RepaymentPeriod> relatedRepaymentPeriods = scheduleModel.getRelatedRepaymentPeriods(calculateFromRepaymentPeriodDueDate);
|
||||
final boolean onlyOnActualModelShouldApply = scheduleModel.isEmpty()
|
||||
|| operation.getAction() == EmiChangeOperation.Action.INTEREST_RATE_CHANGE || scheduleModel.isCopy();
|
||||
@@ -637,6 +669,20 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
final boolean partialPeriodCalculationNeeded = daysInYearType == DaysInYearType.ACTUAL && numberOfYearsDifferenceInPeriod > 0
|
||||
&& (!DaysInYearCustomStrategyType.FEB_29_PERIOD_ONLY.equals(daysInYearCustomStrategy)
|
||||
|| isPeriodContainsFeb29(repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate()));
|
||||
final BigDecimal repaymentEvery = BigDecimal.valueOf(loanProductRelatedDetail.getRepayEvery());
|
||||
|
||||
if (loanProductRelatedDetail.getInterestCalculationPeriodMethod() != null
|
||||
&& loanProductRelatedDetail.getInterestCalculationPeriodMethod().isSameAsRepaymentPeriod()) {
|
||||
|
||||
if (loanProductRelatedDetail.getRepaymentPeriodFrequencyType().isMonthly()) {
|
||||
return rateFactorByRepaymentPeriod(interestRate, BigDecimal.ONE, repaymentEvery, BigDecimal.valueOf(12), actualDaysInPeriod,
|
||||
calculatedDaysInPeriod, mc);
|
||||
}
|
||||
if (loanProductRelatedDetail.getRepaymentPeriodFrequencyType().isWeekly()) {
|
||||
return rateFactorByRepaymentPeriod(interestRate, BigDecimal.ONE, repaymentEvery, BigDecimal.valueOf(52), actualDaysInPeriod,
|
||||
calculatedDaysInPeriod, mc);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check: loanApplicationTerms.calculatePeriodsBetweenDates(startDate, endDate); // calculate period data
|
||||
// TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30)
|
||||
@@ -747,13 +793,26 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate());
|
||||
final BigDecimal actualDaysInPeriod = BigDecimal
|
||||
.valueOf(DateUtils.getDifferenceInDays(interestPeriodFromDate, interestPeriodDueDate));
|
||||
final BigDecimal calculatedDaysInPeriod = BigDecimal
|
||||
final BigDecimal calculatedDaysInRepaymentPeriod = BigDecimal
|
||||
.valueOf(DateUtils.getDifferenceInDays(repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate()));
|
||||
final int numberOfYearsDifferenceInPeriod = interestPeriodDueDate.getYear() - interestPeriodFromDate.getYear();
|
||||
final boolean partialPeriodCalculationNeeded = daysInYearType == DaysInYearType.ACTUAL && numberOfYearsDifferenceInPeriod > 0
|
||||
&& (!DaysInYearCustomStrategyType.FEB_29_PERIOD_ONLY.equals(daysInYearCustomStrategy)
|
||||
|| isPeriodContainsFeb29(repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate()));
|
||||
final BigDecimal daysInMonth = daysInMonthType.isDaysInMonth_30() ? BigDecimal.valueOf(30) : calculatedDaysInPeriod;
|
||||
final BigDecimal daysInMonth = daysInMonthType.isDaysInMonth_30() ? BigDecimal.valueOf(30) : calculatedDaysInRepaymentPeriod;
|
||||
|
||||
if (loanProductRelatedDetail.getInterestCalculationPeriodMethod() != null
|
||||
&& loanProductRelatedDetail.getInterestCalculationPeriodMethod().isSameAsRepaymentPeriod()) {
|
||||
|
||||
if (loanProductRelatedDetail.getRepaymentPeriodFrequencyType().isMonthly()) {
|
||||
return rateFactorByRepaymentPeriod(interestRate, BigDecimal.ONE, repaymentEvery, BigDecimal.valueOf(12), actualDaysInPeriod,
|
||||
calculatedDaysInRepaymentPeriod, mc);
|
||||
}
|
||||
if (loanProductRelatedDetail.getRepaymentPeriodFrequencyType().isWeekly()) {
|
||||
return rateFactorByRepaymentPeriod(interestRate, BigDecimal.ONE, repaymentEvery, BigDecimal.valueOf(52), actualDaysInPeriod,
|
||||
calculatedDaysInRepaymentPeriod, mc);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check: loanApplicationTerms.calculatePeriodsBetweenDates(startDate, endDate); // calculate period data
|
||||
// TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30)
|
||||
@@ -769,7 +828,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
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);
|
||||
daysInMonth, daysInYear, actualDaysInPeriod, calculatedDaysInRepaymentPeriod, mc);
|
||||
default -> throw new UnsupportedOperationException("Unsupported combination: Days in month: " + daysInMonthType);
|
||||
};
|
||||
}
|
||||
@@ -845,10 +904,51 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
};
|
||||
}
|
||||
|
||||
private void calculateEMIOnActualModelWithFlatInterestMethod(List<RepaymentPeriod> repaymentPeriods,
|
||||
ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
|
||||
final MathContext mc = scheduleModel.mc();
|
||||
final CurrencyData currency = scheduleModel.loanProductRelatedDetail().getCurrencyData();
|
||||
RepaymentPeriod first = repaymentPeriods.getFirst();
|
||||
RepaymentPeriod last = repaymentPeriods.getLast();
|
||||
Money sumOfInterest = Money.zero(currency);
|
||||
for (RepaymentPeriod rp : repaymentPeriods) {
|
||||
Money interest = rp.calculateCalculatedDueInterest();
|
||||
sumOfInterest = sumOfInterest.add(interest);
|
||||
rp.setEmi(interest);
|
||||
}
|
||||
|
||||
// already repaid principals should be subtracted from total disbursed amount to calculate correct EMI.
|
||||
BigDecimal alreadyRepaidPrincipals = first.getPrevious()
|
||||
.map(rp -> rp.calculateTotalDisbursedAmountTillGivenPeriod(null).subtract(rp.getOutstandingLoanBalance().getAmount()))
|
||||
.orElse(BigDecimal.ZERO);
|
||||
Money total = Money.of(currency, first.calculateTotalDisbursedAmountTillGivenPeriod(first.getLastInterestPeriod()))
|
||||
.plus(sumOfInterest).minus(alreadyRepaidPrincipals);
|
||||
|
||||
Money periodEmi = total.dividedBy(repaymentPeriods.size(), mc);
|
||||
Money remainder = total.minus(periodEmi.multipliedBy(repaymentPeriods.size(), mc));
|
||||
|
||||
repaymentPeriods.forEach(rp -> {
|
||||
Money emi = rp.equals(last) ? periodEmi.add(remainder) : periodEmi;
|
||||
rp.setEmi(emi);
|
||||
rp.setOriginalEmi(emi);
|
||||
rp.getInterestPeriods().forEach(InterestPeriod::updateOutstandingLoanBalance);
|
||||
});
|
||||
}
|
||||
|
||||
private void calculateEMIOnActualModel(List<RepaymentPeriod> repaymentPeriods, ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
if (repaymentPeriods.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
switch (scheduleModel.loanProductRelatedDetail().getInterestMethod()) {
|
||||
case FLAT -> calculateEMIOnActualModelWithFlatInterestMethod(repaymentPeriods, scheduleModel);
|
||||
case DECLINING_BALANCE -> calculateEMIOnActualModelWithDecliningBalanceInterestMethod(repaymentPeriods, scheduleModel);
|
||||
default -> throw new UnsupportedOperationException("Unsupported interest method");
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateEMIOnActualModelWithDecliningBalanceInterestMethod(List<RepaymentPeriod> repaymentPeriods,
|
||||
ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
final MathContext mc = scheduleModel.mc();
|
||||
final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods, mc));
|
||||
final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods, mc));
|
||||
|
||||
+19
-7
@@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
||||
|
||||
@Getter
|
||||
@ToString(exclude = { "repaymentPeriod" })
|
||||
@@ -55,7 +56,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
|
||||
/** Stores credited principals. Related transactions: Chargeback or Credit Balance Refound */
|
||||
private Money creditedPrincipal;
|
||||
/** Stores credited principals. Related transaction: Chargeback */
|
||||
/** Stores credited interest. Related transaction: Chargeback */
|
||||
private Money creditedInterest;
|
||||
|
||||
private Money disbursementAmount;
|
||||
@@ -139,15 +140,26 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
}
|
||||
|
||||
long lengthTillPeriodDueDate = getLengthTillPeriodDueDate();
|
||||
final BigDecimal interestDueTillRepaymentDueDate = lengthTillPeriodDueDate == 0 //
|
||||
? BigDecimal.ZERO //
|
||||
: getOutstandingLoanBalance().getAmount() //
|
||||
.multiply(getRateFactorTillPeriodDueDate(), mc) //
|
||||
.divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) //
|
||||
.multiply(BigDecimal.valueOf(getLength()), mc); //
|
||||
final BigDecimal interestDueTillRepaymentDueDate = getCalculatedDueInterest(
|
||||
getRepaymentPeriod().getLoanProductRelatedDetail().getInterestMethod(), lengthTillPeriodDueDate); //
|
||||
return MathUtil.negativeToZero(MathUtil.add(mc, creditedInterest.getAmount(), interestDueTillRepaymentDueDate));
|
||||
}
|
||||
|
||||
public BigDecimal getCalculatedDueInterest(InterestMethod method, long lengthTillPeriodDueDate) {
|
||||
if (lengthTillPeriodDueDate == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
BigDecimal baseAmount = switch (method) {
|
||||
case FLAT -> MathUtil.nullToZero(getRepaymentPeriod().calculateTotalDisbursedAmountTillGivenPeriod(this));
|
||||
case DECLINING_BALANCE -> getOutstandingLoanBalance().getAmount();
|
||||
default -> throw new UnsupportedOperationException("Method not implemented: " + method);
|
||||
};
|
||||
return baseAmount //
|
||||
.multiply(getRateFactorTillPeriodDueDate(), mc) //
|
||||
.divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) //
|
||||
.multiply(BigDecimal.valueOf(getLength()), mc);
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
return DateUtils.getDifferenceInDays(fromDate, dueDate);
|
||||
}
|
||||
|
||||
+1
-11
@@ -199,18 +199,8 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
if (balanceChangeDate == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// TODO use isInPeriod
|
||||
return repaymentPeriods.stream()//
|
||||
.filter(repaymentPeriod -> {
|
||||
final boolean isFirstPeriod = repaymentPeriod.getPrevious().isEmpty();
|
||||
if (isFirstPeriod) {
|
||||
return !balanceChangeDate.isBefore(repaymentPeriod.getFromDate())
|
||||
&& !balanceChangeDate.isAfter(repaymentPeriod.getDueDate());
|
||||
} else {
|
||||
return balanceChangeDate.isAfter(repaymentPeriod.getFromDate())
|
||||
&& !balanceChangeDate.isAfter(repaymentPeriod.getDueDate());
|
||||
}
|
||||
})//
|
||||
.filter(period -> isInPeriod(balanceChangeDate, period.getFromDate(), period.getDueDate(), period.isFirstRepaymentPeriod()))//
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
|
||||
+46
-12
@@ -34,6 +34,7 @@ import lombok.ToString;
|
||||
import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
||||
import org.apache.fineract.portfolio.util.Memo;
|
||||
|
||||
@ToString(exclude = { "previous" })
|
||||
@@ -79,8 +80,17 @@ public final class RepaymentPeriod {
|
||||
@Setter
|
||||
private boolean isInterestMoved = false;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private BigDecimal totalDisbursedAmount;
|
||||
|
||||
@JsonExclude
|
||||
@Getter
|
||||
private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail;
|
||||
|
||||
private RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List<InterestPeriod> interestPeriods,
|
||||
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc) {
|
||||
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc,
|
||||
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) {
|
||||
this.previous = previous;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
@@ -91,16 +101,19 @@ public final class RepaymentPeriod {
|
||||
this.paidInterest = paidInterest;
|
||||
this.futureUnrecognizedInterest = futureUnrecognizedInterest;
|
||||
this.mc = mc;
|
||||
this.loanProductRelatedDetail = loanProductRelatedDetail;
|
||||
}
|
||||
|
||||
public static RepaymentPeriod empty(RepaymentPeriod previous, MathContext mc) {
|
||||
return new RepaymentPeriod(previous, null, null, new ArrayList<>(), null, null, null, null, null, mc);
|
||||
public static RepaymentPeriod empty(RepaymentPeriod previous, MathContext mc,
|
||||
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) {
|
||||
return new RepaymentPeriod(previous, null, null, new ArrayList<>(), null, null, null, null, null, mc, loanProductRelatedDetail);
|
||||
}
|
||||
|
||||
public static RepaymentPeriod create(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc) {
|
||||
public static RepaymentPeriod create(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc,
|
||||
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) {
|
||||
final Money zero = emi.zero();
|
||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero, zero,
|
||||
zero, mc);
|
||||
zero, mc, loanProductRelatedDetail);
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
newRepaymentPeriod.interestPeriods.add(InterestPeriod.withEmptyAmounts(newRepaymentPeriod, fromDate, dueDate));
|
||||
return newRepaymentPeriod;
|
||||
@@ -109,7 +122,8 @@ public final class RepaymentPeriod {
|
||||
public static RepaymentPeriod copy(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.fromDate, repaymentPeriod.dueDate,
|
||||
new ArrayList<>(), repaymentPeriod.emi, repaymentPeriod.originalEmi, repaymentPeriod.paidPrincipal,
|
||||
repaymentPeriod.paidInterest, repaymentPeriod.futureUnrecognizedInterest, mc);
|
||||
repaymentPeriod.paidInterest, repaymentPeriod.futureUnrecognizedInterest, mc,
|
||||
repaymentPeriod.getLoanProductRelatedDetail());
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
|
||||
newRepaymentPeriod.interestPeriods.add(InterestPeriod.copy(newRepaymentPeriod, interestPeriod, mc));
|
||||
@@ -120,7 +134,8 @@ public final class RepaymentPeriod {
|
||||
public static RepaymentPeriod copyWithoutPaidAmounts(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||
final Money zero = repaymentPeriod.emi.zero();
|
||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.fromDate, repaymentPeriod.dueDate,
|
||||
new ArrayList<>(), repaymentPeriod.emi, repaymentPeriod.originalEmi, zero, zero, zero, mc);
|
||||
new ArrayList<>(), repaymentPeriod.emi, repaymentPeriod.originalEmi, zero, zero, zero, mc,
|
||||
repaymentPeriod.getLoanProductRelatedDetail());
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
|
||||
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod, interestPeriod);
|
||||
@@ -160,13 +175,13 @@ public final class RepaymentPeriod {
|
||||
@NotNull
|
||||
public Money getCalculatedDueInterest() {
|
||||
if (calculatedDueInterestCalculation == null) {
|
||||
calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest,
|
||||
() -> new Object[] { this.previous, this.interestPeriods, this.futureUnrecognizedInterest, this.isInterestMoved });
|
||||
calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest, () -> new Object[] { this.previous,
|
||||
this.interestPeriods, this.futureUnrecognizedInterest, this.isInterestMoved, this.totalDisbursedAmount });
|
||||
}
|
||||
return calculatedDueInterestCalculation.get();
|
||||
}
|
||||
|
||||
private Money calculateCalculatedDueInterest() {
|
||||
public Money calculateCalculatedDueInterest() {
|
||||
Money calculatedDueInterest = getZero(mc);
|
||||
if (!isInterestMoved) {
|
||||
calculatedDueInterest = Money.of(emi.getCurrencyData(),
|
||||
@@ -192,7 +207,8 @@ public final class RepaymentPeriod {
|
||||
() -> MathUtil.max(getPaidPrincipal().isGreaterThan(getCalculatedDuePrincipal()) ? getPaidInterest()
|
||||
: MathUtil.min(getCalculatedDueInterest(), getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest(), false),
|
||||
getPaidInterest(), false),
|
||||
() -> new Object[] { paidPrincipal, paidInterest, interestPeriods, futureUnrecognizedInterest });
|
||||
() -> new Object[] { paidPrincipal, paidInterest, interestPeriods, futureUnrecognizedInterest, totalDisbursedAmount,
|
||||
emi });
|
||||
}
|
||||
return dueInterestCalculation.get();
|
||||
}
|
||||
@@ -307,7 +323,7 @@ public final class RepaymentPeriod {
|
||||
.minus(getDuePrincipal(), mc)//
|
||||
.plus(getPaidPrincipal(), mc);//
|
||||
return MathUtil.negativeToZero(calculatedOutStandingLoanBalance, mc);
|
||||
}, () -> new Object[] { paidPrincipal, paidInterest, interestPeriods });
|
||||
}, () -> new Object[] { paidPrincipal, paidInterest, interestPeriods, totalDisbursedAmount });
|
||||
}
|
||||
return outstandingBalanceCalculation.get();
|
||||
}
|
||||
@@ -369,4 +385,22 @@ public final class RepaymentPeriod {
|
||||
this.paidInterest = paidInterest.zero();
|
||||
this.paidPrincipal = paidPrincipal.zero();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tillPeriod
|
||||
* can be null. if null it calculates total disbursement including last interest period.
|
||||
* @return disbursed amount til interest period.
|
||||
*/
|
||||
public BigDecimal calculateTotalDisbursedAmountTillGivenPeriod(InterestPeriod tillPeriod) {
|
||||
BigDecimal res = MathUtil.nullToZero(getTotalDisbursedAmount());
|
||||
for (InterestPeriod interestPeriod : this.interestPeriods) {
|
||||
if (interestPeriod.equals(tillPeriod)) {
|
||||
break;
|
||||
}
|
||||
if (!interestPeriod.getDueDate().equals(getFromDate()) && interestPeriod.getDisbursementAmount() != null) {
|
||||
res = res.add(interestPeriod.getDisbursementAmount().getAmount());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user