You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2025/04/15 17:45:32 UTC
(fineract) 01/02: FINERACT-2232: Progressive loan income capitalization config
This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 7df7be2b16dc3ec4b80dd252d901fc7b49a23052
Author: adam.magyari <ma...@gmail.com>
AuthorDate: Thu Apr 10 09:04:43 2025 +0200
FINERACT-2232: Progressive loan income capitalization config
---
.../LoanCapitalizedIncomeCalculationType.java | 48 +++++++
.../domain/LoanCapitalizedIncomeStrategy.java | 47 +++++++
.../loanschedule/domain/LoanApplicationTerms.java | 47 ++++++-
.../loanproduct/LoanProductConstants.java | 5 +
.../api/LoanProductsApiResourceSwagger.java | 34 +++++
.../loanproduct/data/LoanProductData.java | 48 ++++++-
.../portfolio/loanproduct/domain/LoanProduct.java | 9 +-
.../domain/LoanProductRelatedDetail.java | 27 +++-
.../loanaccount/api/LoansApiResource.java | 6 +-
.../loanaccount/api/LoansApiResourceSwagger.java | 12 ++
.../loanaccount/data/LoanAccountData.java | 21 ++-
.../service/LoanScheduleAssembler.java | 5 +-
.../loanaccount/service/LoanProductAssembler.java | 11 +-
.../LoanProductRelatedDetailUpdateUtil.java | 24 ++++
.../service/LoanReadPlatformServiceImpl.java | 13 +-
.../loanproduct/api/LoanProductsApiResource.java | 13 +-
.../serialization/LoanProductDataValidator.java | 45 ++++++-
.../LoanProductReadPlatformServiceImpl.java | 16 ++-
.../db/changelog/tenant/changelog-tenant.xml | 1 +
.../tenant/parts/0171_loan_capitalized_income.xml | 49 +++++++
.../domain/DefaultScheduledDateGeneratorTest.java | 5 +-
.../fineract/integrationtests/LoanProductTest.java | 147 +++++++++++++++++++++
22 files changed, 601 insertions(+), 32 deletions(-)
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCapitalizedIncomeCalculationType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCapitalizedIncomeCalculationType.java
new file mode 100644
index 0000000000..5916b0074e
--- /dev/null
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCapitalizedIncomeCalculationType.java
@@ -0,0 +1,48 @@
+/**
+ * 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 java.util.Arrays;
+import java.util.List;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
+
+@Getter
+@RequiredArgsConstructor
+public enum LoanCapitalizedIncomeCalculationType {
+
+ FLAT("loanCapitalizedIncomeCalculationType.flat", "Flat");
+
+ private final String code;
+ private final String humanReadableName;
+
+ public static List<StringEnumOptionData> getValuesAsStringEnumOptionDataList() {
+ return Arrays.stream(values()).map(v -> new StringEnumOptionData(v.name(), v.getCode(), v.getHumanReadableName())).toList();
+ }
+
+ public StringEnumOptionData getValueAsStringEnumOptionData() {
+ return new StringEnumOptionData(name(), getCode(), getHumanReadableName());
+ }
+
+ public static StringEnumOptionData getStringEnumOptionData(String name) {
+ return name == null || name.trim().isEmpty() ? null
+ : LoanCapitalizedIncomeCalculationType.valueOf(name).getValueAsStringEnumOptionData();
+ }
+}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCapitalizedIncomeStrategy.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCapitalizedIncomeStrategy.java
new file mode 100644
index 0000000000..b0d489ce6b
--- /dev/null
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCapitalizedIncomeStrategy.java
@@ -0,0 +1,47 @@
+/**
+ * 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 java.util.Arrays;
+import java.util.List;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
+
+@Getter
+@RequiredArgsConstructor
+public enum LoanCapitalizedIncomeStrategy {
+
+ EQUAL_AMORTIZATION("capitalizedIncome.strategy.equalAmortization", "Equal amortization");
+
+ private final String code;
+ private final String humanReadableName;
+
+ public static List<StringEnumOptionData> getValuesAsStringEnumOptionDataList() {
+ return Arrays.stream(values()).map(v -> new StringEnumOptionData(v.name(), v.getCode(), v.getHumanReadableName())).toList();
+ }
+
+ public StringEnumOptionData getValueAsStringEnumOptionData() {
+ return new StringEnumOptionData(name(), getCode(), getHumanReadableName());
+ }
+
+ public static StringEnumOptionData getStringEnumOptionData(String name) {
+ return name == null || name.trim().isEmpty() ? null : LoanCapitalizedIncomeStrategy.valueOf(name).getValueAsStringEnumOptionData();
+ }
+}
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 0dcc262483..f517a1b240 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,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductRelatedDetailMinimumData;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
@@ -232,6 +234,9 @@ public final class LoanApplicationTerms {
private LoanChargeOffBehaviour chargeOffBehaviour;
private boolean interestRecognitionOnDisbursementDate;
private DaysInYearCustomStrategyType daysInYearCustomStrategy;
+ private boolean enableIncomeCapitalization;
+ private LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType;
+ private LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy;
private LoanApplicationTerms(Builder builder) {
this.currency = builder.currency;
@@ -268,6 +273,9 @@ public final class LoanApplicationTerms {
} else {
this.downPaymentAmount = Money.zero(getCurrency(), builder.mc);
}
+ this.enableIncomeCapitalization = builder.enableIncomeCapitalization;
+ this.capitalizedIncomeCalculationType = builder.capitalizedIncomeCalculationType;
+ this.capitalizedIncomeStrategy = builder.capitalizedIncomeStrategy;
}
public static class Builder {
@@ -297,6 +305,9 @@ public final class LoanApplicationTerms {
private MathContext mc;
private Boolean interestRecognitionOnDisbursementDate;
private DaysInYearCustomStrategyType daysInYearCustomStrategy;
+ private boolean enableIncomeCapitalization;
+ private LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType;
+ private LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy;
public Builder currency(CurrencyData currency) {
this.currency = currency;
@@ -413,6 +424,21 @@ public final class LoanApplicationTerms {
return this;
}
+ public Builder enableIncomeCapitalization(boolean value) {
+ this.enableIncomeCapitalization = value;
+ return this;
+ }
+
+ public Builder capitalizedIncomeCalculationType(LoanCapitalizedIncomeCalculationType value) {
+ this.capitalizedIncomeCalculationType = value;
+ return this;
+ }
+
+ public Builder capitalizedIncomeStrategy(LoanCapitalizedIncomeStrategy value) {
+ this.capitalizedIncomeStrategy = value;
+ return this;
+ }
+
public LoanApplicationTerms build() {
return new LoanApplicationTerms(this);
}
@@ -480,7 +506,9 @@ public final class LoanApplicationTerms {
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
final boolean enableAccrualActivityPosting, final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
- final DaysInYearCustomStrategyType daysInYearCustomStrategy) {
+ final DaysInYearCustomStrategyType daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
+ final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
+ final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -500,7 +528,8 @@ public final class LoanApplicationTerms {
isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment,
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType,
fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
- interestRecognitionOnDisbursementDate, daysInYearCustomStrategy);
+ interestRecognitionOnDisbursementDate, daysInYearCustomStrategy, enableIncomeCapitalization,
+ capitalizedIncomeCalculationType, capitalizedIncomeStrategy);
}
@@ -574,7 +603,9 @@ public final class LoanApplicationTerms {
disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate,
loanScheduleType, loanScheduleProcessingType, fixedLength, loanProductRelatedDetail.isEnableAccrualActivityPosting(),
loanProductRelatedDetail.getSupportedInterestRefundTypes(), loanProductRelatedDetail.getChargeOffBehaviour(),
- loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(), loanProductRelatedDetail.getDaysInYearCustomStrategy());
+ loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(), loanProductRelatedDetail.getDaysInYearCustomStrategy(),
+ loanProductRelatedDetail.isEnableIncomeCapitalization(), loanProductRelatedDetail.getCapitalizedIncomeCalculationType(),
+ loanProductRelatedDetail.getCapitalizedIncomeStrategy());
}
private LoanApplicationTerms(final CurrencyData currency, final Integer loanTermFrequency,
@@ -605,7 +636,9 @@ public final class LoanApplicationTerms {
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate, final LoanScheduleType loanScheduleType,
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength, boolean enableAccrualActivityPosting,
final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes, final LoanChargeOffBehaviour chargeOffBehaviour,
- final boolean interestRecognitionOnDisbursementDate, final DaysInYearCustomStrategyType daysInYearCustomStrategy) {
+ final boolean interestRecognitionOnDisbursementDate, final DaysInYearCustomStrategyType daysInYearCustomStrategy,
+ final boolean enableIncomeCapitalization, final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
+ final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
@@ -707,6 +740,9 @@ public final class LoanApplicationTerms {
this.chargeOffBehaviour = chargeOffBehaviour;
this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
+ this.enableIncomeCapitalization = enableIncomeCapitalization;
+ this.capitalizedIncomeCalculationType = capitalizedIncomeCalculationType;
+ this.capitalizedIncomeStrategy = capitalizedIncomeStrategy;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -1568,7 +1604,8 @@ public final class LoanApplicationTerms {
this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled,
this.disbursedAmountPercentageForDownPayment, this.isAutoRepaymentForDownPaymentEnabled, this.loanScheduleType,
this.loanScheduleProcessingType, this.fixedLength, this.enableAccrualActivityPosting, this.supportedInterestRefundTypes,
- this.chargeOffBehaviour, this.interestRecognitionOnDisbursementDate, this.daysInYearCustomStrategy);
+ this.chargeOffBehaviour, this.interestRecognitionOnDisbursementDate, this.daysInYearCustomStrategy,
+ this.enableIncomeCapitalization, this.capitalizedIncomeCalculationType, this.capitalizedIncomeStrategy);
}
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 7ee57241e7..1e803ab0af 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
@@ -171,4 +171,9 @@ public interface LoanProductConstants {
String CHARGE_OFF_BEHAVIOUR = "chargeOffBehaviour";
String INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE = "interestRecognitionOnDisbursementDate";
+
+ // Capitalized income
+ String ENABLE_INCOME_CAPITALIZATION_PARAM_NAME = "enableIncomeCapitalization";
+ String CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME = "capitalizedIncomeCalculationType";
+ String CAPITALIZED_INCOME_STRATEGY_PARAM_NAME = "capitalizedIncomeStrategy";
}
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 8e01a143cc..caf9183f79 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
@@ -195,6 +195,12 @@ public final class LoanProductsApiResourceSwagger {
public Boolean enableAccrualActivityPosting;
@Schema(example = "false")
public Boolean interestRecognitionOnDisbursementDate;
+ @Schema(example = "false")
+ public Boolean enableIncomeCapitalization;
+ @Schema(example = "FLAT", allowableValues = "FLAT")
+ public String capitalizedIncomeCalculationType;
+ @Schema(example = "EQUAL_AMORTIZATION", allowableValues = "EQUAL_AMORTIZATION")
+ public String capitalizedIncomeStrategy;
// Interest Recalculation
@Schema(example = "false")
@@ -651,6 +657,12 @@ public final class LoanProductsApiResourceSwagger {
public StringEnumOptionData chargeOffBehaviour;
@Schema(example = "false")
public Boolean interestRecognitionOnDisbursementDate;
+ @Schema(example = "false")
+ public Boolean enableIncomeCapitalization;
+ @Schema(example = "FLAT")
+ public StringEnumOptionData capitalizedIncomeCalculationType;
+ @Schema(example = "EQUAL_AMORTIZATION")
+ public StringEnumOptionData capitalizedIncomeStrategy;
}
@Schema(description = "GetLoanProductsTemplateResponse")
@@ -1097,6 +1109,14 @@ public final class LoanProductsApiResourceSwagger {
public List<GetLoanProductsChargeOffReasonOptions> chargeOffReasonOptions;
public StringEnumOptionData chargeOffBehaviour;
public List<StringEnumOptionData> chargeOffBehaviourOptions;
+ @Schema(example = "false")
+ public Boolean enableIncomeCapitalization;
+ @Schema(example = "FLAT")
+ public StringEnumOptionData capitalizedIncomeCalculationType;
+ @Schema(example = "EQUAL_AMORTIZATION")
+ public StringEnumOptionData capitalizedIncomeStrategy;
+ public List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions;
+ public List<StringEnumOptionData> capitalizedIncomeStrategyOptions;
}
@Schema(description = "GetLoanProductsProductIdResponse")
@@ -1416,6 +1436,14 @@ public final class LoanProductsApiResourceSwagger {
public StringEnumOptionData chargeOffBehaviour;
@Schema(example = "false")
public Boolean interestRecognitionOnDisbursementDate;
+ @Schema(example = "false")
+ public Boolean enableIncomeCapitalization;
+ @Schema(example = "FLAT")
+ public StringEnumOptionData capitalizedIncomeCalculationType;
+ @Schema(example = "EQUAL_AMORTIZATION")
+ public StringEnumOptionData capitalizedIncomeStrategy;
+ public List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions;
+ public List<StringEnumOptionData> capitalizedIncomeStrategyOptions;
}
@Schema(description = "PutLoanProductsProductIdRequest")
@@ -1669,6 +1697,12 @@ public final class LoanProductsApiResourceSwagger {
public List<String> supportedInterestRefundTypes;
@Schema(example = "REGULAR")
public String chargeOffBehaviour;
+ @Schema(example = "false")
+ public Boolean enableIncomeCapitalization;
+ @Schema(example = "FLAT", allowableValues = "FLAT")
+ public String capitalizedIncomeCalculationType;
+ @Schema(example = "EQUAL_AMORTIZATION", allowableValues = "EQUAL_AMORTIZATION")
+ public String capitalizedIncomeStrategy;
}
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 bd579d9840..4ed7c23820 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
@@ -48,6 +48,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -238,6 +240,11 @@ public class LoanProductData implements Serializable {
private final boolean interestRecognitionOnDisbursementDate;
private final List<StringEnumOptionData> daysInYearCustomStrategyOptions;
private final StringEnumOptionData daysInYearCustomStrategy;
+ private Boolean enableIncomeCapitalization;
+ private StringEnumOptionData capitalizedIncomeCalculationType;
+ private StringEnumOptionData capitalizedIncomeStrategy;
+ private List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions;
+ private List<StringEnumOptionData> capitalizedIncomeStrategyOptions;
/**
* Used when returning lookup information about loan product for dropdowns.
@@ -342,6 +349,9 @@ public class LoanProductData implements Serializable {
final StringEnumOptionData chargeOffBehaviour = null;
final boolean interestRecognitionOnDisbursementDate = false;
final StringEnumOptionData daysInYearTypeCustomStrategy = null;
+ final boolean enableIncomeCapitalization = false;
+ final StringEnumOptionData capitalizedIncomeCalculationType = null;
+ final StringEnumOptionData capitalizedIncomeStrategy = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -363,7 +373,8 @@ public class LoanProductData implements Serializable {
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
- interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy);
+ interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
+ capitalizedIncomeCalculationType, capitalizedIncomeStrategy);
}
@@ -468,6 +479,9 @@ public class LoanProductData implements Serializable {
final StringEnumOptionData chargeOffBehaviour = null;
final boolean interestRecognitionOnDisbursementDate = false;
final StringEnumOptionData daysInYearTypeCustomStrategy = null;
+ final boolean enableIncomeCapitalization = false;
+ final StringEnumOptionData capitalizedIncomeCalculationType = null;
+ final StringEnumOptionData capitalizedIncomeStrategy = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -489,7 +503,8 @@ public class LoanProductData implements Serializable {
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
- interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy);
+ interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
+ capitalizedIncomeCalculationType, capitalizedIncomeStrategy);
}
@@ -601,6 +616,9 @@ public class LoanProductData implements Serializable {
final StringEnumOptionData chargeOffBehaviour = LoanChargeOffBehaviour.REGULAR.getValueAsStringEnumOptionData();
final boolean interestRecognitionOnDisbursementDate = false;
final StringEnumOptionData daysInYearTypeCustomStrategy = null;
+ final boolean enableIncomeCapitalization = false;
+ final StringEnumOptionData capitalizedIncomeCalculationType = null;
+ final StringEnumOptionData capitalizedIncomeStrategy = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -622,7 +640,8 @@ public class LoanProductData implements Serializable {
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
- interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy);
+ interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
+ capitalizedIncomeCalculationType, capitalizedIncomeStrategy);
}
@@ -728,6 +747,9 @@ public class LoanProductData implements Serializable {
final StringEnumOptionData chargeOffBehaviour = null;
final boolean interestRecognitionOnDisbursementDate = false;
final StringEnumOptionData daysInYearTypeCustomStrategy = null;
+ final boolean enableIncomeCapitalization = false;
+ final StringEnumOptionData capitalizedIncomeCalculationType = null;
+ final StringEnumOptionData capitalizedIncomeStrategy = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -749,7 +771,8 @@ public class LoanProductData implements Serializable {
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocationData, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
- interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy);
+ interestRecognitionOnDisbursementDate, daysInYearTypeCustomStrategy, enableIncomeCapitalization,
+ capitalizedIncomeCalculationType, capitalizedIncomeStrategy);
}
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
@@ -804,7 +827,8 @@ public class LoanProductData implements Serializable {
final EnumOptionData loanScheduleType, final EnumOptionData loanScheduleProcessingType, final Integer fixedLength,
final boolean enableAccrualActivityPosting, final List<StringEnumOptionData> supportedInterestRefundTypes,
StringEnumOptionData chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
- final StringEnumOptionData daysInYearCustomStrategy) {
+ final StringEnumOptionData daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
+ final StringEnumOptionData capitalizedIncomeCalculationType, final StringEnumOptionData capitalizedIncomeStrategy) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -861,6 +885,9 @@ public class LoanProductData implements Serializable {
this.rates = rates;
this.isRatesEnabled = isRatesEnabled;
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
+ this.enableIncomeCapitalization = enableIncomeCapitalization;
+ this.capitalizedIncomeCalculationType = capitalizedIncomeCalculationType;
+ this.capitalizedIncomeStrategy = capitalizedIncomeStrategy;
this.chargeOptions = null;
this.penaltyOptions = null;
@@ -950,6 +977,8 @@ public class LoanProductData implements Serializable {
this.chargeOffReasonOptions = null;
this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategyOptions = DaysInYearCustomStrategyType.getValuesAsStringEnumOptionDataList();
+ this.capitalizedIncomeCalculationTypeOptions = LoanCapitalizedIncomeCalculationType.getValuesAsStringEnumOptionDataList();
+ this.capitalizedIncomeStrategyOptions = LoanCapitalizedIncomeStrategy.getValuesAsStringEnumOptionDataList();
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -974,7 +1003,9 @@ public class LoanProductData implements Serializable {
final List<EnumOptionData> creditAllocationAllocationTypes,
final List<StringEnumOptionData> supportedInterestRefundTypesOptions,
final List<StringEnumOptionData> chargeOffBehaviourOptions, final List<CodeValueData> chargeOffReasonOptions,
- final List<StringEnumOptionData> daysInYearCustomStrategyOptions) {
+ final List<StringEnumOptionData> daysInYearCustomStrategyOptions,
+ final List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions,
+ final List<StringEnumOptionData> capitalizedIncomeStrategyOptions) {
this.id = productData.id;
this.name = productData.name;
@@ -1138,6 +1169,11 @@ public class LoanProductData implements Serializable {
this.interestRecognitionOnDisbursementDate = productData.interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategyOptions = daysInYearCustomStrategyOptions;
this.daysInYearCustomStrategy = productData.daysInYearCustomStrategy;
+ this.enableIncomeCapitalization = productData.enableIncomeCapitalization;
+ this.capitalizedIncomeCalculationType = productData.capitalizedIncomeCalculationType;
+ this.capitalizedIncomeStrategy = productData.capitalizedIncomeStrategy;
+ this.capitalizedIncomeCalculationTypeOptions = capitalizedIncomeCalculationTypeOptions;
+ this.capitalizedIncomeStrategyOptions = capitalizedIncomeStrategyOptions;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> 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 c9b85ac25f..31e4ab8a1d 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,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -280,7 +282,9 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
final boolean enableAccrualActivityPosting, final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean isInterestRecognitionOnDisbursementDate,
- final DaysInYearCustomStrategyType daysInYearCustomStrategy) {
+ final DaysInYearCustomStrategyType daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
+ final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
+ final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy) {
this.fund = fund;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
@@ -330,7 +334,8 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(),
isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting,
- supportedInterestRefundTypes, chargeOffBehaviour, isInterestRecognitionOnDisbursementDate, daysInYearCustomStrategy);
+ supportedInterestRefundTypes, chargeOffBehaviour, isInterestRecognitionOnDisbursementDate, daysInYearCustomStrategy,
+ enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy);
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 a1a68ae214..29b18e6cea 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,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -168,6 +170,17 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "days_in_year_custom_strategy")
private DaysInYearCustomStrategyType daysInYearCustomStrategy;
+ @Column(name = "enable_income_capitalization")
+ private boolean enableIncomeCapitalization = false;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "capitalized_income_calculation_type")
+ private LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "capitalized_income_strategy")
+ private LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy;
+
public static LoanProductRelatedDetail createFrom(final CurrencyData currencyData, final BigDecimal principal,
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
@@ -182,7 +195,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
final boolean enableAccrualActivityPosting, final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
- final DaysInYearCustomStrategyType daysInYearCustomStrategy) {
+ final DaysInYearCustomStrategyType daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
+ final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
+ final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy) {
final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(currencyData);
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
@@ -192,7 +207,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment,
loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes,
- chargeOffBehaviour, interestRecognitionOnDisbursementDate, daysInYearCustomStrategy);
+ chargeOffBehaviour, interestRecognitionOnDisbursementDate, daysInYearCustomStrategy, enableIncomeCapitalization,
+ capitalizedIncomeCalculationType, capitalizedIncomeStrategy);
}
protected LoanProductRelatedDetail() {
@@ -213,7 +229,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
final boolean enableAccrualActivityPosting, List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
final LoanChargeOffBehaviour chargeOffBehaviour, final boolean interestRecognitionOnDisbursementDate,
- final DaysInYearCustomStrategyType daysInYearCustomStrategy) {
+ final DaysInYearCustomStrategyType daysInYearCustomStrategy, final boolean enableIncomeCapitalization,
+ final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType,
+ final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy) {
this.currency = currency;
this.principal = defaultPrincipal;
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
@@ -251,6 +269,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.chargeOffBehaviour = chargeOffBehaviour;
this.interestRecognitionOnDisbursementDate = interestRecognitionOnDisbursementDate;
this.daysInYearCustomStrategy = daysInYearCustomStrategy;
+ this.enableIncomeCapitalization = enableIncomeCapitalization;
+ this.capitalizedIncomeCalculationType = capitalizedIncomeCalculationType;
+ this.capitalizedIncomeStrategy = capitalizedIncomeStrategy;
}
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 ef4a8c075f..b57888bc03 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
@@ -132,6 +132,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryBalancesRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
@@ -1135,7 +1137,9 @@ public class LoansApiResource {
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
overdueCharges, paidInAdvanceTemplate, interestRatesPeriods, clientActiveLoanOptions, rates, isRatesEnabled, collectionData,
LoanScheduleType.getValuesAsEnumOptionDataList(), LoanScheduleProcessingType.getValuesAsEnumOptionDataList(),
- loanTermVariations, DaysInYearCustomStrategyType.getValuesAsStringEnumOptionDataList());
+ loanTermVariations, DaysInYearCustomStrategyType.getValuesAsStringEnumOptionDataList(),
+ LoanCapitalizedIncomeCalculationType.getValuesAsStringEnumOptionDataList(),
+ LoanCapitalizedIncomeStrategy.getValuesAsStringEnumOptionDataList());
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 d549371e4f..17787426d8 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
@@ -1206,6 +1206,12 @@ final class LoansApiResourceSwagger {
public StringEnumOptionData chargeOffBehaviour;
@Schema(example = "false")
public Boolean interestRecognitionOnDisbursementDate;
+ @Schema(example = "false")
+ public Boolean enableIncomeCapitalization;
+ @Schema(example = "FLAT")
+ public StringEnumOptionData capitalizedIncomeCalculationType;
+ @Schema(example = "EQUAL_AMORTIZATION")
+ public StringEnumOptionData capitalizedIncomeStrategy;
}
@Schema(description = "GetLoansResponse")
@@ -1309,6 +1315,12 @@ final class LoansApiResourceSwagger {
public BigDecimal fixedEmiAmount;
@Schema(example = "false")
public Boolean interestRecognitionOnDisbursementDate;
+ @Schema(example = "false")
+ public Boolean enableIncomeCapitalization;
+ @Schema(example = "FLAT", allowableValues = "FLAT")
+ public String capitalizedIncomeCalculationType;
+ @Schema(example = "EQUAL_AMORTIZATION", allowableValues = "EQUAL_AMORTIZATION")
+ public String capitalizedIncomeStrategy;
public List<PostLoansRequestChargeData> charges;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
index 1d7a6f21c0..825293fdb6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
@@ -172,6 +172,8 @@ public class LoanAccountData {
private List<EnumOptionData> loanScheduleTypeOptions;
private List<EnumOptionData> loanScheduleProcessingTypeOptions;
private List<StringEnumOptionData> daysInYearCustomStrategyOptions;
+ private List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions;
+ private List<StringEnumOptionData> capitalizedIncomeStrategyOptions;
@Transient
private BigDecimal feeChargesAtDisbursementCharged;
@@ -274,6 +276,9 @@ public class LoanAccountData {
private EnumOptionData loanScheduleProcessingType;
private StringEnumOptionData chargeOffBehaviour;
+ private Boolean enableIncomeCapitalization;
+ private StringEnumOptionData capitalizedIncomeCalculationType;
+ private StringEnumOptionData capitalizedIncomeStrategy;
public static LoanAccountData importInstanceIndividual(EnumOptionData loanTypeEnumOption, Long clientId, Long productId,
Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments,
@@ -464,7 +469,9 @@ public class LoanAccountData {
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment,
final boolean enableInstallmentLevelDelinquency, final EnumOptionData loanScheduleType,
final EnumOptionData loanScheduleProcessingType, final Integer fixedLength, final StringEnumOptionData chargeOffBehaviour,
- final boolean isInterestRecognitionOnDisbursementDate, final StringEnumOptionData daysInYearCustomStrategy) {
+ final boolean isInterestRecognitionOnDisbursementDate, final StringEnumOptionData daysInYearCustomStrategy,
+ final boolean enableIncomeCapitalization, final StringEnumOptionData capitalizedIncomeCalculationType,
+ final StringEnumOptionData capitalizedIncomeStrategy) {
final CollectionData delinquent = CollectionData.template();
@@ -510,7 +517,9 @@ public class LoanAccountData {
.setEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency).setLoanScheduleType(loanScheduleType)
.setLoanScheduleProcessingType(loanScheduleProcessingType).setFixedLength(fixedLength)
.setChargeOffBehaviour(chargeOffBehaviour).setInterestRecognitionOnDisbursementDate(isInterestRecognitionOnDisbursementDate)
- .setDaysInYearCustomStrategy(daysInYearCustomStrategy);
+ .setDaysInYearCustomStrategy(daysInYearCustomStrategy).setEnableIncomeCapitalization(enableIncomeCapitalization)
+ .setCapitalizedIncomeCalculationType(capitalizedIncomeCalculationType)
+ .setCapitalizedIncomeStrategy(capitalizedIncomeStrategy);
}
/*
@@ -536,7 +545,9 @@ public class LoanAccountData {
final Collection<LoanAccountSummaryData> clientActiveLoanOptions, final List<RateData> rates, final Boolean isRatesEnabled,
final CollectionData delinquent, final List<EnumOptionData> loanScheduleTypeOptions,
final List<EnumOptionData> loanScheduleProcessingTypeOptions, final List<LoanTermVariationsData> loanTermVariations,
- final List<StringEnumOptionData> daysInYearCustomStrategyOptions) {
+ final List<StringEnumOptionData> daysInYearCustomStrategyOptions,
+ final List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions,
+ final List<StringEnumOptionData> capitalizedIncomeStrategyOptions) {
// TODO: why are these variables 'calendarData', 'chargeTemplate' never used (see original private constructor)
@@ -557,7 +568,9 @@ public class LoanAccountData {
.setClientActiveLoanOptions(clientActiveLoanOptions).setRates(rates).setIsRatesEnabled(isRatesEnabled)
.setDelinquent(delinquent).setLoanScheduleTypeOptions(loanScheduleTypeOptions)
.setLoanScheduleProcessingTypeOptions(loanScheduleProcessingTypeOptions).setLoanTermVariations(loanTermVariations)
- .setDaysInYearCustomStrategyOptions(daysInYearCustomStrategyOptions);
+ .setDaysInYearCustomStrategyOptions(daysInYearCustomStrategyOptions)
+ .setCapitalizedIncomeCalculationTypeOptions(capitalizedIncomeCalculationTypeOptions)
+ .setCapitalizedIncomeStrategyOptions(capitalizedIncomeStrategyOptions);
}
public LoanAccountData associationsAndTemplate(final Collection<LoanProductData> productOptions,
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 f6fb5578f3..b4ac0b05c5 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
@@ -544,7 +544,10 @@ public class LoanScheduleAssembler {
loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting(),
loanProduct.getLoanProductRelatedDetail().getSupportedInterestRefundTypes(),
loanProduct.getLoanProductRelatedDetail().getChargeOffBehaviour(), interestRecognitionOnDisbursementDate,
- loanProduct.getLoanProductRelatedDetail().getDaysInYearCustomStrategy());
+ loanProduct.getLoanProductRelatedDetail().getDaysInYearCustomStrategy(),
+ loanProduct.getLoanProductRelatedDetail().isEnableIncomeCapitalization(),
+ loanProduct.getLoanProductRelatedDetail().getCapitalizedIncomeCalculationType(),
+ loanProduct.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy());
}
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 67b748065d..85522bed1a 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,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
@@ -305,6 +307,13 @@ public class LoanProductAssembler {
chargeOffBehaviour = LoanChargeOffBehaviour.REGULAR;
}
+ final boolean enableIncomeCapitalization = command
+ .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME);
+ final LoanCapitalizedIncomeCalculationType capitalizedIncomeCalculationType = command.enumValueOfParameterNamed(
+ LoanProductConstants.CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME, LoanCapitalizedIncomeCalculationType.class);
+ final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy = command.enumValueOfParameterNamed(
+ LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME, LoanCapitalizedIncomeStrategy.class);
+
return new LoanProduct(fund, loanTransactionProcessingStrategy, loanProductPaymentAllocationRules, loanProductCreditAllocationRules,
name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod,
@@ -325,7 +334,7 @@ public class LoanProductAssembler {
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType, fixedLength,
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour, interestRecognitionOnDisbursementDate,
- daysInYearCustomStrategy);
+ daysInYearCustomStrategy, enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy);
}
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 9e286fe24d..da78ccf984 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,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -296,6 +298,28 @@ public class LoanProductRelatedDetailUpdateUtil {
loanRepaymentScheduleDetail.updateInterestRecognitionOnDisbursementDate(newValue);
}
+ if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME,
+ loanRepaymentScheduleDetail.isEnableIncomeCapitalization())) {
+ final boolean newValue = command
+ .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME);
+ actualChanges.put(LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME, newValue);
+ loanRepaymentScheduleDetail.setEnableIncomeCapitalization(newValue);
+ }
+
+ if (command.parameterExists(LoanProductConstants.CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME)) {
+ final LoanCapitalizedIncomeCalculationType newValue = command.enumValueOfParameterNamed(
+ LoanProductConstants.CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME, LoanCapitalizedIncomeCalculationType.class);
+ actualChanges.put(LoanProductConstants.CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME, newValue);
+ loanRepaymentScheduleDetail.setCapitalizedIncomeCalculationType(newValue);
+ }
+
+ if (command.parameterExists(LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME)) {
+ final LoanCapitalizedIncomeStrategy newValue = command.enumValueOfParameterNamed(
+ LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME, LoanCapitalizedIncomeStrategy.class);
+ actualChanges.put(LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME, newValue);
+ loanRepaymentScheduleDetail.setCapitalizedIncomeStrategy(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 f5f61cdf2b..e2399aeeab 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
@@ -106,6 +106,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
@@ -722,6 +724,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
+ " l.is_floating_interest_rate as isFloatingInterestRate, "
+ " l.interest_rate_differential as interestRateDifferential, "
+ " l.days_in_year_custom_strategy as daysInYearCustomStrategy, "
+ + " l.enable_income_capitalization as enableIncomeCapitalization, "
+ + " l.capitalized_income_calculation_type as capitalizedIncomeCalculationType, "
+ + " l.capitalized_income_strategy as capitalizedIncomeStrategy, "
+ " 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, "
@@ -1098,6 +1103,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
final boolean interestRecognitionOnDisbursementDate = rs.getBoolean("interestRecognitionOnDisbursementDate");
final StringEnumOptionData daysInYearCustomStrategy = DaysInYearCustomStrategyType
.getStringEnumOptionData(rs.getString("daysInYearCustomStrategy"));
+ final boolean enableIncomeCapitalization = rs.getBoolean("enableIncomeCapitalization");
+ final StringEnumOptionData capitalizedIncomeCalculationType = LoanCapitalizedIncomeCalculationType
+ .getStringEnumOptionData(rs.getString("capitalizedIncomeCalculationType"));
+ final StringEnumOptionData capitalizedIncomeStrategy = LoanCapitalizedIncomeStrategy
+ .getStringEnumOptionData(rs.getString("capitalizedIncomeStrategy"));
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName,
clientOfficeId, clientExternalId, groupData, loanType, loanProductId, loanProductName, loanProductDescription,
@@ -1117,7 +1127,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
lastClosedBusinessDate, overpaidOnDate, isChargedOff, enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(),
loanScheduleProcessingType.asEnumOptionData(), fixedLength, chargeOffBehaviour.getValueAsStringEnumOptionData(),
- interestRecognitionOnDisbursementDate, daysInYearCustomStrategy);
+ interestRecognitionOnDisbursementDate, daysInYearCustomStrategy, enableIncomeCapitalization,
+ capitalizedIncomeCalculationType, capitalizedIncomeStrategy);
}
}
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 f45322979d..271b1f7107 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
@@ -81,6 +81,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -127,7 +129,10 @@ public class LoanProductsApiResource {
LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT,
LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.ENABLE_DOWN_PAYMENT,
LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT,
- LoanProductConstants.REPAYMENT_START_DATE_TYPE, LoanProductConstants.DAYS_IN_YEAR_CUSTOM_STRATEGY_TYPE_PARAMETER_NAME));
+ 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));
private static final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions"));
@@ -439,6 +444,10 @@ public class LoanProductsApiResource {
.retrieveCodeValuesByCode(LoanApiConstants.CHARGE_OFF_REASONS);
final List<StringEnumOptionData> daysInYearCustomStrategyOptions = DaysInYearCustomStrategyType
.getValuesAsStringEnumOptionDataList();
+ final List<StringEnumOptionData> capitalizedIncomeCalculationTypeOptions = LoanCapitalizedIncomeCalculationType
+ .getValuesAsStringEnumOptionDataList();
+ final List<StringEnumOptionData> capitalizedIncomeStrategyOptions = LoanCapitalizedIncomeStrategy
+ .getValuesAsStringEnumOptionDataList();
return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions,
@@ -451,7 +460,7 @@ public class LoanProductsApiResource {
advancedPaymentAllocationTypes, LoanScheduleType.getValuesAsEnumOptionDataList(),
LoanScheduleProcessingType.getValuesAsEnumOptionDataList(), creditAllocationTransactionTypes,
creditAllocationAllocationTypes, supportedInterestRefundTypesOptions, chargeOffBehaviourOptions, chargeOffReasonOptions,
- daysInYearCustomStrategyOptions);
+ daysInYearCustomStrategyOptions, capitalizedIncomeCalculationTypeOptions, capitalizedIncomeStrategyOptions);
}
}
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 d2e29d0add..81c8d5b878 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,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
@@ -186,7 +188,10 @@ public final class LoanProductDataValidator {
LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH,
LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES,
LoanProductConstants.CHARGE_OFF_BEHAVIOUR, LoanProductConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE,
- LoanProductConstants.DAYS_IN_YEAR_CUSTOM_STRATEGY_TYPE_PARAMETER_NAME));
+ 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));
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
@@ -877,6 +882,8 @@ public final class LoanProductDataValidator {
"Charge off behaviour is only supported for Progressive loans");
}
+ validateIncomeCapitalization(transactionProcessingStrategyCode, element, baseDataValidator);
+
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -1922,6 +1929,9 @@ public final class LoanProductDataValidator {
validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
+
+ validateIncomeCapitalization(transactionProcessingStrategyCode, element, baseDataValidator);
+
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -2660,6 +2670,39 @@ public final class LoanProductDataValidator {
}
}
+ private void validateIncomeCapitalization(String transactionProcessingStrategyCode, JsonElement element,
+ DataValidatorBuilder baseDataValidator) {
+ if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME, element)) {
+ final String capitalizedIncomeCalculationType = this.fromApiJsonHelper
+ .extractStringNamed(LoanProductConstants.CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME, element);
+ baseDataValidator.reset().parameter(LoanProductConstants.CAPITALIZED_INCOME_CALCULATION_TYPE_PARAM_NAME)
+ .value(capitalizedIncomeCalculationType).isOneOfEnumValues(LoanCapitalizedIncomeCalculationType.class);
+ }
+
+ if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME, element)) {
+ final String capitalizedIncomeStrategy = this.fromApiJsonHelper
+ .extractStringNamed(LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME, element);
+ baseDataValidator.reset().parameter(LoanProductConstants.CAPITALIZED_INCOME_STRATEGY_PARAM_NAME)
+ .value(capitalizedIncomeStrategy).isOneOfEnumValues(LoanCapitalizedIncomeStrategy.class);
+ }
+
+ if (AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategyCode)
+ && this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME, element)) {
+ Boolean enableIncomeCapitalization = this.fromApiJsonHelper
+ .extractBooleanNamed(LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME, element);
+ baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME)
+ .value(enableIncomeCapitalization).ignoreIfNull().validateForBooleanValue();
+ } else if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME, element)) {
+ Boolean enableIncomeCapitalization = this.fromApiJsonHelper
+ .extractBooleanNamed(LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME, element);
+ if (Boolean.TRUE.equals(enableIncomeCapitalization)) {
+ baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_INCOME_CAPITALIZATION_PARAM_NAME).failWithCode(
+ "supported.only.for.progressive.loan.income.capitalization",
+ "Income capitalization 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 c3fe4c728f..4c436216c1 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
@@ -45,6 +45,8 @@ 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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -288,8 +290,11 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lp.allow_variabe_installments as isVariableIntallmentsAllowed, " + "lvi.minimum_gap as minimumGap, "
+ "lvi.maximum_gap as maximumGap, dbuc.id as delinquencyBucketId, dbuc.name as delinquencyBucketName, "
+ "lp.can_use_for_topup as canUseForTopup, lp.is_equal_amortization as isEqualAmortization, lp.loan_schedule_type as loanScheduleType, lp.loan_schedule_processing_type as loanScheduleProcessingType, lp.supported_interest_refund_types as supportedInterestRefundTypes, "
- + "lp.charge_off_behaviour as chargeOffBehaviour" + " from m_product_loan lp "
- + " left join m_fund f on f.id = lp.fund_id "
+ + "lp.charge_off_behaviour as chargeOffBehaviour, " //
+ + "lp.enable_income_capitalization as enableIncomeCapitalization, " //
+ + "lp.capitalized_income_calculation_type as capitalizedIncomeCalculationType, " //
+ + "lp.capitalized_income_strategy as capitalizedIncomeStrategy " //
+ + " 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 "
+ " left join m_product_loan_configurable_attributes lca on lca.loan_product_id = lp.id "
@@ -552,6 +557,11 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final String chargeOffBehaviourStr = rs.getString("chargeOffBehaviour");
final LoanChargeOffBehaviour loanChargeOffBehaviour = LoanChargeOffBehaviour.valueOf(chargeOffBehaviourStr);
final boolean interestRecognitionOnDisbursementDate = rs.getBoolean("interestRecognitionOnDisbursementDate");
+ final boolean enableIncomeCapitalization = rs.getBoolean("enableIncomeCapitalization");
+ final StringEnumOptionData capitalizedIncomeCalculationType = LoanCapitalizedIncomeCalculationType
+ .getStringEnumOptionData(rs.getString("capitalizedIncomeCalculationType"));
+ final StringEnumOptionData capitalizedIncomeStrategy = LoanCapitalizedIncomeStrategy
+ .getStringEnumOptionData(rs.getString("capitalizedIncomeStrategy"));
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -576,7 +586,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(), loanScheduleProcessingType.asEnumOptionData(),
fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes,
loanChargeOffBehaviour.getValueAsStringEnumOptionData(), interestRecognitionOnDisbursementDate,
- daysInYearCustomStrategy);
+ daysInYearCustomStrategy, enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy);
}
}
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 2f294c33ce..09a9f0e42e 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
@@ -189,4 +189,5 @@
<include file="parts/0168_transaction_summary_with_asset_owner_report_add_active_intermediate_filtering.xml" relativeToChangelogFile="true" />
<include file="parts/0169_add_missing_permissions.xml" relativeToChangelogFile="true" />
<include file="parts/0170_days_in_year_custom_strategy.xml" relativeToChangelogFile="true"/>
+ <include file="parts/0171_loan_capitalized_income.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0171_loan_capitalized_income.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0171_loan_capitalized_income.xml
new file mode 100644
index 0000000000..3ac0cf5a20
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0171_loan_capitalized_income.xml
@@ -0,0 +1,49 @@
+<?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_income_capitalization" type="boolean" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
+ <column name="capitalized_income_calculation_type" type="VARCHAR(100)">
+ <constraints nullable="true"/>
+ </column>
+ <column name="capitalized_income_strategy" type="VARCHAR(100)">
+ <constraints nullable="true"/>
+ </column>
+ </addColumn>
+ <addColumn tableName="m_product_loan">
+ <column name="enable_income_capitalization" type="boolean" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
+ <column name="capitalized_income_calculation_type" type="VARCHAR(100)">
+ <constraints nullable="true"/>
+ </column>
+ <column name="capitalized_income_strategy" type="VARCHAR(100)">
+ <constraints nullable="true"/>
+ </column>
+ </addColumn>
+ </changeSet>
+</databaseChangeLog>
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 a7574fd5f2..d0d83caa6c 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
@@ -98,7 +98,8 @@ public class DefaultScheduledDateGeneratorTest {
Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, 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);
+ submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null, null, false, null, false, null,
+ null);
// when
List<? extends LoanScheduleModelPeriod> result = underTest.generateRepaymentPeriods(mathContext, expectedDisbursementDate,
@@ -179,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, false, 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
new file mode 100644
index 0000000000..ea49812563
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTest.java
@@ -0,0 +1,147 @@
+/**
+ * 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.integrationtests;
+
+import java.math.BigDecimal;
+import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostClientsResponse;
+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.LoanCapitalizedIncomeCalculationType;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeStrategy;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class LoanProductTest extends BaseLoanIntegrationTest {
+
+ @Test
+ public void testIncomeCapitalizationEnabled() {
+ final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+
+ final PostLoanProductsResponse loanProductsResponse = loanProductHelper
+ .createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
+ .capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)
+ .capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION));
+
+ final GetLoanProductsProductIdResponse loanProductsProductIdResponse = loanProductHelper
+ .retrieveLoanProductById(loanProductsResponse.getResourceId());
+ Assertions.assertEquals(Boolean.TRUE, loanProductsProductIdResponse.getEnableIncomeCapitalization());
+ Assertions.assertNotNull(loanProductsProductIdResponse.getCapitalizedIncomeCalculationType());
+ Assertions.assertEquals(LoanCapitalizedIncomeCalculationType.FLAT.getCode(),
+ loanProductsProductIdResponse.getCapitalizedIncomeCalculationType().getCode());
+ Assertions.assertNotNull(loanProductsProductIdResponse.getCapitalizedIncomeStrategy());
+ Assertions.assertEquals(LoanCapitalizedIncomeStrategy.EQUAL_AMORTIZATION.getCode(),
+ loanProductsProductIdResponse.getCapitalizedIncomeStrategy().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.getEnableIncomeCapitalization());
+ Assertions.assertNotNull(loanDetails.getCapitalizedIncomeCalculationType());
+ Assertions.assertEquals(LoanCapitalizedIncomeCalculationType.FLAT.getCode(),
+ loanDetails.getCapitalizedIncomeCalculationType().getCode());
+ Assertions.assertNotNull(loanDetails.getCapitalizedIncomeStrategy());
+ Assertions.assertEquals(LoanCapitalizedIncomeStrategy.EQUAL_AMORTIZATION.getCode(),
+ loanDetails.getCapitalizedIncomeStrategy().getCode());
+
+ Assertions.assertDoesNotThrow(() -> disburseLoan(loanId, BigDecimal.valueOf(430), "20 December 2024"));
+ });
+ }
+
+ @Test
+ public void testIncomeCapitalizationDisabled() {
+ final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+
+ final PostLoanProductsResponse loanProductsResponse = loanProductHelper
+ .createLoanProduct(create4IProgressive().enableIncomeCapitalization(false)
+ .capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)
+ .capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION));
+
+ final GetLoanProductsProductIdResponse loanProductsProductIdResponse = loanProductHelper
+ .retrieveLoanProductById(loanProductsResponse.getResourceId());
+ Assertions.assertEquals(Boolean.FALSE, loanProductsProductIdResponse.getEnableIncomeCapitalization());
+ Assertions.assertNotNull(loanProductsProductIdResponse.getCapitalizedIncomeCalculationType());
+ Assertions.assertEquals(LoanCapitalizedIncomeCalculationType.FLAT.getCode(),
+ loanProductsProductIdResponse.getCapitalizedIncomeCalculationType().getCode());
+ Assertions.assertNotNull(loanProductsProductIdResponse.getCapitalizedIncomeStrategy());
+ Assertions.assertEquals(LoanCapitalizedIncomeStrategy.EQUAL_AMORTIZATION.getCode(),
+ loanProductsProductIdResponse.getCapitalizedIncomeStrategy().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.getEnableIncomeCapitalization());
+ Assertions.assertNotNull(loanDetails.getCapitalizedIncomeCalculationType());
+ Assertions.assertEquals(LoanCapitalizedIncomeCalculationType.FLAT.getCode(),
+ loanDetails.getCapitalizedIncomeCalculationType().getCode());
+ Assertions.assertNotNull(loanDetails.getCapitalizedIncomeStrategy());
+ Assertions.assertEquals(LoanCapitalizedIncomeStrategy.EQUAL_AMORTIZATION.getCode(),
+ loanDetails.getCapitalizedIncomeStrategy().getCode());
+
+ Assertions.assertDoesNotThrow(() -> disburseLoan(loanId, BigDecimal.valueOf(430), "20 December 2024"));
+ });
+ }
+
+ @Test
+ public void testIncomeCapitalizationUpdateProduct() {
+ final PostLoanProductsResponse loanProductsResponse = loanProductHelper
+ .createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
+ .capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)
+ .capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION));
+
+ final GetLoanProductsProductIdResponse loanProductsProductIdResponse = loanProductHelper
+ .retrieveLoanProductById(loanProductsResponse.getResourceId());
+ Assertions.assertEquals(Boolean.TRUE, loanProductsProductIdResponse.getEnableIncomeCapitalization());
+ Assertions.assertNotNull(loanProductsProductIdResponse.getCapitalizedIncomeCalculationType());
+ Assertions.assertEquals(LoanCapitalizedIncomeCalculationType.FLAT.getCode(),
+ loanProductsProductIdResponse.getCapitalizedIncomeCalculationType().getCode());
+ Assertions.assertNotNull(loanProductsProductIdResponse.getCapitalizedIncomeStrategy());
+ Assertions.assertEquals(LoanCapitalizedIncomeStrategy.EQUAL_AMORTIZATION.getCode(),
+ loanProductsProductIdResponse.getCapitalizedIncomeStrategy().getCode());
+
+ loanProductHelper.updateLoanProductById(loanProductsResponse.getResourceId(),
+ new PutLoanProductsProductIdRequest().enableIncomeCapitalization(false));
+
+ final GetLoanProductsProductIdResponse updatedLoanProductsProductIdResponse = loanProductHelper
+ .retrieveLoanProductById(loanProductsResponse.getResourceId());
+ Assertions.assertEquals(Boolean.FALSE, updatedLoanProductsProductIdResponse.getEnableIncomeCapitalization());
+ Assertions.assertNotNull(updatedLoanProductsProductIdResponse.getCapitalizedIncomeCalculationType());
+ Assertions.assertEquals(LoanCapitalizedIncomeCalculationType.FLAT.getCode(),
+ updatedLoanProductsProductIdResponse.getCapitalizedIncomeCalculationType().getCode());
+ Assertions.assertNotNull(updatedLoanProductsProductIdResponse.getCapitalizedIncomeStrategy());
+ Assertions.assertEquals(LoanCapitalizedIncomeStrategy.EQUAL_AMORTIZATION.getCode(),
+ updatedLoanProductsProductIdResponse.getCapitalizedIncomeStrategy().getCode());
+ }
+
+ @Test
+ public void testIncomeCapitalizationCumulativeNotSupported() {
+ Assertions.assertThrows(RuntimeException.class,
+ () -> loanProductHelper.createLoanProduct(createOnePeriod30DaysPeriodicAccrualProduct(7.0).enableIncomeCapitalization(true)
+ .capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)
+ .capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)));
+ }
+
+}