FINERACT-2114: Interest rate modification

This commit is contained in:
Janos Meszaros
2024-08-22 13:37:02 +02:00
committed by Adam Saghy
parent 80f4627623
commit 1286b1e72f
8 changed files with 227 additions and 9 deletions
@@ -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());
}
}
@@ -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()) {
@@ -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();
@@ -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,
@@ -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();