FINERACT-2114: Interest rate modification
This commit is contained in:
committed by
Adam Saghy
parent
80f4627623
commit
1286b1e72f
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record ProgressiveLoanInterestRate(LocalDate effectiveFrom, LocalDate validFrom,
|
||||
BigDecimal interestRate) implements Comparable<ProgressiveLoanInterestRate> {
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ProgressiveLoanInterestRate o) {
|
||||
return this.effectiveFrom().compareTo(o.effectiveFrom());
|
||||
}
|
||||
}
|
||||
+29
-2
@@ -18,13 +18,40 @@
|
||||
*/
|
||||
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;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
|
||||
public record ProgressiveLoanInterestScheduleModel(List<ProgressiveLoanInterestRepaymentModel> repayments,
|
||||
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc) {
|
||||
public record ProgressiveLoanInterestScheduleModel(List<ProgressiveLoanInterestRepaymentModel> repayments, //
|
||||
List<ProgressiveLoanInterestRate> interestRates, //
|
||||
LoanProductRelatedDetail loanProductRelatedDetail, //
|
||||
Integer installmentAmountInMultiplesOf, //
|
||||
MathContext mc) {
|
||||
|
||||
public ProgressiveLoanInterestScheduleModel(List<ProgressiveLoanInterestRepaymentModel> repayments,
|
||||
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc) {
|
||||
this(repayments, new ArrayList<>(1), loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
}
|
||||
|
||||
public void addInterestRate(final LocalDate newInterestDueDate, final BigDecimal newInterestRate) {
|
||||
interestRates.add(new ProgressiveLoanInterestRate(newInterestDueDate, newInterestDueDate.plusDays(1), newInterestRate));
|
||||
interestRates.sort(Collections.reverseOrder());
|
||||
}
|
||||
|
||||
public BigDecimal getInterestRate(final LocalDate effectiveDate) {
|
||||
return interestRates.isEmpty() ? loanProductRelatedDetail.getNominalInterestRatePerPeriod() : findInterestRate(effectiveDate);
|
||||
}
|
||||
|
||||
private BigDecimal findInterestRate(final LocalDate effectiveDate) {
|
||||
return interestRates.stream().filter(ir -> !ir.effectiveFrom().isAfter(effectiveDate))
|
||||
.map(ProgressiveLoanInterestRate::interestRate).findFirst()
|
||||
.orElse(loanProductRelatedDetail.getNominalInterestRatePerPeriod());
|
||||
}
|
||||
|
||||
public int getLoanTermInDays() {
|
||||
if (repayments.isEmpty()) {
|
||||
|
||||
+9
-1
@@ -75,7 +75,6 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
if (lastDueDateVariation != null) {
|
||||
loanEndDate = lastDueDateVariation.getDateValue();
|
||||
}
|
||||
loanApplicationTerms.updateLoanEndDate(loanEndDate);
|
||||
|
||||
// determine the total charges due at time of disbursement
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
|
||||
@@ -111,6 +110,15 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
processDisbursements(loanApplicationTerms, scheduleParams, interestScheduleModel, periods, chargesDueAtTimeOfDisbursement);
|
||||
repaymentPeriod.setPeriodNumber(scheduleParams.getInstalmentNumber());
|
||||
|
||||
for (var interestRateChange : loanApplicationTerms.getLoanTermVariations().getInterestRateFromInstallment()) {
|
||||
final LocalDate interestRateChangeEffectiveDate = interestRateChange.getTermVariationApplicableFrom().minusDays(1);
|
||||
final BigDecimal newInterestRate = interestRateChange.getDecimalValue();
|
||||
if (interestRateChangeEffectiveDate.isAfter(repaymentPeriod.getFromDate())
|
||||
&& !interestRateChangeEffectiveDate.isAfter(repaymentPeriod.getDueDate())) {
|
||||
emiCalculator.changeInterestRate(interestScheduleModel, interestRateChangeEffectiveDate, newInterestRate);
|
||||
}
|
||||
}
|
||||
|
||||
emiCalculator.findInterestRepaymentPeriod(interestScheduleModel, repaymentPeriod.getDueDate())
|
||||
.ifPresent(interestRepaymentPeriod -> {
|
||||
final Money principalDue = interestRepaymentPeriod.getPrincipalDue();
|
||||
|
||||
+4
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanproduct.calc;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
@@ -42,6 +43,9 @@ public interface EMICalculator {
|
||||
|
||||
void addDisbursement(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate disbursementDueDate, Money disbursedAmount);
|
||||
|
||||
void changeInterestRate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate newInterestEffectiveDate,
|
||||
BigDecimal newInterestRate);
|
||||
|
||||
ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(ProgressiveLoanInterestScheduleModel scheduleModel);
|
||||
|
||||
ProgressiveLoanInterestScheduleModel makeScheduleModelDeepCopy(ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
|
||||
+58
-1
@@ -97,6 +97,27 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
Optional<ProgressiveLoanInterestRepaymentInterestPeriod> findInterestPeriodForInterestChange(
|
||||
final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final LocalDate interestRateChangeEffectiveDate) {
|
||||
if (repaymentPeriod == null || interestRateChangeEffectiveDate == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return repaymentPeriod.getInterestPeriods().stream()//
|
||||
.filter(interestPeriod -> interestRateChangeEffectiveDate.isEqual(interestPeriod.getFromDate()))//
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
Optional<ProgressiveLoanInterestRepaymentModel> findInterestRepaymentPeriodForInterestChange(
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate interestChangeEffectiveDate) {
|
||||
if (scheduleModel == null || interestChangeEffectiveDate == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return scheduleModel.repayments().stream()//
|
||||
.filter(repaymentPeriod -> !interestChangeEffectiveDate.isBefore(repaymentPeriod.getFromDate())
|
||||
&& interestChangeEffectiveDate.isBefore(repaymentPeriod.getDueDate()))//
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add disbursement to Interest Period
|
||||
*/
|
||||
@@ -154,6 +175,42 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
return new ProgressiveLoanInterestScheduleModel(repayments, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeInterestRate(final ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate newInterestEffectiveDate,
|
||||
final BigDecimal newInterestRate) {
|
||||
final ProgressiveLoanInterestRepaymentModel repaymentPeriod = findInterestRepaymentPeriodForInterestChange(scheduleModel,
|
||||
newInterestEffectiveDate).orElse(null);
|
||||
if (repaymentPeriod == null) {
|
||||
return;
|
||||
}
|
||||
scheduleModel.addInterestRate(newInterestEffectiveDate, newInterestRate);
|
||||
var interestPeriodOptional = findInterestPeriodForInterestChange(repaymentPeriod, newInterestEffectiveDate);
|
||||
if (interestPeriodOptional.isEmpty()) {
|
||||
insertInterestPeriod(scheduleModel, repaymentPeriod, newInterestEffectiveDate);
|
||||
}
|
||||
|
||||
calculateEMIValueAndRateFactors(repaymentPeriod.getDueDate(), scheduleModel);
|
||||
}
|
||||
|
||||
void insertInterestPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
final ProgressiveLoanInterestRepaymentModel repaymentPeriod, final LocalDate interestChangeDueDate) {
|
||||
// period start date
|
||||
final ProgressiveLoanInterestRepaymentInterestPeriod previousInterestPeriod = repaymentPeriod.getInterestPeriods().stream()
|
||||
.filter(interestPeriod -> interestChangeDueDate.isAfter(interestPeriod.getFromDate())
|
||||
&& interestChangeDueDate.isBefore(interestPeriod.getDueDate()))//
|
||||
.findFirst()//
|
||||
.get();//
|
||||
|
||||
final Money zeroAmount = Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency());
|
||||
final var interestPeriod = new ProgressiveLoanInterestRepaymentInterestPeriod(interestChangeDueDate,
|
||||
previousInterestPeriod.getDueDate(), BigDecimal.ZERO, zeroAmount, zeroAmount);
|
||||
|
||||
previousInterestPeriod.setDueDate(interestChangeDueDate);
|
||||
|
||||
repaymentPeriod.getInterestPeriods().add(interestPeriod);
|
||||
Collections.sort(repaymentPeriod.getInterestPeriods());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Equal Monthly Installment value and Rate Factor -1 values for calculate Interest
|
||||
*/
|
||||
@@ -259,7 +316,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
final ProgressiveLoanInterestRepaymentInterestPeriod interestPeriod, final ProgressiveLoanInterestScheduleModel scheduleModel) {
|
||||
final MathContext mc = scheduleModel.mc();
|
||||
final LoanProductRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
|
||||
final BigDecimal interestRate = calcNominalInterestRatePercentage(loanProductRelatedDetail.getNominalInterestRatePerPeriod(), mc);
|
||||
final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriod.getFromDate()), mc);
|
||||
final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
|
||||
final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
|
||||
final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
|
||||
|
||||
Reference in New Issue
Block a user