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
The following commit(s) were added to refs/heads/develop by this push:
new 3c6fc1e47 FINERACT-1958-Loan-Product-repayment-start-date-configuration
3c6fc1e47 is described below
commit 3c6fc1e47b981e98e3357fbfe00b25a7abb58a83
Author: Ruchi Dhamankar <[email protected]>
AuthorDate: Mon Aug 21 19:13:09 2023 +0530
FINERACT-1958-Loan-Product-repayment-start-date-configuration
---
.../portfolio/loanaccount/domain/Loan.java | 7 +-
.../loanschedule/domain/LoanApplicationTerms.java | 27 +-
.../loanproduct/LoanProductConstants.java | 1 +
.../portfolio/loanproduct/domain/LoanProduct.java | 21 +-
.../loanproduct/domain/RepaymentStartDateType.java | 62 +++
.../loanproduct/service/LoanEnumerations.java | 18 +
.../tenant/module/loan/module-changelog-master.xml | 1 +
...product_repayment_start_date_configuration.xml} | 16 +-
.../domain/AbstractLoanScheduleGenerator.java | 26 +-
.../domain/DefaultScheduledDateGenerator.java | 9 +-
.../service/LoanScheduleAssembler.java | 60 ++-
.../loanproduct/api/LoanProductsApiResource.java | 8 +-
.../api/LoanProductsApiResourceSwagger.java | 31 ++
.../loanproduct/data/LoanProductData.java | 23 +-
.../serialization/LoanProductDataValidator.java | 18 +-
.../service/LoanDropdownReadPlatformService.java | 1 +
.../LoanDropdownReadPlatformServiceImpl.java | 8 +
.../LoanProductReadPlatformServiceImpl.java | 6 +-
...ProductRepaymentStartDateConfigurationTest.java | 474 +++++++++++++++++++++
.../common/loans/LoanProductTestBuilder.java | 10 +
20 files changed, 771 insertions(+), 56 deletions(-)
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 ed50efdde..90b52b80d 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
@@ -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());
}
/**
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 6d7ffe7d9..5c0e6d141 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
@@ -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;
+ }
}
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 0d6176b3d..3a3b75523 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
@@ -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";
}
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 e74d9fcb0..89a3fe47a 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
@@ -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;
+ }
+
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/RepaymentStartDateType.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/RepaymentStartDateType.java
new file mode 100644
index 000000000..7d4e88fbc
--- /dev/null
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/RepaymentStartDateType.java
@@ -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);
+ }
+
+}
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 735caf788..affa36385 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
@@ -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));
}
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 f9e4a3859..0f352e5d6 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
@@ -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>
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/parts/1005_add_loan_product_repayment_start_date_configuration.xml
similarity index 56%
copy from
fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
copy to
fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1005_add_loan_product_repayment_start_date_configuration.xml
index f9e4a3859..b0829998d 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/parts/1005_add_loan_product_repayment_start_date_configuration.xml
@@ -20,11 +20,13 @@
-->
<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">
- <!-- Sequence is starting from 1000 to make it easier to move existing
liquibase changesets here -->
- <include relativeToChangelogFile="true"
file="parts/1001_add_audit_fields_to_loan_charge.xml"/>
- <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"/>
+ 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>
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index d7063ad2c..e152c78da 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -57,6 +57,7 @@ import
org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
import
org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.exception.ScheduleDateException;
+import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
public abstract class AbstractLoanScheduleGenerator implements
LoanScheduleGenerator {
@@ -91,14 +92,16 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
final int numberOfRepayments =
loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions();
- LoanScheduleParams scheduleParams;
+ LoanScheduleParams scheduleParams = null;
+ LocalDate periodStartDate =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
+ ? loanApplicationTerms.getExpectedDisbursementDate()
+ : loanApplicationTerms.getSubmittedOnDate();
if (loanScheduleParams == null) {
scheduleParams =
LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency,
chargesDueAtTimeOfDisbursement),
- loanApplicationTerms.getExpectedDisbursementDate(),
getPrincipalToBeScheduled(loanApplicationTerms));
+ periodStartDate,
getPrincipalToBeScheduled(loanApplicationTerms));
} else if (!loanScheduleParams.isPartialUpdate()) {
scheduleParams =
LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency,
chargesDueAtTimeOfDisbursement),
- loanApplicationTerms.getExpectedDisbursementDate(),
getPrincipalToBeScheduled(loanApplicationTerms),
- loanScheduleParams);
+ periodStartDate,
getPrincipalToBeScheduled(loanApplicationTerms), loanScheduleParams);
} else {
scheduleParams = loanScheduleParams;
}
@@ -124,8 +127,11 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
}
boolean isFirstRepayment = true;
- LocalDate firstRepaymentdate = this.scheduledDateGenerator
-
.generateNextRepaymentDate(loanApplicationTerms.getExpectedDisbursementDate(),
loanApplicationTerms, isFirstRepayment);
+ LocalDate lastRepaymentDate =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
+ ? loanApplicationTerms.getExpectedDisbursementDate()
+ : loanApplicationTerms.getSubmittedOnDate();
+ LocalDate firstRepaymentdate =
this.scheduledDateGenerator.generateNextRepaymentDate(lastRepaymentDate,
loanApplicationTerms,
+ isFirstRepayment);
final LocalDate idealDisbursementDate =
this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
loanApplicationTerms.getLoanTermPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate,
loanApplicationTerms.getLoanCalendar(),
loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms);
@@ -2207,7 +2213,9 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
if (loanApplicationTerms.isInterestRecalculationEnabled()) {
lastRestDate =
getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms,
holidayDetailDTO);
}
- LocalDate actualRepaymentDate =
loanApplicationTerms.getExpectedDisbursementDate();
+ LocalDate actualRepaymentDate =
RepaymentStartDateType.DISBURSEMENT_DATE
+ .equals(loanApplicationTerms.getRepaymentStartDateType())
? loanApplicationTerms.getExpectedDisbursementDate()
+ : loanApplicationTerms.getSubmittedOnDate();
boolean isFirstRepayment = true;
// cumulative fields
@@ -2222,7 +2230,9 @@ public abstract class AbstractLoanScheduleGenerator
implements LoanScheduleGener
// Actual period Number plus interest only repayments
int instalmentNumber = 1;
LocalDate lastInstallmentDate = actualRepaymentDate;
- LocalDate periodStartDate =
loanApplicationTerms.getExpectedDisbursementDate();
+ LocalDate periodStartDate =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
+ ? loanApplicationTerms.getExpectedDisbursementDate()
+ : loanApplicationTerms.getSubmittedOnDate();
// Set fixed Amortization Amounts(either EMI or Principal )
updateAmortization(mc, loanApplicationTerms, periodNumber,
outstandingBalance);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
index 04cc076c3..804cb1c40 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
@@ -32,6 +32,7 @@ import
org.apache.fineract.portfolio.calendar.domain.CalendarHistory;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
+import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,7 +45,11 @@ public class DefaultScheduledDateGenerator implements
ScheduledDateGenerator {
final int numberOfRepayments =
loanApplicationTerms.getNumberOfRepayments();
- LocalDate lastRepaymentDate =
loanApplicationTerms.getExpectedDisbursementDate();
+ RepaymentStartDateType repaymentStartDateType =
loanApplicationTerms.getRepaymentStartDateType();
+
+ LocalDate lastRepaymentDate =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
+ ? loanApplicationTerms.getExpectedDisbursementDate()
+ : loanApplicationTerms.getSubmittedOnDate();
boolean isFirstRepayment = true;
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments;
repaymentPeriod++) {
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate,
loanApplicationTerms, isFirstRepayment);
@@ -125,8 +130,6 @@ public class DefaultScheduledDateGenerator implements
ScheduledDateGenerator {
* @param adjustedDateDetailsDTO
* @param loanApplicationTerms
* @param holidayDetailDTO
- * @param nextRepaymentPeriodDueDate
- * @param rescheduleType
* @param isFirstRepayment
* @return
*/
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 55384b5b7..9b7d5e077 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
@@ -112,6 +112,7 @@ import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductVariableInstallmentConfig;
import
org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
+import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import
org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.beans.factory.annotation.Autowired;
@@ -234,7 +235,11 @@ public class LoanScheduleAssembler {
final Money principalMoney = Money.of(currency, principal);
final LocalDate expectedDisbursementDate =
this.fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate",
element);
- final LocalDate repaymentsStartingFromDate =
this.fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate",
element);
+ LocalDate repaymentsStartingFromDate =
this.fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate",
element);
+ final LocalDate submittedOnDate =
this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element);
+
+ final RepaymentStartDateType repaymentStartDateType =
loanProduct.getRepaymentStartDateType();
+
LocalDate calculatedRepaymentsStartingFromDate =
repaymentsStartingFromDate;
final Boolean synchDisbursement =
this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting",
element);
@@ -261,7 +266,8 @@ public class LoanScheduleAssembler {
&& !nthDay.equals(NthDayType.INVALID.getValue())) {
LocalDate calendarStartDate = repaymentsStartingFromDate;
if (calendarStartDate == null) {
- calendarStartDate = expectedDisbursementDate;
+ calendarStartDate =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType) ?
expectedDisbursementDate
+ : submittedOnDate;
}
calendar = createLoanCalendar(calendarStartDate,
repaymentEvery, CalendarFrequencyType.MONTHLY, dayOfWeek, nthDay);
}
@@ -272,7 +278,8 @@ public class LoanScheduleAssembler {
*/
if (calculatedRepaymentsStartingFromDate == null) {
calculatedRepaymentsStartingFromDate =
deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
- repaymentPeriodFrequencyType,
loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar);
+ repaymentPeriodFrequencyType,
loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar,
submittedOnDate,
+ repaymentStartDateType);
}
/*
@@ -305,8 +312,10 @@ public class LoanScheduleAssembler {
}
}
-
validateMinimumDaysBetweenDisbursalAndFirstRepayment(expectedDisbursementDate,
calculatedRepaymentsStartingFromDate,
- loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment());
+ if
(RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)) {
+
validateMinimumDaysBetweenDisbursalAndFirstRepayment(expectedDisbursementDate,
calculatedRepaymentsStartingFromDate,
+
loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment());
+ }
// grace details
final Integer graceOnPrincipalPayment =
this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment",
element);
@@ -471,7 +480,7 @@ public class LoanScheduleAssembler {
loanTermVariations,
isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays,
isSkipMeetingOnFirstDay, detailDTO,
allowCompoundingOnEod, isEqualAmortization,
isInterestToBeRecoveredFirstWhenGreaterThanEMI,
fixedPrincipalPercentagePerInstallment,
isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled,
- disbursedAmountPercentageForDownPayment,
isAutoRepaymentForDownPaymentEnabled);
+ disbursedAmountPercentageForDownPayment,
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate);
}
private CalendarInstance createCalendarForSameAsRepayment(final Integer
repaymentEvery,
@@ -1070,19 +1079,20 @@ public class LoanScheduleAssembler {
private LocalDate deriveFirstRepaymentDate(final AccountType loanType,
final Integer repaymentEvery,
final LocalDate expectedDisbursementDate, final
PeriodFrequencyType repaymentPeriodFrequencyType,
- final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final
Calendar calendar) {
+ final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final
Calendar calendar, final LocalDate submittedOnDate,
+ final RepaymentStartDateType repaymentStartDateType) {
LocalDate derivedFirstRepayment = null;
- final LocalDate
dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment =
expectedDisbursementDate
- .plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
+ final LocalDate
dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(
+ repaymentStartDateType) ?
expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment)
: submittedOnDate;
if (calendar != null) {
final LocalDate refernceDateForCalculatingFirstRepaymentDate =
expectedDisbursementDate;
derivedFirstRepayment =
deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate,
refernceDateForCalculatingFirstRepaymentDate,
repaymentPeriodFrequencyType,
- minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
+ minimumDaysBetweenDisbursalAndFirstRepayment, calendar,
submittedOnDate, repaymentStartDateType);
} /*** Individual or group account, or JLG not linked to a meeting ***/
else {
@@ -1092,14 +1102,26 @@ public class LoanScheduleAssembler {
// (disbursement date + minimum between disbursal and first
// repayment )
if (repaymentPeriodFrequencyType.isDaily()) {
- dateBasedOnRepaymentFrequency =
expectedDisbursementDate.plusDays(repaymentEvery);
+ dateBasedOnRepaymentFrequency =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
+ ? expectedDisbursementDate.plusDays(repaymentEvery)
+ : submittedOnDate.plusDays(repaymentEvery);
+
} else if (repaymentPeriodFrequencyType.isWeekly()) {
- dateBasedOnRepaymentFrequency =
expectedDisbursementDate.plusWeeks(repaymentEvery);
+ dateBasedOnRepaymentFrequency =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
+ ? expectedDisbursementDate.plusWeeks(repaymentEvery)
+ : submittedOnDate.plusWeeks(repaymentEvery);
+
} else if (repaymentPeriodFrequencyType.isMonthly()) {
- dateBasedOnRepaymentFrequency =
expectedDisbursementDate.plusMonths(repaymentEvery);
+ dateBasedOnRepaymentFrequency =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
+ ? expectedDisbursementDate.plusMonths(repaymentEvery)
+ : submittedOnDate.plusMonths(repaymentEvery);
+
} /** yearly loan **/
else {
- dateBasedOnRepaymentFrequency =
expectedDisbursementDate.plusYears(repaymentEvery);
+ dateBasedOnRepaymentFrequency =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
+ ? expectedDisbursementDate.plusYears(repaymentEvery)
+ : submittedOnDate.plusYears(repaymentEvery);
+
}
derivedFirstRepayment =
dateBasedOnRepaymentFrequency.isAfter(dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment)
? dateBasedOnRepaymentFrequency
@@ -1111,16 +1133,20 @@ public class LoanScheduleAssembler {
private LocalDate deriveFirstRepaymentDateForLoans(final Integer
repaymentEvery, final LocalDate expectedDisbursementDate,
final LocalDate refernceDateForCalculatingFirstRepaymentDate,
final PeriodFrequencyType repaymentPeriodFrequencyType,
- final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final
Calendar calendar) {
+ final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final
Calendar calendar, final LocalDate submittedOnDate,
+ final RepaymentStartDateType repaymentStartDateType) {
boolean isMeetingSkipOnFirstDayOfMonth =
configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
int numberOfDays =
configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
final String frequency =
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
final LocalDate derivedFirstRepayment =
CalendarUtils.getFirstRepaymentMeetingDate(calendar,
refernceDateForCalculatingFirstRepaymentDate, repaymentEvery,
frequency, isMeetingSkipOnFirstDayOfMonth, numberOfDays);
- final LocalDate minimumFirstRepaymentDate =
expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
+ final LocalDate minimumFirstRepaymentDate =
RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
+ ?
expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment)
+ : submittedOnDate;
return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ?
derivedFirstRepayment
: deriveFirstRepaymentDateForLoans(repaymentEvery,
expectedDisbursementDate, derivedFirstRepayment,
- repaymentPeriodFrequencyType,
minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
+ repaymentPeriodFrequencyType,
minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate,
+ repaymentStartDateType);
}
private void validateMinimumDaysBetweenDisbursalAndFirstRepayment(final
LocalDate disbursalDate, final LocalDate firstRepaymentDate,
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 7e427ff67..67fda06e3 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
@@ -112,7 +112,8 @@ public class LoanProductsApiResource {
LoanProductConstants.CAN_USE_FOR_TOPUP,
LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM,
LoanProductConstants.RATES_PARAM_NAME,
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.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT,
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT,
+ LoanProductConstants.REPAYMENT_START_DATE_TYPE));
private static final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new
HashSet<>(
Arrays.asList("productId", "productName", "restrictedProducts",
"allowedProducts", "productOptions"));
@@ -147,7 +148,7 @@ public class LoanProductsApiResource {
@Operation(summary = "Create a Loan Product", description = "Depending of
the Accounting Rule (accountingRule) selected, additional fields with details
of the appropriate Ledger Account identifiers would need to be passed in.\n"
+ "\n" + "Refer MifosX Accounting Specs Draft for more details
regarding the significance of the selected accounting rule\n\n"
+ "Mandatory Fields: name, shortName, currencyCode,
digitsAfterDecimal, inMultiplesOf, principal, numberOfRepayments,
repaymentEvery, repaymentFrequencyType, interestRatePerPeriod,
interestRateFrequencyType, amortizationType, interestType,
interestCalculationPeriodType, transactionProcessingStrategyCode,
accountingRule, isInterestRecalculationEnabled, daysInYearType,
daysInMonthType\n\n"
- + "Optional Fields: inArrearsTolerance, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges,
paymentChannelToFundSourceMappings, feeToIncomeAccountMappings,
penaltyToIncomeAccountMappings, includeInBorrowerCycle,
useBorrowerCycle,principalVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle,
interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount,
outstandingLoanBalance,overdueDaysForNPA,h [...]
+ + "Optional Fields: inArrearsTolerance, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges,
paymentChannelToFundSourceMappings, feeToIncomeAccountMappings,
penaltyToIncomeAccountMappings, includeInBorrowerCycle,
useBorrowerCycle,principalVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle,
interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount,
outstandingLoanBalance,overdueDaysForNPA,h [...]
+ "Additional Mandatory Fields for Cash(2) based accounting:
fundSourceAccountId, loanPortfolioAccountId, interestOnLoanAccountId,
incomeFromFeeAccountId, incomeFromPenaltyAccountId, writeOffAccountId,
transfersInSuspenseAccountId, overpaymentLiabilityAccountId\n\n"
+ "Additional Mandatory Fields for periodic (3) and upfront
(4)accrual accounting: fundSourceAccountId, loanPortfolioAccountId,
interestOnLoanAccountId, incomeFromFeeAccountId, incomeFromPenaltyAccountId,
writeOffAccountId, receivableInterestAccountId, receivableFeeAccountId,
receivablePenaltyAccountId, transfersInSuspenseAccountId,
overpaymentLiabilityAccountId\n\n"
+ "Additional Mandatory Fields if interest recalculation is
enabled(true): interestRecalculationCompoundingMethod,
rescheduleStrategyMethod, recalculationRestFrequencyType\n\n"
@@ -405,6 +406,7 @@ public class LoanProductsApiResource {
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions
= dropdownReadPlatformService
.retrievePreCloseInterestCalculationStrategyOptions();
final List<FloatingRateData> floatingRateOptions =
this.floatingRateReadPlatformService.retrieveLookupActive();
+ final List<EnumOptionData> repaymentStartDateTypeOptions =
dropdownReadPlatformService.retrieveRepaymentStartDateTypeOptions();
return new LoanProductData(productData, chargeOptions, penaltyOptions,
paymentTypeOptions, currencyOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions,
repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions,
@@ -412,7 +414,7 @@ public class LoanProductsApiResource {
loanCycleValueConditionTypeOptions, daysInMonthTypeOptions,
daysInYearTypeOptions,
interestRecalculationCompoundingTypeOptions,
rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions,
preCloseInterestCalculationStrategyOptions,
floatingRateOptions, interestRecalculationNthDayTypeOptions,
- interestRecalculationDayOfWeekTypeOptions, isRatesEnabled,
delinquencyBucketOptions);
+ interestRecalculationDayOfWeekTypeOptions, isRatesEnabled,
delinquencyBucketOptions, repaymentStartDateTypeOptions);
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
index 42b284e07..2b586572f 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
@@ -162,6 +162,8 @@ final class LoanProductsApiResourceSwagger {
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
+ @Schema(example = "1")
+ public Integer repaymentStartDateType;
// Interest Recalculation
@Schema(example = "false")
@@ -419,6 +421,18 @@ final class LoanProductsApiResourceSwagger {
public String description;
}
+ static final class GetLoanProductsRepaymentStartDateType {
+
+ private GetLoanProductsRepaymentStartDateType() {}
+
+ @Schema(example = "1")
+ public Long id;
+ @Schema(example = "repaymentStartDateType.disbursementDate")
+ public String code;
+ @Schema(example = "Disbursement Date")
+ public String description;
+ }
+
static final class GetLoanProductsInterestRecalculationData {
private GetLoanProductsInterestRecalculationData() {}
@@ -562,6 +576,7 @@ final class LoanProductsApiResourceSwagger {
public GetLoanProductsResponse.GetLoanProductsAccountingRule
accountingRule;
@Schema(example = "0")
public Integer principalThresholdForLastInstalment;
+ public GetLoanProductsResponse.GetLoanProductsRepaymentStartDateType
repaymentStartDateType;
}
@Schema(description = "GetLoanProductsTemplateResponse")
@@ -1015,6 +1030,7 @@ final class LoanProductsApiResourceSwagger {
public
Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsInterestRecalculationCompoundingType>
interestRecalculationCompoundingTypeOptions;
public
Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsRescheduleStrategyType>
rescheduleStrategyTypeOptions;
public
Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsInterestRecalculationCompoundingFrequencyType>
interestRecalculationFrequencyTypeOptions;
+ public
Set<GetLoanProductsResponse.GetLoanProductsRepaymentStartDateType>
repaymentStartDateTypeOptions;
}
@Schema(description = "GetLoanProductsProductIdResponse")
@@ -1034,6 +1050,18 @@ final class LoanProductsApiResourceSwagger {
public String description;
}
+ static final class GetLoanProductsRepaymentStartDateType {
+
+ private GetLoanProductsRepaymentStartDateType() {}
+
+ @Schema(example = "1")
+ public Long id;
+ @Schema(example = "repaymentStartDateType.disbursementDate")
+ public String code;
+ @Schema(example = "Disbursement Date")
+ public String description;
+ }
+
static final class GetLoanProductsPrincipalVariationsForBorrowerCycle {
private GetLoanProductsPrincipalVariationsForBorrowerCycle() {}
@@ -1218,6 +1246,7 @@ final class LoanProductsApiResourceSwagger {
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
+ public GetLoanProductsRepaymentStartDateType repaymentStartDateType;
}
@Schema(description = "PutLoanProductsProductIdRequest")
@@ -1346,6 +1375,8 @@ final class LoanProductsApiResourceSwagger {
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
+ @Schema(example = "1")
+ public Integer repaymentStartDateType;
// Interest Recalculation
@Schema(example = "false")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
index 06392b404..014ac6d75 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -48,6 +48,7 @@ import
org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import
org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
+import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.rate.data.RateData;
@@ -123,6 +124,7 @@ public class LoanProductData implements Serializable {
private final Integer minimumDaysBetweenDisbursalAndFirstRepayment;
private final boolean canDefineInstallmentAmount;
private final Integer installmentAmountInMultiplesOf;
+ private final EnumOptionData repaymentStartDateType;
// charges
private final Collection<ChargeData> charges;
@@ -168,6 +170,7 @@ public class LoanProductData implements Serializable {
private final List<EnumOptionData>
interestRecalculationFrequencyTypeOptions;
private final List<FloatingRateData> floatingRateOptions;
+ private final List<EnumOptionData> repaymentStartDateTypeOptions;
private final Boolean multiDisburseLoan;
private final Integer maxTrancheCount;
@@ -290,6 +293,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null;
final Collection<AdvancedPaymentData> paymentAllocation = null;
final boolean enableAutoRepaymentForDownPayment = false;
+ final EnumOptionData repaymentStartDateType = null;
return new LoanProductData(id, name, shortName, description, currency,
principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments,
maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -309,7 +313,7 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment,
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
- paymentAllocation);
+ paymentAllocation, repaymentStartDateType);
}
@@ -403,6 +407,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null;
final boolean enableAutoRepaymentForDownPayment = false;
final Collection<AdvancedPaymentData> paymentAllocation = null;
+ final EnumOptionData repaymentStartDateType = null;
return new LoanProductData(id, name, shortName, description, currency,
principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments,
maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -422,7 +427,7 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment,
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
- paymentAllocation);
+ paymentAllocation, repaymentStartDateType);
}
@@ -523,6 +528,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null;
final boolean enableAutoRepaymentForDownPayment = false;
final Collection<AdvancedPaymentData> paymentAllocation = null;
+ final EnumOptionData repaymentStartDateType =
LoanEnumerations.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE);
return new LoanProductData(id, name, shortName, description, currency,
principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments,
maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -542,7 +548,7 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment,
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
- paymentAllocation);
+ paymentAllocation, repaymentStartDateType);
}
@@ -637,6 +643,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null;
final boolean enableAutoRepaymentForDownPayment = false;
final Collection<AdvancedPaymentData> paymentAllocation = null;
+ final EnumOptionData repaymentStartDateType =
LoanEnumerations.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE);
return new LoanProductData(id, name, shortName, description, currency,
principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments,
maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -656,7 +663,7 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment,
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
- paymentAllocation);
+ paymentAllocation, repaymentStartDateType);
}
public static LoanProductData withAccountingDetails(final LoanProductData
productData, final Map<String, Object> accountingMappings,
@@ -704,7 +711,7 @@ public class LoanProductData implements Serializable {
final DelinquencyBucketData delinquencyBucket, final Integer
dueDaysForRepaymentEvent,
final Integer overDueDaysForRepaymentEvent, final boolean
enableDownPayment,
final BigDecimal disbursedAmountPercentageForDownPayment, final
boolean enableAutoRepaymentForDownPayment,
- final Collection<AdvancedPaymentData> paymentAllocation) {
+ final Collection<AdvancedPaymentData> paymentAllocation, final
EnumOptionData repaymentStartDateType) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -826,6 +833,8 @@ public class LoanProductData implements Serializable {
this.disbursedAmountPercentageForDownPayment =
disbursedAmountPercentageForDownPayment;
this.paymentAllocation = paymentAllocation;
this.enableAutoRepaymentForDownPayment =
enableAutoRepaymentForDownPayment;
+ this.repaymentStartDateType = repaymentStartDateType;
+ this.repaymentStartDateTypeOptions = null;
}
public LoanProductData(final LoanProductData productData, final
Collection<ChargeData> chargeOptions,
@@ -842,7 +851,7 @@ public class LoanProductData implements Serializable {
final List<EnumOptionData>
preCloseInterestCalculationStrategyOptions, final List<FloatingRateData>
floatingRateOptions,
final List<EnumOptionData> interestRecalculationNthDayTypeOptions,
final List<EnumOptionData>
interestRecalculationDayOfWeekTypeOptions, final boolean isRatesEnabled,
- final Collection<DelinquencyBucketData> delinquencyBucketOptions) {
+ final Collection<DelinquencyBucketData> delinquencyBucketOptions,
final List<EnumOptionData> repaymentStartDateTypeOptions) {
this.id = productData.id;
this.name = productData.name;
this.shortName = productData.shortName;
@@ -981,6 +990,8 @@ public class LoanProductData implements Serializable {
this.disbursedAmountPercentageForDownPayment =
productData.disbursedAmountPercentageForDownPayment;
this.enableAutoRepaymentForDownPayment =
productData.enableAutoRepaymentForDownPayment;
this.paymentAllocation = productData.paymentAllocation;
+ this.repaymentStartDateType = productData.repaymentStartDateType;
+ this.repaymentStartDateTypeOptions = repaymentStartDateTypeOptions;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData>
charges) {
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 c6769617b..c130c51a8 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
@@ -162,7 +162,7 @@ public final class LoanProductDataValidator {
LoanProductConstants.OVER_APPLIED_NUMBER,
LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME,
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.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT,
LoanProductConstants.REPAYMENT_START_DATE_TYPE));
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = {
LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName,
LoanProductConstants.transactionProcessingStrategyCodeParamName,
@@ -755,6 +755,13 @@ public final class LoanProductDataValidator {
validateAutoRepaymentForDownPayment(enableDownPayment,
baseDataValidator, element);
}
+ if
(this.fromApiJsonHelper.parameterExists(LoanProductConstants.REPAYMENT_START_DATE_TYPE,
element)) {
+ final Integer repaymentStartDateType = this.fromApiJsonHelper
+
.extractIntegerNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE, element,
Locale.getDefault());
+
baseDataValidator.reset().parameter(LoanProductConstants.REPAYMENT_START_DATE_TYPE).value(repaymentStartDateType).notNull()
+ .isOneOfTheseValues(1, 2);
+ }
+
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -1679,6 +1686,15 @@ public final class LoanProductDataValidator {
validateDownPaymentPercentage(enableDownPayment,
baseDataValidator, element);
validateAutoRepaymentForDownPayment(enableDownPayment,
baseDataValidator, element);
}
+
+ Integer repaymentStartDateType =
loanProduct.getRepaymentStartDateType().getValue();
+ if
(this.fromApiJsonHelper.parameterExists(LoanProductConstants.REPAYMENT_START_DATE_TYPE,
element)) {
+ repaymentStartDateType =
this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE,
element,
+ Locale.getDefault());
+ }
+
baseDataValidator.reset().parameter(LoanProductConstants.REPAYMENT_START_DATE_TYPE).value(repaymentStartDateType).notNull()
+ .isOneOfTheseValues(1, 2);
+
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java
index c07784a97..91cda2f6e 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java
@@ -57,4 +57,5 @@ public interface LoanDropdownReadPlatformService {
List<EnumOptionData> retrievePreCloseInterestCalculationStrategyOptions();
+ List<EnumOptionData> retrieveRepaymentStartDateTypeOptions();
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java
index c9659445e..a1fa9f192 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java
@@ -32,6 +32,7 @@ import static
org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations
import static
org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyDayOfWeekType;
import static
org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyNthDayType;
import static
org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyType;
+import static
org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentStartDateType;
import static
org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.rescheduleStrategyType;
import java.util.Arrays;
@@ -52,6 +53,7 @@ import
org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCa
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;
import org.springframework.stereotype.Service;
@Service
@@ -177,4 +179,10 @@ public class LoanDropdownReadPlatformServiceImpl
implements LoanDropdownReadPlat
return
Arrays.asList(preCloseInterestCalculationStrategy(LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE),
preCloseInterestCalculationStrategy(LoanPreClosureInterestCalculationStrategy.TILL_REST_FREQUENCY_DATE));
}
+
+ @Override
+ public List<EnumOptionData> retrieveRepaymentStartDateTypeOptions() {
+ return
Arrays.asList(repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE),
+
repaymentStartDateType(RepaymentStartDateType.SUBMITTED_ON_DATE));
+ }
}
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 67ad626fc..d8e890604 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
@@ -229,7 +229,7 @@ public class LoanProductReadPlatformServiceImpl implements
LoanProductReadPlatfo
+ "lp.disallow_expected_disbursements as
disallowExpectedDisbursements, lp.allow_approved_disbursed_amounts_over_applied
as allowApprovedDisbursedAmountsOverApplied, lp.over_applied_calculation_type
as overAppliedCalculationType, over_applied_number as overAppliedNumber, "
+ "lp.days_in_month_enum as daysInMonth,
lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as
isInterestRecalculationEnabled, "
+ "lp.can_define_fixed_emi_amount as
canDefineInstallmentAmount, lp.instalment_amount_in_multiples_of as
installmentAmountInMultiplesOf, "
- + "lp.due_days_for_repayment_event as
dueDaysForRepaymentEvent, lp.overdue_days_for_repayment_event as
overDueDaysForRepaymentEvent, lp.enable_down_payment as enableDownPayment,
lp.disbursed_amount_percentage_for_down_payment as
disbursedAmountPercentageForDownPayment,
lp.enable_auto_repayment_for_down_payment as enableAutoRepaymentForDownPayment,
"
+ + "lp.due_days_for_repayment_event as
dueDaysForRepaymentEvent, lp.overdue_days_for_repayment_event as
overDueDaysForRepaymentEvent, lp.enable_down_payment as enableDownPayment,
lp.disbursed_amount_percentage_for_down_payment as
disbursedAmountPercentageForDownPayment,
lp.enable_auto_repayment_for_down_payment as enableAutoRepaymentForDownPayment,
lp.repayment_start_date_type_enum as repaymentStartDateType, "
+ "lpr.pre_close_interest_calculation_strategy as
preCloseInterestCalculationStrategy, "
+ "lpr.id as lprId, lpr.product_id as productId,
lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as
rescheduleStrategy, "
+ "lpr.rest_frequency_type_enum as restFrequencyEnum,
lpr.rest_frequency_interval as restFrequencyInterval, "
@@ -364,6 +364,8 @@ public class LoanProductReadPlatformServiceImpl implements
LoanProductReadPlatfo
final boolean enableDownPayment =
rs.getBoolean("enableDownPayment");
final BigDecimal disbursedAmountPercentageForDownPayment =
rs.getBigDecimal("disbursedAmountPercentageForDownPayment");
final boolean enableAutoRepaymentForDownPayment =
rs.getBoolean("enableAutoRepaymentForDownPayment");
+ final Integer repaymentStartDateTypeId =
JdbcSupport.getInteger(rs, "repaymentStartDateType");
+ final EnumOptionData repaymentStartDateType =
LoanEnumerations.repaymentStartDateType(repaymentStartDateTypeId);
String status = "";
if (closeDate != null &&
closeDate.isBefore(DateUtils.getBusinessLocalDate())) {
@@ -521,7 +523,7 @@ public class LoanProductReadPlatformServiceImpl implements
LoanProductReadPlatfo
maximumGap, syncExpectedWithDisbursementDate,
canUseForTopup, isEqualAmortization, rateOptions, this.rates,
isRatesEnabled, fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket,
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent,
enableDownPayment, disbursedAmountPercentageForDownPayment,
- enableAutoRepaymentForDownPayment, advancedPaymentData);
+ enableAutoRepaymentForDownPayment, advancedPaymentData,
repaymentStartDateType);
}
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductRepaymentStartDateConfigurationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductRepaymentStartDateConfigurationTest.java
new file mode 100644
index 000000000..c13939f80
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductRepaymentStartDateConfigurationTest.java
@@ -0,0 +1,474 @@
+/**
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.UUID;
+import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
+import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
+import org.apache.fineract.client.models.PutLoanProductsProductIdResponse;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.integrationtests.common.BusinessDateHelper;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
+import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
+import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import
org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class LoanProductRepaymentStartDateConfigurationTest {
+
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+ private LoanTransactionHelper loanTransactionHelper;
+ private ClientHelper clientHelper;
+
+ @BeforeEach
+ public void setup() {
+ Utils.initializeRESTAssured();
+ this.requestSpec = new
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+ this.requestSpec.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+ this.responseSpec = new
ResponseSpecBuilder().expectStatusCode(200).build();
+ this.loanTransactionHelper = new
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+ this.clientHelper = new ClientHelper(this.requestSpec,
this.responseSpec);
+ }
+
+ @Test
+ public void
loanProductWithRepaymentStartDateTypeConfigurationCreateAndModifyTest() {
+ // create product with repayment start date configuration, get , modify
+
+ // Delinquency Bucket
+ final Integer delinquencyBucketId =
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
+ final GetDelinquencyBucketsResponse delinquencyBucket =
DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+ delinquencyBucketId);
+
+ final Integer repaymentStartDateType = 2;
+
+ // create loan product with repayment start date configuration
+ Integer loanProductId =
createLoanProductWithRepaymentStartDateTypeConfiguration(loanTransactionHelper,
delinquencyBucketId,
+ repaymentStartDateType);
+
+ GetLoanProductsProductIdResponse getLoanProductsProductResponse =
loanTransactionHelper.getLoanProduct(loanProductId);
+ assertNotNull(getLoanProductsProductResponse);
+ assertEquals(repaymentStartDateType,
getLoanProductsProductResponse.getRepaymentStartDateType().getId().intValue());
+ assertEquals("repaymentStartDateType.submittedOnDate",
getLoanProductsProductResponse.getRepaymentStartDateType().getCode());
+
+ // modify loan product repayment start date configuration to
disbursement date
+
+ PutLoanProductsProductIdResponse loanProductModifyResponse =
updateLoanProduct(loanTransactionHelper,
+ getLoanProductsProductResponse.getId());
+ assertNotNull(loanProductModifyResponse);
+
+ getLoanProductsProductResponse =
loanTransactionHelper.getLoanProduct(loanProductId);
+ assertNotNull(getLoanProductsProductResponse);
+ assertEquals(1,
getLoanProductsProductResponse.getRepaymentStartDateType().getId().intValue());
+ assertEquals("repaymentStartDateType.disbursementDate",
getLoanProductsProductResponse.getRepaymentStartDateType().getCode());
+
+ }
+
+ @Test
+ public void
loanProductWithNoRepaymentStartDateTypeConfigurationDefaultsToDisbursementDateTest()
{
+ // create loan product with no configuration for repayment start date
and verify that it is disbursement date by
+ // default
+ // Delinquency Bucket
+ final Integer delinquencyBucketId =
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
+ final GetDelinquencyBucketsResponse delinquencyBucket =
DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
+ delinquencyBucketId);
+
+ final Integer repaymentStartDateType = null;
+
+ // create loan product with repayment start date configuration
+ Integer loanProductId =
createLoanProductWithRepaymentStartDateTypeConfiguration(loanTransactionHelper,
delinquencyBucketId,
+ repaymentStartDateType);
+
+ GetLoanProductsProductIdResponse getLoanProductsProductResponse =
loanTransactionHelper.getLoanProduct(loanProductId);
+ assertNotNull(getLoanProductsProductResponse);
+ assertEquals(1,
getLoanProductsProductResponse.getRepaymentStartDateType().getId().intValue());
+ assertEquals("repaymentStartDateType.disbursementDate",
getLoanProductsProductResponse.getRepaymentStartDateType().getCode());
+ }
+
+ @Test
+ public void
loanAccountWithLoanProductRepaymentStartDateTypeAsSubmittedOnDateScheduleTest()
{
+ // create loan account with product with repayment start date type
configuration as submitted on date, verify
+ // repayment schedule is according to submitted on date, before and
after disbursements
+ try {
+
+ // Set business date
+ LocalDate businessDate = LocalDate.of(2023, 03, 3);
+
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.TRUE);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec,
BusinessDateType.BUSINESS_DATE, businessDate);
+
+ // Loan ExternalId
+ String loanExternalIdStr = UUID.randomUUID().toString();
+
+ final Integer clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+
+ // set repayment start date type as submittedOn date
+ final Integer repaymentStartDateType = 2;
+
+ // Loan Product creation with repayment start date type
configuration
+ final GetLoanProductsProductIdResponse
getLoanProductsProductResponse =
createLoanProductWithRepaymentStartDateTypeConfigurationAndMultipleDisbursements(
+ loanTransactionHelper, repaymentStartDateType);
+
+ assertNotNull(getLoanProductsProductResponse);
+ assertEquals(repaymentStartDateType,
getLoanProductsProductResponse.getRepaymentStartDateType().getId().intValue());
+ assertEquals("repaymentStartDateType.submittedOnDate",
getLoanProductsProductResponse.getRepaymentStartDateType().getCode());
+
+ // create loan account with submitted date as business date (03
March 2023) and expected disbursement date
+ // as future date (07 March 2023)
+ final Integer loanId =
createLoanAccountMultipleRepaymentsDisbursement(clientId,
getLoanProductsProductResponse.getId(),
+ loanExternalIdStr);
+
+ // Retrieve Loan with loanId
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
+
+ assertNotNull(loanDetails);
+
+ // verify loan schedule is according to submitted on date
+
+ assertNotNull(loanDetails.getRepaymentSchedule());
+ // loan term
+ assertEquals(92,
loanDetails.getRepaymentSchedule().getLoanTermInDays());
+
+ assertEquals(4,
loanDetails.getRepaymentSchedule().getPeriods().size());
+
+ // verify amounts
+ assertEquals(1000.0,
loanDetails.getRepaymentSchedule().getTotalPrincipalExpected());
+
+ // first period [2023-03-03 to 2023-04-03]
+ assertEquals(1,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod());
+ assertEquals(LocalDate.of(2023, 03, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate());
+ assertEquals(LocalDate.of(2023, 04, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate());
+ assertEquals(333.33,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod());
+
+ // second period [2023-04-03 to 2023-05-03]
+ assertEquals(2,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod());
+ assertEquals(LocalDate.of(2023, 04, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate());
+ assertEquals(LocalDate.of(2023, 05, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
+ assertEquals(333.33,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod());
+
+ // third period [2023-05-03 to 2023-06-03]
+ assertEquals(3,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod());
+ assertEquals(LocalDate.of(2023, 05, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate());
+ assertEquals(LocalDate.of(2023, 06, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate());
+ assertEquals(333.34,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod());
+
+ // first disbursement on a future date (7 March 2023)
+
+ LocalDate disbursementDate = LocalDate.of(2023, 03, 7);
+
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec,
BusinessDateType.BUSINESS_DATE, disbursementDate);
+
+ loanTransactionHelper.disburseLoanWithTransactionAmount("07 March
2023", loanId, "500");
+
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
+
+ // verify loan schedule is according to submitted on date after
first disbursement
+ assertNotNull(loanDetails);
+ assertNotNull(loanDetails.getRepaymentSchedule());
+ // loan term
+ assertEquals(92,
loanDetails.getRepaymentSchedule().getLoanTermInDays());
+ assertEquals(4,
loanDetails.getRepaymentSchedule().getPeriods().size());
+
+ // verify amounts
+ assertEquals(500.0,
loanDetails.getRepaymentSchedule().getTotalPrincipalExpected());
+ assertEquals(500.0,
loanDetails.getRepaymentSchedule().getTotalPrincipalDisbursed());
+
+ // first period [2023-03-03 to 2023-04-03]
+ assertEquals(1,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod());
+ assertEquals(LocalDate.of(2023, 03, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate());
+ assertEquals(LocalDate.of(2023, 04, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate());
+ assertEquals(166.67,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod());
+
+ // second period [2023-04-03 to 2023-05-03]
+ assertEquals(2,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod());
+ assertEquals(LocalDate.of(2023, 04, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate());
+ assertEquals(LocalDate.of(2023, 05, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
+ assertEquals(166.67,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod());
+
+ // third period [2023-05-03 to 2023-06-03]
+ assertEquals(3,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod());
+ assertEquals(LocalDate.of(2023, 05, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate());
+ assertEquals(LocalDate.of(2023, 06, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate());
+ assertEquals(166.66,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod());
+
+ // second disbursement next month (7 April 2023)
+
+ disbursementDate = LocalDate.of(2023, 04, 7);
+
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec,
BusinessDateType.BUSINESS_DATE, disbursementDate);
+
+ loanTransactionHelper.disburseLoanWithTransactionAmount("07 April
2023", loanId, "500");
+
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
+
+ // verify loan schedule is according to submitted on date after
second disbursement
+
+ assertNotNull(loanDetails);
+ assertNotNull(loanDetails.getRepaymentSchedule());
+ // loan term
+ assertEquals(92,
loanDetails.getRepaymentSchedule().getLoanTermInDays());
+ assertEquals(5,
loanDetails.getRepaymentSchedule().getPeriods().size());
+
+ // verify amounts
+ assertEquals(1000.0,
loanDetails.getRepaymentSchedule().getTotalPrincipalExpected());
+ assertEquals(1000.0,
loanDetails.getRepaymentSchedule().getTotalPrincipalDisbursed());
+
+ // first period [2023-03-03 to 2023-04-03]
+ assertEquals(1,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod());
+ assertEquals(LocalDate.of(2023, 03, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate());
+ assertEquals(LocalDate.of(2023, 04, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate());
+ assertEquals(166.67,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod());
+
+ // second period [2023-04-03 to 2023-05-03]
+ assertEquals(2,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod());
+ assertEquals(LocalDate.of(2023, 04, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate());
+ assertEquals(LocalDate.of(2023, 05, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate());
+ assertEquals(333.33,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod());
+
+ // third period [2023-05-03 to 2023-06-03]
+ assertEquals(3,
loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod());
+ assertEquals(LocalDate.of(2023, 05, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate());
+ assertEquals(LocalDate.of(2023, 06, 3),
loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate());
+ assertEquals(500.00,
loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod());
+
+ } finally {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.FALSE);
+ }
+
+ }
+
+ @Test
+ public void
loanAccountWithLoanProductRepaymentStartDateTypeAsDisbursementDateScheduleTest()
{
+ // create loan account with loan product with repayment start date
type configuration as disbursement date ,
+ // verify repayment schedule is as per disbursement date before and
after disbursements
+
+ try {
+
+ // Set business date
+ LocalDate businessDate = LocalDate.of(2023, 03, 3);
+
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.TRUE);
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec,
BusinessDateType.BUSINESS_DATE, businessDate);
+
+ // Loan ExternalId
+ String loanExternalIdStr = UUID.randomUUID().toString();
+
+ final Integer clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+
+ // set repayment start date type as default, disbursement date
+ final Integer repaymentStartDateType = 1;
+
+ // Loan Product creation with repayment date type configuration
+ final GetLoanProductsProductIdResponse
getLoanProductsProductResponse =
createLoanProductWithRepaymentStartDateTypeConfigurationAndMultipleDisbursements(
+ loanTransactionHelper, repaymentStartDateType);
+
+ assertNotNull(getLoanProductsProductResponse);
+ assertEquals(repaymentStartDateType,
getLoanProductsProductResponse.getRepaymentStartDateType().getId().intValue());
+ assertEquals("repaymentStartDateType.disbursementDate",
getLoanProductsProductResponse.getRepaymentStartDateType().getCode());
+
+ // create loan account with submitted date as business date (03
March 2023) and expected disbursement date
+ // (07 March 2023)
+ final Integer loanId =
createLoanAccountMultipleRepaymentsDisbursement(clientId,
getLoanProductsProductResponse.getId(),
+ loanExternalIdStr);
+
+ // Retrieve Loan with loanId
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
+
+ assertNotNull(loanDetails);
+
+ // verify loan schedule is according to disbursement date
+
+ assertNotNull(loanDetails.getRepaymentSchedule());
+
+ // loan term
+ assertEquals(92,
loanDetails.getRepaymentSchedule().getLoanTermInDays());
+
+ assertEquals(4,
loanDetails.getRepaymentSchedule().getPeriods().size());
+
+ // verify amounts
+ assertEquals(1000.0,
loanDetails.getRepaymentSchedule().getTotalPrincipalExpected());
+
+ // first period [2023-03-07 to 2023-04-07]
+ assertEquals(1,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod());
+ assertEquals(LocalDate.of(2023, 03, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate());
+ assertEquals(LocalDate.of(2023, 04, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate());
+ assertEquals(333.33,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod());
+
+ // second period [2023-04-07 to 2023-05-07]
+ assertEquals(2,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod());
+ assertEquals(LocalDate.of(2023, 04, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate());
+ assertEquals(LocalDate.of(2023, 05, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
+ assertEquals(333.33,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod());
+
+ // third period [2023-05-07 to 2023-06-07]
+ assertEquals(3,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod());
+ assertEquals(LocalDate.of(2023, 05, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate());
+ assertEquals(LocalDate.of(2023, 06, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate());
+ assertEquals(333.34,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod());
+
+ // first disbursement (7 March 2023)
+
+ LocalDate disbursementDate = LocalDate.of(2023, 03, 7);
+
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec,
BusinessDateType.BUSINESS_DATE, disbursementDate);
+
+ loanTransactionHelper.disburseLoanWithTransactionAmount("07 March
2023", loanId, "500");
+
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
+
+ // verify loan schedule is according to disbursement date
+ assertNotNull(loanDetails);
+ assertNotNull(loanDetails.getRepaymentSchedule());
+
+ // loan term
+ assertEquals(92,
loanDetails.getRepaymentSchedule().getLoanTermInDays());
+ assertEquals(4,
loanDetails.getRepaymentSchedule().getPeriods().size());
+
+ // verify amounts
+ assertEquals(500.0,
loanDetails.getRepaymentSchedule().getTotalPrincipalExpected());
+ assertEquals(500.0,
loanDetails.getRepaymentSchedule().getTotalPrincipalDisbursed());
+
+ // first period [2023-03-07 to 2023-04-07]
+ assertEquals(1,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod());
+ assertEquals(LocalDate.of(2023, 03, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate());
+ assertEquals(LocalDate.of(2023, 04, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate());
+ assertEquals(166.67,
loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod());
+
+ // second period [2023-04-07 to 2023-05-07]
+ assertEquals(2,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod());
+ assertEquals(LocalDate.of(2023, 04, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate());
+ assertEquals(LocalDate.of(2023, 05, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
+ assertEquals(166.67,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod());
+
+ // third period [2023-05-07 to 2023-06-07]
+ assertEquals(3,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod());
+ assertEquals(LocalDate.of(2023, 05, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate());
+ assertEquals(LocalDate.of(2023, 06, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate());
+ assertEquals(166.66,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod());
+
+ // second disbursement next month (7 April 2023)
+
+ disbursementDate = LocalDate.of(2023, 04, 7);
+
+ BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec,
BusinessDateType.BUSINESS_DATE, disbursementDate);
+
+ loanTransactionHelper.disburseLoanWithTransactionAmount("07 April
2023", loanId, "500");
+
+ loanDetails =
loanTransactionHelper.getLoanDetails(loanId.longValue());
+
+ // verify loan schedule is according to disbursement after second
disbursement
+
+ assertNotNull(loanDetails);
+ assertNotNull(loanDetails.getRepaymentSchedule());
+
+ // loan term
+ assertEquals(92,
loanDetails.getRepaymentSchedule().getLoanTermInDays());
+ assertEquals(5,
loanDetails.getRepaymentSchedule().getPeriods().size());
+
+ // verify amounts
+ assertEquals(1000.0,
loanDetails.getRepaymentSchedule().getTotalPrincipalExpected());
+ assertEquals(1000.0,
loanDetails.getRepaymentSchedule().getTotalPrincipalDisbursed());
+
+ // first period [2023-03-07 to 2023-04-07]
+ assertEquals(1,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod());
+ assertEquals(LocalDate.of(2023, 03, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate());
+ assertEquals(LocalDate.of(2023, 04, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
+ assertEquals(333.33,
loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod());
+
+ // second period [2023-04-07 to 2023-05-07]
+ assertEquals(2,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod());
+ assertEquals(LocalDate.of(2023, 04, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate());
+ assertEquals(LocalDate.of(2023, 05, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate());
+ assertEquals(333.33,
loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod());
+
+ // third period [2023-05-07 to 2023-06-07]
+ assertEquals(3,
loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod());
+ assertEquals(LocalDate.of(2023, 05, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate());
+ assertEquals(LocalDate.of(2023, 06, 7),
loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate());
+ assertEquals(333.34,
loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod());
+
+ } finally {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.FALSE);
+ }
+
+ }
+
+ private PutLoanProductsProductIdResponse
updateLoanProduct(LoanTransactionHelper loanTransactionHelper, Long id) {
+ // repayment start date configuration
+ final Integer repaymentStartDateType = 1;
+ final PutLoanProductsProductIdRequest requestModifyLoan = new
PutLoanProductsProductIdRequest()
+ .repaymentStartDateType(repaymentStartDateType).locale("en");
+ return loanTransactionHelper.updateLoanProduct(id, requestModifyLoan);
+ }
+
+ private Integer
createLoanProductWithRepaymentStartDateTypeConfiguration(final
LoanTransactionHelper loanTransactionHelper,
+ final Integer delinquencyBucketId, final Integer
repaymentStartDateType) {
+ final HashMap<String, Object> loanProductMap = new
LoanProductTestBuilder().withRepaymentStartDateType(repaymentStartDateType)
+ .build(null, delinquencyBucketId);
+ final Integer loanProductId =
loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
+ return loanProductId;
+
+ }
+
+ private Integer createLoanAccountMultipleRepaymentsDisbursement(final
Integer clientID, final Long loanProductID,
+ final String externalId) {
+
+ String loanApplicationJSON = new
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("3")
+
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("3").withRepaymentEveryAfter("1")
+
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
+
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
+ .withExpectedDisbursementDate("07 March
2023").withSubmittedOnDate("03 March 2023").withLoanType("individual")
+ .withExternalId(externalId).build(clientID.toString(),
loanProductID.toString(), null);
+
+ final Integer loanId =
loanTransactionHelper.getLoanId(loanApplicationJSON);
+ loanTransactionHelper.approveLoan("03 March 2023", "1000", loanId,
null);
+ return loanId;
+ }
+
+ private GetLoanProductsProductIdResponse
createLoanProductWithRepaymentStartDateTypeConfigurationAndMultipleDisbursements(
+ LoanTransactionHelper loanTransactionHelper, final Integer
repaymentStartDateType) {
+ final String loanProductJSON = new
LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth()
+
.withRepaymentAfterEvery("1").withNumberOfRepayments("3").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0")
+
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
+
.withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withDaysInMonth("30").withDaysInYear("365")
+ .withMoratorium("0",
"0").withMultiDisburse().withDisallowExpectedDisbursements(true)
+
.withRepaymentStartDateType(repaymentStartDateType).build(null);
+ final Integer loanProductId =
loanTransactionHelper.getLoanProductId(loanProductJSON);
+ return loanTransactionHelper.getLoanProduct(loanProductId);
+ }
+
+}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
index 411c0b1c6..c648c544c 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
@@ -149,6 +149,7 @@ public class LoanProductTestBuilder {
private boolean enableDownPayment = false;
private String disbursedAmountPercentageForDownPayment = null;
private boolean enableAutoRepaymentForDownPayment = false;
+ private Integer repaymentStartDateType = null;
public String build() {
final HashMap<String, Object> map = build(null, null);
@@ -299,6 +300,10 @@ public class LoanProductTestBuilder {
map.put("enableAutoRepaymentForDownPayment",
enableAutoRepaymentForDownPayment);
}
+ if (this.repaymentStartDateType != null) {
+ map.put("repaymentStartDateType", repaymentStartDateType);
+ }
+
return map;
}
@@ -723,4 +728,9 @@ public class LoanProductTestBuilder {
return this;
}
+ public LoanProductTestBuilder withRepaymentStartDateType(final Integer
repaymentStartDateType) {
+ this.repaymentStartDateType = repaymentStartDateType;
+ return this;
+ }
+
}