FINERACT-2311: Loan product - Buy down fees configuration

This commit is contained in:
adam.magyari
2025-06-24 15:06:54 +02:00
committed by Adam Saghy
parent 29d1ed3cd4
commit 1b1d6be956
23 changed files with 708 additions and 31 deletions
@@ -174,6 +174,9 @@ public class LoanAccountData {
private List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions;
private List<StringEnumOptionData> capitalizedIncomeStrategyOptions;
private List<StringEnumOptionData> capitalizedIncomeTypeOptions;
private List<StringEnumOptionData> buyDownFeeCalculationTypeOptions;
private List<StringEnumOptionData> buyDownFeeStrategyOptions;
private List<StringEnumOptionData> buyDownFeeIncomeTypeOptions;
@Transient
private BigDecimal feeChargesAtDisbursementCharged;
@@ -281,6 +284,11 @@ public class LoanAccountData {
private StringEnumOptionData capitalizedIncomeStrategy;
private StringEnumOptionData capitalizedIncomeType;
private Boolean enableBuyDownFee;
private StringEnumOptionData buyDownFeeCalculationType;
private StringEnumOptionData buyDownFeeStrategy;
private StringEnumOptionData buyDownFeeIncomeType;
public static LoanAccountData importInstanceIndividual(EnumOptionData loanTypeEnumOption, Long clientId, Long productId,
Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments,
Integer repaymentEvery, EnumOptionData repaidEveryFrequencyEnums, Integer loanTermFrequency,
@@ -473,7 +481,9 @@ public class LoanAccountData {
final EnumOptionData loanScheduleProcessingType, final Integer fixedLength, final StringEnumOptionData chargeOffBehaviour,
final boolean isInterestRecognitionOnDisbursementDate, final StringEnumOptionData daysInYearCustomStrategy,
final boolean enableIncomeCapitalization, final StringEnumOptionData capitalizedIncomeCalculationType,
final StringEnumOptionData capitalizedIncomeStrategy, StringEnumOptionData capitalizedIncomeType) {
final StringEnumOptionData capitalizedIncomeStrategy, StringEnumOptionData capitalizedIncomeType,
final boolean enableBuyDownFee, final StringEnumOptionData buyDownFeeCalculationType,
final StringEnumOptionData buyDownFeeStrategy, final StringEnumOptionData buyDownFeeIncomeType) {
final CollectionData delinquent = CollectionData.template();
@@ -521,7 +531,9 @@ public class LoanAccountData {
.setChargeOffBehaviour(chargeOffBehaviour).setInterestRecognitionOnDisbursementDate(isInterestRecognitionOnDisbursementDate)
.setDaysInYearCustomStrategy(daysInYearCustomStrategy).setEnableIncomeCapitalization(enableIncomeCapitalization)
.setCapitalizedIncomeCalculationType(capitalizedIncomeCalculationType)
.setCapitalizedIncomeStrategy(capitalizedIncomeStrategy).setCapitalizedIncomeType(capitalizedIncomeType);
.setCapitalizedIncomeStrategy(capitalizedIncomeStrategy).setCapitalizedIncomeType(capitalizedIncomeType)
.setEnableBuyDownFee(enableBuyDownFee).setBuyDownFeeCalculationType(buyDownFeeCalculationType)
.setBuyDownFeeStrategy(buyDownFeeStrategy).setBuyDownFeeIncomeType(buyDownFeeIncomeType);
}
/*
@@ -550,7 +562,9 @@ public class LoanAccountData {
final List<StringEnumOptionData> daysInYearCustomStrategyOptions,
final List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions,
final List<StringEnumOptionData> capitalizedIncomeStrategyOptions,
final List<StringEnumOptionData> capitalizedIncomeTypeOptions) {
final List<StringEnumOptionData> capitalizedIncomeTypeOptions,
final List<StringEnumOptionData> buyDownFeeCalculationTypeOptions, final List<StringEnumOptionData> buyDownFeeStrategyOptions,
final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions) {
// TODO: why are these variables 'calendarData', 'chargeTemplate' never used (see original private constructor)
@@ -574,7 +588,9 @@ public class LoanAccountData {
.setDaysInYearCustomStrategyOptions(daysInYearCustomStrategyOptions)
.setCapitalizedIncomeCalculationTypeOptions(capitalizedIncomeCalculationTypeOptions)
.setCapitalizedIncomeStrategyOptions(capitalizedIncomeStrategyOptions)
.setCapitalizedIncomeTypeOptions(capitalizedIncomeTypeOptions);
.setCapitalizedIncomeTypeOptions(capitalizedIncomeTypeOptions)
.setBuyDownFeeCalculationTypeOptions(buyDownFeeCalculationTypeOptions)
.setBuyDownFeeStrategyOptions(buyDownFeeStrategyOptions).setBuyDownFeeIncomeTypeOptions(buyDownFeeIncomeTypeOptions);
}
public LoanAccountData associationsAndTemplate(final Collection<LoanProductData> productOptions,
@@ -0,0 +1,33 @@
/**
* 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.domain;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;
@Getter
@RequiredArgsConstructor
public enum LoanBuyDownFeeCalculationType implements ApiFacingEnum<LoanBuyDownFeeCalculationType> {
FLAT("loanBuyDownFeeCalculationType.flat", "Flat");
private final String code;
private final String humanReadableName;
}
@@ -0,0 +1,34 @@
/**
* 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.domain;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;
@Getter
@RequiredArgsConstructor
public enum LoanBuyDownFeeIncomeType implements ApiFacingEnum<LoanBuyDownFeeIncomeType> {
FEE("buyDownFee.incomeType.fee", "Fee"), //
INTEREST("buyDownFee.incomeType.interest", "Interest"); //
private final String code;
private final String humanReadableName;
}
@@ -0,0 +1,33 @@
/**
* 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.domain;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;
@Getter
@RequiredArgsConstructor
public enum LoanBuyDownFeeStrategy implements ApiFacingEnum<LoanBuyDownFeeStrategy> {
EQUAL_AMORTIZATION("buyDownFee.strategy.equalAmortization", "Equal amortization");
private final String code;
private final String humanReadableName;
}
@@ -46,6 +46,9 @@ import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -239,6 +242,10 @@ public final class LoanApplicationTerms {
private LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType;
private LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy;
private LoanCapitalizedIncomeType capitalizedIncomeType;
private boolean enableBuyDownFee;
private LoanBuyDownFeeCalculationType buyDownFeeCalculationType;
private LoanBuyDownFeeStrategy buyDownFeeStrategy;
private LoanBuyDownFeeIncomeType buyDownFeeIncomeType;
private LoanApplicationTerms(Builder builder) {
this.currency = builder.currency;
@@ -279,6 +286,10 @@ public final class LoanApplicationTerms {
this.capitalizedIncomeCalculationType = builder.capitalizedIncomeCalculationType;
this.capitalizedIncomeStrategy = builder.capitalizedIncomeStrategy;
this.capitalizedIncomeType = builder.capitalizedIncomeType;
this.enableBuyDownFee = builder.enableBuyDownFee;
this.buyDownFeeCalculationType = builder.buyDownFeeCalculationType;
this.buyDownFeeStrategy = builder.buyDownFeeStrategy;
this.buyDownFeeIncomeType = builder.buyDownFeeIncomeType;
}
public static class Builder {
@@ -312,6 +323,10 @@ public final class LoanApplicationTerms {
private LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType;
private LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy;
private LoanCapitalizedIncomeType capitalizedIncomeType;
private boolean enableBuyDownFee;
private LoanBuyDownFeeCalculationType buyDownFeeCalculationType;
private LoanBuyDownFeeStrategy buyDownFeeStrategy;
private LoanBuyDownFeeIncomeType buyDownFeeIncomeType;
public Builder currency(CurrencyData currency) {
this.currency = currency;
@@ -448,6 +463,26 @@ public final class LoanApplicationTerms {
return this;
}
public Builder enableBuyDownFee(boolean value) {
this.enableBuyDownFee = value;
return this;
}
public Builder buyDownFeeCalculationType(LoanBuyDownFeeCalculationType value) {
this.buyDownFeeCalculationType = value;
return this;
}
public Builder buyDownFeeStrategy(LoanBuyDownFeeStrategy value) {
this.buyDownFeeStrategy = value;
return this;
}
public Builder buyDownFeeIncomeType(LoanBuyDownFeeIncomeType value) {
this.buyDownFeeIncomeType = value;
return this;
}
public LoanApplicationTerms build() {
return new LoanApplicationTerms(this);
}
@@ -517,7 +552,9 @@ public final class LoanApplicationTerms {
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
final DaysInYearCustomStrategyType daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType) {
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType,
final boolean enableBuyDownFee, final LoanBuyDownFeeCalculationType buyDownFeeCalculationType,
final LoanBuyDownFeeStrategy buyDownFeeStrategy, final LoanBuyDownFeeIncomeType buyDownFeeIncomeType) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -538,7 +575,8 @@ public final class LoanApplicationTerms {
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType,
fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
interestRecognitionOnDisbursementDate, daysInYearCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType);
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType);
}
@@ -614,7 +652,9 @@ public final class LoanApplicationTerms {
loanProductRelatedDetail.getSupportedInterestRefundTypes(), loanProductRelatedDetail.getChargeOffBehaviour(),
loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(), loanProductRelatedDetail.getDaysInYearCustomStrategy(),
loanProductRelatedDetail.isEnableIncomeCapitalization(), loanProductRelatedDetail.getCapitalizedIncomeCalculationType(),
loanProductRelatedDetail.getCapitalizedIncomeStrategy(), loanProductRelatedDetail.getCapitalizedIncomeType());
loanProductRelatedDetail.getCapitalizedIncomeStrategy(), loanProductRelatedDetail.getCapitalizedIncomeType(),
loanProductRelatedDetail.isEnableBuyDownFee(), loanProductRelatedDetail.getBuyDownFeeCalculationType(),
loanProductRelatedDetail.getBuyDownFeeStrategy(), loanProductRelatedDetail.getBuyDownFeeIncomeType());
}
private LoanApplicationTerms(final CurrencyData currency, final Integer loanTermFrequency,
@@ -647,7 +687,9 @@ public final class LoanApplicationTerms {
final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes, final LoanChargeOffBehaviour chargeOffBehaviour,
final boolean interestRecognitionOnDisbursementDate, final DaysInYearCustomStrategyType daysInYearCustomStrategy,
final boolean enableIncomeCapitalization, final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType) {
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType,
final boolean enableBuyDownFee, final LoanBuyDownFeeCalculationType buyDownFeeCalculationType,
final LoanBuyDownFeeStrategy buyDownFeeStrategy, final LoanBuyDownFeeIncomeType buyDownFeeIncomeType) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
@@ -753,6 +795,10 @@ public final class LoanApplicationTerms {
this.capitalizedIncomeCalculationType = capitalizedIncomeCalculationType;
this.capitalizedIncomeStrategy = capitalizedIncomeStrategy;
this.capitalizedIncomeType = capitalizedIncomeType;
this.enableBuyDownFee = enableBuyDownFee;
this.buyDownFeeCalculationType = buyDownFeeCalculationType;
this.buyDownFeeStrategy = buyDownFeeStrategy;
this.buyDownFeeIncomeType = buyDownFeeIncomeType;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -1616,7 +1662,8 @@ public final class LoanApplicationTerms {
this.loanScheduleProcessingType, this.fixedLength, this.enableAccrualActivityPosting, this.supportedInterestRefundTypes,
this.chargeOffBehaviour, this.interestRecognitionOnDisbursementDate, this.daysInYearCustomStrategy,
this.enableIncomeCapitalization, this.capitalizedIncomeCalculationType, this.capitalizedIncomeStrategy,
this.capitalizedIncomeType, this.installmentAmountInMultiplesOf);
this.capitalizedIncomeType, this.installmentAmountInMultiplesOf, this.enableBuyDownFee, this.buyDownFeeCalculationType,
this.buyDownFeeStrategy, this.buyDownFeeIncomeType);
}
public LoanProductMinimumRepaymentScheduleRelatedDetail toLoanProductRelatedDetailMinimumData() {
@@ -177,4 +177,10 @@ public interface LoanProductConstants {
String CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME = "capitalizedIncomeCalculationType";
String CAPITALIZED_INCOME_STRATEGY_PARAM_NAME = "capitalizedIncomeStrategy";
String CAPITALIZED_INCOME_TYPE_PARAM_NAME = "capitalizedIncomeType";
// Buy down fee
String ENABLE_BUY_DOWN_FEE_PARAM_NAME = "enableBuyDownFee";
String BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME = "buyDownFeeCalculationType";
String BUY_DOWN_FEE_STRATEGY_PARAM_NAME = "buyDownFeeStrategy";
String BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME = "buyDownFeeIncomeType";
}
@@ -203,6 +203,14 @@ public final class LoanProductsApiResourceSwagger {
public String capitalizedIncomeStrategy;
@Schema(example = "FEE", allowableValues = { "FEE", "INTEREST" })
public String capitalizedIncomeType;
@Schema(example = "false")
public Boolean enableBuyDownFee;
@Schema(example = "FLAT", allowableValues = "FLAT")
public String buyDownFeeCalculationType;
@Schema(example = "EQUAL_AMORTIZATION", allowableValues = "EQUAL_AMORTIZATION")
public String buyDownFeeStrategy;
@Schema(example = "FEE", allowableValues = { "FEE", "INTEREST" })
public String buyDownFeeIncomeType;
// Interest Recalculation
@Schema(example = "false")
@@ -671,6 +679,14 @@ public final class LoanProductsApiResourceSwagger {
public StringEnumOptionData capitalizedIncomeStrategy;
@Schema(example = "FEE")
public StringEnumOptionData capitalizedIncomeType;
@Schema(example = "false")
public Boolean enableBuyDownFee;
@Schema(example = "FLAT")
public StringEnumOptionData buyDownFeeCalculationType;
@Schema(example = "EQUAL_AMORTIZATION")
public StringEnumOptionData buyDownFeeStrategy;
@Schema(example = "FEE")
public StringEnumOptionData buyDownFeeIncomeType;
}
@Schema(description = "GetLoanProductsTemplateResponse")
@@ -1128,6 +1144,18 @@ public final class LoanProductsApiResourceSwagger {
public List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions;
public List<StringEnumOptionData> capitalizedIncomeStrategyOptions;
public List<StringEnumOptionData> capitalizedIncomeTypeOptions;
@Schema(example = "false")
public Boolean enableBuyDownFee;
@Schema(example = "FLAT")
public StringEnumOptionData buyDownFeeCalculationType;
@Schema(example = "EQUAL_AMORTIZATION")
public StringEnumOptionData buyDownFeeStrategy;
@Schema(example = "FEE")
public StringEnumOptionData buyDownFeeIncomeType;
public List<StringEnumOptionData> buyDownFeeCalculationTypeOptions;
public List<StringEnumOptionData> buyDownFeeStrategyOptions;
public List<StringEnumOptionData> buyDownFeeIncomeTypeOptions;
}
@Schema(description = "GetLoanProductsProductIdResponse")
@@ -1460,6 +1488,18 @@ public final class LoanProductsApiResourceSwagger {
public List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions;
public List<StringEnumOptionData> capitalizedIncomeStrategyOptions;
public List<StringEnumOptionData> capitalizedIncomeTypeOptions;
@Schema(example = "false")
public Boolean enableBuyDownFee;
@Schema(example = "FLAT")
public StringEnumOptionData buyDownFeeCalculationType;
@Schema(example = "EQUAL_AMORTIZATION")
public StringEnumOptionData buyDownFeeStrategy;
@Schema(example = "FEE")
public StringEnumOptionData buyDownFeeIncomeType;
public List<StringEnumOptionData> buyDownFeeCalculationTypeOptions;
public List<StringEnumOptionData> buyDownFeeStrategyOptions;
public List<StringEnumOptionData> buyDownFeeIncomeTypeOptions;
}
@Schema(description = "PutLoanProductsProductIdRequest")
@@ -1727,6 +1767,14 @@ public final class LoanProductsApiResourceSwagger {
public String capitalizedIncomeStrategy;
@Schema(example = "FEE", allowableValues = { "FEE", "INTEREST" })
public String capitalizedIncomeType;
@Schema(example = "false")
public Boolean enableBuyDownFee;
@Schema(example = "FLAT", allowableValues = "FLAT")
public String buyDownFeeCalculationType;
@Schema(example = "EQUAL_AMORTIZATION", allowableValues = "EQUAL_AMORTIZATION")
public String buyDownFeeStrategy;
@Schema(example = "FEE", allowableValues = { "FEE", "INTEREST" })
public String buyDownFeeIncomeType;
}
public static final class AdvancedPaymentData {
@@ -49,6 +49,9 @@ import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
import org.apache.fineract.portfolio.fund.data.FundData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -249,6 +252,13 @@ public class LoanProductData implements Serializable {
private List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions;
private List<StringEnumOptionData> capitalizedIncomeStrategyOptions;
private List<StringEnumOptionData> capitalizedIncomeTypeOptions;
private Boolean enableBuyDownFee;
private StringEnumOptionData buyDownFeeCalculationType;
private StringEnumOptionData buyDownFeeStrategy;
private StringEnumOptionData buyDownFeeIncomeType;
private List<StringEnumOptionData> buyDownFeeCalculationTypeOptions;
private List<StringEnumOptionData> buyDownFeeStrategyOptions;
private List<StringEnumOptionData> buyDownFeeIncomeTypeOptions;
/**
* Used when returning lookup information about loan product for dropdowns.
@@ -357,6 +367,10 @@ public class LoanProductData implements Serializable {
final StringEnumOptionData capitalizedIncomeCalculationType = null;
final StringEnumOptionData capitalizedIncomeStrategy = null;
final StringEnumOptionData capitalizedIncomeType = null;
final boolean enableBuyDownFee = false;
final StringEnumOptionData buyDownFeeCalculationType = null;
final StringEnumOptionData buyDownFeeStrategy = null;
final StringEnumOptionData buyDownFeeIncomeType = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -379,7 +393,8 @@ public class LoanProductData implements Serializable {
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType);
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType);
}
@@ -488,6 +503,10 @@ public class LoanProductData implements Serializable {
final StringEnumOptionData capitalizedIncomeCalculationType = null;
final StringEnumOptionData capitalizedIncomeStrategy = null;
final StringEnumOptionData capitalizedIncomeType = null;
final boolean enableBuyDownFee = false;
final StringEnumOptionData buyDownFeeCalculationType = null;
final StringEnumOptionData buyDownFeeStrategy = null;
final StringEnumOptionData buyDownFeeIncomeType = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -510,7 +529,8 @@ public class LoanProductData implements Serializable {
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType);
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType);
}
@@ -626,6 +646,10 @@ public class LoanProductData implements Serializable {
final StringEnumOptionData capitalizedIncomeCalculationType = null;
final StringEnumOptionData capitalizedIncomeStrategy = null;
final StringEnumOptionData capitalizedIncomeType = null;
final boolean enableBuyDownFee = false;
final StringEnumOptionData buyDownFeeCalculationType = null;
final StringEnumOptionData buyDownFeeStrategy = null;
final StringEnumOptionData buyDownFeeIncomeType = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -648,7 +672,8 @@ public class LoanProductData implements Serializable {
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType);
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType);
}
@@ -758,6 +783,10 @@ public class LoanProductData implements Serializable {
final StringEnumOptionData capitalizedIncomeCalculationType = null;
final StringEnumOptionData capitalizedIncomeStrategy = null;
final StringEnumOptionData capitalizedIncomeType = null;
final boolean enableBuyDownFee = false;
final StringEnumOptionData buyDownFeeCalculationType = null;
final StringEnumOptionData buyDownFeeStrategy = null;
final StringEnumOptionData buyDownFeeIncomeType = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -780,7 +809,8 @@ public class LoanProductData implements Serializable {
paymentAllocation, creditAllocationData, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType);
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType);
}
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
@@ -837,7 +867,9 @@ public class LoanProductData implements Serializable {
StringEnumOptionData chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
final StringEnumOptionData daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
final StringEnumOptionData capitalizedIncomeCalculationType, final StringEnumOptionData capitalizedIncomeStrategy,
final StringEnumOptionData capitalizedIncomeType) {
final StringEnumOptionData capitalizedIncomeType, final boolean enableBuyDownFee,
final StringEnumOptionData buyDownFeeCalculationType, final StringEnumOptionData buyDownFeeStrategy,
final StringEnumOptionData buyDownFeeIncomeType) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -898,6 +930,10 @@ public class LoanProductData implements Serializable {
this.capitalizedIncomeCalculationType = capitalizedIncomeCalculationType;
this.capitalizedIncomeStrategy = capitalizedIncomeStrategy;
this.capitalizedIncomeType = capitalizedIncomeType;
this.enableBuyDownFee = enableBuyDownFee;
this.buyDownFeeCalculationType = buyDownFeeCalculationType;
this.buyDownFeeStrategy = buyDownFeeStrategy;
this.buyDownFeeIncomeType = buyDownFeeIncomeType;
this.chargeOptions = null;
this.penaltyOptions = null;
@@ -991,6 +1027,9 @@ public class LoanProductData implements Serializable {
.getValuesAsStringEnumOptionDataList(LoanCapitalizedIncomeCalculationType.class);
this.capitalizedIncomeStrategyOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanCapitalizedIncomeStrategy.class);
this.capitalizedIncomeTypeOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanCapitalizedIncomeType.class);
this.buyDownFeeCalculationTypeOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeCalculationType.class);
this.buyDownFeeStrategyOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class);
this.buyDownFeeIncomeTypeOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeIncomeType.class);
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -1018,7 +1057,9 @@ public class LoanProductData implements Serializable {
final List<StringEnumOptionData> daysInYearCustomStrategyOptions,
final List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions,
final List<StringEnumOptionData> capitalizedIncomeStrategyOptions,
final List<StringEnumOptionData> capitalizedIncomeTypeOptions) {
final List<StringEnumOptionData> capitalizedIncomeTypeOptions,
final List<StringEnumOptionData> buyDownFeeCalculationTypeOptions, final List<StringEnumOptionData> buyDownFeeStrategyOptions,
final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions) {
this.id = productData.id;
this.name = productData.name;
@@ -1189,6 +1230,14 @@ public class LoanProductData implements Serializable {
this.capitalizedIncomeCalculationTypeOptions = capitalizedIncomeCalculationTypeOptions;
this.capitalizedIncomeStrategyOptions = capitalizedIncomeStrategyOptions;
this.capitalizedIncomeTypeOptions = capitalizedIncomeTypeOptions;
this.enableBuyDownFee = productData.enableBuyDownFee;
this.buyDownFeeCalculationType = productData.buyDownFeeCalculationType;
this.buyDownFeeStrategy = productData.buyDownFeeStrategy;
this.buyDownFeeIncomeType = productData.buyDownFeeIncomeType;
this.buyDownFeeCalculationTypeOptions = buyDownFeeCalculationTypeOptions;
this.buyDownFeeStrategyOptions = buyDownFeeStrategyOptions;
this.buyDownFeeIncomeTypeOptions = buyDownFeeIncomeTypeOptions;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -59,6 +59,9 @@ import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -282,7 +285,9 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean isInterestRecognitionOnDisbursementDate,
final DaysInYearCustomStrategyType daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType) {
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType,
final boolean enableBuyDownFee, final LoanBuyDownFeeCalculationType buyDownFeeCalculationType,
final LoanBuyDownFeeStrategy buyDownFeeStrategy, final LoanBuyDownFeeIncomeType buyDownFeeIncomeType) {
this.fund = fund;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
@@ -334,7 +339,7 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting,
supportedInterestRefundTypes, chargeOffBehaviour, isInterestRecognitionOnDisbursementDate, daysInYearCustomStrategy,
enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType,
installmentAmountInMultiplesOf);
installmentAmountInMultiplesOf, enableBuyDownFee, buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType);
this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal,
defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments,
@@ -36,6 +36,9 @@ 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;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -186,6 +189,21 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "capitalized_income_type")
private LoanCapitalizedIncomeType capitalizedIncomeType;
@Column(name = "enable_buy_down_fee")
private boolean enableBuyDownFee = false;
@Enumerated(EnumType.STRING)
@Column(name = "buy_down_fee_calculation_type")
private LoanBuyDownFeeCalculationType buyDownFeeCalculationType;
@Enumerated(EnumType.STRING)
@Column(name = "buy_down_fee_strategy")
private LoanBuyDownFeeStrategy buyDownFeeStrategy;
@Enumerated(EnumType.STRING)
@Column(name = "buy_down_fee_income_type")
private LoanBuyDownFeeIncomeType buyDownFeeIncomeType;
@Column(name = "installment_amount_in_multiples_of")
private Integer installmentAmountInMultiplesOf;
@@ -206,7 +224,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final DaysInYearCustomStrategyType daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType,
final Integer installmentAmountInMultiplesOf) {
final Integer installmentAmountInMultiplesOf, final boolean enableBuyDownFee,
final LoanBuyDownFeeCalculationType buyDownFeeCalculationType, final LoanBuyDownFeeStrategy buyDownFeeStrategy,
final LoanBuyDownFeeIncomeType buyDownFeeIncomeType) {
final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(currencyData);
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
@@ -217,7 +237,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment,
loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes,
chargeOffBehaviour, interestRecognitionOnDisbursementDate, daysInYearCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, installmentAmountInMultiplesOf);
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, installmentAmountInMultiplesOf,
enableBuyDownFee, buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType);
}
protected LoanProductRelatedDetail() {
@@ -241,7 +262,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final DaysInYearCustomStrategyType daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType,
final Integer installmentAmountInMultiplesOf) {
final Integer installmentAmountInMultiplesOf, final boolean enableBuyDownFee,
final LoanBuyDownFeeCalculationType buyDownFeeCalculationType, final LoanBuyDownFeeStrategy buyDownFeeStrategy,
final LoanBuyDownFeeIncomeType buyDownFeeIncomeType) {
this.currency = currency;
this.principal = defaultPrincipal;
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
@@ -284,6 +307,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.capitalizedIncomeStrategy = capitalizedIncomeStrategy;
this.capitalizedIncomeType = capitalizedIncomeType;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.enableBuyDownFee = enableBuyDownFee;
this.buyDownFeeCalculationType = buyDownFeeCalculationType;
this.buyDownFeeStrategy = buyDownFeeStrategy;
this.buyDownFeeIncomeType = buyDownFeeIncomeType;
}
private Integer defaultToNullIfZero(final Integer value) {
@@ -133,6 +133,9 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -1169,7 +1172,10 @@ public class LoansApiResource {
loanTermVariations, ApiFacingEnum.getValuesAsStringEnumOptionDataList(DaysInYearCustomStrategyType.class),
ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanCapitalizedIncomeCalculationType.class),
ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanCapitalizedIncomeStrategy.class),
ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanCapitalizedIncomeType.class));
ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanCapitalizedIncomeType.class),
ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeCalculationType.class),
ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class),
ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeIncomeType.class));
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters);
@@ -1227,6 +1227,14 @@ final class LoansApiResourceSwagger {
public StringEnumOptionData capitalizedIncomeStrategy;
@Schema(example = "FEE")
public StringEnumOptionData capitalizedIncomeType;
@Schema(example = "false")
public Boolean enableBuyDownFee;
@Schema(example = "FLAT")
public StringEnumOptionData buyDownFeeCalculationType;
@Schema(example = "EQUAL_AMORTIZATION")
public StringEnumOptionData buyDownFeeStrategy;
@Schema(example = "FEE")
public StringEnumOptionData buyDownFeeIncomeType;
}
@Schema(description = "GetLoansResponse")
@@ -1348,6 +1356,14 @@ final class LoansApiResourceSwagger {
public String capitalizedIncomeStrategy;
@Schema(example = "FEE")
public StringEnumOptionData capitalizedIncomeType;
@Schema(example = "false")
public Boolean enableBuyDownFee;
@Schema(example = "FLAT", allowableValues = "FLAT")
public String buyDownFeeCalculationType;
@Schema(example = "EQUAL_AMORTIZATION", allowableValues = "EQUAL_AMORTIZATION")
public String buyDownFeeStrategy;
@Schema(example = "FEE", allowableValues = { "FEE", "INTEREST" })
public String buyDownFeeIncomeType;
@Schema(example = "List of PostLoansDataTable")
public List<PostLoansDataTable> datatables;
@@ -550,7 +550,11 @@ public class LoanScheduleAssembler {
loanProduct.getLoanProductRelatedDetail().isEnableIncomeCapitalization(),
loanProduct.getLoanProductRelatedDetail().getCapitalizedIncomeCalculationType(),
loanProduct.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(),
loanProduct.getLoanProductRelatedDetail().getCapitalizedIncomeType());
loanProduct.getLoanProductRelatedDetail().getCapitalizedIncomeType(),
loanProduct.getLoanProductRelatedDetail().isEnableBuyDownFee(),
loanProduct.getLoanProductRelatedDetail().getBuyDownFeeCalculationType(),
loanProduct.getLoanProductRelatedDetail().getBuyDownFeeStrategy(),
loanProduct.getLoanProductRelatedDetail().getBuyDownFeeIncomeType());
}
private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery,
@@ -40,6 +40,9 @@ import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -317,6 +320,14 @@ public class LoanProductAssembler {
final LoanCapitalizedIncomeType capitalizedIncomeType = command
.enumValueOfParameterNamed(LoanProductConstants.CAPITALIZED_INCOME_TYPE_PARAM_NAME, LoanCapitalizedIncomeType.class);
final boolean enableBuyDownFee = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME);
final LoanBuyDownFeeCalculationType buyDownFeeCalculationType = command.enumValueOfParameterNamed(
LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME, LoanBuyDownFeeCalculationType.class);
final LoanBuyDownFeeStrategy buyDownFeeStrategy = command
.enumValueOfParameterNamed(LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME, LoanBuyDownFeeStrategy.class);
final LoanBuyDownFeeIncomeType buyDownFeeIncomeType = command
.enumValueOfParameterNamed(LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME, LoanBuyDownFeeIncomeType.class);
return new LoanProduct(fund, loanTransactionProcessingStrategy, loanProductPaymentAllocationRules, loanProductCreditAllocationRules,
name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod,
@@ -338,7 +349,7 @@ public class LoanProductAssembler {
repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType, fixedLength,
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour, interestRecognitionOnDisbursementDate,
daysInYearCustomStrategy, enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
capitalizedIncomeType);
capitalizedIncomeType, enableBuyDownFee, buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType);
}
@@ -26,6 +26,9 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -328,6 +331,34 @@ public class LoanProductRelatedDetailUpdateUtil {
loanRepaymentScheduleDetail.setCapitalizedIncomeType(newValue);
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME,
loanRepaymentScheduleDetail.isEnableBuyDownFee())) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME);
actualChanges.put(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, newValue);
loanRepaymentScheduleDetail.setEnableBuyDownFee(newValue);
}
if (command.parameterExists(LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME)) {
final LoanBuyDownFeeCalculationType newValue = command.enumValueOfParameterNamed(
LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME, LoanBuyDownFeeCalculationType.class);
actualChanges.put(LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME, newValue);
loanRepaymentScheduleDetail.setBuyDownFeeCalculationType(newValue);
}
if (command.parameterExists(LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME)) {
final LoanBuyDownFeeStrategy newValue = command.enumValueOfParameterNamed(LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME,
LoanBuyDownFeeStrategy.class);
actualChanges.put(LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME, newValue);
loanRepaymentScheduleDetail.setBuyDownFeeStrategy(newValue);
}
if (command.parameterExists(LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME)) {
final LoanBuyDownFeeIncomeType newValue = command
.enumValueOfParameterNamed(LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME, LoanBuyDownFeeIncomeType.class);
actualChanges.put(LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME, newValue);
loanRepaymentScheduleDetail.setBuyDownFeeIncomeType(newValue);
}
return actualChanges;
}
@@ -109,6 +109,9 @@ import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
@@ -819,6 +822,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
+ " l.capitalized_income_calculation_type as capitalizedIncomeCalculationType, "
+ " l.capitalized_income_strategy as capitalizedIncomeStrategy, "
+ " l.capitalized_income_type as capitalizedIncomeType, " //
+ " l.enable_buy_down_fee as enableBuyDownFee, " + " l.buy_down_fee_calculation_type as buyDownFeeCalculationType, "
+ " l.buy_down_fee_strategy as buyDownFeeStrategy, " + " l.buy_down_fee_income_type as buyDownFeeIncomeType, "
+ " l.create_standing_instruction_at_disbursement as createStandingInstructionAtDisbursement, "
+ " lpvi.minimum_gap as minimuminstallmentgap, lpvi.maximum_gap as maximuminstallmentgap, "
+ " lp.can_use_for_topup as canUseForTopup, l.is_topup as isTopup, topup.closure_loan_id as closureLoanId, "
@@ -1208,6 +1213,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
.getStringEnumOptionData(LoanCapitalizedIncomeStrategy.class, rs.getString("capitalizedIncomeStrategy"));
final StringEnumOptionData capitalizedIncomeType = ApiFacingEnum.getStringEnumOptionData(LoanCapitalizedIncomeType.class,
rs.getString("capitalizedIncomeType"));
final boolean enableBuyDownFee = rs.getBoolean("enableBuyDownFee");
final StringEnumOptionData buyDownFeeCalculationType = ApiFacingEnum
.getStringEnumOptionData(LoanBuyDownFeeCalculationType.class, rs.getString("buyDownFeeCalculationType"));
final StringEnumOptionData buyDownFeeStrategy = ApiFacingEnum.getStringEnumOptionData(LoanBuyDownFeeStrategy.class,
rs.getString("buyDownFeeStrategy"));
final StringEnumOptionData buyDownFeeIncomeType = ApiFacingEnum.getStringEnumOptionData(LoanBuyDownFeeIncomeType.class,
rs.getString("buyDownFeeIncomeType"));
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName,
clientOfficeId, clientExternalId, groupData, loanType, loanProductId, loanProductName, loanProductDescription,
@@ -1228,7 +1240,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
enableAutoRepaymentForDownPayment, enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(),
loanScheduleProcessingType.asEnumOptionData(), fixedLength, chargeOffBehaviour.getValueAsStringEnumOptionData(),
interestRecognitionOnDisbursementDate, daysInYearCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType);
capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType);
}
}
@@ -82,6 +82,9 @@ import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlat
import org.apache.fineract.portfolio.fund.data.FundData;
import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -134,7 +137,9 @@ public class LoanProductsApiResource {
LoanProductConstants.REPAYMENT_START_DATE_TYPE, LoanProductConstants.DAYS_IN_YEAR_CUSTOM_STRATEGY_TYPE_PARAMETER_NAME,
LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME,
LoanProductConstants.CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME,
LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME, LoanProductConstants.CAPITALIZED_INCOME_TYPE_PARAM_NAME));
LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME, LoanProductConstants.CAPITALIZED_INCOME_TYPE_PARAM_NAME,
LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME,
LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME, LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME));
private static final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions"));
@@ -453,6 +458,12 @@ public class LoanProductsApiResource {
.getValuesAsStringEnumOptionDataList(LoanCapitalizedIncomeStrategy.class);
final List<StringEnumOptionData> capitalizedIncomeTypeOptions = ApiFacingEnum
.getValuesAsStringEnumOptionDataList(LoanCapitalizedIncomeType.class);
final List<StringEnumOptionData> buyDownFeeCalculationTypeOptions = ApiFacingEnum
.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeCalculationType.class);
final List<StringEnumOptionData> buyDownFeeStrategyOptions = ApiFacingEnum
.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class);
final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions = ApiFacingEnum
.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeIncomeType.class);
return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions,
@@ -466,7 +477,7 @@ public class LoanProductsApiResource {
LoanScheduleProcessingType.getValuesAsEnumOptionDataList(), creditAllocationTransactionTypes,
creditAllocationAllocationTypes, supportedInterestRefundTypesOptions, chargeOffBehaviourOptions, chargeOffReasonOptions,
daysInYearCustomStrategyOptions, capitalizedIncomeCalculationTypeOptions, capitalizedIncomeStrategyOptions,
capitalizedIncomeTypeOptions);
capitalizedIncomeTypeOptions, buyDownFeeCalculationTypeOptions, buyDownFeeStrategyOptions, buyDownFeeIncomeTypeOptions);
}
}
@@ -49,6 +49,9 @@ import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -194,7 +197,9 @@ public final class LoanProductDataValidator {
LoanProductConstants.DAYS_IN_YEAR_CUSTOM_STRATEGY_TYPE_PARAMETER_NAME,
LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME,
LoanProductConstants.CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME,
LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME, LoanProductConstants.CAPITALIZED_INCOME_TYPE_PARAM_NAME));
LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME, LoanProductConstants.CAPITALIZED_INCOME_TYPE_PARAM_NAME,
LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME,
LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME, LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME));
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
@@ -887,6 +892,8 @@ public final class LoanProductDataValidator {
validateIncomeCapitalization(transactionProcessingStrategyCode, element, baseDataValidator, accountingRuleType);
validateBuyDownFee(transactionProcessingStrategyCode, element, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -1935,6 +1942,8 @@ public final class LoanProductDataValidator {
validateIncomeCapitalization(transactionProcessingStrategyCode, element, baseDataValidator, accountingRuleType);
validateBuyDownFee(transactionProcessingStrategyCode, element, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -2745,6 +2754,61 @@ public final class LoanProductDataValidator {
}
}
private void validateBuyDownFee(String transactionProcessingStrategyCode, JsonElement element, DataValidatorBuilder baseDataValidator) {
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME, element)) {
final String buyDownFeeCalculationType = this.fromApiJsonHelper
.extractStringNamed(LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME, element);
baseDataValidator.reset().parameter(LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME)
.value(buyDownFeeCalculationType).isOneOfEnumValues(LoanBuyDownFeeCalculationType.class);
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME, element)) {
final String buyDownFeeStrategy = this.fromApiJsonHelper
.extractStringNamed(LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME, element);
baseDataValidator.reset().parameter(LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME).value(buyDownFeeStrategy)
.isOneOfEnumValues(LoanBuyDownFeeStrategy.class);
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME, element)) {
final String buyDownFeeIncomeType = this.fromApiJsonHelper
.extractStringNamed(LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME, element);
baseDataValidator.reset().parameter(LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME).value(buyDownFeeIncomeType)
.isOneOfEnumValues(LoanBuyDownFeeIncomeType.class);
}
if (AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategyCode)
&& this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, element)) {
Boolean enableBuyDownFee = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME,
element);
baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME).value(enableBuyDownFee).ignoreIfNull()
.validateForBooleanValue();
if (enableBuyDownFee) {
final String buyDownFeeCalculationType = this.fromApiJsonHelper
.extractStringNamed(LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME, element);
baseDataValidator.reset().parameter(LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME)
.value(buyDownFeeCalculationType).isOneOfEnumValues(LoanBuyDownFeeCalculationType.class)
.cantBeBlankWhenParameterProvidedIs(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, true);
final String buyDownFeeStrategy = this.fromApiJsonHelper
.extractStringNamed(LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME, element);
baseDataValidator.reset().parameter(LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME).value(buyDownFeeStrategy)
.isOneOfEnumValues(LoanBuyDownFeeStrategy.class)
.cantBeBlankWhenParameterProvidedIs(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, true);
final String buyDownFeeIncomeType = this.fromApiJsonHelper
.extractStringNamed(LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME, element);
baseDataValidator.reset().parameter(LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME).value(buyDownFeeIncomeType)
.isOneOfEnumValues(LoanBuyDownFeeIncomeType.class)
.cantBeBlankWhenParameterProvidedIs(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, true);
}
} else if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, element)) {
Boolean enableBuyDownFee = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME,
element);
if (Boolean.TRUE.equals(enableBuyDownFee)) {
baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME).failWithCode(
"supported.only.for.progressive.loan.buyDownFee", "Buy down fee is only supported for Progressive loans");
}
}
}
private Integer defaultToZeroIfNull(Integer value) {
return value != null ? value : 0;
}
@@ -46,6 +46,9 @@ import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -296,7 +299,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lp.enable_income_capitalization as enableIncomeCapitalization, " //
+ "lp.capitalized_income_calculation_type as capitalizedIncomeCalculationType, " //
+ "lp.capitalized_income_strategy as capitalizedIncomeStrategy, " //
+ "lp.capitalized_income_type as capitalizedIncomeType " //
+ "lp.capitalized_income_type as capitalizedIncomeType, " //
+ "lp.enable_buy_down_fee as enableBuyDownFee, " + "lp.buy_down_fee_calculation_type as buyDownFeeCalculationType, "
+ "lp.buy_down_fee_strategy as buyDownFeeStrategy, " + "lp.buy_down_fee_income_type as buyDownFeeIncomeType "
+ " from m_product_loan lp " + " left join m_fund f on f.id = lp.fund_id "
+ " left join m_product_loan_recalculation_details lpr on lpr.product_id=lp.id "
+ " left join m_product_loan_guarantee_details lpg on lpg.loan_product_id=lp.id "
@@ -567,6 +572,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
.getStringEnumOptionData(LoanCapitalizedIncomeStrategy.class, rs.getString("capitalizedIncomeStrategy"));
final StringEnumOptionData capitalizedIncome = ApiFacingEnum.getStringEnumOptionData(LoanCapitalizedIncomeType.class,
rs.getString("capitalizedIncomeType"));
final boolean enableBuyDownFee = rs.getBoolean("enableBuyDownFee");
final StringEnumOptionData buyDownFeeCalculationType = ApiFacingEnum
.getStringEnumOptionData(LoanBuyDownFeeCalculationType.class, rs.getString("buyDownFeeCalculationType"));
final StringEnumOptionData buyDownFeeStrategy = ApiFacingEnum.getStringEnumOptionData(LoanBuyDownFeeStrategy.class,
rs.getString("buyDownFeeStrategy"));
final StringEnumOptionData buyDownFeeIncomeType = ApiFacingEnum.getStringEnumOptionData(LoanBuyDownFeeIncomeType.class,
rs.getString("buyDownFeeIncomeType"));
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -592,7 +604,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes,
loanChargeOffBehaviour.getValueAsStringEnumOptionData(), interestRecognitionOnDisbursementDate,
daysInYearCustomStrategy, enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
capitalizedIncome);
capitalizedIncome, enableBuyDownFee, buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType);
}
}
@@ -203,4 +203,5 @@
<include file="parts/0182_transaction_summary_with_asset_owner_report_fix_charge_reason_and_add_buyback_intermediate.xml" relativeToChangelogFile="true" />
<include file="parts/0183_add_LoanCapitalizedIncomeTransactionCreatedBusinessEvent.xml" relativeToChangelogFile="true" />
<include file="parts/0184_add_document_event_configuration.xml" relativeToChangelogFile="true" />
<include file="parts/0185_loan_buy_down_fee.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
<changeSet author="fineract" id="1">
<addColumn tableName="m_loan">
<column name="enable_buy_down_fee" type="boolean" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="buy_down_fee_calculation_type" type="VARCHAR(100)">
<constraints nullable="true"/>
</column>
<column name="buy_down_fee_strategy" type="VARCHAR(100)">
<constraints nullable="true"/>
</column>
<column name="buy_down_fee_income_type" type="VARCHAR(100)">
<constraints nullable="true"/>
</column>
</addColumn>
<addColumn tableName="m_product_loan">
<column name="enable_buy_down_fee" type="boolean" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="buy_down_fee_calculation_type" type="VARCHAR(100)">
<constraints nullable="true"/>
</column>
<column name="buy_down_fee_strategy" type="VARCHAR(100)">
<constraints nullable="true"/>
</column>
<column name="buy_down_fee_income_type" type="VARCHAR(100)">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -99,7 +99,7 @@ public class DefaultScheduledDateGeneratorTest {
DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null, null, ZERO, null, NONE, null, ZERO,
EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false, null, false, DISBURSEMENT_DATE,
submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null, null, false, null, false, null, null,
null);
null, false, null, null, null);
// when
List<? extends LoanScheduleModelPeriod> result = underTest.generateRepaymentPeriods(mathContext, expectedDisbursementDate,
@@ -180,7 +180,7 @@ public class DefaultScheduledDateGeneratorTest {
EMPTY_LIST, BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null,
null, ZERO, null, NONE, null, ZERO, EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false,
null, false, DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null, null,
false, null, false, null, null, null);
false, null, false, null, null, null, false, null, null, null);
}
private HolidayDetailDTO createHolidayDTO() {
@@ -26,6 +26,9 @@ import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeType;
@@ -237,4 +240,156 @@ public class LoanProductTest extends BaseLoanIntegrationTest {
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue())));
}
}
@Nested
public class BuyDownFeeTest {
@Test
public void testBuyDownFeeEnabled() {
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive()
.enableBuyDownFee(true).buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)
.buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE));
final GetLoanProductsProductIdResponse loanProductsProductIdResponse = loanProductHelper
.retrieveLoanProductById(loanProductsResponse.getResourceId());
Assertions.assertEquals(Boolean.TRUE, loanProductsProductIdResponse.getEnableBuyDownFee());
Assertions.assertNotNull(loanProductsProductIdResponse.getBuyDownFeeCalculationType());
Assertions.assertEquals(LoanBuyDownFeeCalculationType.FLAT.getCode(),
loanProductsProductIdResponse.getBuyDownFeeCalculationType().getCode());
Assertions.assertNotNull(loanProductsProductIdResponse.getBuyDownFeeStrategy());
Assertions.assertEquals(LoanBuyDownFeeStrategy.EQUAL_AMORTIZATION.getCode(),
loanProductsProductIdResponse.getBuyDownFeeStrategy().getCode());
Assertions.assertNotNull(loanProductsProductIdResponse.getBuyDownFeeIncomeType());
Assertions.assertEquals(LoanBuyDownFeeIncomeType.FEE.getCode(),
loanProductsProductIdResponse.getBuyDownFeeIncomeType().getCode());
runAt("20 December 2024", () -> {
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "20 December 2024",
430.0, 7.0, 6, null);
final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertEquals(Boolean.TRUE, loanDetails.getEnableBuyDownFee());
Assertions.assertNotNull(loanDetails.getBuyDownFeeCalculationType());
Assertions.assertEquals(LoanBuyDownFeeCalculationType.FLAT.getCode(), loanDetails.getBuyDownFeeCalculationType().getCode());
Assertions.assertNotNull(loanDetails.getBuyDownFeeStrategy());
Assertions.assertEquals(LoanBuyDownFeeStrategy.EQUAL_AMORTIZATION.getCode(), loanDetails.getBuyDownFeeStrategy().getCode());
Assertions.assertNotNull(loanDetails.getBuyDownFeeIncomeType());
Assertions.assertEquals(LoanBuyDownFeeIncomeType.FEE.getCode(), loanDetails.getBuyDownFeeIncomeType().getCode());
Assertions.assertDoesNotThrow(() -> disburseLoan(loanId, BigDecimal.valueOf(430), "20 December 2024"));
});
}
@Test
public void testBuyDownFeeDisabled() {
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive()
.enableBuyDownFee(false).buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)
.buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE));
final GetLoanProductsProductIdResponse loanProductsProductIdResponse = loanProductHelper
.retrieveLoanProductById(loanProductsResponse.getResourceId());
Assertions.assertEquals(Boolean.FALSE, loanProductsProductIdResponse.getEnableBuyDownFee());
Assertions.assertNotNull(loanProductsProductIdResponse.getBuyDownFeeCalculationType());
Assertions.assertEquals(LoanBuyDownFeeCalculationType.FLAT.getCode(),
loanProductsProductIdResponse.getBuyDownFeeCalculationType().getCode());
Assertions.assertNotNull(loanProductsProductIdResponse.getBuyDownFeeStrategy());
Assertions.assertEquals(LoanBuyDownFeeStrategy.EQUAL_AMORTIZATION.getCode(),
loanProductsProductIdResponse.getBuyDownFeeStrategy().getCode());
Assertions.assertNotNull(loanProductsProductIdResponse.getBuyDownFeeIncomeType());
Assertions.assertEquals(LoanBuyDownFeeIncomeType.FEE.getCode(),
loanProductsProductIdResponse.getBuyDownFeeIncomeType().getCode());
runAt("20 December 2024", () -> {
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "20 December 2024",
430.0, 7.0, 6, null);
final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
Assertions.assertEquals(Boolean.FALSE, loanDetails.getEnableBuyDownFee());
Assertions.assertNotNull(loanDetails.getBuyDownFeeCalculationType());
Assertions.assertEquals(LoanBuyDownFeeCalculationType.FLAT.getCode(), loanDetails.getBuyDownFeeCalculationType().getCode());
Assertions.assertNotNull(loanDetails.getBuyDownFeeStrategy());
Assertions.assertEquals(LoanBuyDownFeeStrategy.EQUAL_AMORTIZATION.getCode(), loanDetails.getBuyDownFeeStrategy().getCode());
Assertions.assertNotNull(loanDetails.getBuyDownFeeIncomeType());
Assertions.assertEquals(LoanBuyDownFeeIncomeType.FEE.getCode(), loanDetails.getBuyDownFeeIncomeType().getCode());
Assertions.assertDoesNotThrow(() -> disburseLoan(loanId, BigDecimal.valueOf(430), "20 December 2024"));
});
}
@Test
public void testBuyDownFeeUpdateProduct() {
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive()
.enableBuyDownFee(true).buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)
.buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE));
final GetLoanProductsProductIdResponse loanProductsProductIdResponse = loanProductHelper
.retrieveLoanProductById(loanProductsResponse.getResourceId());
Assertions.assertEquals(Boolean.TRUE, loanProductsProductIdResponse.getEnableBuyDownFee());
Assertions.assertNotNull(loanProductsProductIdResponse.getBuyDownFeeCalculationType());
Assertions.assertEquals(LoanBuyDownFeeCalculationType.FLAT.getCode(),
loanProductsProductIdResponse.getBuyDownFeeCalculationType().getCode());
Assertions.assertNotNull(loanProductsProductIdResponse.getBuyDownFeeStrategy());
Assertions.assertEquals(LoanBuyDownFeeStrategy.EQUAL_AMORTIZATION.getCode(),
loanProductsProductIdResponse.getBuyDownFeeStrategy().getCode());
Assertions.assertNotNull(loanProductsProductIdResponse.getBuyDownFeeIncomeType());
Assertions.assertEquals(LoanBuyDownFeeIncomeType.FEE.getCode(),
loanProductsProductIdResponse.getBuyDownFeeIncomeType().getCode());
loanProductHelper.updateLoanProductById(loanProductsResponse.getResourceId(), new PutLoanProductsProductIdRequest()
.enableBuyDownFee(false).buyDownFeeIncomeType(PutLoanProductsProductIdRequest.BuyDownFeeIncomeTypeEnum.INTEREST));
final GetLoanProductsProductIdResponse updatedLoanProductsProductIdResponse = loanProductHelper
.retrieveLoanProductById(loanProductsResponse.getResourceId());
Assertions.assertEquals(Boolean.FALSE, updatedLoanProductsProductIdResponse.getEnableBuyDownFee());
Assertions.assertNotNull(updatedLoanProductsProductIdResponse.getBuyDownFeeCalculationType());
Assertions.assertEquals(LoanBuyDownFeeCalculationType.FLAT.getCode(),
updatedLoanProductsProductIdResponse.getBuyDownFeeCalculationType().getCode());
Assertions.assertNotNull(updatedLoanProductsProductIdResponse.getBuyDownFeeStrategy());
Assertions.assertEquals(LoanBuyDownFeeStrategy.EQUAL_AMORTIZATION.getCode(),
updatedLoanProductsProductIdResponse.getBuyDownFeeStrategy().getCode());
Assertions.assertNotNull(updatedLoanProductsProductIdResponse.getBuyDownFeeIncomeType());
Assertions.assertEquals(LoanBuyDownFeeIncomeType.INTEREST.getCode(),
updatedLoanProductsProductIdResponse.getBuyDownFeeIncomeType().getCode());
}
@Test
public void testBuyDownFeeCumulativeNotSupported() {
Assertions.assertThrows(RuntimeException.class,
() -> loanProductHelper.createLoanProduct(createOnePeriod30DaysPeriodicAccrualProduct(7.0).enableBuyDownFee(true)
.buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)
.buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE)));
}
@Test
public void testBuyDownFeeEnabledCalculationTypeNotProvided() {
Assertions.assertThrows(RuntimeException.class,
() -> loanProductHelper.createLoanProduct(create4IProgressive().enableBuyDownFee(true)
.buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE)));
}
@Test
public void testBuyDownFeeEnabledStrategyNotProvided() {
Assertions.assertThrows(RuntimeException.class,
() -> loanProductHelper.createLoanProduct(create4IProgressive().enableBuyDownFee(true)
.buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE)));
}
@Test
public void testBuyDownFeeEnabledIncomeTypeNotProvided() {
Assertions.assertThrows(RuntimeException.class,
() -> loanProductHelper.createLoanProduct(create4IProgressive().enableBuyDownFee(true)
.buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)
.buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)));
}
}
}