FINERACT-2359: Wrong Days in Year configuration used for calculating interest

This commit is contained in:
kengneruphine
2025-10-06 20:00:27 +02:00
committed by Arnold Gálovics
parent e0e4415727
commit 8e3de2b72a
4 changed files with 27 additions and 23 deletions
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -27,11 +28,12 @@ import org.springframework.stereotype.Component;
public class AprCalculator { public class AprCalculator {
public BigDecimal calculateFrom(final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal interestRatePerPeriod, public BigDecimal calculateFrom(final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal interestRatePerPeriod,
final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType) { final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType,
final DaysInYearType daysInYearType) {
BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO; BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO;
switch (interestPeriodFrequencyType) { switch (interestPeriodFrequencyType) {
case DAYS: case DAYS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(365)); defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(daysInYearType.getValue()));
break; break;
case WEEKS: case WEEKS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(52)); defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(52));
@@ -48,7 +50,7 @@ public class AprCalculator {
switch (repaymentPeriodFrequencyType) { switch (repaymentPeriodFrequencyType) {
case DAYS: case DAYS:
defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(365)); defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(daysInYearType.getValue()));
break; break;
case WEEKS: case WEEKS:
defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(52)); defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(52));
@@ -219,6 +219,20 @@ public class LoanScheduleAssembler {
BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element); .extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element);
/**
* Interest recalculation settings copy from product definition
*/
final DaysInMonthType daysInMonthType = loanProduct.fetchDaysInMonthType();
DaysInYearType daysInYearType = null;
final Integer daysInYearTypeIntFromApplication = this.fromApiJsonHelper
.extractIntegerNamed(LoanApiConstants.daysInYearTypeParameterName, element, Locale.getDefault());
if (daysInYearTypeIntFromApplication != null) {
daysInYearType = DaysInYearType.fromInt(daysInYearTypeIntFromApplication);
} else {
daysInYearType = loanProduct.fetchDaysInYearType();
}
// interest terms // interest terms
final Integer interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element); final Integer interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element);
final InterestMethod interestMethod = allowOverridingInterestMethod ? InterestMethod.fromInt(interestType) final InterestMethod interestMethod = allowOverridingInterestMethod ? InterestMethod.fromInt(interestType)
@@ -246,7 +260,7 @@ public class LoanScheduleAssembler {
BigDecimal annualNominalInterestRate = BigDecimal.ZERO; BigDecimal annualNominalInterestRate = BigDecimal.ZERO;
if (interestRatePerPeriod != null) { if (interestRatePerPeriod != null) {
annualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType, interestRatePerPeriod, annualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType, interestRatePerPeriod,
numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType); numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, daysInYearType);
} }
// disbursement details // disbursement details
@@ -365,20 +379,6 @@ public class LoanScheduleAssembler {
final List<DisbursementData> disbursementDatas = fetchDisbursementData(element.getAsJsonObject()); final List<DisbursementData> disbursementDatas = fetchDisbursementData(element.getAsJsonObject());
/**
* Interest recalculation settings copy from product definition
*/
final DaysInMonthType daysInMonthType = loanProduct.fetchDaysInMonthType();
DaysInYearType daysInYearType = null;
final Integer daysInYearTypeIntFromApplication = this.fromApiJsonHelper
.extractIntegerNamed(LoanApiConstants.daysInYearTypeParameterName, element, Locale.getDefault());
if (daysInYearTypeIntFromApplication != null) {
daysInYearType = DaysInYearType.fromInt(daysInYearTypeIntFromApplication);
} else {
daysInYearType = loanProduct.fetchDaysInYearType();
}
final boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled(); final boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
RecalculationFrequencyType recalculationFrequencyType = null; RecalculationFrequencyType recalculationFrequencyType = null;
CalendarInstance restCalendarInstance = null; CalendarInstance restCalendarInstance = null;
@@ -113,6 +113,10 @@ public class LoanProductAssembler {
Integer minimumGapBetweenInstallments = null; Integer minimumGapBetweenInstallments = null;
Integer maximumGapBetweenInstallments = null; Integer maximumGapBetweenInstallments = null;
// Declaring this variable here to be used throughout the file
final DaysInYearType daysInYearType = DaysInYearType
.fromInt(command.integerValueOfParameterNamed(LoanProductConstants.DAYS_IN_YEAR_TYPE_PARAMETER_NAME));
final Integer repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery"); final Integer repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery");
final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments"); final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments");
final Boolean isLinkedToFloatingInterestRates = command.booleanObjectValueOfParameterNamed("isLinkedToFloatingInterestRates"); final Boolean isLinkedToFloatingInterestRates = command.booleanObjectValueOfParameterNamed("isLinkedToFloatingInterestRates");
@@ -129,7 +133,7 @@ public class LoanProductAssembler {
minInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("minInterestRatePerPeriod"); minInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("minInterestRatePerPeriod");
maxInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("maxInterestRatePerPeriod"); maxInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("maxInterestRatePerPeriod");
annualInterestRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, numberOfRepayments, annualInterestRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, numberOfRepayments,
repaymentEvery, repaymentFrequencyType); repaymentEvery, repaymentFrequencyType, daysInYearType);
} }
@@ -204,9 +208,6 @@ public class LoanProductAssembler {
final DaysInMonthType daysInMonthType = DaysInMonthType final DaysInMonthType daysInMonthType = DaysInMonthType
.fromInt(command.integerValueOfParameterNamed(LoanProductConstants.DAYS_IN_MONTH_TYPE_PARAMETER_NAME)); .fromInt(command.integerValueOfParameterNamed(LoanProductConstants.DAYS_IN_MONTH_TYPE_PARAMETER_NAME));
final DaysInYearType daysInYearType = DaysInYearType
.fromInt(command.integerValueOfParameterNamed(LoanProductConstants.DAYS_IN_YEAR_TYPE_PARAMETER_NAME));
final DaysInYearCustomStrategyType daysInYearCustomStrategy = command.enumValueOfParameterNamed( final DaysInYearCustomStrategyType daysInYearCustomStrategy = command.enumValueOfParameterNamed(
LoanProductConstants.DAYS_IN_YEAR_CUSTOM_STRATEGY_TYPE_PARAMETER_NAME, DaysInYearCustomStrategyType.class); LoanProductConstants.DAYS_IN_YEAR_CUSTOM_STRATEGY_TYPE_PARAMETER_NAME, DaysInYearCustomStrategyType.class);
@@ -373,7 +373,8 @@ public class LoanProductRelatedDetailUpdateUtil {
final AprCalculator aprCalculator) { final AprCalculator aprCalculator) {
BigDecimal annualNominalInterestRate = aprCalculator.calculateFrom(loanRepaymentScheduleDetail.getInterestPeriodFrequencyType(), BigDecimal annualNominalInterestRate = aprCalculator.calculateFrom(loanRepaymentScheduleDetail.getInterestPeriodFrequencyType(),
loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod(), loanRepaymentScheduleDetail.getNumberOfRepayments(), loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod(), loanRepaymentScheduleDetail.getNumberOfRepayments(),
loanRepaymentScheduleDetail.getRepayEvery(), loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType()); loanRepaymentScheduleDetail.getRepayEvery(), loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType(),
loanRepaymentScheduleDetail.fetchDaysInYearType());
loanRepaymentScheduleDetail.setAnnualNominalInterestRate(annualNominalInterestRate); loanRepaymentScheduleDetail.setAnnualNominalInterestRate(annualNominalInterestRate);
} }
} }