From 1b1d6be95618a500e10b2545e0bcbeac425cfe62 Mon Sep 17 00:00:00 2001 From: "adam.magyari" Date: Tue, 17 Jun 2025 10:27:07 +0200 Subject: [PATCH] FINERACT-2311: Loan product - Buy down fees configuration --- .../loanaccount/data/LoanAccountData.java | 24 ++- .../domain/LoanBuyDownFeeCalculationType.java | 33 ++++ .../domain/LoanBuyDownFeeIncomeType.java | 34 ++++ .../domain/LoanBuyDownFeeStrategy.java | 33 ++++ .../domain/LoanApplicationTerms.java | 57 ++++++- .../loanproduct/LoanProductConstants.java | 6 + .../api/LoanProductsApiResourceSwagger.java | 48 ++++++ .../loanproduct/data/LoanProductData.java | 61 ++++++- .../loanproduct/domain/LoanProduct.java | 9 +- .../domain/LoanProductRelatedDetail.java | 33 +++- .../loanaccount/api/LoansApiResource.java | 8 +- .../api/LoansApiResourceSwagger.java | 16 ++ .../service/LoanScheduleAssembler.java | 6 +- .../service/LoanProductAssembler.java | 13 +- .../LoanProductRelatedDetailUpdateUtil.java | 31 ++++ .../service/LoanReadPlatformServiceImpl.java | 15 +- .../api/LoanProductsApiResource.java | 15 +- .../LoanProductDataValidator.java | 66 +++++++- .../LoanProductReadPlatformServiceImpl.java | 16 +- .../db/changelog/tenant/changelog-tenant.xml | 1 + .../tenant/parts/0185_loan_buy_down_fee.xml | 55 +++++++ .../DefaultScheduledDateGeneratorTest.java | 4 +- .../integrationtests/LoanProductTest.java | 155 ++++++++++++++++++ 23 files changed, 708 insertions(+), 31 deletions(-) create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeCalculationType.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeIncomeType.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeStrategy.java create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0185_loan_buy_down_fee.xml diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java index 333da377d..221509905 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java @@ -174,6 +174,9 @@ public class LoanAccountData { private List capitalizedIncomeCalculationTypeOptions; private List capitalizedIncomeStrategyOptions; private List capitalizedIncomeTypeOptions; + private List buyDownFeeCalculationTypeOptions; + private List buyDownFeeStrategyOptions; + private List 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 daysInYearCustomStrategyOptions, final List capitalizedIncomeCalculationTypeOptions, final List capitalizedIncomeStrategyOptions, - final List capitalizedIncomeTypeOptions) { + final List capitalizedIncomeTypeOptions, + final List buyDownFeeCalculationTypeOptions, final List buyDownFeeStrategyOptions, + final List 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 productOptions, diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeCalculationType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeCalculationType.java new file mode 100644 index 000000000..4991cf498 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeCalculationType.java @@ -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 { + + FLAT("loanBuyDownFeeCalculationType.flat", "Flat"); + + private final String code; + private final String humanReadableName; +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeIncomeType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeIncomeType.java new file mode 100644 index 000000000..aa0b73082 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeIncomeType.java @@ -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 { + + FEE("buyDownFee.incomeType.fee", "Fee"), // + INTEREST("buyDownFee.incomeType.interest", "Interest"); // + + private final String code; + private final String humanReadableName; +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeStrategy.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeStrategy.java new file mode 100644 index 000000000..a58d13bc6 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeStrategy.java @@ -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 { + + EQUAL_AMORTIZATION("buyDownFee.strategy.equalAmortization", "Equal amortization"); + + private final String code; + private final String humanReadableName; +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 308cc55ed..96e13f745 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -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 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() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java index bda183949..a08c767bf 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java @@ -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"; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java index b17ecff9d..9bdcc114d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java @@ -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 capitalizedIncomeCalculationTypeOptions; public List capitalizedIncomeStrategyOptions; public List 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 buyDownFeeCalculationTypeOptions; + public List buyDownFeeStrategyOptions; + public List buyDownFeeIncomeTypeOptions; } @Schema(description = "GetLoanProductsProductIdResponse") @@ -1460,6 +1488,18 @@ public final class LoanProductsApiResourceSwagger { public List capitalizedIncomeCalculationTypeOptions; public List capitalizedIncomeStrategyOptions; public List 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 buyDownFeeCalculationTypeOptions; + public List buyDownFeeStrategyOptions; + public List 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 { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java index a9e0bfbcc..f7da2ba2b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java @@ -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 capitalizedIncomeCalculationTypeOptions; private List capitalizedIncomeStrategyOptions; private List capitalizedIncomeTypeOptions; + private Boolean enableBuyDownFee; + private StringEnumOptionData buyDownFeeCalculationType; + private StringEnumOptionData buyDownFeeStrategy; + private StringEnumOptionData buyDownFeeIncomeType; + private List buyDownFeeCalculationTypeOptions; + private List buyDownFeeStrategyOptions; + private List 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 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 chargeOptions, @@ -1018,7 +1057,9 @@ public class LoanProductData implements Serializable { final List daysInYearCustomStrategyOptions, final List capitalizedIncomeCalculationTypeOptions, final List capitalizedIncomeStrategyOptions, - final List capitalizedIncomeTypeOptions) { + final List capitalizedIncomeTypeOptions, + final List buyDownFeeCalculationTypeOptions, final List buyDownFeeStrategyOptions, + final List 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 nullIfEmpty(final Collection charges) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java index b90217360..5f73f1985 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java @@ -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 { 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 { 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, diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index e0723e8a5..c200f657b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -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) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index 058c7f914..f5beeeace 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -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); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index ce9379f75..44a8738b6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -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 datatables; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 1e22e540c..12cab7ea5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -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, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductAssembler.java index 2e2a3beb6..5bcb32f48 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductAssembler.java @@ -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); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java index b84cc31e0..1d99f0d56 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java @@ -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; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 83fa05f8b..3bff93be0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -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); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java index 5604d7ff5..a8c62d091 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -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 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 capitalizedIncomeTypeOptions = ApiFacingEnum .getValuesAsStringEnumOptionDataList(LoanCapitalizedIncomeType.class); + final List buyDownFeeCalculationTypeOptions = ApiFacingEnum + .getValuesAsStringEnumOptionDataList(LoanBuyDownFeeCalculationType.class); + final List buyDownFeeStrategyOptions = ApiFacingEnum + .getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class); + final List 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); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index c119a31f8..ef77da83b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -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; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index 181410b91..eb0f838b9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -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); } } diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index a66325bad..42b41ea10 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -203,4 +203,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0185_loan_buy_down_fee.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0185_loan_buy_down_fee.xml new file mode 100644 index 000000000..00700f5ea --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0185_loan_buy_down_fee.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java index 43052d0f8..6fde5c64b 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java @@ -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 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() { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTest.java index 6d2b2f33b..8a2ec2e3d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTest.java @@ -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))); + } + } }