diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java index b1313d24c..87ac64e78 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java @@ -1141,6 +1141,21 @@ public class DataValidatorBuilder { return this; } + public DataValidatorBuilder scaleNotGreaterThan(Integer scale) { + final BigDecimal value = BigDecimal.valueOf(Double.parseDouble(this.value.toString())); + if (value.scale() > scale.intValue()) { + final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".") + .append(this.parameter).append(".scale.is.greater.than.").append(scale); + final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter `").append(this.parameter).append("` value ") + .append(value).append(" decimal place must not be more than ").append(scale).append(" places"); + final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), + defaultEnglishMessage.toString(), this.parameter, value, scale); + this.dataValidationErrors.add(error); + return this; + } + return this; + } + /** * Throws Exception if validation errors. * 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 bfa24b54d..0a7243e5c 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 @@ -207,6 +207,8 @@ public final class LoanApplicationTerms { private final BigDecimal fixedPrincipalPercentagePerInstallment; private LocalDate newScheduledDueDateStart; + private boolean isDownPaymentEnabled; + private BigDecimal disbursedAmountPercentageForDownPayment; public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, @@ -230,7 +232,8 @@ public final class LoanApplicationTerms { Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, final Integer numberOfDays, boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod, final boolean isEqualAmortization, final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI, - final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans) { + final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans, + final Boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment) { final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; @@ -247,7 +250,7 @@ public final class LoanApplicationTerms { calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false, isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, - isPrincipalCompoundingDisabledForOverdueLoans); + isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment); } @@ -293,6 +296,11 @@ public final class LoanApplicationTerms { final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false; final boolean isEqualAmortization = loanProductRelatedDetail.isEqualAmortization(); + final boolean isDownPaymentEnabled = loanProductRelatedDetail.isEnableDownPayment(); + BigDecimal disbursedAmountPercentageForDownPayment = null; + if (isDownPaymentEnabled) { + disbursedAmountPercentageForDownPayment = loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment(); + } return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, @@ -307,7 +315,7 @@ public final class LoanApplicationTerms { isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday, isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, - isPrincipalCompoundingDisabledForOverdueLoans); + isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -333,7 +341,8 @@ public final class LoanApplicationTerms { final Integer numberOfDays, final boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod, final boolean isEqualAmortization, final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI, final BigDecimal fixedPrincipalPercentagePerInstallment, - final boolean isPrincipalCompoundingDisabledForOverdueLoans) { + final boolean isPrincipalCompoundingDisabledForOverdueLoans, final boolean isDownPaymentEnabled, + final BigDecimal disbursedAmountPercentageForDownPayment) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; @@ -410,6 +419,8 @@ public final class LoanApplicationTerms { this.isInterestToBeRecoveredFirstWhenGreaterThanEMI = isInterestToBeRecoveredFirstWhenGreaterThanEMI; this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment; this.isPrincipalCompoundingDisabledForOverdueLoans = isPrincipalCompoundingDisabledForOverdueLoans; + this.isDownPaymentEnabled = isDownPaymentEnabled; + this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment; } public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, @@ -1253,7 +1264,8 @@ public final class LoanApplicationTerms { this.repaymentPeriodFrequencyType, this.numberOfRepayments, this.principalGrace, this.recurringMoratoriumOnPrincipalPeriods, this.interestPaymentGrace, this.interestChargingGrace, this.amortizationMethod, this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), this.daysInYearType.getValue(), - this.interestRecalculationEnabled, this.isEqualAmortization); + this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled, + this.disbursedAmountPercentageForDownPayment); } public Integer getLoanTermFrequency() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java index ce041b03f..dd3a67710 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 @@ -147,4 +147,8 @@ public interface LoanProductConstants { String DUE_DAYS_FOR_REPAYMENT_EVENT = "dueDaysForRepaymentEvent"; String OVER_DUE_DAYS_FOR_REPAYMENT_EVENT = "overDueDaysForRepaymentEvent"; + // down-payment related + String ENABLE_DOWN_PAYMENT = "enableDownPayment"; + String DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT = "disbursedAmountPercentageForDownPayment"; + } 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 5218cba42..178cd2abb 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 @@ -385,6 +385,10 @@ public class LoanProduct extends AbstractPersistableCustom { final Integer overDueDaysForRepaymentEvent = command .integerValueOfParameterNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT); + final boolean enableDownPayment = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT); + final BigDecimal disbursedAmountPercentageDownPayment = command + .bigDecimalValueOfParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT); + return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery, @@ -401,7 +405,7 @@ public class LoanProduct extends AbstractPersistableCustom { minimumGapBetweenInstallments, maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment, disallowExpectedDisbursements, allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber, dueDaysForRepaymentEvent, - overDueDaysForRepaymentEvent); + overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment); } @@ -612,7 +616,8 @@ public class LoanProduct extends AbstractPersistableCustom { final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization, final List rates, final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean disallowExpectedDisbursements, final boolean allowApprovedDisbursedAmountsOverApplied, final String overAppliedCalculationType, - final Integer overAppliedNumber, final Integer dueDaysForRepaymentEvent, final Integer overDueDaysForRepaymentEvent) { + final Integer overAppliedNumber, final Integer dueDaysForRepaymentEvent, final Integer overDueDaysForRepaymentEvent, + final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment) { this.fund = fund; this.transactionProcessingStrategyCode = transactionProcessingStrategyCode; this.name = name.trim(); @@ -645,7 +650,7 @@ public class LoanProduct extends AbstractPersistableCustom { considerPartialPeriodInterest, repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(), - isInterestRecalculationEnabled, isEqualAmortization); + isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment); this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings(); @@ -1216,6 +1221,20 @@ public class LoanProduct extends AbstractPersistableCustom { this.overDueDaysForRepaymentEvent = newValue; } + if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT, + this.loanProductRelatedDetail.isEnableDownPayment())) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT); + actualChanges.put(LoanProductConstants.ENABLE_DOWN_PAYMENT, newValue); + this.loanProductRelatedDetail.updateEnableDownPayment(newValue); + } + + if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, + this.loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment())) { + BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT); + actualChanges.put(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, newValue); + this.loanProductRelatedDetail.updateDisbursedAmountPercentageForDownPayment(newValue); + } + return actualChanges; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index 54328ba56..d16c51b03 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -129,6 +129,12 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "is_equal_amortization", nullable = false) private boolean isEqualAmortization = false; + @Column(name = "enable_down_payment", nullable = false) + private boolean enableDownPayment; + + @Column(name = "disbursed_amount_percentage_for_down_payment", scale = 6, precision = 9) + private BigDecimal disbursedAmountPercentageForDownPayment; + public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal, final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod, @@ -137,14 +143,15 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType, - final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization) { + final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization, + final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment) { return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, - isEqualAmortization); + isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment); } protected LoanProductRelatedDetail() { @@ -159,7 +166,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType, - final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization) { + final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization, + final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment) { this.currency = currency; this.principal = defaultPrincipal; this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod; @@ -186,6 +194,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.daysInYearType = daysInYearType; this.isInterestRecalculationEnabled = isInterestRecalculationEnabled; this.isEqualAmortization = isEqualAmortization; + this.enableDownPayment = enableDownPayment; + this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment; } private Integer defaultToNullIfZero(final Integer value) { @@ -675,4 +685,20 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche public void setNominalInterestRatePerPeriod(BigDecimal nominalInterestRatePerPeriod) { this.nominalInterestRatePerPeriod = nominalInterestRatePerPeriod; } + + public boolean isEnableDownPayment() { + return enableDownPayment; + } + + public BigDecimal getDisbursedAmountPercentageForDownPayment() { + return disbursedAmountPercentageForDownPayment; + } + + public void updateEnableDownPayment(boolean enableDownPayment) { + this.enableDownPayment = enableDownPayment; + } + + public void updateDisbursedAmountPercentageForDownPayment(BigDecimal disbursedAmountPercentageForDownPayment) { + this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index e9a44cf40..dafac9e54 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -1074,6 +1074,10 @@ final class LoansApiResourceSwagger { public Boolean chargedOff; @Schema(example = "3") public Integer inArrearsTolerance; + @Schema(example = "false") + public Boolean enableDownPayment; + @Schema(example = "0.000000") + public BigDecimal disbursedAmountPercentageForDownPayment; } @Schema(description = "GetLoansResponse") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java index 9d93ce4b2..be54f2672 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java @@ -256,6 +256,9 @@ public class LoanAccountData { private LocalDate lastClosedBusinessDate; private Boolean chargedOff; + private Boolean enableDownPayment; + private BigDecimal disbursedAmountPercentageForDownPayment; + public static LoanAccountData importInstanceIndividual(EnumOptionData loanTypeEnumOption, Long clientId, Long productId, Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments, Integer repaymentEvery, EnumOptionData repaidEveryFrequencyEnums, Integer loanTermFrequency, @@ -656,7 +659,8 @@ public class LoanAccountData { final boolean canUseForTopup, final boolean isTopup, final Long closureLoanId, final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment, final DelinquencyRangeData delinquencyRange, final boolean disallowExpectedDisbursements, final boolean fraud, - LocalDate lastClosedBusinessDate, LocalDate overpaidOnDate, final boolean chargedOff) { + LocalDate lastClosedBusinessDate, LocalDate overpaidOnDate, final boolean chargedOff, final boolean enableDownPayment, + final BigDecimal disbursedAmountPercentageForDownPayment) { final CollectionData delinquent = CollectionData.template(); @@ -696,7 +700,9 @@ public class LoanAccountData { .setClosureLoanAccountNo(closureLoanAccountNo).setTopupAmount(topupAmount).setIsEqualAmortization(isEqualAmortization) .setFixedPrincipalPercentagePerInstallment(fixedPrincipalPercentagePerInstallment).setDelinquent(delinquent) .setDelinquencyRange(delinquencyRange).setDisallowExpectedDisbursements(disallowExpectedDisbursements).setFraud(fraud) - .setLastClosedBusinessDate(lastClosedBusinessDate).setOverpaidOnDate(overpaidOnDate).setChargedOff(chargedOff); + .setLastClosedBusinessDate(lastClosedBusinessDate).setOverpaidOnDate(overpaidOnDate).setChargedOff(chargedOff) + .setEnableDownPayment(enableDownPayment) + .setDisbursedAmountPercentageForDownPayment(disbursedAmountPercentageForDownPayment); } /* @@ -781,7 +787,8 @@ public class LoanAccountData { .setFixedPrincipalPercentagePerInstallment(acc.fixedPrincipalPercentagePerInstallment) .setDelinquencyRange(acc.delinquencyRange).setDisallowExpectedDisbursements(acc.disallowExpectedDisbursements) .setFraud(acc.fraud).setLastClosedBusinessDate(acc.getLastClosedBusinessDate()).setOverpaidOnDate(acc.overpaidOnDate) - .setChargedOff(acc.chargedOff); + .setChargedOff(acc.chargedOff).setEnableDownPayment(acc.enableDownPayment) + .setDisbursedAmountPercentageForDownPayment(acc.disbursedAmountPercentageForDownPayment); } public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection productOptions, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 531029f6d..f533b028d 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 @@ -449,6 +449,13 @@ public class LoanScheduleAssembler { .isInterestToBeRecoveredFirstWhenGreaterThanEMI(); final boolean isPrincipalCompoundingDisabledForOverdueLoans = this.configurationDomainService .isPrincipalCompoundingDisabledForOverdueLoans(); + + final boolean isDownPaymentEnabled = loanProduct.getLoanProductRelatedDetail().isEnableDownPayment(); + BigDecimal disbursedAmountPercentageForDownPayment = null; + if (isDownPaymentEnabled) { + disbursedAmountPercentageForDownPayment = loanProduct.getLoanProductRelatedDetail() + .getDisbursedAmountPercentageForDownPayment(); + } return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, @@ -461,7 +468,8 @@ public class LoanScheduleAssembler { installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO, loanTermVariations, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, isSkipMeetingOnFirstDay, detailDTO, allowCompoundingOnEod, isEqualAmortization, isInterestToBeRecoveredFirstWhenGreaterThanEMI, - fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans); + fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, + disbursedAmountPercentageForDownPayment); } private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index e71b9f525..cd2e61499 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -693,7 +693,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa + " lp.can_use_for_topup as canUseForTopup, l.is_topup as isTopup, topup.closure_loan_id as closureLoanId, " + " l.total_recovered_derived as totalRecovered, topuploan.account_no as closureLoanAccountNo, " + " topup.topup_amount as topupAmount, l.last_closed_business_date as lastClosedBusinessDate,l.overpaidon_date as overpaidOnDate, " - + " l.is_charged_off as isChargedOff, l.charge_off_reason_cv_id as chargeOffReasonId, codec.code_value as chargeOffReason, l.charged_off_on_date as chargedOffOnDate, " + + " l.is_charged_off as isChargedOff, l.charge_off_reason_cv_id as chargeOffReasonId, codec.code_value as chargeOffReason, l.charged_off_on_date as chargedOffOnDate, l.enable_down_payment as enableDownPayment, l.disbursed_amount_percentage_for_down_payment as disbursedAmountPercentageForDownPayment, " + " cobu.username as chargedOffByUsername, cobu.firstname as chargedOffByFirstname, cobu.lastname as chargedOffByLastname " + " from m_loan l" // + " join m_product_loan lp on lp.id = l.product_id" // @@ -1037,6 +1037,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa final LocalDate lastClosedBusinessDate = JdbcSupport.getLocalDate(rs, "lastClosedBusinessDate"); final LocalDate overpaidOnDate = JdbcSupport.getLocalDate(rs, "overpaidOnDate"); + final boolean enableDownPayment = rs.getBoolean("enableDownPayment"); + final BigDecimal disbursedAmountPercentageForDownPayment = rs.getBigDecimal("disbursedAmountPercentageForDownPayment"); + return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, clientExternalId, groupData, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, @@ -1052,7 +1055,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap, maximumGap, loanSubStatus, canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, fixedPrincipalPercentagePerInstallment, delinquencyRange, disallowExpectedDisbursements, isFraud, - lastClosedBusinessDate, overpaidOnDate, isChargedOff); + lastClosedBusinessDate, overpaidOnDate, isChargedOff, enableDownPayment, disbursedAmountPercentageForDownPayment); } } 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 910b8069e..dc7a22a2f 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 @@ -111,7 +111,8 @@ public class LoanProductsApiResource { "defaultDifferentialLendingRate", "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", 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.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.ENABLE_DOWN_PAYMENT, + LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT)); private static final Set PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>( Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions")); @@ -146,7 +147,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\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\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" 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 26fad80e1..b31fd1648 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 @@ -155,6 +155,10 @@ final class LoanProductsApiResourceSwagger { public Integer dueDaysForRepaymentEvent; @Schema(example = "3") public Integer overDueDaysForRepaymentEvent; + @Schema(example = "false") + public Boolean enableDownPayment; + @Schema(example = "5.5") + public BigDecimal disbursedAmountPercentageForDownPayment; // Interest Recalculation @Schema(example = "false") @@ -1203,6 +1207,10 @@ final class LoanProductsApiResourceSwagger { public Integer overDueDaysForRepaymentEvent; @Schema(example = "3") public Integer inArrearsTolerance; + @Schema(example = "false") + public Boolean enableDownPayment; + @Schema(example = "5.5") + public BigDecimal disbursedAmountPercentageForDownPayment; } @Schema(description = "PutLoanProductsProductIdRequest") @@ -1324,6 +1332,10 @@ final class LoanProductsApiResourceSwagger { public Integer dueDaysForRepaymentEvent; @Schema(example = "3") public Integer overDueDaysForRepaymentEvent; + @Schema(example = "false") + public Boolean enableDownPayment; + @Schema(example = "5.5") + public BigDecimal disbursedAmountPercentageForDownPayment; // 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 805200367..3c41d3903 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 @@ -193,6 +193,9 @@ public class LoanProductData implements Serializable { private final Integer dueDaysForRepaymentEvent; private final Integer overDueDaysForRepaymentEvent; + private final boolean enableDownPayment; + private final BigDecimal disbursedAmountPercentageForDownPayment; + /** * Used when returning lookup information about loan product for dropdowns. */ @@ -281,6 +284,8 @@ public class LoanProductData implements Serializable { final DelinquencyBucketData delinquencyBucket = null; final Integer dueDaysForRepaymentEvent = null; final Integer overDueDaysForRepaymentEvent = null; + final boolean enableDownPayment = false; + final BigDecimal disbursedAmountPercentageDownPayment = null; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -299,7 +304,7 @@ public class LoanProductData implements Serializable { maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, - overDueDaysForRepaymentEvent); + overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment); } @@ -389,6 +394,8 @@ public class LoanProductData implements Serializable { final DelinquencyBucketData delinquencyBucket = null; final Integer dueDaysForRepaymentEvent = null; final Integer overDueDaysForRepaymentEvent = null; + final boolean enableDownPayment = false; + final BigDecimal disbursedAmountPercentageDownPayment = null; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -407,7 +414,7 @@ public class LoanProductData implements Serializable { maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, - overDueDaysForRepaymentEvent); + overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment); } @@ -504,6 +511,8 @@ public class LoanProductData implements Serializable { final DelinquencyBucketData delinquencyBucket = null; final Integer dueDaysForRepaymentEvent = null; final Integer overDueDaysForRepaymentEvent = null; + final boolean enableDownPayment = false; + final BigDecimal disbursedAmountPercentageDownPayment = null; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -522,7 +531,7 @@ public class LoanProductData implements Serializable { maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, - overDueDaysForRepaymentEvent); + overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment); } @@ -613,6 +622,8 @@ public class LoanProductData implements Serializable { final DelinquencyBucketData delinquencyBucket = null; final Integer dueDaysForRepaymentEvent = null; final Integer overDueDaysForRepaymentEvent = null; + final boolean enableDownPayment = false; + final BigDecimal disbursedAmountPercentageDownPayment = null; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -631,7 +642,7 @@ public class LoanProductData implements Serializable { maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, - overDueDaysForRepaymentEvent); + overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment); } @@ -678,7 +689,8 @@ public class LoanProductData implements Serializable { Collection rateOptions, Collection rates, final boolean isRatesEnabled, final BigDecimal fixedPrincipalPercentagePerInstallment, final Collection delinquencyBucketOptions, final DelinquencyBucketData delinquencyBucket, final Integer dueDaysForRepaymentEvent, - final Integer overDueDaysForRepaymentEvent) { + final Integer overDueDaysForRepaymentEvent, final boolean enableDownPayment, + final BigDecimal disbursedAmountPercentageForDownPayment) { this.id = id; this.name = name; this.shortName = shortName; @@ -796,6 +808,8 @@ public class LoanProductData implements Serializable { this.delinquencyBucket = delinquencyBucket; this.dueDaysForRepaymentEvent = dueDaysForRepaymentEvent; this.overDueDaysForRepaymentEvent = overDueDaysForRepaymentEvent; + this.enableDownPayment = enableDownPayment; + this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment; } public LoanProductData(final LoanProductData productData, final Collection chargeOptions, @@ -947,6 +961,8 @@ public class LoanProductData implements Serializable { this.delinquencyBucket = productData.delinquencyBucket; this.dueDaysForRepaymentEvent = productData.dueDaysForRepaymentEvent; this.overDueDaysForRepaymentEvent = productData.overDueDaysForRepaymentEvent; + this.enableDownPayment = productData.enableDownPayment; + this.disbursedAmountPercentageForDownPayment = productData.disbursedAmountPercentageForDownPayment; } 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 892d0e8e7..25bbd701c 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 @@ -159,7 +159,8 @@ public final class LoanProductDataValidator { LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS, LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED, LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE, LoanProductConstants.OVER_APPLIED_NUMBER, LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME, - LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT)); + LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, + LoanProductConstants.ENABLE_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT)); private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName, LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName, @@ -744,9 +745,38 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT).value(overDueDaysForRepaymentEvent) .integerZeroOrGreater(); + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_DOWN_PAYMENT, element)) { + final Boolean enableDownPayment = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT, element); + baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_DOWN_PAYMENT).value(enableDownPayment).ignoreIfNull() + .validateForBooleanValue(); + validateDownPaymentPercentage(enableDownPayment, baseDataValidator, element); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } + private void validateDownPaymentPercentage(Boolean enableDownPayment, DataValidatorBuilder baseDataValidator, JsonElement element) { + if (enableDownPayment) { + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, element)) { + BigDecimal disbursedAmountPercentageDownPayment = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, element); + baseDataValidator.reset().parameter(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT) + .value(disbursedAmountPercentageDownPayment).notLessThanMin(BigDecimal.ONE) + .notGreaterThanMax(BigDecimal.valueOf(100)).scaleNotGreaterThan(6); + } else { + baseDataValidator.reset().parameter(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT).failWithCode( + "required.for.enable.down.payment.true", + "Disbursed amount percentage for down-payment is required if enable down-payment is true"); + } + } else { + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, element)) { + baseDataValidator.reset().parameter(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT).failWithCode( + "supported.only.for.enable.down.payment.true", + "Disbursed amount percentage for down-payment is supported only if enable down-payment is true"); + } + } + } + private void validateVariableInstallmentSettings(final DataValidatorBuilder baseDataValidator, final JsonElement element) { if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.allowVariableInstallmentsParamName, element) && this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.allowVariableInstallmentsParamName, element)) { @@ -1621,6 +1651,12 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT).value(overDueDaysForRepaymentEvent) .integerZeroOrGreater(); + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_DOWN_PAYMENT, element)) { + final Boolean enableDownPayment = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT, element); + baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_DOWN_PAYMENT).value(enableDownPayment).ignoreIfNull() + .validateForBooleanValue(); + validateDownPaymentPercentage(enableDownPayment, baseDataValidator, element); + } throwExceptionIfValidationWarningsExist(dataValidationErrors); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index 01cd94103..5d73aea85 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 @@ -212,7 +212,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.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, " + "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, " @@ -344,6 +344,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final LocalDate closeDate = JdbcSupport.getLocalDate(rs, "closeDate"); final Integer dueDaysForRepaymentEvent = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "dueDaysForRepaymentEvent"); final Integer overDueDaysForRepaymentEvent = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "overDueDaysForRepaymentEvent"); + final boolean enableDownPayment = rs.getBoolean("enableDownPayment"); + final BigDecimal disbursedAmountPercentageForDownPayment = rs.getBigDecimal("disbursedAmountPercentageForDownPayment"); + String status = ""; if (closeDate != null && closeDate.isBefore(DateUtils.getBusinessLocalDate())) { status = "loanProduct.inActive"; @@ -499,7 +502,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap, maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, - dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent); + dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageForDownPayment); } } diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 9ccec4c98..c71f4c20d 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -138,4 +138,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0119_add_loan_product_down_payment_configuration.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0119_add_loan_product_down_payment_configuration.xml new file mode 100644 index 000000000..6d722d83c --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0119_add_loan_product_down_payment_configuration.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithDownPaymentConfigurationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithDownPaymentConfigurationTest.java new file mode 100644 index 000000000..99c4f36da --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithDownPaymentConfigurationTest.java @@ -0,0 +1,316 @@ +/** + * 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.math.BigDecimal; +import java.util.ArrayList; +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.PostLoansLoanIdTransactionsRequest; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; +import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; +import org.apache.fineract.client.models.PutLoanProductsProductIdResponse; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.CommonConstants; +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 LoanProductWithDownPaymentConfigurationTest { + + 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 loanProductCreationWithDownPaymentConfigurationTest() { + // Delinquency Bucket + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); + final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, + delinquencyBucketId); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); + + // Loan Product creation with down-payment configuration + Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment, + "25"); + + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId); + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + } + + @Test + public void loanProductUpdateWithEnableDownPaymentConfigurationTest() { + // Delinquency Bucket + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); + final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, + delinquencyBucketId); + // Loan Product without enable down payment configuration + GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper, delinquencyBucketId); + assertNotNull(getLoanProductsProductResponse); + assertEquals(false, getLoanProductsProductResponse.getEnableDownPayment()); + + // Modify Loan Product to update enable down payment configuration + PutLoanProductsProductIdResponse loanProductModifyResponse = updateLoanProduct(loanTransactionHelper, + getLoanProductsProductResponse.getId()); + assertNotNull(loanProductModifyResponse); + + getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductModifyResponse.getResourceId().intValue()); + assertNotNull(getLoanProductsProductResponse); + assertEquals(true, getLoanProductsProductResponse.getEnableDownPayment()); + + } + + @Test + public void loanProductEnableDownPaymentConfigurationValidationTests() { + final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build(); + final LoanTransactionHelper validationErrorHelper = new LoanTransactionHelper(this.requestSpec, errorResponse); + + // Delinquency Bucket + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); + final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, + delinquencyBucketId); + + // down-payment configuration + Boolean enableDownPayment = true; + + // Loan Product with enable down payment and with disbursed amount percentage as zero + final HashMap loanProductMap = new LoanProductTestBuilder().withEnableDownPayment(enableDownPayment, "0") + .build(null, delinquencyBucketId); + + ArrayList loanProductErrorData = (ArrayList) validationErrorHelper + .getLoanProductError(Utils.convertToJson(loanProductMap), CommonConstants.RESPONSE_ERROR); + assertNotNull(loanProductErrorData); + assertEquals("validation.msg.loanproduct.disbursedAmountPercentageForDownPayment.is.less.than.min", + loanProductErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + // Loan Product with enable down payment and with disbursed amount percentage as greater than 100 + final HashMap loanProductMap_1 = new LoanProductTestBuilder().withEnableDownPayment(enableDownPayment, "101") + .build(null, delinquencyBucketId); + + loanProductErrorData = (ArrayList) validationErrorHelper.getLoanProductError(Utils.convertToJson(loanProductMap_1), + CommonConstants.RESPONSE_ERROR); + assertNotNull(loanProductErrorData); + assertEquals("validation.msg.loanproduct.disbursedAmountPercentageForDownPayment.is.greater.than.max", + loanProductErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + // Loan Product with enable down payment and with disbursed amount percentage precision greater than 6 + final HashMap loanProductMap_2 = new LoanProductTestBuilder() + .withEnableDownPayment(enableDownPayment, "12.55555555").build(null, delinquencyBucketId); + + loanProductErrorData = (ArrayList) validationErrorHelper.getLoanProductError(Utils.convertToJson(loanProductMap_2), + CommonConstants.RESPONSE_ERROR); + assertNotNull(loanProductErrorData); + assertEquals("validation.msg.loanproduct.disbursedAmountPercentageForDownPayment.scale.is.greater.than.6", + loanProductErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + // Loan Product with disable down payment and with disbursed amount percentage + final HashMap loanProductMap_3 = new LoanProductTestBuilder().withEnableDownPayment(false, "12.5").build(null, + delinquencyBucketId); + + loanProductErrorData = (ArrayList) validationErrorHelper.getLoanProductError(Utils.convertToJson(loanProductMap_3), + CommonConstants.RESPONSE_ERROR); + assertNotNull(loanProductErrorData); + assertEquals("validation.msg.loanproduct.disbursedAmountPercentageForDownPayment.supported.only.for.enable.down.payment.true", + loanProductErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + // Loan Product with enable down payment and without disbursed amount percentage + final HashMap loanProductMap_4 = new LoanProductTestBuilder().withEnableDownPayment(enableDownPayment, null) + .build(null, delinquencyBucketId); + + loanProductErrorData = (ArrayList) validationErrorHelper.getLoanProductError(Utils.convertToJson(loanProductMap_4), + CommonConstants.RESPONSE_ERROR); + assertNotNull(loanProductErrorData); + assertEquals("validation.msg.loanproduct.disbursedAmountPercentageForDownPayment.required.for.enable.down.payment.true", + loanProductErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + } + + @Test + public void loanApplicationCreationWithLoanProductWithEnableDownPaymentConfiguration() { + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // Delinquency Bucket + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); + final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, + delinquencyBucketId); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + // Loan Product creation with down-payment configuration + Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment, + "25"); + + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId); + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + + final Integer loanId = createLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr); + + // Retrieve Loan with loanId + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + + } + + @Test + public void loanApplicationWithLoanProductWithEnableDownPaymentConfigurationDoesNotChangeWithUpdateProductConfiguration() { + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // Delinquency Bucket + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); + final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, + delinquencyBucketId); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(12.5); + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + // Loan Product creation with down-payment configuration + Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment, + "12.5"); + + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId); + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + + final Integer loanId = createLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr); + + // Retrieve Loan with loanId + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + + // Modify Loan Product to update enable down payment configuration + PutLoanProductsProductIdResponse loanProductModifyResponse = updateLoanProduct(loanTransactionHelper, + getLoanProductsProductResponse.getId()); + assertNotNull(loanProductModifyResponse); + + // verify Loan product configuration change + GetLoanProductsProductIdResponse getLoanProductsProductResponse_1 = loanTransactionHelper.getLoanProduct(loanProductId); + assertNotNull(getLoanProductsProductResponse_1); + assertEquals(enableDownPayment, getLoanProductsProductResponse_1.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse_1.getDisbursedAmountPercentageForDownPayment().compareTo(BigDecimal.valueOf(25.0))); + + // make repayment for loan + final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("5 September 2022").locale("en") + .transactionAmount(100.0)); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan does not change + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + + } + + private PutLoanProductsProductIdResponse updateLoanProduct(LoanTransactionHelper loanTransactionHelper, Long id) { + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25.0); + final PutLoanProductsProductIdRequest requestModifyLoan = new PutLoanProductsProductIdRequest().enableDownPayment(enableDownPayment) + .disbursedAmountPercentageForDownPayment(disbursedAmountPercentageForDownPayment).locale("en"); + return loanTransactionHelper.updateLoanProduct(id, requestModifyLoan); + } + + private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper, + final Integer delinquencyBucketId) { + final HashMap loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucketId); + final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap)); + return loanTransactionHelper.getLoanProduct(loanProductId); + } + + private Integer createLoanProductWithDownPaymentConfiguration(final LoanTransactionHelper loanTransactionHelper, + final Integer delinquencyBucketId, Boolean enableDownPayment, String disbursedAmountPercentageForDownPayment) { + final HashMap loanProductMap = new LoanProductTestBuilder() + .withEnableDownPayment(enableDownPayment, disbursedAmountPercentageForDownPayment).build(null, delinquencyBucketId); + final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap)); + return loanProductId; + } + + private Integer createLoanAccount(final Integer clientID, final Long loanProductID, final String externalId) { + + String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1") + .withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1") + .withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance() + .withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod() + .withExpectedDisbursementDate("03 September 2022").withSubmittedOnDate("01 September 2022").withLoanType("individual") + .withExternalId(externalId).build(clientID.toString(), loanProductID.toString(), null); + + final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON); + loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId, null); + loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanId, "1000"); + return loanId; + } + +} 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 49c6c9181..2b6cbe2e4 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 @@ -143,6 +143,8 @@ public class LoanProductTestBuilder { private Integer delinquencyBucketId; private Integer dueDaysForRepaymentEvent = null; private Integer overDueDaysForRepaymentEvent = null; + private boolean enableDownPayment = false; + private String disbursedAmountPercentageForDownPayment = null; public String build(final String chargeId) { final HashMap map = build(chargeId, null); @@ -279,6 +281,10 @@ public class LoanProductTestBuilder { if (this.overDueDaysForRepaymentEvent != null) { map.put("overDueDaysForRepaymentEvent", this.overDueDaysForRepaymentEvent); } + map.put("enableDownPayment", enableDownPayment); + if (this.disbursedAmountPercentageForDownPayment != null) { + map.put("disbursedAmountPercentageForDownPayment", disbursedAmountPercentageForDownPayment); + } return map; } @@ -690,4 +696,11 @@ public class LoanProductTestBuilder { return this; } + public LoanProductTestBuilder withEnableDownPayment(final Boolean enableDownPayment, + final String disbursedAmountPercentageForDownPayment) { + this.enableDownPayment = enableDownPayment; + this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment; + return this; + } + } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index 8a774dcc1..f7dc3cf7e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -132,6 +132,11 @@ public class LoanTransactionHelper extends IntegrationTest { return Utils.performServerPost(this.requestSpec, this.responseSpec, CREATE_LOAN_PRODUCT_URL, loanProductJSON, "resourceId"); } + public Object getLoanProductError(final String loanProductJSON, final String jsonAttributeToGetBack) { + return Utils.performServerPost(this.requestSpec, this.responseSpec, CREATE_LOAN_PRODUCT_URL, loanProductJSON, + jsonAttributeToGetBack); + } + public Integer getLoanId(final String loanApplicationJSON) { return this.getLoanId(loanApplicationJSON, this.requestSpec, this.responseSpec); }