diff --git a/fineract-investor/src/main/resources/jpa/investor/persistence.xml b/fineract-investor/src/main/resources/jpa/investor/persistence.xml
index b52d34a10..1d136770f 100644
--- a/fineract-investor/src/main/resources/jpa/investor/persistence.xml
+++ b/fineract-investor/src/main/resources/jpa/investor/persistence.xml
@@ -118,6 +118,7 @@
org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule
org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType
org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTypeListConverter
+ org.apache.fineract.portfolio.loanproduct.domain.SupportedInterestRefundTypesListConverter
org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest
org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 4e232000e..f9e4eeea6 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -4457,8 +4457,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
calendarHistoryDataWrapper, scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(),
holidayDetailDTO, allowCompoundingOnEod, scheduleGeneratorDTO.isFirstRepaymentDateAllowedOnHoliday(),
scheduleGeneratorDTO.isInterestToBeRecoveredFirstWhenGreaterThanEMI(), this.fixedPrincipalPercentagePerInstallment,
- scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans(), repaymentStartDateType, getSubmittedOnDate(),
- loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting());
+ scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans(), repaymentStartDateType, getSubmittedOnDate());
}
public BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate,
@@ -4706,8 +4705,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(),
rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations,
calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst, holidayDetailDTO, allowCompoundingOnEod, false,
- false, this.fixedPrincipalPercentagePerInstallment, false, repaymentStartDateType, getSubmittedOnDate(),
- loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting());
+ false, this.fixedPrincipalPercentagePerInstallment, false, repaymentStartDateType, getSubmittedOnDate());
}
public void updateRescheduledByUser(AppUser rescheduledByUser) {
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
index fada088d8..2af4b1d90 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -65,6 +65,7 @@ public enum LoanTransactionType {
REAMORTIZE(30, "loanTransactionType.reAmortize"), //
INTEREST_PAYMENT_WAIVER(31, "loanTransactionType.interestPaymentWaiver"), //
ACCRUAL_ACTIVITY(32, "loanTransactionType.accrualActivity"), //
+ INTEREST_REFUND(33, "loanTransactionType.interestRefund"), //
;
private final Integer value;
@@ -113,6 +114,7 @@ public enum LoanTransactionType {
case 30 -> LoanTransactionType.REAMORTIZE;
case 31 -> LoanTransactionType.INTEREST_PAYMENT_WAIVER;
case 32 -> LoanTransactionType.ACCRUAL_ACTIVITY;
+ case 33 -> LoanTransactionType.INTEREST_REFUND;
default -> LoanTransactionType.INVALID;
};
}
@@ -221,4 +223,9 @@ public enum LoanTransactionType {
public boolean isAccrualActivity() {
return this.equals(LoanTransactionType.ACCRUAL_ACTIVITY);
}
+
+ public boolean isInterestRefund() {
+ return this.equals(LoanTransactionType.INTEREST_REFUND);
+ }
+
}
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 54cc4e570..ed0813cc5 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
@@ -53,6 +53,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCom
import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
@@ -225,6 +226,7 @@ public final class LoanApplicationTerms {
private final LoanScheduleType loanScheduleType;
private final LoanScheduleProcessingType loanScheduleProcessingType;
private final boolean enableAccrualActivityPosting;
+ private final List supportedInterestRefundTypes;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
@@ -253,7 +255,7 @@ public final class LoanApplicationTerms {
final Boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType,
final LocalDate submittedOnDate, final LoanScheduleType loanScheduleType,
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
- final boolean enableAccrualActivityPosting) {
+ final boolean enableAccrualActivityPosting, final List supportedInterestRefundTypes) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -272,7 +274,7 @@ public final class LoanApplicationTerms {
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment,
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType,
- fixedLength, enableAccrualActivityPosting);
+ fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes);
}
@@ -293,8 +295,7 @@ public final class LoanApplicationTerms {
final boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod,
final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI,
final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans,
- final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate,
- final boolean enableAccrualActivityPosting) {
+ final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -346,7 +347,8 @@ public final class LoanApplicationTerms {
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment,
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType,
- fixedLength, enableAccrualActivityPosting);
+ fixedLength, loanProductRelatedDetail.isEnableAccrualActivityPosting(),
+ loanProductRelatedDetail.getSupportedInterestRefundTypes());
}
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -375,7 +377,8 @@ public final class LoanApplicationTerms {
final boolean isPrincipalCompoundingDisabledForOverdueLoans, final boolean isDownPaymentEnabled,
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean isAutoRepaymentForDownPaymentEnabled,
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate, final LoanScheduleType loanScheduleType,
- final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength, boolean enableAccrualActivityPosting) {
+ final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength, boolean enableAccrualActivityPosting,
+ final List supportedInterestRefundTypes) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
@@ -473,6 +476,7 @@ public final class LoanApplicationTerms {
this.loanScheduleType = loanScheduleType;
this.loanScheduleProcessingType = loanScheduleProcessingType;
this.fixedLength = fixedLength;
+ this.supportedInterestRefundTypes = supportedInterestRefundTypes;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -1337,7 +1341,7 @@ public final class LoanApplicationTerms {
this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), this.daysInYearType.getValue(),
this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled,
this.disbursedAmountPercentageForDownPayment, this.isAutoRepaymentForDownPaymentEnabled, this.loanScheduleType,
- this.loanScheduleProcessingType, this.fixedLength, this.enableAccrualActivityPosting);
+ this.loanScheduleProcessingType, this.fixedLength, this.enableAccrualActivityPosting, this.supportedInterestRefundTypes);
}
public Integer getLoanTermFrequency() {
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 29b6cbcf9..90308e5b3 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
@@ -165,5 +165,6 @@ public interface LoanProductConstants {
String FIXED_LENGTH = "fixedLength";
String ENABLE_ACCRUAL_ACTIVITY_POSTING = "enableAccrualActivityPosting";
+ String SUPPORTED_INTEREST_REFUND_TYPES = "supportedInterestRefundTypes";
}
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 8bfcbd1b5..cd04ef8e8 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
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Set;
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiResourceSwagger.GetDelinquencyBucketsResponse;
/**
@@ -318,6 +319,7 @@ final class LoanProductsApiResourceSwagger {
public boolean active;
}
+ public List supportedInterestRefundTypes;
}
@Schema(description = "PostLoanProductsResponse")
@@ -600,6 +602,7 @@ final class LoanProductsApiResourceSwagger {
@Schema(example = "0")
public Integer principalThresholdForLastInstalment;
public GetLoanProductsResponse.GetLoanProductsRepaymentStartDateType repaymentStartDateType;
+ public List supportedInterestRefundTypes;
}
@Schema(description = "GetLoanProductsTemplateResponse")
@@ -1063,6 +1066,8 @@ final class LoanProductsApiResourceSwagger {
public List creditAllocationAllocationTypes;
public List creditAllocationTransactionTypes;
+ public List supportedInterestRefundTypes;
+ public List supportedInterestRefundTypesOptions;
}
@Schema(description = "GetLoanProductsProductIdResponse")
@@ -1336,6 +1341,7 @@ final class LoanProductsApiResourceSwagger {
public EnumOptionData loanScheduleProcessingType;
@Schema(example = "false")
public Boolean enableAccrualActivityPosting;
+ public List supportedInterestRefundTypes;
}
@Schema(description = "PutLoanProductsProductIdRequest")
@@ -1627,6 +1633,7 @@ final class LoanProductsApiResourceSwagger {
public boolean active;
}
+ public List supportedInterestRefundTypes;
}
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 f455b26da..f1c232985 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
@@ -33,6 +33,7 @@ import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.calendar.data.CalendarData;
import org.apache.fineract.portfolio.charge.data.ChargeData;
@@ -134,6 +135,7 @@ public class LoanProductData implements Serializable {
private final boolean canDefineInstallmentAmount;
private final Integer installmentAmountInMultiplesOf;
private final EnumOptionData repaymentStartDateType;
+ private final List supportedInterestRefundTypes;
// charges
private final Collection charges;
@@ -190,6 +192,7 @@ public class LoanProductData implements Serializable {
private final List interestRecalculationFrequencyTypeOptions;
private final List floatingRateOptions;
private final List repaymentStartDateTypeOptions;
+ private final List supportedInterestRefundTypesOptions;
private final Boolean multiDisburseLoan;
private final Integer maxTrancheCount;
@@ -325,6 +328,7 @@ public class LoanProductData implements Serializable {
final EnumOptionData loanScheduleTypeOptions = null;
final EnumOptionData loanScheduleProcessingTypeOptions = null;
final boolean enableAccrualActivityPosting = false;
+ final List supportedInterestRefundTypes = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -345,7 +349,7 @@ public class LoanProductData implements Serializable {
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
- loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting);
+ loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes);
}
@@ -446,6 +450,7 @@ public class LoanProductData implements Serializable {
final EnumOptionData loanScheduleType = null;
final EnumOptionData loanScheduleProcessingType = null;
final boolean enableAccrualActivityPosting = false;
+ final List supportedInterestRefundTypes = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -466,7 +471,7 @@ public class LoanProductData implements Serializable {
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
- loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting);
+ loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes);
}
@@ -574,6 +579,7 @@ public class LoanProductData implements Serializable {
final EnumOptionData loanScheduleType = LoanScheduleType.CUMULATIVE.asEnumOptionData();
final EnumOptionData loanScheduleProcessingType = LoanScheduleProcessingType.HORIZONTAL.asEnumOptionData();
final boolean enableAccrualActivityPosting = false;
+ final List supportedInterestRefundTypes = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -594,7 +600,7 @@ public class LoanProductData implements Serializable {
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
- loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting);
+ loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes);
}
@@ -696,6 +702,7 @@ public class LoanProductData implements Serializable {
final EnumOptionData loanScheduleType = null;
final EnumOptionData loanScheduleProcessingType = null;
final boolean enableAccrualActivityPosting = false;
+ final List supportedInterestRefundTypes = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -716,7 +723,7 @@ public class LoanProductData implements Serializable {
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation, creditAllocationData, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
- loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting);
+ loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes);
}
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map accountingMappings,
@@ -767,7 +774,7 @@ public class LoanProductData implements Serializable {
final Collection paymentAllocation, final Collection creditAllocation,
final EnumOptionData repaymentStartDateType, final boolean enableInstallmentLevelDelinquency,
final EnumOptionData loanScheduleType, final EnumOptionData loanScheduleProcessingType, final Integer fixedLength,
- final boolean enableAccrualActivityPosting) {
+ final boolean enableAccrualActivityPosting, final List supportedInterestRefundTypes) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -904,6 +911,8 @@ public class LoanProductData implements Serializable {
this.loanScheduleTypeOptions = null;
this.loanScheduleProcessingTypeOptions = null;
this.enableAccrualActivityPosting = enableAccrualActivityPosting;
+ this.supportedInterestRefundTypes = supportedInterestRefundTypes;
+ this.supportedInterestRefundTypesOptions = null;
}
public LoanProductData(final LoanProductData productData, final Collection chargeOptions,
@@ -925,7 +934,8 @@ public class LoanProductData implements Serializable {
final List advancedPaymentAllocationFutureInstallmentAllocationRules,
final List advancedPaymentAllocationTypes, final List loanScheduleTypeOptions,
final List loanScheduleProcessingTypeOptions, final List creditAllocationTransactionTypes,
- final List creditAllocationAllocationTypes) {
+ final List creditAllocationAllocationTypes,
+ final List supportedInterestRefundTypesOptions) {
this.id = productData.id;
this.name = productData.name;
@@ -1080,6 +1090,8 @@ public class LoanProductData implements Serializable {
this.loanScheduleProcessingTypeOptions = loanScheduleProcessingTypeOptions;
this.loanScheduleTypeOptions = loanScheduleTypeOptions;
this.enableAccrualActivityPosting = productData.enableAccrualActivityPosting;
+ this.supportedInterestRefundTypesOptions = supportedInterestRefundTypesOptions;
+ this.supportedInterestRefundTypes = productData.supportedInterestRefundTypes;
}
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 4ccadd68c..9d1920e45 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
@@ -442,6 +442,15 @@ public class LoanProduct extends AbstractPersistableCustom {
final boolean enableAccrualActivityPosting = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING);
+ List supportedInterestRefundTypes = new ArrayList<>();
+ if (command.parameterExists(LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES)) {
+ JsonArray supportedTransactionsForInterestRefund = command
+ .arrayOfParameterNamed(LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES);
+ supportedTransactionsForInterestRefund.iterator().forEachRemaining(value -> {
+ supportedInterestRefundTypes.add(LoanSupportedInterestRefundTypes.valueOf(value.getAsString()));
+ });
+ }
+
return new LoanProduct(fund, loanTransactionProcessingStrategy, loanProductPaymentAllocationRules, loanProductCreditAllocationRules,
name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod,
@@ -461,7 +470,7 @@ public class LoanProduct extends AbstractPersistableCustom {
allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType, fixedLength,
- enableAccrualActivityPosting);
+ enableAccrualActivityPosting, supportedInterestRefundTypes);
}
@@ -679,7 +688,7 @@ public class LoanProduct extends AbstractPersistableCustom {
final boolean enableAutoRepaymentForDownPayment, final RepaymentStartDateType repaymentStartDateType,
final boolean enableInstallmentLevelDelinquency, final LoanScheduleType loanScheduleType,
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
- final boolean enableAccrualActivityPosting) {
+ final boolean enableAccrualActivityPosting, final List supportedInterestRefundTypes) {
this.fund = fund;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
@@ -728,7 +737,8 @@ public class LoanProduct extends AbstractPersistableCustom {
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(),
isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment,
- enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting);
+ enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting,
+ supportedInterestRefundTypes);
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 a9096d074..d6790db78 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
@@ -19,12 +19,14 @@
package org.apache.fineract.portfolio.loanproduct.domain;
import jakarta.persistence.Column;
+import jakarta.persistence.Convert;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
@@ -153,6 +155,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "enable_accrual_activity_posting", nullable = false)
private boolean enableAccrualActivityPosting = false;
+ @Convert(converter = SupportedInterestRefundTypesListConverter.class)
+ @Column(name = "supported_interest_refund_types")
+ private List supportedInterestRefundTypes = List.of();
+
public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal,
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
@@ -165,7 +171,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
final boolean enableAutoRepaymentForDownPayment, final LoanScheduleType loanScheduleType,
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
- final boolean enableAccrualActivityPosting) {
+ final boolean enableAccrualActivityPosting, final List supportedInterestRefundTypes) {
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion,
@@ -173,7 +179,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment,
- loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting);
+ loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes);
}
protected LoanProductRelatedDetail() {
@@ -192,7 +198,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
final boolean enableAutoRepaymentForDownPayment, final LoanScheduleType loanScheduleType,
final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength,
- final boolean enableAccrualActivityPosting) {
+ final boolean enableAccrualActivityPosting, List supportedInterestRefundTypes) {
this.currency = currency;
this.principal = defaultPrincipal;
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
@@ -226,6 +232,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.loanScheduleType = loanScheduleType;
this.loanScheduleProcessingType = loanScheduleProcessingType;
this.enableAccrualActivityPosting = enableAccrualActivityPosting;
+ this.supportedInterestRefundTypes = supportedInterestRefundTypes;
}
private Integer defaultToNullIfZero(final Integer value) {
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanSupportedInterestRefundTypes.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanSupportedInterestRefundTypes.java
new file mode 100644
index 000000000..565c862ee
--- /dev/null
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanSupportedInterestRefundTypes.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.loanproduct.domain;
+
+import java.util.Arrays;
+import java.util.List;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+
+@Getter
+@RequiredArgsConstructor
+public enum LoanSupportedInterestRefundTypes {
+
+ MERCHANT_ISSUED_REFUND(LoanTransactionType.MERCHANT_ISSUED_REFUND, "loanRefundType.merchant_issued_refund", "Merchant issued refund"), //
+ PAYOUT_REFUND(LoanTransactionType.PAYOUT_REFUND, "loanRefundType.payout_refund", "Payout refund"), //
+ ;
+
+ private final LoanTransactionType transactionType;
+ private final String code;
+ private final String humanReadableName;
+
+ public static List 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());
+ }
+}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
index 7842dd7b4..b9d8b1172 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/PaymentAllocationTransactionType.java
@@ -41,7 +41,8 @@ public enum PaymentAllocationTransactionType {
WAIVE_INTEREST(LoanTransactionType.WAIVE_INTEREST, "Waive interest"), //
CHARGE_PAYMENT(LoanTransactionType.CHARGE_PAYMENT, "Charge payment"), //
REFUND_FOR_ACTIVE_LOAN(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, "Refund for active loan"), //
- INTEREST_PAYMENT_WAIVER(LoanTransactionType.INTEREST_PAYMENT_WAIVER, "Interest payment waiver");
+ INTEREST_PAYMENT_WAIVER(LoanTransactionType.INTEREST_PAYMENT_WAIVER, "Interest payment waiver"), //
+ INTEREST_REFUND(LoanTransactionType.INTEREST_REFUND, "Interest refund");
private final LoanTransactionType loanTransactionType;
private final String humanReadableName;
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/SupportedInterestRefundTypesListConverter.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/SupportedInterestRefundTypesListConverter.java
new file mode 100644
index 000000000..5476e7453
--- /dev/null
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/SupportedInterestRefundTypesListConverter.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanproduct.domain;
+
+// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanProduct classes
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+import java.util.List;
+import org.apache.fineract.infrastructure.core.data.GenericEnumListConverter;
+
+@Converter(autoApply = true)
+public class SupportedInterestRefundTypesListConverter extends GenericEnumListConverter
+ implements AttributeConverter, String> {
+
+ @Override
+ public boolean isUnique() {
+ return true;
+ }
+
+ public SupportedInterestRefundTypesListConverter() {
+ super(LoanSupportedInterestRefundTypes.class);
+ }
+
+}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
index 4901cccbb..434a219b1 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
@@ -321,6 +321,8 @@ public final class LoanEnumerations {
LoanTransactionType.REAMORTIZE.getCode(), "Re-amortize");
case ACCRUAL_ACTIVITY -> new LoanTransactionEnumData(LoanTransactionType.ACCRUAL_ACTIVITY.getValue().longValue(),
LoanTransactionType.ACCRUAL_ACTIVITY.getCode(), "Accrual Activity");
+ case INTEREST_REFUND -> new LoanTransactionEnumData(LoanTransactionType.INTEREST_REFUND.getValue().longValue(),
+ LoanTransactionType.INTEREST_REFUND.getCode(), "Interest Refund");
};
}
diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
index 4fffd9b3e..e3b76c417 100644
--- a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
+++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
@@ -44,4 +44,5 @@
+
diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1022_add_interest_refund_support.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1022_add_interest_refund_support.xml
new file mode 100644
index 000000000..6f7d4ecf2
--- /dev/null
+++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1022_add_interest_refund_support.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fineract-progressive-loan/src/main/resources/jpa/progressiveloan/persistence.xml b/fineract-progressive-loan/src/main/resources/jpa/progressiveloan/persistence.xml
index 24ff3eead..ca8dabe36 100644
--- a/fineract-progressive-loan/src/main/resources/jpa/progressiveloan/persistence.xml
+++ b/fineract-progressive-loan/src/main/resources/jpa/progressiveloan/persistence.xml
@@ -114,6 +114,7 @@
org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule
org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType
org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTypeListConverter
+ org.apache.fineract.portfolio.loanproduct.domain.SupportedInterestRefundTypesListConverter
org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest
org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks
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 dbed762c6..2bca76f2d 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
@@ -522,7 +522,8 @@ public class LoanScheduleAssembler {
fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled,
disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate,
loanScheduleType, loanScheduleProcessingType, fixedLength,
- loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting());
+ loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting(),
+ loanProduct.getLoanProductRelatedDetail().getSupportedInterestRefundTypes());
}
private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery,
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 8be92659d..75d16770b 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
@@ -59,6 +59,7 @@ import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
@@ -84,6 +85,7 @@ import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrat
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
@@ -421,6 +423,8 @@ public class LoanProductsApiResource {
final List advancedPaymentAllocationTypes = PaymentAllocationType.getValuesAsEnumOptionDataList();
final List creditAllocationTransactionTypes = CreditAllocationTransactionType.getValuesAsEnumOptionDataList();
final List creditAllocationAllocationTypes = AllocationType.getValuesAsEnumOptionDataList();
+ final List supportedInterestRefundTypesOptions = LoanSupportedInterestRefundTypes
+ .getValuesAsStringEnumOptionDataList();
return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions,
@@ -432,7 +436,7 @@ public class LoanProductsApiResource {
advancedPaymentAllocationTransactionTypes, advancedPaymentAllocationFutureInstallmentAllocationRules,
advancedPaymentAllocationTypes, LoanScheduleType.getValuesAsEnumOptionDataList(),
LoanScheduleProcessingType.getValuesAsEnumOptionDataList(), creditAllocationTransactionTypes,
- creditAllocationAllocationTypes);
+ creditAllocationAllocationTypes, supportedInterestRefundTypesOptions);
}
}
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 7be800757..a38400074 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
@@ -61,6 +61,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCa
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.exception.EqualAmortizationUnsupportedFeatureException;
import org.springframework.stereotype.Component;
@@ -175,7 +176,7 @@ public final class LoanProductDataValidator {
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.REPAYMENT_START_DATE_TYPE,
LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, LoanProductConstants.LOAN_SCHEDULE_TYPE,
LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH,
- LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING));
+ LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES));
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
@@ -841,6 +842,19 @@ public final class LoanProductDataValidator {
validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
+ if (AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategyCode)
+ && this.fromApiJsonHelper.parameterExists(LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES, element)) {
+ String[] supportedTransactionsForInterestRefund = this.fromApiJsonHelper
+ .extractArrayNamed(LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES, element);
+ Arrays.stream(supportedTransactionsForInterestRefund)
+ .forEach(value -> baseDataValidator.reset().parameter(LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES).value(value)
+ .isOneOfEnumValues(LoanSupportedInterestRefundTypes.class));
+ } else if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES, element)) {
+ baseDataValidator.reset().parameter(LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES).failWithCode(
+ "supported.only.for.progressive.loan.schedule.handling",
+ "Automatic interest refund functionality is only supported for Progressive loans");
+ }
+
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
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 d9d8c63a1..b63935129 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
@@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingEnumerations;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -56,6 +57,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductParamType;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.rate.data.RateData;
import org.apache.fineract.portfolio.rate.service.RateReadService;
@@ -276,7 +278,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lfr.is_floating_interest_rate_calculation_allowed as isFloatingInterestRateCalculationAllowed, "
+ "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.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 "
+ " 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 "
@@ -526,6 +528,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final String loanScheduleProcessingTypeStr = rs.getString("loanScheduleProcessingType");
final LoanScheduleProcessingType loanScheduleProcessingType = LoanScheduleProcessingType.valueOf(loanScheduleProcessingTypeStr);
final boolean enableAccrualActivityPosting = rs.getBoolean("enableAccrualActivityPosting");
+ final String supportedInterestRefundTypesString = rs.getString("supportedInterestRefundTypes");
+ List supportedInterestRefundTypes = List.of();
+ if (supportedInterestRefundTypesString != null && !supportedInterestRefundTypesString.isEmpty()) {
+ supportedInterestRefundTypes = Arrays.stream(supportedInterestRefundTypesString.split(","))
+ .map(LoanSupportedInterestRefundTypes::valueOf)
+ .map(LoanSupportedInterestRefundTypes::getValueAsStringEnumOptionData).toList();
+ }
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -548,7 +557,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, advancedPaymentData, creditAllocationData, repaymentStartDateType,
enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(), loanScheduleProcessingType.asEnumOptionData(),
- fixedLength, enableAccrualActivityPosting);
+ fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes);
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
index 55266d185..593e47e1b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java
@@ -60,6 +60,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductCreditAllocationRule;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductCannotBeModifiedDueToNonClosedLoansException;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductDateException;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
@@ -122,6 +123,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
}
final LoanProduct loanProduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategyCode, charges, command,
this.aprCalculator, floatingRate, rates, loanProductPaymentAllocationRules, loanProductCreditAllocationRules);
+
loanProduct.updateLoanProductInRelatedClasses();
loanProduct.setTransactionProcessingStrategyName(
loanRepaymentScheduleTransactionProcessorFactory.determineProcessor(loanTransactionProcessingStrategyCode).getName());
@@ -264,6 +266,16 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
}
}
+ if (command.parameterExists(LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES)) {
+ JsonArray supportedTransactionsForInterestRefund = command
+ .arrayOfParameterNamed(LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES);
+ List supportedInterestRefundTypes = new ArrayList<>();
+ supportedTransactionsForInterestRefund.iterator().forEachRemaining(value -> {
+ supportedInterestRefundTypes.add(LoanSupportedInterestRefundTypes.valueOf(value.getAsString()));
+ });
+ product.getLoanProductRelatedDetail().setSupportedInterestRefundTypes(supportedInterestRefundTypes);
+ }
+
if (!changes.isEmpty()) {
product.validateLoanProductPreSave();
this.loanProductRepository.saveAndFlush(product);
diff --git a/fineract-provider/src/main/resources/jpa/persistence.xml b/fineract-provider/src/main/resources/jpa/persistence.xml
index f2eb0777d..4419151df 100644
--- a/fineract-provider/src/main/resources/jpa/persistence.xml
+++ b/fineract-provider/src/main/resources/jpa/persistence.xml
@@ -121,6 +121,7 @@
org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule
org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType
org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTypeListConverter
+ org.apache.fineract.portfolio.loanproduct.domain.SupportedInterestRefundTypesListConverter
org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest
org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks
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 c87f77407..442e4cd33 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
@@ -95,7 +95,7 @@ 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);
+ submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null);
// when
List result = underTest.generateRepaymentPeriods(expectedDisbursementDate, loanApplicationTerms,
@@ -166,7 +166,7 @@ public class DefaultScheduledDateGeneratorTest {
null, null, null, null, null, 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);
+ DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null);
}
private HolidayDetailDTO createHolidayDTO() {
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
index fda6d2f07..a3303169a 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithAdvancedPaymentAllocationIntegrationTests.java
@@ -28,6 +28,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.accounting.common.AccountingConstants;
import org.apache.fineract.client.models.AdvancedPaymentData;
@@ -94,7 +95,8 @@ public class LoanProductWithAdvancedPaymentAllocationIntegrationTests {
AdvancedPaymentData repaymentPaymentAllocation = createRepaymentPaymentAllocation();
// when
- Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(createLoanJSON(defaultAllocation, repaymentPaymentAllocation));
+ Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductTestBuilder(
+ customization -> customization.addAdvancedPaymentAllocation(defaultAllocation, repaymentPaymentAllocation)));
Assertions.assertNotNull(loanProductId);
GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
@@ -117,7 +119,8 @@ public class LoanProductWithAdvancedPaymentAllocationIntegrationTests {
// given a loan with two allocations
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
AdvancedPaymentData repaymentPaymentAllocation = createRepaymentPaymentAllocation();
- Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(createLoanJSON(defaultAllocation, repaymentPaymentAllocation));
+ Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductTestBuilder(
+ customization -> customization.addAdvancedPaymentAllocation(defaultAllocation, repaymentPaymentAllocation)));
Assertions.assertNotNull(loanProductId);
GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertNotNull(loanProduct.getPaymentAllocation());
@@ -138,7 +141,8 @@ public class LoanProductWithAdvancedPaymentAllocationIntegrationTests {
// given a loan with one allocation
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
AdvancedPaymentData repaymentPaymentAllocation = createRepaymentPaymentAllocation();
- Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(createLoanJSON(defaultAllocation));
+ Integer loanProductId = LOAN_TRANSACTION_HELPER
+ .getLoanProductId(loanProductTestBuilder(customization -> customization.addAdvancedPaymentAllocation(defaultAllocation)));
Assertions.assertNotNull(loanProductId);
GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertNotNull(loanProduct.getPaymentAllocation());
@@ -168,7 +172,8 @@ public class LoanProductWithAdvancedPaymentAllocationIntegrationTests {
// given a loan with two allocations
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
AdvancedPaymentData repaymentPaymentAllocation = createRepaymentPaymentAllocation();
- Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(createLoanJSON(defaultAllocation, repaymentPaymentAllocation));
+ Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductTestBuilder(
+ customization -> customization.addAdvancedPaymentAllocation(defaultAllocation, repaymentPaymentAllocation)));
Assertions.assertNotNull(loanProductId);
GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertNotNull(loanProduct.getPaymentAllocation());
@@ -188,7 +193,8 @@ public class LoanProductWithAdvancedPaymentAllocationIntegrationTests {
// given a loan with two allocations
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
AdvancedPaymentData repaymentPaymentAllocation = createRepaymentPaymentAllocation();
- Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(createLoanJSON(defaultAllocation, repaymentPaymentAllocation));
+ Integer loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductTestBuilder(
+ customization -> customization.addAdvancedPaymentAllocation(defaultAllocation, repaymentPaymentAllocation)));
Assertions.assertNotNull(loanProductId);
GetLoanProductsProductIdResponse loanProduct = LOAN_TRANSACTION_HELPER.getLoanProduct(loanProductId);
Assertions.assertNotNull(loanProduct.getPaymentAllocation());
@@ -229,8 +235,8 @@ public class LoanProductWithAdvancedPaymentAllocationIntegrationTests {
LoanTransactionHelper validationErrorHelper = new LoanTransactionHelper(REQUEST_SPEC, errorResponse);
// when
- List