FINERACT-1968: Progressive Loan schedule handling - interest handling (FLAT interest, Same as repayment period, Equal installments)

This commit is contained in:
Soma Sörös
2025-07-09 14:39:18 +02:00
committed by Adam Saghy
parent 3f359bb368
commit 40f8b59ab4
21 changed files with 710 additions and 77 deletions
@@ -290,10 +290,13 @@ public final class LoanApplicationTerms {
this.buyDownFeeCalculationType = builder.buyDownFeeCalculationType;
this.buyDownFeeStrategy = builder.buyDownFeeStrategy;
this.buyDownFeeIncomeType = builder.buyDownFeeIncomeType;
this.interestMethod = builder.interestMethod;
this.allowPartialPeriodInterestCalcualtion = builder.allowPartialPeriodInterestCalculation;
}
public static class Builder {
private InterestMethod interestMethod;
private CurrencyData currency;
private Integer loanTermFrequency;
private PeriodFrequencyType loanTermPeriodFrequencyType;
@@ -327,6 +330,12 @@ public final class LoanApplicationTerms {
private LoanBuyDownFeeCalculationType buyDownFeeCalculationType;
private LoanBuyDownFeeStrategy buyDownFeeStrategy;
private LoanBuyDownFeeIncomeType buyDownFeeIncomeType;
private boolean allowPartialPeriodInterestCalculation;
public Builder interestMethod(InterestMethod interestMethod) {
this.interestMethod = interestMethod;
return this;
}
public Builder currency(CurrencyData currency) {
this.currency = currency;
@@ -491,6 +500,12 @@ public final class LoanApplicationTerms {
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
return this;
}
public Builder allowPartialPeriodInterestCalculation(boolean allowPartialPeriodInterestCalculation) {
this.allowPartialPeriodInterestCalculation = allowPartialPeriodInterestCalculation;
return this;
}
}
public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData modelData, MathContext mc) {
@@ -518,7 +533,8 @@ public final class LoanApplicationTerms {
.isDownPaymentEnabled(modelData.downPaymentEnabled()).downPaymentPercentage(downPaymentPercentage)
.submittedOnDate(modelData.scheduleGenerationStartDate()).seedDate(seedDate)
.interestRecognitionOnDisbursementDate(modelData.interestRecognitionOnDisbursementDate())
.daysInYearCustomStrategy(modelData.daysInYearCustomStrategy()).mc(mc).build();
.daysInYearCustomStrategy(modelData.daysInYearCustomStrategy()).interestMethod(modelData.interestMethod())
.allowPartialPeriodInterestCalculation(modelData.allowPartialPeriodInterestCalculation()).mc(mc).build();
}
public static LoanApplicationTerms assembleFrom(final CurrencyData currency, final Integer loanTermFrequency,
@@ -526,7 +542,7 @@ public final class LoanApplicationTerms {
final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType,
final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod,
final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualNominalInterestRate,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalculation,
final Money principalMoney, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate,
final LocalDate calculatedRepaymentsStartingFromDate, final Integer graceOnPrincipalPayment,
final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
@@ -561,7 +577,7 @@ public final class LoanApplicationTerms {
return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery,
repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod,
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate,
allowPartialPeriodInterestCalculation, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate,
calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods,
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
disbursementDatas, maxOutstandingBalance, graceOnArrearsAgeing, daysInMonthType, daysInYearType,
@@ -608,7 +624,7 @@ public final class LoanApplicationTerms {
final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType();
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail
.getInterestCalculationPeriodMethod();
final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion();
final boolean allowPartialPeriodInterestCalculation = loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation();
final Money principalMoney = loanProductRelatedDetail.getPrincipal();
//
@@ -636,7 +652,7 @@ public final class LoanApplicationTerms {
return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery,
repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod, interestMethod,
interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate,
allowPartialPeriodInterestCalculation, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate,
calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods,
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
disbursementDatas, maxOutstandingBalance, loanProductRelatedDetail.getGraceOnArrearsAgeing(), daysInMonthType,
@@ -1674,7 +1690,7 @@ public final class LoanApplicationTerms {
interestCalculationPeriodMethod, daysInYearType, daysInMonthType, amortizationMethod, repaymentPeriodFrequencyType,
repaymentEvery, numberOfRepayments,
isInterestChargedFromDateSameAsDisbursalDateEnabled != null && isInterestChargedFromDateSameAsDisbursalDateEnabled,
daysInYearCustomStrategy);
daysInYearCustomStrategy, allowPartialPeriodInterestCalcualtion);
}
public Integer getLoanTermFrequency() {
@@ -27,11 +27,13 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
public record LoanRepaymentScheduleModelData(@NotNull LocalDate scheduleGenerationStartDate, @NotNull CurrencyData currency,
@NotNull BigDecimal disbursementAmount, @NotNull LocalDate disbursementDate, @NotNull int numberOfRepayments,
@NotNull int repaymentFrequency, @NotBlank String repaymentFrequencyType, @NotNull BigDecimal annualNominalInterestRate,
@NotNull boolean downPaymentEnabled, @NotNull DaysInMonthType daysInMonth, @NotNull DaysInYearType daysInYear,
BigDecimal downPaymentPercentage, Integer installmentAmountInMultiplesOf, Integer fixedLength,
@NotNull Boolean interestRecognitionOnDisbursementDate, @Nullable DaysInYearCustomStrategyType daysInYearCustomStrategy) {
@NotNull Boolean interestRecognitionOnDisbursementDate, @Nullable DaysInYearCustomStrategyType daysInYearCustomStrategy,
@NotNull InterestMethod interestMethod, @NotNull boolean allowPartialPeriodInterestCalculation) {
}
@@ -48,6 +48,7 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
private final Integer numberOfRepayments;
private final boolean interestRecognitionOnDisbursementDate;
private final DaysInYearCustomStrategyType daysInYearCustomStrategy;
private final boolean allowPartialPeriodInterestCalculation;
public LoanProductRelatedDetailMinimumData(CurrencyData currency, BigDecimal interestRatePerPeriod,
BigDecimal annualNominalInterestRate, Integer interestChargingGrace, Integer interestPaymentGrace, Integer principalGrace,
@@ -55,7 +56,7 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
InterestCalculationPeriodMethod interestCalculationPeriodMethod, DaysInYearType daysInYearType, DaysInMonthType daysInMonthType,
AmortizationMethod amortizationMethod, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaymentEvery,
Integer numberOfRepayments, boolean interestRecognitionOnDisbursementDate,
DaysInYearCustomStrategyType daysInYearCustomStrategy) {
DaysInYearCustomStrategyType daysInYearCustomStrategy, boolean allowPartialPeriodInterestCalculation) {
this.currency = currency;
this.interestRatePerPeriod = interestRatePerPeriod;
this.annualNominalInterestRate = annualNominalInterestRate;
@@ -73,6 +74,7 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
this.numberOfRepayments = numberOfRepayments;
this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
this.allowPartialPeriodInterestCalculation = allowPartialPeriodInterestCalculation;
}
private Integer defaultToNullIfZero(final Integer value) {
@@ -83,6 +85,11 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
return defaultTo;
}
@Override
public boolean isAllowPartialPeriodInterestCalculation() {
return allowPartialPeriodInterestCalculation;
}
@Override
public CurrencyData getCurrencyData() {
return currency;
@@ -53,4 +53,8 @@ public enum InterestCalculationPeriodMethod {
return this.value.equals(InterestCalculationPeriodMethod.DAILY.getValue());
}
public boolean isSameAsRepaymentPeriod() {
return this.value.equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue());
}
}
@@ -52,4 +52,8 @@ public enum InterestMethod {
public boolean isDecliningBalance() {
return this.value.equals(InterestMethod.DECLINING_BALANCE.getValue());
}
public boolean isFlat() {
return this.value.equals(InterestMethod.FLAT.getValue());
}
}
@@ -48,6 +48,8 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
InterestCalculationPeriodMethod getInterestCalculationPeriodMethod();
boolean isAllowPartialPeriodInterestCalculation();
Integer getRepayEvery();
PeriodFrequencyType getRepaymentPeriodFrequencyType();
@@ -86,7 +86,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
private InterestCalculationPeriodMethod interestCalculationPeriodMethod;
@Column(name = "allow_partial_period_interest_calcualtion", nullable = false)
private boolean allowPartialPeriodInterestCalcualtion;
private boolean allowPartialPeriodInterestCalculation;
@Column(name = "repay_every", nullable = false)
private Integer repayEvery;
@@ -248,7 +248,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
public LoanProductRelatedDetail(final MonetaryCurrency currency, final BigDecimal defaultPrincipal,
final BigDecimal defaultNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType,
final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalculation,
final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfRepayments,
final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods,
final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final AmortizationMethod amortizationMethod,
@@ -272,7 +272,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.annualNominalInterestRate = defaultAnnualNominalInterestRate;
this.interestMethod = interestMethod;
this.interestCalculationPeriodMethod = interestCalculationPeriodMethod;
this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion;
this.allowPartialPeriodInterestCalculation = allowPartialPeriodInterestCalculation;
this.repayEvery = repayEvery;
this.repaymentPeriodFrequencyType = repaymentFrequencyType;
this.numberOfRepayments = defaultNumberOfRepayments;