FINERACT-2197: Introduce a new type of ACTUAL during calculation of days in year

This commit is contained in:
Soma Sörös
2025-03-14 13:01:06 +00:00
committed by Adam Saghy
parent 2ac1445dba
commit 3cf85e2977
42 changed files with 974 additions and 122 deletions
@@ -93,6 +93,7 @@ public interface LoanApiConstants {
String linkAccountIdParameterName = "linkAccountId";
String createStandingInstructionAtDisbursementParameterName = "createStandingInstructionAtDisbursement";
String daysInYearTypeParameterName = "daysInYearType";
String daysInYearCustomStrategyParameterName = "daysInYearCustomStrategy";
String daysInMonthTypeParameterName = "daysInMonthType";
String MULTIDISBURSE_DETAILS_PARAMNAME = "multiDisburseDetails";
@@ -38,6 +38,7 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
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.common.domain.NthDayType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -230,6 +231,7 @@ public final class LoanApplicationTerms {
private List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes;
private LoanChargeOffBehaviour chargeOffBehaviour;
private boolean interestRecognitionOnDisbursementDate;
private DaysInYearCustomStrategyType daysInYearCustomStrategy;
private LoanApplicationTerms(Builder builder) {
this.currency = builder.currency;
@@ -294,6 +296,7 @@ public final class LoanApplicationTerms {
private LocalDate seedDate;
private MathContext mc;
private Boolean interestRecognitionOnDisbursementDate;
private DaysInYearCustomStrategyType daysInYearCustomStrategy;
public Builder currency(CurrencyData currency) {
this.currency = currency;
@@ -413,6 +416,11 @@ public final class LoanApplicationTerms {
public LoanApplicationTerms build() {
return new LoanApplicationTerms(this);
}
public Builder daysInYearCustomStrategy(DaysInYearCustomStrategyType daysInYearCustomStrategy) {
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
return this;
}
}
public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData modelData, MathContext mc) {
@@ -439,7 +447,8 @@ public final class LoanApplicationTerms {
.inArrearsTolerance(Money.zero(modelData.currency(), mc)).disbursementDatas(new ArrayList<>())
.isDownPaymentEnabled(modelData.downPaymentEnabled()).downPaymentPercentage(downPaymentPercentage)
.submittedOnDate(modelData.scheduleGenerationStartDate()).seedDate(seedDate)
.interestRecognitionOnDisbursementDate(modelData.interestRecognitionOnDisbursementDate()).mc(mc).build();
.interestRecognitionOnDisbursementDate(modelData.interestRecognitionOnDisbursementDate())
.daysInYearCustomStrategy(modelData.daysInYearCustomStrategy()).mc(mc).build();
}
public static LoanApplicationTerms assembleFrom(final CurrencyData currency, final Integer loanTermFrequency,
@@ -470,7 +479,8 @@ public final class LoanApplicationTerms {
final LocalDate submittedOnDate, final LoanScheduleType loanScheduleType,
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
final boolean enableAccrualActivityPosting, final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate) {
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
final DaysInYearCustomStrategyType daysInYearCustomStrategy) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -490,7 +500,7 @@ public final class LoanApplicationTerms {
isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment,
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType,
fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
interestRecognitionOnDisbursementDate);
interestRecognitionOnDisbursementDate, daysInYearCustomStrategy);
}
@@ -564,7 +574,7 @@ public final class LoanApplicationTerms {
disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate,
loanScheduleType, loanScheduleProcessingType, fixedLength, loanProductRelatedDetail.isEnableAccrualActivityPosting(),
loanProductRelatedDetail.getSupportedInterestRefundTypes(), loanProductRelatedDetail.getChargeOffBehaviour(),
loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate());
loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(), loanProductRelatedDetail.getDaysInYearCustomStrategy());
}
private LoanApplicationTerms(final CurrencyData currency, final Integer loanTermFrequency,
@@ -595,7 +605,7 @@ public final class LoanApplicationTerms {
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate, final LoanScheduleType loanScheduleType,
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength, boolean enableAccrualActivityPosting,
final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes, final LoanChargeOffBehaviour chargeOffBehaviour,
final boolean interestRecognitionOnDisbursementDate) {
final boolean interestRecognitionOnDisbursementDate, final DaysInYearCustomStrategyType daysInYearCustomStrategy) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
@@ -696,6 +706,7 @@ public final class LoanApplicationTerms {
this.supportedInterestRefundTypes = supportedInterestRefundTypes;
this.chargeOffBehaviour = chargeOffBehaviour;
this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -1557,7 +1568,7 @@ public final class LoanApplicationTerms {
this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled,
this.disbursedAmountPercentageForDownPayment, this.isAutoRepaymentForDownPaymentEnabled, this.loanScheduleType,
this.loanScheduleProcessingType, this.fixedLength, this.enableAccrualActivityPosting, this.supportedInterestRefundTypes,
this.chargeOffBehaviour, this.interestRecognitionOnDisbursementDate);
this.chargeOffBehaviour, this.interestRecognitionOnDisbursementDate, this.daysInYearCustomStrategy);
}
public LoanProductMinimumRepaymentScheduleRelatedDetail toLoanProductRelatedDetailMinimumData() {
@@ -1567,7 +1578,8 @@ public final class LoanApplicationTerms {
interestPaymentGrace, principalGrace, recurringMoratoriumOnPrincipalPeriods, interestMethod,
interestCalculationPeriodMethod, daysInYearType, daysInMonthType, amortizationMethod, repaymentPeriodFrequencyType,
repaymentEvery, numberOfRepayments,
isInterestChargedFromDateSameAsDisbursalDateEnabled != null && isInterestChargedFromDateSameAsDisbursalDateEnabled);
isInterestChargedFromDateSameAsDisbursalDateEnabled != null && isInterestChargedFromDateSameAsDisbursalDateEnabled,
daysInYearCustomStrategy);
}
public Integer getLoanTermFrequency() {
@@ -18,12 +18,14 @@
*/
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDate;
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;
public record LoanRepaymentScheduleModelData(@NotNull LocalDate scheduleGenerationStartDate, @NotNull CurrencyData currency,
@@ -31,15 +33,5 @@ public record LoanRepaymentScheduleModelData(@NotNull LocalDate scheduleGenerati
@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) {
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) {
this(scheduleGenerationStartDate, currency, disbursementAmount, disbursementDate, numberOfRepayments, repaymentFrequency,
repaymentFrequencyType, annualNominalInterestRate, downPaymentEnabled, daysInMonth, daysInYear, downPaymentPercentage,
installmentAmountInMultiplesOf, fixedLength, false);
}
@NotNull Boolean interestRecognitionOnDisbursementDate, @Nullable DaysInYearCustomStrategyType daysInYearCustomStrategy) {
}
@@ -81,6 +81,7 @@ public interface LoanProductConstants {
// Interest recalculation related
String IS_INTEREST_RECALCULATION_ENABLED_PARAMETER_NAME = "isInterestRecalculationEnabled";
String DAYS_IN_YEAR_TYPE_PARAMETER_NAME = "daysInYearType";
String DAYS_IN_YEAR_CUSTOM_STRATEGY_TYPE_PARAMETER_NAME = "daysInYearCustomStrategy";
String DAYS_IN_MONTH_TYPE_PARAMETER_NAME = "daysInMonthType";
String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod";
String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod";
@@ -134,6 +134,8 @@ final class LoanProductsApiResourceSwagger {
public Integer daysInMonthType;
@Schema(example = "1")
public Integer daysInYearType;
@Schema(example = "FULL_LEAP_YEAR")
public String daysInYearCustomStrategy;
@Schema(example = "true")
public Boolean allowPartialPeriodInterestCalcualtion;
@Schema(example = "179")
@@ -457,6 +459,18 @@ final class LoanProductsApiResourceSwagger {
public String description;
}
static final class GetLoanProductsDaysInYearCustomStrategy {
private GetLoanProductsDaysInYearCustomStrategy() {}
@Schema(example = "FULL_LEAP_YEAR")
public String id;
@Schema(example = "DaysInYearCustomStrategyType.fullLeapYear")
public String code;
@Schema(example = "Full Leap Year")
public String description;
}
static final class GetLoanProductsRepaymentStartDateType {
private GetLoanProductsRepaymentStartDateType() {}
@@ -614,6 +628,7 @@ final class LoanProductsApiResourceSwagger {
public List<Integer> numberOfRepaymentVariationsForBorrowerCycle;
public GetLoansProductsDaysInMonthType daysInMonthType;
public GetLoansProductsDaysInYearType daysInYearType;
public GetLoanProductsDaysInYearCustomStrategy daysInYearCustomStrategy;
@Schema(example = "true")
public Boolean isInterestRecalculationEnabled;
public GetLoanProductsInterestRecalculationData interestRecalculationData;
@@ -722,6 +737,18 @@ final class LoanProductsApiResourceSwagger {
public String description;
}
static final class GetLoanProductsDaysInYearCustomStrategyTemplateType {
private GetLoanProductsDaysInYearCustomStrategyTemplateType() {}
@Schema(example = "FULL_LEAP_YEAR")
public String id;
@Schema(example = "DaysInYearCustomStrategyType.fullLeapYear")
public String code;
@Schema(example = "Full Leap Year")
public String description;
}
static final class GetLoanProductsInterestRecalculationTemplateData {
private GetLoanProductsInterestRecalculationTemplateData() {}
@@ -1073,6 +1100,7 @@ final class LoanProductsApiResourceSwagger {
public GetLoanProductsTemplateResponse.GetLoanProductsAccountingRule accountingRule;
public GetLoansProductsDaysInMonthTemplateType daysInMonthType;
public GetLoanProductsDaysInYearTemplateType daysInYearType;
public GetLoanProductsDaysInYearCustomStrategyTemplateType daysInYearCustomStrategy;
@Schema(example = "false")
public Boolean isInterestRecalculationEnabled;
public GetLoanProductsInterestRecalculationTemplateData interestRecalculationData;
@@ -1091,6 +1119,7 @@ final class LoanProductsApiResourceSwagger {
public Set<GetLoanProductsValueConditionTypeOptions> valueConditionTypeOptions;
public Set<GetLoansProductsDaysInMonthTemplateType> daysInMonthTypeOptions;
public Set<GetLoanProductsInterestTemplateType> daysInYearTypeOptions;
public Set<GetLoanProductsDaysInYearCustomStrategyTemplateType> daysInYearTypeCustomStrategyOptions;
public Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsInterestRecalculationCompoundingType> interestRecalculationCompoundingTypeOptions;
public Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsRescheduleStrategyType> rescheduleStrategyTypeOptions;
public Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsInterestRecalculationCompoundingFrequencyType> interestRecalculationFrequencyTypeOptions;
@@ -40,6 +40,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.calendar.data.CalendarData;
import org.apache.fineract.portfolio.charge.data.ChargeData;
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.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
@@ -235,6 +236,8 @@ public class LoanProductData implements Serializable {
private final EnumOptionData loanScheduleType;
private final EnumOptionData loanScheduleProcessingType;
private final boolean interestRecognitionOnDisbursementDate;
private final List<StringEnumOptionData> daysInYearCustomStrategyOptions;
private final StringEnumOptionData daysInYearCustomStrategy;
/**
* Used when returning lookup information about loan product for dropdowns.
@@ -334,12 +337,11 @@ public class LoanProductData implements Serializable {
final boolean enableInstallmentLevelDelinquency = false;
final EnumOptionData loanScheduleType = null;
final EnumOptionData loanScheduleProcessingType = null;
final EnumOptionData loanScheduleTypeOptions = null;
final EnumOptionData loanScheduleProcessingTypeOptions = null;
final boolean enableAccrualActivityPosting = false;
final List<StringEnumOptionData> supportedInterestRefundTypes = null;
final StringEnumOptionData chargeOffBehaviour = null;
final boolean interestRecognitionOnDisbursementDate = false;
final StringEnumOptionData daysInYearTypeCustomStrategy = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -361,7 +363,7 @@ public class LoanProductData implements Serializable {
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
interestRecognitionOnDisbursementDate);
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy);
}
@@ -465,6 +467,7 @@ public class LoanProductData implements Serializable {
final List<StringEnumOptionData> supportedInterestRefundTypes = null;
final StringEnumOptionData chargeOffBehaviour = null;
final boolean interestRecognitionOnDisbursementDate = false;
final StringEnumOptionData daysInYearTypeCustomStrategy = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -486,7 +489,7 @@ public class LoanProductData implements Serializable {
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
interestRecognitionOnDisbursementDate);
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy);
}
@@ -597,6 +600,7 @@ public class LoanProductData implements Serializable {
final List<StringEnumOptionData> supportedInterestRefundTypes = null;
final StringEnumOptionData chargeOffBehaviour = LoanChargeOffBehaviour.REGULAR.getValueAsStringEnumOptionData();
final boolean interestRecognitionOnDisbursementDate = false;
final StringEnumOptionData daysInYearTypeCustomStrategy = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -618,7 +622,7 @@ public class LoanProductData implements Serializable {
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
interestRecognitionOnDisbursementDate);
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy);
}
@@ -723,6 +727,7 @@ public class LoanProductData implements Serializable {
final List<StringEnumOptionData> supportedInterestRefundTypes = null;
final StringEnumOptionData chargeOffBehaviour = null;
final boolean interestRecognitionOnDisbursementDate = false;
final StringEnumOptionData daysInYearTypeCustomStrategy = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -744,7 +749,7 @@ public class LoanProductData implements Serializable {
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocationData, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
interestRecognitionOnDisbursementDate);
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy);
}
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
@@ -798,7 +803,8 @@ public class LoanProductData implements Serializable {
final EnumOptionData repaymentStartDateType, final boolean enableInstallmentLevelDelinquency,
final EnumOptionData loanScheduleType, final EnumOptionData loanScheduleProcessingType, final Integer fixedLength,
final boolean enableAccrualActivityPosting, final List<StringEnumOptionData> supportedInterestRefundTypes,
StringEnumOptionData chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate) {
StringEnumOptionData chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
final StringEnumOptionData daysInYearCustomStrategy) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -854,6 +860,7 @@ public class LoanProductData implements Serializable {
this.rateOptions = rateOptions;
this.rates = rates;
this.isRatesEnabled = isRatesEnabled;
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
this.chargeOptions = null;
this.penaltyOptions = null;
@@ -942,6 +949,7 @@ public class LoanProductData implements Serializable {
this.chargeOffBehaviourOptions = null;
this.chargeOffReasonOptions = null;
this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategyOptions = DaysInYearCustomStrategyType.getValuesAsStringEnumOptionDataList();
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -965,7 +973,8 @@ public class LoanProductData implements Serializable {
final List<EnumOptionData> loanScheduleProcessingTypeOptions, final List<EnumOptionData> creditAllocationTransactionTypes,
final List<EnumOptionData> creditAllocationAllocationTypes,
final List<StringEnumOptionData> supportedInterestRefundTypesOptions,
final List<StringEnumOptionData> chargeOffBehaviourOptions, final List<CodeValueData> chargeOffReasonOptions) {
final List<StringEnumOptionData> chargeOffBehaviourOptions, final List<CodeValueData> chargeOffReasonOptions,
final List<StringEnumOptionData> daysInYearCustomStrategyOptions) {
this.id = productData.id;
this.name = productData.name;
@@ -1127,6 +1136,8 @@ public class LoanProductData implements Serializable {
this.chargeOffBehaviourOptions = chargeOffBehaviourOptions;
this.chargeOffReasonOptions = chargeOffReasonOptions;
this.interestRecognitionOnDisbursementDate = productData.interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategyOptions = daysInYearCustomStrategyOptions;
this.daysInYearCustomStrategy = productData.daysInYearCustomStrategy;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanproduct.data;
import java.math.BigDecimal;
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.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
@@ -46,13 +47,15 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
private final Integer repaymentEvery;
private final Integer numberOfRepayments;
private final boolean interestRecognitionOnDisbursementDate;
private final DaysInYearCustomStrategyType daysInYearCustomStrategy;
public LoanProductRelatedDetailMinimumData(CurrencyData currency, BigDecimal interestRatePerPeriod,
BigDecimal annualNominalInterestRate, Integer interestChargingGrace, Integer interestPaymentGrace, Integer principalGrace,
Integer recurringMoratoriumOnPrincipalPeriods, InterestMethod interestMethod,
InterestCalculationPeriodMethod interestCalculationPeriodMethod, DaysInYearType daysInYearType, DaysInMonthType daysInMonthType,
AmortizationMethod amortizationMethod, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaymentEvery,
Integer numberOfRepayments, boolean interestRecognitionOnDisbursementDate) {
Integer numberOfRepayments, boolean interestRecognitionOnDisbursementDate,
DaysInYearCustomStrategyType daysInYearCustomStrategy) {
this.currency = currency;
this.interestRatePerPeriod = interestRatePerPeriod;
this.annualNominalInterestRate = annualNominalInterestRate;
@@ -69,6 +72,7 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
this.repaymentEvery = repaymentEvery;
this.numberOfRepayments = numberOfRepayments;
this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
}
private Integer defaultToNullIfZero(final Integer value) {
@@ -168,4 +172,9 @@ public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRe
public boolean isInterestRecognitionOnDisbursementDate() {
return interestRecognitionOnDisbursementDate;
}
@Override
public DaysInYearCustomStrategyType getDaysInYearCustomStrategy() {
return daysInYearCustomStrategy;
}
}
@@ -56,6 +56,7 @@ import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.charge.domain.Charge;
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.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
@@ -366,6 +367,9 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
final DaysInYearType daysInYearType = DaysInYearType
.fromInt(command.integerValueOfParameterNamed(LoanProductConstants.DAYS_IN_YEAR_TYPE_PARAMETER_NAME));
final DaysInYearCustomStrategyType daysInYearCustomStrategy = command.enumValueOfParameterNamed(
LoanProductConstants.DAYS_IN_YEAR_CUSTOM_STRATEGY_TYPE_PARAMETER_NAME, DaysInYearCustomStrategyType.class);
LoanProductInterestRecalculationDetails interestRecalculationSettings = null;
if (isInterestRecalculationEnabled) {
@@ -486,7 +490,8 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType, fixedLength,
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour, interestRecognitionOnDisbursementDate);
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour, interestRecognitionOnDisbursementDate,
daysInYearCustomStrategy);
}
@@ -705,7 +710,8 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
final boolean enableInstallmentLevelDelinquency, final LoanScheduleType loanScheduleType,
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
final boolean enableAccrualActivityPosting, final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean isInterestRecognitionOnDisbursementDate) {
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean isInterestRecognitionOnDisbursementDate,
final DaysInYearCustomStrategyType daysInYearCustomStrategy) {
this.fund = fund;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
@@ -755,7 +761,7 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(),
isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting,
supportedInterestRefundTypes, chargeOffBehaviour, isInterestRecognitionOnDisbursementDate);
supportedInterestRefundTypes, chargeOffBehaviour, isInterestRecognitionOnDisbursementDate, daysInYearCustomStrategy);
this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal,
defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments,
@@ -883,6 +889,18 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
}
if (this.getLoanProductRelatedDetail().getDaysInYearCustomStrategy() != null
&& this.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.CUMULATIVE)) {
throw new LoanProductGeneralRuleException("days.in.year.custom.strategy.is.only.supported.for.progressive.loan.schedule.type",
"daysInYearCustomStrategy is only supported for progressive loan schedule type");
}
if (this.getLoanProductRelatedDetail().getDaysInYearCustomStrategy() != null
&& !this.getLoanProductRelatedDetail().getDaysInYearType().equals(DaysInYearType.ACTUAL.getValue())) {
throw new LoanProductGeneralRuleException("days.in.year.custom.strategy.is.only.applicable.for.actual.days.in.year.type",
"daysInYearCustomStrategy is only applicable for ACTUAL days in year type");
}
}
public MonetaryCurrency getCurrency() {
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanproduct.domain;
import java.math.BigDecimal;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
/**
@@ -62,4 +63,6 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
Integer getDaysInYearType();
boolean isInterestRecognitionOnDisbursementDate();
DaysInYearCustomStrategyType getDaysInYearCustomStrategy();
}
@@ -35,6 +35,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
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.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -168,6 +169,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "interest_recognition_on_disbursement_date", nullable = false)
private boolean interestRecognitionOnDisbursementDate = false;
@Enumerated(EnumType.STRING)
@Column(name = "days_in_year_custom_strategy")
private DaysInYearCustomStrategyType daysInYearCustomStrategy;
public static LoanProductRelatedDetail createFrom(final CurrencyData currencyData, final BigDecimal principal,
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
@@ -181,7 +186,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final boolean enableAutoRepaymentForDownPayment, final LoanScheduleType loanScheduleType,
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
final boolean enableAccrualActivityPosting, final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate) {
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
final DaysInYearCustomStrategyType daysInYearCustomStrategy) {
final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(currencyData);
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
@@ -191,7 +197,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment,
loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes,
chargeOffBehaviour, interestRecognitionOnDisbursementDate);
chargeOffBehaviour, interestRecognitionOnDisbursementDate, daysInYearCustomStrategy);
}
protected LoanProductRelatedDetail() {
@@ -211,7 +217,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final boolean enableAutoRepaymentForDownPayment, final LoanScheduleType loanScheduleType,
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
final boolean enableAccrualActivityPosting, List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate) {
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
final DaysInYearCustomStrategyType daysInYearCustomStrategy) {
this.currency = currency;
this.principal = defaultPrincipal;
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
@@ -248,6 +255,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.supportedInterestRefundTypes = supportedInterestRefundTypes;
this.chargeOffBehaviour = chargeOffBehaviour;
this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
}
private Integer defaultToNullIfZero(final Integer value) {
@@ -295,6 +303,11 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
: BigDecimal.valueOf(Double.parseDouble(this.annualNominalInterestRate.stripTrailingZeros().toString()));
}
@Override
public DaysInYearCustomStrategyType getDaysInYearCustomStrategy() {
return daysInYearCustomStrategy;
}
// TODO: Move into assembler
public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) {
@@ -0,0 +1,67 @@
/**
* 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.loanproduct.domain;
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class LoanProductTest {
@Test
void testCanCreateLoanProductWithDaysInYearCustomStrategy() {
LoanProduct loanProduct = new LoanProduct();
loanProduct.setLoanProductRelatedDetail(new LoanProductRelatedDetail());
loanProduct.getLoanProductRelatedDetail().setLoanScheduleType(LoanScheduleType.PROGRESSIVE);
loanProduct.getLoanProductRelatedDetail().setDaysInYearType(1);
loanProduct.getLoanProductRelatedDetail().setDaysInYearCustomStrategy(DaysInYearCustomStrategyType.FEB_29_PERIOD_ONLY);
loanProduct.validateLoanProductPreSave();
}
@Test
void testValidationExceptionForCreateCumulativeLoanProductWithDaysInYearCustomStrategy() {
LoanProduct loanProduct = new LoanProduct();
loanProduct.setLoanProductRelatedDetail(new LoanProductRelatedDetail());
loanProduct.getLoanProductRelatedDetail().setLoanScheduleType(LoanScheduleType.CUMULATIVE);
loanProduct.getLoanProductRelatedDetail().setDaysInYearType(1);
loanProduct.getLoanProductRelatedDetail().setDaysInYearCustomStrategy(DaysInYearCustomStrategyType.FEB_29_PERIOD_ONLY);
Assertions.assertThrowsExactly(LoanProductGeneralRuleException.class, loanProduct::validateLoanProductPreSave);
}
@Test
void testValidationExceptionForCreate360LoanProductWithDaysInYearCustomStrategy() {
LoanProduct loanProduct = new LoanProduct();
loanProduct.setLoanProductRelatedDetail(new LoanProductRelatedDetail());
loanProduct.getLoanProductRelatedDetail().setLoanScheduleType(LoanScheduleType.PROGRESSIVE);
loanProduct.getLoanProductRelatedDetail().setDaysInYearType(360);
loanProduct.getLoanProductRelatedDetail().setDaysInYearCustomStrategy(DaysInYearCustomStrategyType.FEB_29_PERIOD_ONLY);
Assertions.assertThrowsExactly(LoanProductGeneralRuleException.class, loanProduct::validateLoanProductPreSave);
}
@Test
void testCanCreate360LoanProductWithoutDaysInYearCustomStrategy() {
LoanProduct loanProduct = new LoanProduct();
loanProduct.setLoanProductRelatedDetail(new LoanProductRelatedDetail());
loanProduct.getLoanProductRelatedDetail().setLoanScheduleType(LoanScheduleType.PROGRESSIVE);
loanProduct.getLoanProductRelatedDetail().setDaysInYearType(360);
loanProduct.validateLoanProductPreSave();
}
}