From b2685ce9cf9360a6503bcf76e081d6426de9c804 Mon Sep 17 00:00:00 2001 From: Peter Bagrij Date: Wed, 13 Mar 2024 14:54:40 +0100 Subject: [PATCH] FINERACT-2065: Fixed length loan configuration --- .../domain/LoanApplicationTerms.java | 18 +-- .../loanproduct/LoanProductConstants.java | 2 + .../loanproduct/domain/LoanProduct.java | 14 ++- .../domain/LoanProductRelatedDetail.java | 18 ++- .../module/loan/module-changelog-master.xml | 1 + .../loan/parts/1019_add_fixed_length.xml | 33 ++++++ .../importhandler/loan/LoanImportHandler.java | 6 +- .../api/LoansApiResourceSwagger.java | 6 + .../loanaccount/data/LoanAccountData.java | 34 +++--- .../service/LoanScheduleAssembler.java | 17 ++- ...anApplicationCommandFromApiJsonHelper.java | 2 +- .../service/LoanReadPlatformServiceImpl.java | 4 +- .../api/LoanProductsApiResourceSwagger.java | 8 ++ .../loanproduct/data/LoanProductData.java | 17 ++- .../LoanProductDataValidator.java | 2 +- .../LoanProductReadPlatformServiceImpl.java | 5 +- .../DefaultScheduledDateGeneratorTest.java | 2 +- ...ntAllocationLoanRepaymentScheduleTest.java | 16 ++- ...FixedLengthLoanProductIntegrationTest.java | 108 ++++++++++++++++++ .../common/loans/LoanProductHelper.java | 4 + 20 files changed, 266 insertions(+), 51 deletions(-) create mode 100644 fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1019_add_fixed_length.xml create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java 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 f908d0df4..32f855433 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 @@ -68,6 +68,8 @@ public final class LoanApplicationTerms { private Integer actualNumberOfRepayments; private final Integer repaymentEvery; private final PeriodFrequencyType repaymentPeriodFrequencyType; + + private final Integer fixedLength; private final Integer nthDay; private final DayOfWeekType weekDayType; @@ -249,7 +251,7 @@ public final class LoanApplicationTerms { final Boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final Boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate, final LoanScheduleType loanScheduleType, - final LoanScheduleProcessingType loanScheduleProcessingType) { + final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength) { final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; @@ -267,8 +269,8 @@ public final class LoanApplicationTerms { isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false, isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment, - isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, - loanScheduleProcessingType); + isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType, + fixedLength); } @@ -324,6 +326,7 @@ public final class LoanApplicationTerms { } LoanScheduleType loanScheduleType = loanProductRelatedDetail.getLoanScheduleType(); LoanScheduleProcessingType loanScheduleProcessingType = loanProductRelatedDetail.getLoanScheduleProcessingType(); + final Integer fixedLength = loanProductRelatedDetail.getFixedLength(); return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, @@ -339,8 +342,8 @@ public final class LoanApplicationTerms { allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday, isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment, - isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, - loanScheduleProcessingType); + isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType, + fixedLength); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -369,7 +372,7 @@ public final class LoanApplicationTerms { final boolean isPrincipalCompoundingDisabledForOverdueLoans, final boolean isDownPaymentEnabled, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate, final LoanScheduleType loanScheduleType, - final LoanScheduleProcessingType loanScheduleProcessingType) { + final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; @@ -463,6 +466,7 @@ public final class LoanApplicationTerms { this.submittedOnDate = submittedOnDate; this.loanScheduleType = loanScheduleType; this.loanScheduleProcessingType = loanScheduleProcessingType; + this.fixedLength = fixedLength; } public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, @@ -1326,7 +1330,7 @@ public final class LoanApplicationTerms { this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), this.daysInYearType.getValue(), this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled, this.disbursedAmountPercentageForDownPayment, this.isAutoRepaymentForDownPaymentEnabled, this.loanScheduleType, - this.loanScheduleProcessingType); + this.loanScheduleProcessingType, this.fixedLength); } 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 532bd5efa..d18c5da69 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 @@ -161,4 +161,6 @@ public interface LoanProductConstants { // Repayment Strategies String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy"; + + String FIXED_LENGTH = "fixedLength"; } 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 83a848d60..53abbff42 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 @@ -429,6 +429,8 @@ public class LoanProduct extends AbstractPersistableCustom { final boolean enableInstallmentLevelDelinquency = command .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY); + final Integer fixedLength = command.integerValueOfParameterNamed(LoanProductConstants.FIXED_LENGTH); + return new LoanProduct(fund, loanTransactionProcessingStrategy, loanProductPaymentAllocationRules, loanProductCreditAllocationRules, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, @@ -447,7 +449,7 @@ public class LoanProduct extends AbstractPersistableCustom { isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment, disallowExpectedDisbursements, allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, - repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType); + repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType, fixedLength); } @@ -664,7 +666,7 @@ public class LoanProduct extends AbstractPersistableCustom { final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final RepaymentStartDateType repaymentStartDateType, final boolean enableInstallmentLevelDelinquency, final LoanScheduleType loanScheduleType, - final LoanScheduleProcessingType loanScheduleProcessingType) { + final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength) { this.fund = fund; this.transactionProcessingStrategyCode = transactionProcessingStrategyCode; @@ -713,7 +715,7 @@ public class LoanProduct extends AbstractPersistableCustom { recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(), isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, - enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType); + enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength); this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings(); @@ -1364,6 +1366,12 @@ public class LoanProduct extends AbstractPersistableCustom { this.updateEnableInstallmentLevelDelinquency(newValue); } + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.FIXED_LENGTH, loanProductRelatedDetail.getFixedLength())) { + final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.FIXED_LENGTH); + actualChanges.put(LoanProductConstants.FIXED_LENGTH, newValue); + loanProductRelatedDetail.setFixedLength(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 f956cf963..6c74c7534 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 @@ -92,6 +92,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "repayment_period_frequency_enum", nullable = false) private PeriodFrequencyType repaymentPeriodFrequencyType; + @Column(name = "fixed_length", nullable = false) + private Integer fixedLength; + @Column(name = "number_of_repayments", nullable = false) private Integer numberOfRepayments; @@ -159,7 +162,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final LoanScheduleType loanScheduleType, - final LoanScheduleProcessingType loanScheduleProcessingType) { + final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength) { return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, @@ -167,7 +170,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment, - loanScheduleType, loanScheduleProcessingType); + loanScheduleType, loanScheduleProcessingType, fixedLength); } protected LoanProductRelatedDetail() { @@ -185,7 +188,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final LoanScheduleType loanScheduleType, - final LoanScheduleProcessingType loanScheduleProcessingType) { + final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength) { this.currency = currency; this.principal = defaultPrincipal; this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod; @@ -197,6 +200,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.repayEvery = repayEvery; this.repaymentPeriodFrequencyType = repaymentFrequencyType; this.numberOfRepayments = defaultNumberOfRepayments; + this.fixedLength = fixedLength; this.graceOnPrincipalPayment = defaultToNullIfZero(graceOnPrincipalPayment); this.recurringMoratoriumOnPrincipalPeriods = recurringMoratoriumOnPrincipalPeriods; this.graceOnInterestPayment = defaultToNullIfZero(graceOnInterestPayment); @@ -753,4 +757,12 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche public LoanScheduleProcessingType getLoanScheduleProcessingType() { return loanScheduleProcessingType; } + + public Integer getFixedLength() { + return this.fixedLength; + } + + public void setFixedLength(Integer fixedLength) { + this.fixedLength = fixedLength; + } } 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 b90df0097..530477091 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 @@ -41,4 +41,5 @@ + diff --git a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1019_add_fixed_length.xml b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1019_add_fixed_length.xml new file mode 100644 index 000000000..e557d9388 --- /dev/null +++ b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1019_add_fixed_length.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java index 401cd13ab..dea87fc35 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java @@ -351,7 +351,7 @@ public class LoanImportHandler implements ImportHandler { nominalInterestRate, submittedOnDate, amortizationEnumOption, interestMethodEnum, interestCalculationPeriodEnum, arrearsTolerance, repaymentStrategyCode, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, firstRepaymentOnDate, row.getRowNum(), externalId, null, charges, linkAccountId, locale, - dateFormat, loanCollateralManagementData); + dateFormat, loanCollateralManagementData, null); } else if (loanType.equals("jlg")) { Long clientId = ImportHandlerUtils.getIdByName(workbook.getSheet(TemplatePopulateImportConstants.CLIENT_SHEET_NAME), clientOrGroupName); @@ -360,7 +360,7 @@ public class LoanImportHandler implements ImportHandler { nominalInterestRate, submittedOnDate, amortizationEnumOption, interestMethodEnum, interestCalculationPeriodEnum, arrearsTolerance, repaymentStrategyCode, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, firstRepaymentOnDate, row.getRowNum(), externalId, groupId, charges, linkAccountId, locale, - dateFormat, null); + dateFormat, null, null); } else { Long groupIdforGroupLoan = ImportHandlerUtils .getIdByName(workbook.getSheet(TemplatePopulateImportConstants.GROUP_SHEET_NAME), clientOrGroupName); @@ -369,7 +369,7 @@ public class LoanImportHandler implements ImportHandler { loanTermFrequencyEnum, nominalInterestRate, amortizationEnumOption, interestMethodEnum, interestCalculationPeriodEnum, arrearsTolerance, repaymentStrategyCode, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, firstRepaymentOnDate, row.getRowNum(), - externalId, linkAccountId, locale, dateFormat); + externalId, linkAccountId, locale, dateFormat, null); } } 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 375d506e2..67002b0d9 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 @@ -1085,6 +1085,8 @@ final class LoansApiResourceSwagger { @Schema(example = "1") public Integer repaymentEvery; public GetLoansLoanIdRepaymentFrequencyType repaymentFrequencyType; + @Schema(example = "1") + public Integer fixedLength; @Schema(example = "24") public BigDecimal interestRatePerPeriod; public GetLoansLoanIdInterestRateFrequencyType interestRateFrequencyType; @@ -1181,6 +1183,8 @@ final class LoansApiResourceSwagger { public Integer repaymentEvery; @Schema(example = "2") public Integer repaymentFrequencyType; + @Schema(example = "1") + public Integer fixedLength; @Schema(example = "2") public BigDecimal interestRatePerPeriod; @Schema(example = "1") @@ -1317,6 +1321,8 @@ final class LoansApiResourceSwagger { public Integer repaymentEvery; @Schema(example = "0") public Integer repaymentFrequencyType; + @Schema(example = "1") + public Integer fixedLength; @Schema(example = "2") public BigDecimal interestRatePerPeriod; @Schema(example = "0") 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 3e315a2ac..20350f534 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 @@ -104,6 +104,7 @@ public class LoanAccountData { private EnumOptionData termPeriodFrequencyType; private Integer numberOfRepayments; private Integer repaymentEvery; + private Integer fixedLength; private EnumOptionData repaymentFrequencyType; private EnumOptionData repaymentFrequencyNthDayType; private EnumOptionData repaymentFrequencyDayOfWeekType; @@ -274,7 +275,8 @@ public class LoanAccountData { BigDecimal inArrearsTolerance, String transactionProcessingStrategyCode, Integer graceOnPrincipalPayment, Integer graceOnInterestPayment, Integer graceOnInterestCharged, LocalDate interestChargedFromDate, LocalDate repaymentsStartingFromDate, Integer rowIndex, ExternalId externalId, Long groupId, Collection charges, - String linkAccountId, String locale, String dateFormat, List loanCollateralManagementData) { + String linkAccountId, String locale, String dateFormat, List loanCollateralManagementData, + Integer fixedLength) { return new LoanAccountData().setLoanType(loanTypeEnumOption).setClientId(clientId).setProductId(productId) .setLoanOfficerId(loanOfficerId).setSubmittedOnDate(submittedOnDate).setFundId(fundId).setPrincipal(principal) @@ -288,7 +290,7 @@ public class LoanAccountData { .setGraceOnInterestCharged(graceOnInterestCharged).setInterestChargedFromDate(interestChargedFromDate) .setRepaymentsStartingFromDate(repaymentsStartingFromDate).setRowIndex(rowIndex).setExternalId(externalId) .setGroupId(groupId).setCharges(charges).setLinkAccountId(linkAccountId).setLocale(locale).setDateFormat(dateFormat) - .setCollateral(loanCollateralManagementData); + .setCollateral(loanCollateralManagementData).setFixedLength(fixedLength); } public static LoanAccountData importInstanceGroup(EnumOptionData loanTypeEnumOption, Long groupIdforGroupLoan, Long productId, @@ -298,7 +300,7 @@ public class LoanAccountData { EnumOptionData interestMethodEnum, EnumOptionData interestCalculationPeriodEnum, BigDecimal arrearsTolerance, String transactionProcessingStrategyCode, Integer graceOnPrincipalPayment, Integer graceOnInterestPayment, Integer graceOnInterestCharged, LocalDate interestChargedFromDate, LocalDate repaymentsStartingFromDate, Integer rowIndex, - ExternalId externalId, String linkAccountId, String locale, String dateFormat) { + ExternalId externalId, String linkAccountId, String locale, String dateFormat, Integer fixedLength) { return new LoanAccountData().setLoanType(loanTypeEnumOption).setGroupId(groupIdforGroupLoan).setProductId(productId) .setLoanOfficerId(loanOfficerId).setSubmittedOnDate(submittedOnDate).setFundId(fundId).setPrincipal(principal) @@ -310,8 +312,8 @@ public class LoanAccountData { .setTransactionProcessingStrategyCode(transactionProcessingStrategyCode).setGraceOnPrincipalPayment(graceOnPrincipalPayment) .setGraceOnInterestPayment(graceOnInterestPayment).setGraceOnInterestCharged(graceOnInterestCharged) .setInterestChargedFromDate(interestChargedFromDate).setRepaymentsStartingFromDate(repaymentsStartingFromDate) - .setRowIndex(rowIndex).setExternalId(externalId).setLinkAccountId(linkAccountId).setLocale(locale) - .setDateFormat(dateFormat); + .setRowIndex(rowIndex).setExternalId(externalId).setLinkAccountId(linkAccountId).setLocale(locale).setDateFormat(dateFormat) + .setFixedLength(fixedLength); } /** @@ -350,7 +352,7 @@ public class LoanAccountData { .setTermPeriodFrequencyType(acc.termPeriodFrequencyType).setNumberOfRepayments(acc.numberOfRepayments) .setRepaymentEvery(acc.repaymentEvery).setRepaymentFrequencyType(acc.repaymentFrequencyType) .setRepaymentFrequencyNthDayType(acc.repaymentFrequencyNthDayType) - .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType) + .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType).setFixedLength(acc.fixedLength) .setTransactionProcessingStrategyCode(acc.transactionProcessingStrategyCode) .setTransactionProcessingStrategyName(acc.transactionProcessingStrategyName).setAmortizationType(acc.amortizationType) .setInterestRatePerPeriod(acc.interestRatePerPeriod).setInterestRateFrequencyType(acc.interestRateFrequencyType) @@ -424,7 +426,7 @@ public class LoanAccountData { .setTermPeriodFrequencyType(acc.termPeriodFrequencyType).setNumberOfRepayments(acc.numberOfRepayments) .setRepaymentEvery(acc.repaymentEvery).setRepaymentFrequencyType(acc.repaymentFrequencyType) .setRepaymentFrequencyNthDayType(acc.repaymentFrequencyNthDayType) - .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType) + .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType).setFixedLength(acc.fixedLength) .setTransactionProcessingStrategyCode(acc.transactionProcessingStrategyCode) .setTransactionProcessingStrategyName(acc.transactionProcessingStrategyName).setAmortizationType(acc.amortizationType) .setInterestRatePerPeriod(acc.interestRatePerPeriod).setInterestRateFrequencyType(acc.interestRateFrequencyType) @@ -643,7 +645,7 @@ public class LoanAccountData { .setFixedPrincipalPercentagePerInstallment(product.getFixedPrincipalPercentagePerInstallment()).setDelinquent(delinquent) .setDisallowExpectedDisbursements(product.getDisallowExpectedDisbursements()).setFraud(acc.fraud) .setOverpaidOnDate(acc.overpaidOnDate).setChargedOff(acc.chargedOff).setLoanScheduleType(acc.getLoanScheduleType()) - .setLoanScheduleProcessingType(acc.getLoanScheduleProcessingType()); + .setLoanScheduleProcessingType(acc.getLoanScheduleProcessingType()).setFixedLength(product.getFixedLength()); } /* @@ -680,7 +682,7 @@ public class LoanAccountData { LocalDate lastClosedBusinessDate, LocalDate overpaidOnDate, final boolean chargedOff, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final boolean enableInstallmentLevelDelinquency, final EnumOptionData loanScheduleType, - final EnumOptionData loanScheduleProcessingType) { + final EnumOptionData loanScheduleProcessingType, final Integer fixedLength) { final CollectionData delinquent = CollectionData.template(); @@ -724,7 +726,7 @@ public class LoanAccountData { .setEnableDownPayment(enableDownPayment).setDisbursedAmountPercentageForDownPayment(disbursedAmountPercentageForDownPayment) .setEnableAutoRepaymentForDownPayment(enableAutoRepaymentForDownPayment) .setEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency).setLoanScheduleType(loanScheduleType) - .setLoanScheduleProcessingType(loanScheduleProcessingType); + .setLoanScheduleProcessingType(loanScheduleProcessingType).setFixedLength(fixedLength); } /* @@ -764,7 +766,7 @@ public class LoanAccountData { .setPrincipal(acc.principal).setApprovedPrincipal(acc.approvedPrincipal).setNetDisbursalAmount(acc.netDisbursalAmount) .setTotalOverpaid(acc.totalOverpaid).setInArrearsTolerance(acc.inArrearsTolerance).setTermFrequency(acc.termFrequency) .setTermPeriodFrequencyType(acc.termPeriodFrequencyType).setNumberOfRepayments(acc.numberOfRepayments) - .setRepaymentEvery(acc.repaymentEvery).setRepaymentFrequencyType(acc.repaymentFrequencyType) + .setRepaymentEvery(acc.repaymentEvery).setRepaymentFrequencyType(acc.repaymentFrequencyType).setFixedLength(acc.fixedLength) .setRepaymentFrequencyNthDayType(acc.repaymentFrequencyNthDayType) .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType) .setTransactionProcessingStrategyCode(acc.transactionProcessingStrategyCode) @@ -848,7 +850,7 @@ public class LoanAccountData { .setTermPeriodFrequencyType(acc.termPeriodFrequencyType).setNumberOfRepayments(acc.numberOfRepayments) .setRepaymentEvery(acc.repaymentEvery).setRepaymentFrequencyType(acc.repaymentFrequencyType) .setRepaymentFrequencyNthDayType(acc.repaymentFrequencyNthDayType) - .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType) + .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType).setFixedLength(acc.fixedLength) .setTransactionProcessingStrategyCode(acc.transactionProcessingStrategyCode) .setTransactionProcessingStrategyName(acc.transactionProcessingStrategyName).setAmortizationType(acc.amortizationType) .setInterestRatePerPeriod(acc.interestRatePerPeriod).setInterestRateFrequencyType(acc.interestRateFrequencyType) @@ -947,7 +949,7 @@ public class LoanAccountData { .setTotalOverpaid(acc.totalOverpaid).setInArrearsTolerance(acc.inArrearsTolerance).setTermFrequency(acc.termFrequency) .setTermPeriodFrequencyType(acc.termPeriodFrequencyType).setNumberOfRepayments(acc.numberOfRepayments) .setRepaymentEvery(acc.repaymentEvery).setRepaymentFrequencyType(acc.repaymentFrequencyType) - .setRepaymentFrequencyNthDayType(acc.repaymentFrequencyNthDayType) + .setRepaymentFrequencyNthDayType(acc.repaymentFrequencyNthDayType).setFixedLength(acc.fixedLength) .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType) .setTransactionProcessingStrategyCode(acc.transactionProcessingStrategyCode) .setTransactionProcessingStrategyName(acc.transactionProcessingStrategyName).setAmortizationType(acc.amortizationType) @@ -1016,7 +1018,7 @@ public class LoanAccountData { .setTermPeriodFrequencyType(acc.termPeriodFrequencyType).setNumberOfRepayments(acc.numberOfRepayments) .setRepaymentEvery(acc.repaymentEvery).setRepaymentFrequencyType(acc.repaymentFrequencyType) .setRepaymentFrequencyNthDayType(acc.repaymentFrequencyNthDayType) - .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType) + .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType).setFixedLength(acc.fixedLength) .setTransactionProcessingStrategyCode(acc.transactionProcessingStrategyCode) .setTransactionProcessingStrategyName(acc.transactionProcessingStrategyName).setAmortizationType(acc.amortizationType) .setInterestRatePerPeriod(acc.interestRatePerPeriod).setInterestRateFrequencyType(acc.interestRateFrequencyType) @@ -1094,7 +1096,7 @@ public class LoanAccountData { .setMeeting(acc.meeting).setProductOptions(acc.productOptions).setTermFrequencyTypeOptions(acc.termFrequencyTypeOptions) .setRepaymentFrequencyTypeOptions(acc.repaymentFrequencyTypeOptions) .setRepaymentFrequencyNthDayTypeOptions(acc.repaymentFrequencyNthDayTypeOptions) - .setRepaymentFrequencyDaysOfWeekTypeOptions(acc.repaymentFrequencyDaysOfWeekTypeOptions) + .setRepaymentFrequencyDaysOfWeekTypeOptions(acc.repaymentFrequencyDaysOfWeekTypeOptions).setFixedLength(acc.fixedLength) .setTransactionProcessingStrategyOptions(acc.transactionProcessingStrategyOptions) .setInterestRateFrequencyTypeOptions(acc.interestRateFrequencyTypeOptions) .setAmortizationTypeOptions(acc.amortizationTypeOptions).setInterestTypeOptions(acc.interestTypeOptions) @@ -1141,7 +1143,7 @@ public class LoanAccountData { .setTermPeriodFrequencyType(acc.termPeriodFrequencyType).setNumberOfRepayments(acc.numberOfRepayments) .setRepaymentEvery(acc.repaymentEvery).setRepaymentFrequencyType(acc.repaymentFrequencyType) .setRepaymentFrequencyNthDayType(acc.repaymentFrequencyNthDayType) - .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType) + .setRepaymentFrequencyDayOfWeekType(acc.repaymentFrequencyDayOfWeekType).setFixedLength(acc.fixedLength) .setTransactionProcessingStrategyCode(acc.transactionProcessingStrategyCode) .setTransactionProcessingStrategyName(acc.transactionProcessingStrategyName).setAmortizationType(acc.amortizationType) .setInterestRatePerPeriod(acc.interestRatePerPeriod).setInterestRateFrequencyType(acc.interestRateFrequencyType) 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 946d1e565..ba296caf0 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 @@ -469,14 +469,21 @@ public class LoanScheduleAssembler { LoanScheduleType loanScheduleType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleType(); if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_TYPE, element)) { - LoanScheduleType.valueOf(this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, element)); + loanScheduleType = LoanScheduleType + .valueOf(this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, element)); } - LoanScheduleProcessingType loanScheduleProcessingType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleProcessingType(); - if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_TYPE, element)) { - LoanScheduleProcessingType + LoanScheduleProcessingType loanScheduleProcessingType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleProcessingType(); + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, element)) { + loanScheduleProcessingType = LoanScheduleProcessingType .valueOf(this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, element)); } + + Integer fixedLength = loanProduct.getLoanProductRelatedDetail().getFixedLength(); + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.FIXED_LENGTH, element)) { + fixedLength = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanProductConstants.FIXED_LENGTH, element); + } + return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, @@ -491,7 +498,7 @@ public class LoanScheduleAssembler { allowCompoundingOnEod, isEqualAmortization, isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, - loanScheduleType, loanScheduleProcessingType); + loanScheduleType, loanScheduleProcessingType, fixedLength); } private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index f075d7fbe..2c1251f49 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -104,7 +104,7 @@ public final class LoanApplicationCommandFromApiJsonHelper { LoanApiConstants.lastApplication, // glim specific LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanApiConstants.DISALLOW_EXPECTED_DISBURSEMENTS, LoanApiConstants.FRAUD_ATTRIBUTE_NAME, - LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE)); + LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH)); public static final String LOANAPPLICATION_UNDO = "loanapplication.undo"; private final FromJsonHelper fromApiJsonHelper; 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 a84d4127b..bfa5b96b5 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 @@ -637,6 +637,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa + " l.grace_on_principal_periods as graceOnPrincipalPayment, l.recurring_moratorium_principal_periods as recurringMoratoriumOnPrincipalPeriods, l.grace_on_interest_periods as graceOnInterestPayment, l.grace_interest_free_periods as graceOnInterestCharged,l.grace_on_arrears_ageing as graceOnArrearsAgeing," + " l.nominal_interest_rate_per_period as interestRatePerPeriod, l.annual_nominal_interest_rate as annualInterestRate, " + " l.repayment_period_frequency_enum as repaymentFrequencyType, l.interest_period_frequency_enum as interestRateFrequencyType, " + + " l.fixed_length as fixedLength, " + " l.term_frequency as termFrequency, l.term_period_frequency_enum as termPeriodFrequencyType, " + " l.amortization_method_enum as amortizationType, l.interest_method_enum as interestType, l.is_equal_amortization as isEqualAmortization, l.interest_calculated_in_period_enum as interestCalculationPeriodType," + " l.fixed_principal_percentage_per_installment fixedPrincipalPercentagePerInstallment, " @@ -1059,6 +1060,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa final LoanScheduleType loanScheduleType = LoanScheduleType.valueOf(loanScheduleTypeStr); final String loanScheduleProcessingTypeStr = rs.getString("loanScheduleProcessingType"); final LoanScheduleProcessingType loanScheduleProcessingType = LoanScheduleProcessingType.valueOf(loanScheduleProcessingTypeStr); + final Integer fixedLength = JdbcSupport.getInteger(rs, "fixedLength"); return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, clientExternalId, groupData, loanType, loanProductId, loanProductName, loanProductDescription, @@ -1077,7 +1079,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa fixedPrincipalPercentagePerInstallment, delinquencyRange, disallowExpectedDisbursements, isFraud, lastClosedBusinessDate, overpaidOnDate, isChargedOff, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment, enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(), - loanScheduleProcessingType.asEnumOptionData()); + loanScheduleProcessingType.asEnumOptionData(), fixedLength); } } 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 ed031bb64..c96e00148 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 @@ -95,6 +95,8 @@ final class LoanProductsApiResourceSwagger { public BigDecimal fixedPrincipalPercentagePerInstallment; @Schema(example = "true") public Boolean canDefineInstallmentAmount; + @Schema(example = "10") + public Integer fixedLength; // Settings @Schema(example = "false") @@ -555,6 +557,8 @@ final class LoanProductsApiResourceSwagger { @Schema(example = "7") public Integer repaymentEvery; public GetLoanProductsRepaymentFrequencyType repaymentFrequencyType; + @Schema(example = "10") + public Integer fixedLength; @Schema(example = "15.000000") public Double interestRatePerPeriod; public GetLoanProductsResponse.GetLoanProductsInterestRateFrequencyType interestRateFrequencyType; @@ -1210,6 +1214,8 @@ final class LoanProductsApiResourceSwagger { @Schema(example = "7") public Integer repaymentEvery; public GetLoanProductsResponse.GetLoanProductsRepaymentFrequencyType repaymentFrequencyType; + @Schema(example = "10") + public Integer fixedLength; @Schema(example = "5.000000") public Double interestRatePerPeriod; public GetLoanProductsProductIdResponse.GetLoanProductsInterestRateFrequencyType interestRateFrequencyType; @@ -1332,6 +1338,8 @@ final class LoanProductsApiResourceSwagger { public BigDecimal fixedPrincipalPercentagePerInstallment; @Schema(example = "true") public Boolean canDefineInstallmentAmount; + @Schema(example = "10.0") + public Integer fixedLength; // Settings @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 ddded6d7c..7403e82e4 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 @@ -89,6 +89,7 @@ public class LoanProductData implements Serializable { private final Integer maxNumberOfRepayments; private final Integer repaymentEvery; private final EnumOptionData repaymentFrequencyType; + private final Integer fixedLength; private final BigDecimal interestRatePerPeriod; private final BigDecimal minInterestRatePerPeriod; private final BigDecimal maxInterestRatePerPeriod; @@ -237,6 +238,7 @@ public class LoanProductData implements Serializable { final Integer minNumberOfRepayments = null; final Integer maxNumberOfRepayments = null; final Integer repaymentEvery = null; + final Integer fixedLength = null; final BigDecimal interestRatePerPeriod = null; final BigDecimal minInterestRatePerPeriod = null; final BigDecimal maxInterestRatePerPeriod = null; @@ -341,7 +343,7 @@ public class LoanProductData implements Serializable { fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, - loanScheduleProcessingType); + loanScheduleProcessingType, fixedLength); } @@ -356,6 +358,7 @@ public class LoanProductData implements Serializable { final Integer minNumberOfRepayments = null; final Integer maxNumberOfRepayments = null; final Integer repaymentEvery = null; + final Integer fixedLength = null; final BigDecimal interestRatePerPeriod = null; final BigDecimal minInterestRatePerPeriod = null; final BigDecimal maxInterestRatePerPeriod = null; @@ -460,7 +463,7 @@ public class LoanProductData implements Serializable { fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, - loanScheduleProcessingType); + loanScheduleProcessingType, fixedLength); } @@ -479,6 +482,7 @@ public class LoanProductData implements Serializable { final Integer maxNumberOfRepayments = null; final Integer repaymentEvery = null; + final Integer fixedLength = null; final BigDecimal interestRatePerPeriod = null; final BigDecimal minInterestRatePerPeriod = null; final BigDecimal maxInterestRatePerPeriod = null; @@ -586,7 +590,7 @@ public class LoanProductData implements Serializable { fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, - loanScheduleProcessingType); + loanScheduleProcessingType, fixedLength); } @@ -607,6 +611,7 @@ public class LoanProductData implements Serializable { final Integer maxNumberOfRepayments = null; final Integer repaymentEvery = null; + final Integer fixedLength = null; final BigDecimal interestRatePerPeriod = null; final BigDecimal minInterestRatePerPeriod = null; final BigDecimal maxInterestRatePerPeriod = null; @@ -706,7 +711,7 @@ public class LoanProductData implements Serializable { fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocationData, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, - loanScheduleProcessingType); + loanScheduleProcessingType, fixedLength); } public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map accountingMappings, @@ -756,7 +761,7 @@ public class LoanProductData implements Serializable { final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final Collection paymentAllocation, final Collection creditAllocation, final EnumOptionData repaymentStartDateType, final boolean enableInstallmentLevelDelinquency, - final EnumOptionData loanScheduleType, final EnumOptionData loanScheduleProcessingType) { + final EnumOptionData loanScheduleType, final EnumOptionData loanScheduleProcessingType, final Integer fixedLength) { this.id = id; this.name = name; this.shortName = shortName; @@ -774,6 +779,7 @@ public class LoanProductData implements Serializable { this.graceOnInterestPayment = graceOnInterestPayment; this.graceOnInterestCharged = graceOnInterestCharged; this.repaymentEvery = repaymentEvery; + this.fixedLength = fixedLength; this.interestRatePerPeriod = interestRatePerPeriod; this.minInterestRatePerPeriod = minInterestRatePerPeriod; this.maxInterestRatePerPeriod = maxInterestRatePerPeriod; @@ -929,6 +935,7 @@ public class LoanProductData implements Serializable { this.minNumberOfRepayments = productData.minNumberOfRepayments; this.maxNumberOfRepayments = productData.maxNumberOfRepayments; this.repaymentEvery = productData.repaymentEvery; + this.fixedLength = productData.fixedLength; this.interestRatePerPeriod = productData.interestRatePerPeriod; this.minInterestRatePerPeriod = productData.minInterestRatePerPeriod; this.maxInterestRatePerPeriod = productData.maxInterestRatePerPeriod; 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 c5ee18fbf..625c7de60 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 @@ -171,7 +171,7 @@ public final class LoanProductDataValidator { LoanProductConstants.ENABLE_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.REPAYMENT_START_DATE_TYPE, LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, LoanProductConstants.LOAN_SCHEDULE_TYPE, - LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE)); + LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH)); private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName, LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName, 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 a09ac1f58..31b776163 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 @@ -234,6 +234,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "lp.nominal_interest_rate_per_period as interestRatePerPeriod, lp.min_nominal_interest_rate_per_period as minInterestRatePerPeriod, lp.max_nominal_interest_rate_per_period as maxInterestRatePerPeriod, lp.interest_period_frequency_enum as interestRatePerPeriodFreq, " + "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod,lp.allow_partial_period_interest_calcualtion as allowPartialPeriodInterestCalcualtion, " + "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, lp.min_number_of_repayments as minNumberOfRepayments, lp.max_number_of_repayments as maxNumberOfRepayments, " + + "lp.fixed_length as fixedLength, " + "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.recurring_moratorium_principal_periods as recurringMoratoriumOnPrincipalPeriods, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,lp.grace_on_arrears_ageing as graceOnArrearsAgeing,lp.overdue_days_for_npa as overdueDaysForNPA, " + "lp.min_days_between_disbursal_and_first_repayment As minimumDaysBetweenDisbursalAndFirstRepayment, " + "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, " @@ -319,6 +320,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final Integer minNumberOfRepayments = JdbcSupport.getInteger(rs, "minNumberOfRepayments"); final Integer maxNumberOfRepayments = JdbcSupport.getInteger(rs, "maxNumberOfRepayments"); final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaidEvery"); + final Integer fixedLength = JdbcSupport.getInteger(rs, "fixedLength"); final Integer graceOnPrincipalPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnPrincipalPayment"); final Integer recurringMoratoriumOnPrincipalPeriods = JdbcSupport.getIntegerDefaultToNullIfZero(rs, @@ -544,7 +546,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment, advancedPaymentData, creditAllocationData, repaymentStartDateType, - enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(), loanScheduleProcessingType.asEnumOptionData()); + enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(), loanScheduleProcessingType.asEnumOptionData(), + fixedLength); } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java index 82421fa84..10d966e4d 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java @@ -123,7 +123,7 @@ public class DefaultScheduledDateGeneratorTest { null, null, null, null, null, Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, EMPTY_LIST, BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null, null, ZERO, null, NONE, null, ZERO, EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false, null, false, - DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL); + DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null); } private HolidayDetailDTO createHolidayDTO() { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java index 7a25c2e47..e42afa47a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java @@ -1962,7 +1962,8 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("500", "15", "4", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023", + LoanScheduleProcessingType.VERTICAL); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -2489,7 +2490,8 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(1000.0), 45, 15, 3, BigDecimal.ZERO, "01 September 2023", "01 September 2023"); + BigDecimal.valueOf(1000.0), 45, 15, 3, BigDecimal.ZERO, "01 September 2023", "01 September 2023", + LoanScheduleProcessingType.VERTICAL); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) @@ -3621,11 +3623,17 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal, final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate, final String expectedDisbursementDate, final String submittedOnDate) { + return applyForLoanApplication(clientId, loanProductId, principal, loanTermFrequency, repaymentAfterEvery, numberOfRepayments, + interestRate, expectedDisbursementDate, submittedOnDate, LoanScheduleProcessingType.HORIZONTAL); + } + + private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal, + final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate, + final String expectedDisbursementDate, final String submittedOnDate, LoanScheduleProcessingType loanScheduleProcessingType) { LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); return applyForLoanApplication(clientId, loanProductId, principal, loanTermFrequency, repaymentAfterEvery, numberOfRepayments, interestRate, expectedDisbursementDate, submittedOnDate, - AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY, - LoanScheduleProcessingType.HORIZONTAL.name()); + AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY, loanScheduleProcessingType.name()); } private static void validateLoanTransaction(GetLoansLoanIdResponse loanDetails, int index, double transactionAmount, diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java new file mode 100644 index 000000000..a85931ed5 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedLengthLoanProductIntegrationTest.java @@ -0,0 +1,108 @@ +/** + * 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 org.apache.fineract.client.models.GetLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; +import org.apache.fineract.client.models.PostLoanProductsRequest; +import org.apache.fineract.client.models.PostLoanProductsResponse; +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.loans.LoanTestLifecycleExtension; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(LoanTestLifecycleExtension.class) +public class FixedLengthLoanProductIntegrationTest extends BaseLoanIntegrationTest { + + @Test + public void testCreateReadUpdateReadLoanProductWithFixedLength() { + // create with 5 + PostLoanProductsRequest loanProductsRequest = fixedLengthLoanProduct(5); + PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(loanProductsRequest); + Assertions.assertNotNull(loanProduct.getResourceId()); + + // read + GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = loanProductHelper + .retrieveLoanProductById(loanProduct.getResourceId()); + Assertions.assertEquals(5, getLoanProductsProductIdResponse.getFixedLength()); + + // update to 6 + PutLoanProductsProductIdRequest updateRequest = new PutLoanProductsProductIdRequest().fixedLength(6).locale("en"); + PutLoanProductsProductIdResponse putLoanProductsProductIdResponse = loanProductHelper + .updateLoanProductById(loanProduct.getResourceId(), updateRequest); + Assertions.assertNotNull(putLoanProductsProductIdResponse.getResourceId()); + + // read again + getLoanProductsProductIdResponse = loanProductHelper.retrieveLoanProductById(loanProduct.getResourceId()); + Assertions.assertEquals(6, getLoanProductsProductIdResponse.getFixedLength()); + + // update to null + loanTransactionHelper.updateLoanProduct(putLoanProductsProductIdResponse.getResourceId(), """ + { + "fixedLength": null, + "locale": "en" + } + """); + + // read again + getLoanProductsProductIdResponse = loanProductHelper.retrieveLoanProductById(loanProduct.getResourceId()); + Assertions.assertNull(getLoanProductsProductIdResponse.getFixedLength()); + } + + @Test + public void testLoanApplicationWithFixedLengthInheritedFromLoanProduct() { + runAt("01 January 2023", () -> { + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(fixedLengthLoanProduct(6)); + Assertions.assertNotNull(loanProduct.getResourceId()); + + Long loanId = applyAndApproveLoan(clientId, loanProduct.getResourceId(), "01 January 2023", 1000.0); + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + Assertions.assertEquals(6, loanDetails.getFixedLength()); + }); + } + + @Test + public void testLoanApplicationWithFixedLengthOverriddenByLoanApplication() { + runAt("01 January 2023", () -> { + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + PostLoanProductsResponse loanProduct = loanProductHelper.createLoanProduct(fixedLengthLoanProduct(6)); + Assertions.assertNotNull(loanProduct.getResourceId()); + + Long loanId = applyAndApproveLoan(clientId, loanProduct.getResourceId(), "01 January 2023", 1000.0, 1, // + loanApplication -> loanApplication.fixedLength(5) // + ); + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + Assertions.assertEquals(5, loanDetails.getFixedLength()); + }); + } + + private PostLoanProductsRequest fixedLengthLoanProduct(Integer fixedLength) { + return createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().numberOfRepayments(4)// + .repaymentEvery(1)// + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())// + .transactionProcessingStrategyCode("mifos-standard-strategy").fixedLength(fixedLength); + } + +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java index b899f20d1..5e20943e6 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java @@ -46,6 +46,10 @@ public class LoanProductHelper extends IntegrationTest { return ok(fineract().loanProducts.updateLoanProduct1(externalId, request)); } + public PutLoanProductsProductIdResponse updateLoanProductById(Long loanProductId, PutLoanProductsProductIdRequest request) { + return ok(fineract().loanProducts.updateLoanProduct(loanProductId, request)); + } + public GetLoanProductsTemplateResponse getLoanProductTemplate(boolean isProductMixTemplate) { return ok(fineract().loanProducts.retrieveTemplate11(isProductMixTemplate)); }