FINERACT-1958-Loan-Product-repayment-start-date-configuration

This commit is contained in:
Ruchi Dhamankar
2023-08-22 12:28:36 +02:00
committed by Adam Saghy
parent b1cde8a809
commit 3c6fc1e47b
20 changed files with 794 additions and 49 deletions
@@ -139,6 +139,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.rate.domain.Rate;
@@ -5853,6 +5854,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
RecalculationFrequencyType compoundingFrequencyType = null;
LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
CalendarHistoryDataWrapper calendarHistoryDataWrapper;
RepaymentStartDateType repaymentStartDateType = this.getLoanProduct().getRepaymentStartDateType();
boolean allowCompoundingOnEod = false;
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = scheduleGeneratorDTO.getCalendarInstanceForInterestRecalculation();
@@ -5886,7 +5888,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
calendarHistoryDataWrapper, scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(),
holidayDetailDTO, allowCompoundingOnEod, scheduleGeneratorDTO.isFirstRepaymentDateAllowedOnHoliday(),
scheduleGeneratorDTO.isInterestToBeRecoveredFirstWhenGreaterThanEMI(), this.fixedPrincipalPercentagePerInstallment,
scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans());
scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans(), repaymentStartDateType, getSubmittedOnDate());
}
public BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate,
@@ -6153,6 +6155,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
InterestRecalculationCompoundingMethod compoundingMethod = null;
RecalculationFrequencyType compoundingFrequencyType = null;
LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
RepaymentStartDateType repaymentStartDateType = loanProduct.getRepaymentStartDateType();
boolean allowCompoundingOnEod = false;
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
recalculationFrequencyType = this.loanInterestRecalculationDetails.getRestFrequencyType();
@@ -6173,7 +6176,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);
false, this.fixedPrincipalPercentagePerInstallment, false, repaymentStartDateType, getSubmittedOnDate());
}
/**
@@ -51,6 +51,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCa
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
@Slf4j
public final class LoanApplicationTerms {
@@ -211,6 +212,9 @@ public final class LoanApplicationTerms {
private BigDecimal disbursedAmountPercentageForDownPayment;
private boolean isAutoRepaymentForDownPaymentEnabled;
private RepaymentStartDateType repaymentStartDateType;
private LocalDate submittedOnDate;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType,
@@ -235,7 +239,8 @@ public final class LoanApplicationTerms {
final boolean isEqualAmortization, final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI,
final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans,
final Boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
final Boolean isAutoRepaymentForDownPaymentEnabled) {
final Boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType,
final LocalDate submittedOnDate) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -253,7 +258,7 @@ public final class LoanApplicationTerms {
isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false,
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment,
isAutoRepaymentForDownPaymentEnabled);
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate);
}
@@ -273,7 +278,8 @@ public final class LoanApplicationTerms {
final CalendarHistoryDataWrapper calendarHistoryDataWrapper, final Integer numberOfDays,
final boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod,
final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI,
final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans) {
final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans,
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -321,7 +327,7 @@ public final class LoanApplicationTerms {
allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday,
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment,
isAutoRepaymentForDownPaymentEnabled);
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate);
}
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -348,7 +354,8 @@ public final class LoanApplicationTerms {
final boolean allowCompoundingOnEod, final boolean isEqualAmortization, final boolean isFirstRepaymentDateAllowedOnHoliday,
final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI, final BigDecimal fixedPrincipalPercentagePerInstallment,
final boolean isPrincipalCompoundingDisabledForOverdueLoans, final boolean isDownPaymentEnabled,
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean isAutoRepaymentForDownPaymentEnabled) {
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean isAutoRepaymentForDownPaymentEnabled,
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
@@ -428,6 +435,8 @@ public final class LoanApplicationTerms {
this.isDownPaymentEnabled = isDownPaymentEnabled;
this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment;
this.isAutoRepaymentForDownPaymentEnabled = isAutoRepaymentForDownPaymentEnabled;
this.repaymentStartDateType = repaymentStartDateType;
this.submittedOnDate = submittedOnDate;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -1745,4 +1754,12 @@ public final class LoanApplicationTerms {
public BigDecimal getDisbursedAmountPercentageForDownPayment() {
return disbursedAmountPercentageForDownPayment;
}
public RepaymentStartDateType getRepaymentStartDateType() {
return repaymentStartDateType;
}
public LocalDate getSubmittedOnDate() {
return submittedOnDate;
}
}
@@ -151,5 +151,6 @@ public interface LoanProductConstants {
String ENABLE_DOWN_PAYMENT = "enableDownPayment";
String DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT = "disbursedAmountPercentageForDownPayment";
String ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT = "enableAutoRepaymentForDownPayment";
String REPAYMENT_START_DATE_TYPE = "repaymentStartDateType";
}
@@ -213,6 +213,9 @@ public class LoanProduct extends AbstractPersistableCustom {
@Column(name = "overdue_days_for_repayment_event")
private Integer overDueDaysForRepaymentEvent;
@Column(name = "repayment_start_date_type_enum", nullable = false)
private RepaymentStartDateType repaymentStartDateType;
public static LoanProduct assembleFromJson(final Fund fund, final String loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate,
final List<Rate> productRates, List<LoanProductPaymentAllocationRule> loanProductPaymentAllocationRules) {
@@ -394,6 +397,9 @@ public class LoanProduct extends AbstractPersistableCustom {
final boolean enableAutoRepaymentForDownPayment = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT);
final RepaymentStartDateType repaymentStartDateType = RepaymentStartDateType
.fromInt(command.integerValueOfParameterNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE));
return new LoanProduct(fund, loanTransactionProcessingStrategy, loanProductPaymentAllocationRules, name, shortName, description,
currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod,
interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod,
@@ -411,7 +417,7 @@ public class LoanProduct extends AbstractPersistableCustom {
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment,
disallowExpectedDisbursements, allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber,
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment,
enableAutoRepaymentForDownPayment);
enableAutoRepaymentForDownPayment, repaymentStartDateType);
}
@@ -625,7 +631,7 @@ public class LoanProduct extends AbstractPersistableCustom {
final boolean allowApprovedDisbursedAmountsOverApplied, final String overAppliedCalculationType,
final Integer overAppliedNumber, final Integer dueDaysForRepaymentEvent, final Integer overDueDaysForRepaymentEvent,
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
final boolean enableAutoRepaymentForDownPayment) {
final boolean enableAutoRepaymentForDownPayment, final RepaymentStartDateType repaymentStartDateType) {
this.fund = fund;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
@@ -719,6 +725,7 @@ public class LoanProduct extends AbstractPersistableCustom {
this.dueDaysForRepaymentEvent = dueDaysForRepaymentEvent;
this.overDueDaysForRepaymentEvent = overDueDaysForRepaymentEvent;
this.repaymentStartDateType = repaymentStartDateType;
validateLoanProductPreSave();
}
@@ -1281,6 +1288,12 @@ public class LoanProduct extends AbstractPersistableCustom {
this.loanProductRelatedDetail.updateEnableAutoRepaymentForDownPayment(newValue);
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE,
this.repaymentStartDateType.getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE);
actualChanges.put(LoanProductConstants.REPAYMENT_START_DATE_TYPE, newValue);
this.repaymentStartDateType = RepaymentStartDateType.fromInt(newValue);
}
return actualChanges;
}
@@ -1676,4 +1689,8 @@ public class LoanProduct extends AbstractPersistableCustom {
return this.overDueDaysForRepaymentEvent;
}
public RepaymentStartDateType getRepaymentStartDateType() {
return this.repaymentStartDateType == null ? RepaymentStartDateType.INVALID : this.repaymentStartDateType;
}
}
@@ -0,0 +1,62 @@
/**
* 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;
public enum RepaymentStartDateType {
INVALID(0, "repaymentStartDateType.invalid"), //
DISBURSEMENT_DATE(1, "repaymentStartDateType.disbursementDate"), //
SUBMITTED_ON_DATE(2, "repaymentStartDateType.submittedOnDate");
private final Integer value;
private final String code;
RepaymentStartDateType(Integer value, String code) {
this.value = value;
this.code = code;
}
public static RepaymentStartDateType fromInt(final Integer repaymentStartDateType) {
if (repaymentStartDateType == null) {
return RepaymentStartDateType.DISBURSEMENT_DATE;
}
return switch (repaymentStartDateType) {
case 1 -> RepaymentStartDateType.DISBURSEMENT_DATE;
case 2 -> RepaymentStartDateType.SUBMITTED_ON_DATE;
default -> RepaymentStartDateType.INVALID;
};
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isDisbursementDate() {
return RepaymentStartDateType.DISBURSEMENT_DATE.equals(this);
}
public boolean isSubmittedOnDate() {
return RepaymentStartDateType.SUBMITTED_ON_DATE.equals(this);
}
}
@@ -38,6 +38,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductParamType;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
public final class LoanEnumerations {
@@ -57,6 +58,7 @@ public final class LoanEnumerations {
public static final String LOAN_TYPE = "loanType";
public static final String INTEREST_RECALCULATION_COMPOUNDING_TYPE = "interestRecalculationCompoundingType";
public static final String RESCHEDULE_STRATEGY_TYPE = "rescheduleStrategyType";
public static final String REPAYMENT_START_DATE_TYPE = "repaymentStartDateType";
public static EnumOptionData loanEnumeration(final String typeName, final int id) {
return switch (typeName) {
@@ -71,10 +73,26 @@ public final class LoanEnumerations {
case LOAN_TYPE -> AccountEnumerations.loanType(id);
case INTEREST_RECALCULATION_COMPOUNDING_TYPE -> interestRecalculationCompoundingType(id);
case RESCHEDULE_STRATEGY_TYPE -> rescheduleStrategyType(id);
case REPAYMENT_START_DATE_TYPE -> repaymentStartDateType(id);
default -> null;
};
}
public static EnumOptionData repaymentStartDateType(int id) {
return repaymentStartDateType(RepaymentStartDateType.fromInt(id));
}
public static EnumOptionData repaymentStartDateType(final RepaymentStartDateType type) {
return switch (type) {
case DISBURSEMENT_DATE -> new EnumOptionData(RepaymentStartDateType.DISBURSEMENT_DATE.getValue().longValue(),
RepaymentStartDateType.DISBURSEMENT_DATE.getCode(), "Disbursement Date");
case SUBMITTED_ON_DATE -> new EnumOptionData(RepaymentStartDateType.SUBMITTED_ON_DATE.getValue().longValue(),
RepaymentStartDateType.SUBMITTED_ON_DATE.getCode(), "Submitted On Date");
default -> new EnumOptionData(RepaymentStartDateType.INVALID.getValue().longValue(), RepaymentStartDateType.INVALID.getCode(),
"Invalid");
};
}
public static EnumOptionData loanTermFrequencyType(final int id) {
return loanTermFrequencyType(PeriodFrequencyType.fromInt(id));
}
@@ -27,4 +27,5 @@
<include relativeToChangelogFile="true" file="parts/1002_add_payment_allocation_rule.xml"/>
<include relativeToChangelogFile="true" file="parts/1003_add_loan_product_auto_repayment_down_payment_configuration.xml"/>
<include relativeToChangelogFile="true" file="parts/1004_add_external_event_configuration_for_down_payment_transaction_event.xml"/>
<include relativeToChangelogFile="true" file="parts/1005_add_loan_product_repayment_start_date_configuration.xml"/>
</databaseChangeLog>
@@ -0,0 +1,32 @@
<?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.1.xsd">
<changeSet author="fineract" id="1">
<addColumn tableName="m_product_loan">
<column defaultValueNumeric="1" name="repayment_start_date_type_enum" type="SMALLINT">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>