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 productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate, final List productRates, List 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 @@ + diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1005_add_loan_product_repayment_start_date_configuration.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1005_add_loan_product_repayment_start_date_configuration.xml new file mode 100644 index 000000000..b0829998d --- /dev/null +++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1005_add_loan_product_repayment_start_date_configuration.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + 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 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,holdGuaranteeFunds, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, allowAttributeOverrides, allowPartialPeriodInterestCalcualtion,dueDaysForRepaymentEvent,overDueDaysForRepaymentEvent,enableDownPayment,disbursedAmountPercentageDownPayment,enableAutoRepaymentForDownPayment\n\n" + + "Optional Fields: inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, includeInBorrowerCycle, useBorrowerCycle,principalVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount, outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, allowAttributeOverrides, allowPartialPeriodInterestCalcualtion,dueDaysForRepaymentEvent,overDueDaysForRepaymentEvent,enableDownPayment,disbursedAmountPercentageDownPayment,enableAutoRepaymentForDownPayment,repaymentStartDateType\n\n" + "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 preCloseInterestCalculationStrategyOptions = dropdownReadPlatformService .retrievePreCloseInterestCalculationStrategyOptions(); final List floatingRateOptions = this.floatingRateReadPlatformService.retrieveLookupActive(); + final List 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 interestRecalculationCompoundingTypeOptions; public Set rescheduleStrategyTypeOptions; public Set interestRecalculationFrequencyTypeOptions; + public Set 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 charges; @@ -168,6 +170,7 @@ public class LoanProductData implements Serializable { private final List interestRecalculationFrequencyTypeOptions; private final List floatingRateOptions; + private final List repaymentStartDateTypeOptions; private final Boolean multiDisburseLoan; private final Integer maxTrancheCount; @@ -290,6 +293,7 @@ public class LoanProductData implements Serializable { final BigDecimal disbursedAmountPercentageDownPayment = null; final Collection 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 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 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 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 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 paymentAllocation) { + final Collection 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 chargeOptions, @@ -842,7 +851,7 @@ public class LoanProductData implements Serializable { final List preCloseInterestCalculationStrategyOptions, final List floatingRateOptions, final List interestRecalculationNthDayTypeOptions, final List interestRecalculationDayOfWeekTypeOptions, final boolean isRatesEnabled, - final Collection delinquencyBucketOptions) { + final Collection delinquencyBucketOptions, final List 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 nullIfEmpty(final Collection 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 retrievePreCloseInterestCalculationStrategyOptions(); + List 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 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 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 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; + } + }