diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java index 045c3fc9f..0bac3570e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java @@ -135,4 +135,6 @@ public interface LoanApiConstants { String applicationId = "applicationId"; String lastApplication = "lastApplication"; + String fixedPrincipalPercentagePerInstallmentParamName = "fixedPrincipalPercentagePerInstallment"; + } 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 ad1db14fa..dfc05cbf3 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 @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanaccount.api; import io.swagger.v3.oas.annotations.media.Schema; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.Set; @@ -462,6 +463,8 @@ final class LoansApiResourceSwagger { @Schema(example = "24") public Integer annualInterestRate; public GetLoansLoanIdAmortizationType amortizationType; + @Schema(example = "5.5") + public BigDecimal fixedPrincipalPercentagePerInstallment; public GetLoansLoanIdInterestType interestType; public GetLoansLoanIdInterestCalculationPeriodType interestCalculationPeriodType; @Schema(example = "2") @@ -507,6 +510,8 @@ final class LoansApiResourceSwagger { public Integer interestRatePerPeriod; @Schema(example = "1") public Integer amortizationType; + @Schema(example = "5.5") + public BigDecimal fixedPrincipalPercentagePerInstallment; @Schema(example = "0") public Integer interestType; @Schema(example = "1") @@ -611,6 +616,8 @@ final class LoansApiResourceSwagger { public Integer interestCalculationPeriodType; @Schema(example = "1") public Integer amortizationType; + @Schema(example = "5.5") + public BigDecimal fixedPrincipalPercentagePerInstallment; @Schema(example = "04 March 2014") public String expectedDisbursementDate; @Schema(example = "1") 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 d1576499f..61accd2d9 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 @@ -219,6 +219,7 @@ public final class LoanAccountData { private List datatables = null; private final Boolean isEqualAmortization; + private final BigDecimal fixedPrincipalPercentagePerInstallment; // Rate private final List rates; @@ -421,6 +422,7 @@ public final class LoanAccountData { this.maximumGap = null; this.isEqualAmortization = null; this.isRatesEnabled = false; + this.fixedPrincipalPercentagePerInstallment = null; } public Integer getRowIndex() { @@ -585,6 +587,7 @@ public final class LoanAccountData { final boolean isEqualAmortization = false; final List rates = null; final Boolean isRatesEnabled = false; + final BigDecimal fixedPrincipalPercentagePerInstallment = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, @@ -605,7 +608,8 @@ public final class LoanAccountData { inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, - closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled); + closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled, + fixedPrincipalPercentagePerInstallment); } @@ -729,6 +733,7 @@ public final class LoanAccountData { final boolean isEqualAmortization = false; final List rates = null; final Boolean isRatesEnabled = false; + final BigDecimal fixedPrincipalPercentagePerInstallment = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, @@ -749,7 +754,8 @@ public final class LoanAccountData { overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, - closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled); + closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled, + fixedPrincipalPercentagePerInstallment); } @@ -780,7 +786,8 @@ public final class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup, - acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled); + acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled, + acc.fixedPrincipalPercentagePerInstallment); } /** @@ -905,6 +912,7 @@ public final class LoanAccountData { final boolean isEqualAmortization = false; final List rates = null; final Boolean isRatesEnabled = false; + final BigDecimal fixedPrincipalPercentagePerInstallment = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, @@ -925,7 +933,8 @@ public final class LoanAccountData { overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, - closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled); + closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled, + fixedPrincipalPercentagePerInstallment); } @@ -955,7 +964,8 @@ public final class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup, - acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled); + acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled, + acc.fixedPrincipalPercentagePerInstallment); } @@ -1122,7 +1132,8 @@ public final class LoanAccountData { originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(), product.getMaximumGapBetweenInstallments(), subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, - closureLoanAccountNo, topupAmount, product.isEqualAmortization(), rates, isRatesEnabled); + closureLoanAccountNo, topupAmount, product.isEqualAmortization(), rates, isRatesEnabled, + product.getFixedPrincipalPercentagePerInstallment()); } public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) { @@ -1190,7 +1201,8 @@ public final class LoanAccountData { acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance, acc.interestRatesPeriods, product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(), product.getMaximumGapBetweenInstallments(), acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup, - acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, product.isEqualAmortization(), acc.rates, acc.isRatesEnabled); + acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, product.isEqualAmortization(), acc.rates, acc.isRatesEnabled, + product.getFixedPrincipalPercentagePerInstallment()); } @@ -1222,7 +1234,7 @@ public final class LoanAccountData { final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement, final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap, final EnumOptionData subStatus, final boolean canUseForTopup, final boolean isTopup, final Long closureLoanId, final String closureLoanAccountNo, - final BigDecimal topupAmount, final boolean isEqualAmortization) { + final BigDecimal topupAmount, final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment) { final LoanScheduleData repaymentSchedule = null; final Collection transactions = null; @@ -1281,7 +1293,7 @@ public final class LoanAccountData { isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, - isEqualAmortization, rates, isRatesEnabled); + isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment); } /* @@ -1333,7 +1345,8 @@ public final class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, - acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, rates, isRatesEnabled); + acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, rates, isRatesEnabled, + acc.fixedPrincipalPercentagePerInstallment); } public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection productOptions, @@ -1375,7 +1388,8 @@ public final class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup, - acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled); + acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled, + acc.fixedPrincipalPercentagePerInstallment); } public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map memberLoanCycle) { @@ -1440,7 +1454,8 @@ public final class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup, - acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled); + acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled, + acc.fixedPrincipalPercentagePerInstallment); } @@ -1474,7 +1489,8 @@ public final class LoanAccountData { acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup, - acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled); + acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled, + acc.fixedPrincipalPercentagePerInstallment); } public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) { @@ -1502,7 +1518,8 @@ public final class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup, - acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled); + acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled, + acc.fixedPrincipalPercentagePerInstallment); } public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) { @@ -1531,7 +1548,8 @@ public final class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup, - acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled); + acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled, + acc.fixedPrincipalPercentagePerInstallment); } private LoanAccountData(final Long id, // @@ -1584,7 +1602,7 @@ public final class LoanAccountData { final Integer minimumGap, final Integer maximumGap, final EnumOptionData subStatus, final Boolean canUseForTopup, final Collection clientActiveLoanOptions, final boolean isTopup, final Long closureLoanId, final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization, final List rates, - final Boolean isRatesEnabled) { + final Boolean isRatesEnabled, final BigDecimal fixedPrincipalPercentagePerInstallment) { this.id = id; this.accountNo = accountNo; @@ -1770,7 +1788,7 @@ public final class LoanAccountData { this.topupAmount = topupAmount; this.isEqualAmortization = isEqualAmortization; this.rates = rates; - + this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment; } public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 3d3643c49..9f93db5db 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -415,6 +415,9 @@ public class Loan extends AbstractPersistableCustom { @JoinTable(name = "m_loan_rate", joinColumns = @JoinColumn(name = "loan_id"), inverseJoinColumns = @JoinColumn(name = "rate_id")) private List rates; + @Column(name = "fixed_principal_percentage_per_installment", scale = 2, precision = 5, nullable = true) + private BigDecimal fixedPrincipalPercentagePerInstallment; + public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy, @@ -422,14 +425,14 @@ public class Loan extends AbstractPersistableCustom { final Set collateral, final BigDecimal fixedEmiAmount, final List disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, - final BigDecimal interestRateDifferential, final List rates) { + final BigDecimal interestRateDifferential, final List rates, final BigDecimal fixedPrincipalPercentagePerInstallment) { final LoanStatus status = null; final Group group = null; final Boolean syncDisbursementWithMeeting = null; return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, - interestRateDifferential, rates); + interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment); } public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType, @@ -439,13 +442,13 @@ public class Loan extends AbstractPersistableCustom { final Set collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final List disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, - final BigDecimal interestRateDifferential, final List rates) { + final BigDecimal interestRateDifferential, final List rates, final BigDecimal fixedPrincipalPercentagePerInstallment) { final LoanStatus status = null; final Client client = null; return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, - interestRateDifferential, rates); + interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment); } public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group, @@ -455,12 +458,12 @@ public class Loan extends AbstractPersistableCustom { final Set collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final List disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, - final BigDecimal interestRateDifferential, final List rates) { + final BigDecimal interestRateDifferential, final List rates, final BigDecimal fixedPrincipalPercentagePerInstallment) { final LoanStatus status = null; return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, - interestRateDifferential, rates); + interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment); } protected Loan() { @@ -473,7 +476,8 @@ public class Loan extends AbstractPersistableCustom { final Set loanCharges, final Set collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final List disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, - final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List rates) { + final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List rates, + final BigDecimal fixedPrincipalPercentagePerInstallment) { this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail; this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings(); @@ -532,6 +536,7 @@ public class Loan extends AbstractPersistableCustom { // rates added here this.rates = rates; + this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment; // Add net get net disbursal amount from charges and principal this.netDisbursalAmount = this.approvedPrincipal.subtract(deriveSumTotalOfChargesDueAtDisbursement()); @@ -1638,6 +1643,13 @@ public class Loan extends AbstractPersistableCustom { this.fixedEmiAmount = null; } + if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, + this.fixedPrincipalPercentagePerInstallment)) { + this.fixedPrincipalPercentagePerInstallment = command + .bigDecimalValueOfParameterNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName); + actualChanges.put(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, this.fixedPrincipalPercentagePerInstallment); + } + return actualChanges; } @@ -5610,7 +5622,7 @@ public class Loan extends AbstractPersistableCustom { rescheduleStrategyMethod, calendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper, scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), holidayDetailDTO, allowCompoundingOnEod, scheduleGeneratorDTO.isFirstRepaymentDateAllowedOnHoliday(), - scheduleGeneratorDTO.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI()); + scheduleGeneratorDTO.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI(), this.fixedPrincipalPercentagePerInstallment); return loanApplicationTerms; } @@ -5892,7 +5904,7 @@ public class Loan extends AbstractPersistableCustom { compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst, holidayDetailDTO, allowCompoundingOnEod, false, - false); + false, this.fixedPrincipalPercentagePerInstallment); } /** diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index cd30c1edc..6386d37bb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -207,6 +207,7 @@ public final class LoanApplicationTerms { private int extraPeriods = 0; private boolean isEqualAmortization; private Money interestTobeApproppriated; + private final BigDecimal fixedPrincipalPercentagePerInstallment; public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, @@ -229,7 +230,7 @@ public final class LoanApplicationTerms { BigDecimal approvedAmount, List loanTermVariations, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, final Integer numberOfDays, boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod, - final boolean isEqualAmortization) { + final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment) { final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; @@ -244,7 +245,8 @@ public final class LoanApplicationTerms { recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, - isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false, false); + isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false, false, + fixedPrincipalPercentagePerInstallment); } @@ -271,7 +273,7 @@ public final class LoanApplicationTerms { compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar, approvedAmount, annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper, numberOfDays, isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, false, - false); + false, null); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -289,7 +291,8 @@ public final class LoanApplicationTerms { BigDecimal annualNominalInterestRate, final List loanTermVariations, final CalendarHistoryDataWrapper calendarHistoryDataWrapper, final Integer numberOfDays, final boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod, - final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI) { + final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI, + final BigDecimal fixedPrincipalPercentagePerInstallment) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -328,7 +331,7 @@ public final class LoanApplicationTerms { loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday, - isInterestToBeAppropriatedEquallyWhenGreaterThanEMI); + isInterestToBeAppropriatedEquallyWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -387,7 +390,7 @@ public final class LoanApplicationTerms { recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, - isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false, false); + isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false, false, null); } @@ -414,8 +417,8 @@ public final class LoanApplicationTerms { applicationTerms.calendarHistoryDataWrapper, applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled, applicationTerms.numberOfDays, applicationTerms.isSkipRepaymentOnFirstDayOfMonth, applicationTerms.holidayDetailDTO, applicationTerms.allowCompoundingOnEod, applicationTerms.isEqualAmortization, - applicationTerms.isFirstRepaymentDateAllowedOnHoliday, - applicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI); + applicationTerms.isFirstRepaymentDateAllowedOnHoliday, applicationTerms.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI, + applicationTerms.fixedPrincipalPercentagePerInstallment); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -440,7 +443,7 @@ public final class LoanApplicationTerms { final CalendarHistoryDataWrapper calendarHistoryDataWrapper, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, final Integer numberOfDays, final boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod, final boolean isEqualAmortization, final boolean isFirstRepaymentDateAllowedOnHoliday, - final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI) { + final boolean isInterestToBeAppropriatedEquallyWhenGreaterThanEMI, final BigDecimal fixedPrincipalPercentagePerInstallment) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; @@ -515,6 +518,7 @@ public final class LoanApplicationTerms { this.isEqualAmortization = isEqualAmortization; this.isFirstRepaymentDateAllowedOnHoliday = isFirstRepaymentDateAllowedOnHoliday; this.isInterestToBeAppropriatedEquallyWhenGreaterThanEMI = isInterestToBeAppropriatedEquallyWhenGreaterThanEMI; + this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment; } public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, @@ -906,8 +910,14 @@ public final class LoanApplicationTerms { final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment(); Money principalPerPeriod = null; if (getFixedEmiAmount() == null) { - principalPerPeriod = this.principal.minus(totalPrincipalAccounted) - .dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus(this.adjustPrincipalForFlatLoans); + if (this.fixedPrincipalPercentagePerInstallment != null) { + principalPerPeriod = this.principal.minus(totalPrincipalAccounted) + .percentageOf(this.fixedPrincipalPercentagePerInstallment, mc.getRoundingMode()) + .plus(this.adjustPrincipalForFlatLoans); + } else { + principalPerPeriod = this.principal.minus(totalPrincipalAccounted) + .dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus(this.adjustPrincipalForFlatLoans); + } if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { principalPerPeriod = principalPerPeriod.zero(); } @@ -1289,8 +1299,11 @@ public final class LoanApplicationTerms { principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); this.fixedPrincipalAmount = principal.getAmount(); } - principal = Money.of(getCurrency(), getFixedPrincipalAmount()); - + if (this.fixedPrincipalPercentagePerInstallment != null) { + principal = this.principal.percentageOf(this.fixedPrincipalPercentagePerInstallment, mc.getRoundingMode()); + } else { + principal = Money.of(getCurrency(), getFixedPrincipalAmount()); + } if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { principal = principal.zero(); } 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 e217fc898..d41e0e35b 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 @@ -206,6 +206,9 @@ public class LoanScheduleAssembler { isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isEqualAmortizationParam, element); } + BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element); + // interest terms final Integer interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element); final InterestMethod interestMethod = InterestMethod.fromInt(interestType); @@ -457,7 +460,7 @@ public class LoanScheduleAssembler { compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO, loanTermVariations, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, isSkipMeetingOnFirstDay, detailDTO, - allowCompoundingOnEod, isEqualAmortization); + allowCompoundingOnEod, isEqualAmortization, fixedPrincipalPercentagePerInstallment); } private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java index 6f3a60708..a057418ae 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java @@ -69,7 +69,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper { LoanApiConstants.interestRateDifferentialParameterName, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName, LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, LoanProductConstants.RATES_PARAM_NAME, - LoanApiConstants.daysInYearTypeParameterName)); + LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName)); private final FromJsonHelper fromApiJsonHelper; 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 db0450609..26d216db3 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 @@ -49,6 +49,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollateralQuantity; import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollaterals; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; +import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; @@ -92,8 +93,8 @@ public final class LoanApplicationCommandFromApiJsonHelper { LoanApiConstants.createStandingInstructionAtDisbursementParameterName, LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, LoanProductConstants.RATES_PARAM_NAME, LoanApiConstants.applicationId, // glim specific - LoanApiConstants.lastApplication, LoanApiConstants.daysInYearTypeParameterName)); // glim - // specific + LoanApiConstants.lastApplication, // glim specific + LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName)); private final FromJsonHelper fromApiJsonHelper; private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper; @@ -176,6 +177,11 @@ public final class LoanApplicationCommandFromApiJsonHelper { } } + BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element); + baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName) + .value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE).notGreaterThanMax(BigDecimal.valueOf(100)); + final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element); baseDataValidator.reset().parameter("productId").value(productId).notNull().integerGreaterThanZero(); @@ -309,6 +315,12 @@ public final class LoanApplicationCommandFromApiJsonHelper { final Integer amortizationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(amortizationTypeParameterName, element); baseDataValidator.reset().parameter(amortizationTypeParameterName).value(amortizationType).notNull().inMinMaxRange(0, 1); + if (!amortizationType.equals(AmortizationMethod.EQUAL_PRINCIPAL.getValue()) && fixedPrincipalPercentagePerInstallment != null) { + baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName).failWithCode( + "not.supported.principal.fixing.not.allowed.with.equal.installments", + "Principal fixing cannot be done with equal installment amortization"); + } + final String expectedDisbursementDateParameterName = "expectedDisbursementDate"; final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(expectedDisbursementDateParameterName, element); @@ -574,6 +586,11 @@ public final class LoanApplicationCommandFromApiJsonHelper { } } + BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element); + baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName) + .value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE).notGreaterThanMax(BigDecimal.valueOf(100)); + final String externalIdParameterName = "externalId"; if (this.fromApiJsonHelper.parameterExists(externalIdParameterName, element)) { atLeastOneParameterPassedForUpdate = true; @@ -752,12 +769,20 @@ public final class LoanApplicationCommandFromApiJsonHelper { } final String amortizationTypeParameterName = "amortizationType"; + Integer amortizationType = null; if (this.fromApiJsonHelper.parameterExists(amortizationTypeParameterName, element)) { atLeastOneParameterPassedForUpdate = true; - final Integer amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(amortizationTypeParameterName, element); + amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(amortizationTypeParameterName, element); baseDataValidator.reset().parameter(amortizationTypeParameterName).value(amortizationType).notNull().inMinMaxRange(0, 1); } + if (!Integer.valueOf(AmortizationMethod.EQUAL_PRINCIPAL.getValue()).equals(amortizationType) + && fixedPrincipalPercentagePerInstallment != null) { + baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName).failWithCode( + "not.supported.principal.fixing.not.allowed.with.equal.installments", + "Principal fixing cannot be done with equal installment amortization"); + } + final String expectedDisbursementDateParameterName = "expectedDisbursementDate"; LocalDate expectedDisbursementDate = null; if (this.fromApiJsonHelper.parameterExists(expectedDisbursementDateParameterName, element)) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java index 805bf3434..06514ebf2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java @@ -260,6 +260,8 @@ public class LoanAssembler { } } } + BigDecimal fixedPrincipalPercentagePerInstallment = fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element); Loan loanApplication = null; Client client = null; @@ -298,20 +300,22 @@ public class LoanAssembler { loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, - createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates); + createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates, + fixedPrincipalPercentagePerInstallment); } else if (group != null) { loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, - createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates); + createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates, + fixedPrincipalPercentagePerInstallment); } else if (client != null) { loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, - isFloatingInterestRate, interestRateDifferential, rates); + isFloatingInterestRate, interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment); } 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 2de00d3b6..5f37345e5 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 @@ -610,6 +610,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " l.repayment_period_frequency_enum as repaymentFrequencyType, l.interest_period_frequency_enum as interestRateFrequencyType, " + " 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, " + " l.allow_partial_period_interest_calcualtion as allowPartialPeriodInterestCalcualtion," + " l.loan_status_id as lifeCycleStatusId, l.loan_transaction_strategy_id as transactionStrategyId, " + " lps.name as transactionStrategyName, " @@ -822,6 +823,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final int interestCalculationPeriodTypeInt = JdbcSupport.getInteger(rs, "interestCalculationPeriodType"); final boolean isEqualAmortization = rs.getBoolean("isEqualAmortization"); final EnumOptionData amortizationType = LoanEnumerations.amortizationType(amortizationTypeInt); + final BigDecimal fixedPrincipalPercentagePerInstallment = rs.getBigDecimal("fixedPrincipalPercentagePerInstallment"); final EnumOptionData interestType = LoanEnumerations.interestType(interestTypeInt); final EnumOptionData interestCalculationPeriodType = LoanEnumerations .interestCalculationPeriodType(interestCalculationPeriodTypeInt); @@ -997,7 +999,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap, maximumGap, loanSubStatus, - canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization); + canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, + fixedPrincipalPercentagePerInstallment); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java index b5ec60f72..f937ae124 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java @@ -110,6 +110,7 @@ public interface LoanProductConstants { // Fixed installment configuration related String canDefineEmiAmountParamName = "canDefineInstallmentAmount"; String installmentAmountInMultiplesOfParamName = "installmentAmountInMultiplesOf"; + String fixedPrincipalPercentagePerInstallmentParamName = "fixedPrincipalPercentagePerInstallment"; // Loan Configurable Attributes String allowAttributeOverridesParamName = "allowAttributeOverrides"; 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 f2a8c8eb4..b07cba39a 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 @@ -69,6 +69,7 @@ import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData; import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.apache.fineract.portfolio.fund.data.FundData; import org.apache.fineract.portfolio.fund.service.FundReadPlatformService; +import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData; @@ -103,8 +104,8 @@ public class LoanProductsApiResource { "accountingOptions", "accountingRuleOptions", "accountingMappingOptions", "floatingRateOptions", "isLinkedToFloatingInterestRates", "floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate", "defaultDifferentialLendingRate", "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", - LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, - LoanProductConstants.RATES_PARAM_NAME)); + LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, LoanProductConstants.RATES_PARAM_NAME, + LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName)); private final Set productMixDataParameters = new HashSet<>( Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions")); 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 0f389ef3e..6d18aaf66 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 @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanproduct.api; import io.swagger.v3.oas.annotations.media.Schema; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.Set; @@ -63,6 +64,8 @@ final class LoanProductsApiResourceSwagger { public Integer interestRateFrequencyType; @Schema(example = "1") public Integer amortizationType; + @Schema(example = "5.5") + public BigDecimal fixedPrincipalPercentagePerInstallment; @Schema(example = "0") public Integer interestType; @Schema(example = "1") @@ -330,6 +333,8 @@ final class LoanProductsApiResourceSwagger { @Schema(example = "15.000000") public Float annualInterestRate; public GetLoanProductsAmortizationType amortizationType; + @Schema(example = "5.5") + public BigDecimal fixedPrincipalPercentagePerInstallment; public GetLoanProductsInterestType interestType; public GetLoansProductsInterestCalculationPeriodType interestCalculationPeriodType; @Schema(example = "1") @@ -1044,6 +1049,8 @@ final class LoanProductsApiResourceSwagger { @Schema(example = "60.000000") public Float annualInterestRate; public GetLoanProductsResponse.GetLoanProductsAmortizationType amortizationType; + @Schema(example = "5.5") + public BigDecimal fixedPrincipalPercentagePerInstallment; public GetLoanProductsTemplateResponse.GetLoanProductsInterestTemplateType interestType; public GetLoanProductsResponse.GetLoansProductsInterestCalculationPeriodType interestCalculationPeriodType; @Schema(example = "1") 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 82b5b5006..6aaf01e76 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 @@ -191,6 +191,7 @@ public class LoanProductData implements Serializable { private LoanProductConfigurableAttributes allowAttributeOverrides; private final boolean syncExpectedWithDisbursementDate; private final boolean isEqualAmortization; + private final BigDecimal fixedPrincipalPercentagePerInstallment; /** * Used when returning lookup information about loan product for dropdowns. @@ -254,6 +255,7 @@ public class LoanProductData implements Serializable { final LoanProductGuaranteeData productGuaranteeData = null; final Boolean holdGuaranteeFunds = false; final BigDecimal principalThresholdForLastInstallment = null; + final BigDecimal fixedPrincipalPercentagePerInstallment = null; final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false; final EnumOptionData daysInMonthType = null; @@ -284,7 +286,8 @@ public class LoanProductData implements Serializable { installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, - syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled); + syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, + fixedPrincipalPercentagePerInstallment); } @@ -355,6 +358,7 @@ public class LoanProductData implements Serializable { final Boolean holdGuaranteeFunds = false; final LoanProductGuaranteeData productGuaranteeData = null; final BigDecimal principalThresholdForLastInstallment = null; + final BigDecimal fixedPrincipalPercentagePerInstallment = null; final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false; final boolean canDefineInstallmentAmount = false; final Integer installmentAmountInMultiplesOf = null; @@ -380,7 +384,8 @@ public class LoanProductData implements Serializable { installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, - syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled); + syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled, + fixedPrincipalPercentagePerInstallment); } @@ -458,6 +463,7 @@ public class LoanProductData implements Serializable { final Boolean holdGuaranteeFunds = false; final LoanProductGuaranteeData productGuaranteeData = null; final BigDecimal principalThresholdForLastInstallment = null; + final BigDecimal fixedPrincipalPercentagePerInstallment = null; final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false; final boolean canDefineInstallmentAmount = false; final Integer installmentAmountInMultiplesOf = null; @@ -484,7 +490,7 @@ public class LoanProductData implements Serializable { isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, - isEqualAmortization, rateOptions, rates, isRatesEnabled); + isEqualAmortization, rateOptions, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment); } @@ -556,6 +562,7 @@ public class LoanProductData implements Serializable { final Boolean holdGuaranteeFunds = false; final LoanProductGuaranteeData productGuaranteeData = null; final BigDecimal principalThresholdForLastInstallment = null; + final BigDecimal fixedPrincipalPercentagePerInstallment = null; final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false; final boolean canDefineInstallmentAmount = false; final Integer installmentAmountInMultiplesOf = null; @@ -582,7 +589,7 @@ public class LoanProductData implements Serializable { isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, - isEqualAmortization, rateOptions, rates, isRatesEnabled); + isEqualAmortization, rateOptions, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment); } @@ -624,7 +631,8 @@ public class LoanProductData implements Serializable { boolean isFloatingInterestRateCalculationAllowed, final boolean isVariableInstallmentsAllowed, final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments, final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization, - Collection rateOptions, Collection rates, final boolean isRatesEnabled) { + Collection rateOptions, Collection rates, final boolean isRatesEnabled, + final BigDecimal fixedPrincipalPercentagePerInstallment) { this.id = id; this.name = name; this.shortName = shortName; @@ -715,6 +723,7 @@ public class LoanProductData implements Serializable { this.holdGuaranteeFunds = holdGuaranteeFunds; this.productGuaranteeData = loanProductGuaranteeData; this.principalThresholdForLastInstallment = principalThresholdForLastInstallment; + this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment; this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion; this.allowAttributeOverrides = allowAttributeOverrides; @@ -851,6 +860,7 @@ public class LoanProductData implements Serializable { this.holdGuaranteeFunds = productData.holdGuaranteeFunds; this.productGuaranteeData = productData.productGuaranteeData; this.principalThresholdForLastInstallment = productData.principalThresholdForLastInstallment; + this.fixedPrincipalPercentagePerInstallment = productData.fixedPrincipalPercentagePerInstallment; this.accountMovesOutOfNPAOnlyOnArrearsCompletion = productData.accountMovesOutOfNPAOnlyOnArrearsCompletion; this.daysInMonthTypeOptions = daysInMonthTypeOptions; @@ -1291,4 +1301,8 @@ public class LoanProductData implements Serializable { public BigDecimal getMaxInterestRatePerPeriod() { return maxInterestRatePerPeriod; } + + public BigDecimal getFixedPrincipalPercentagePerInstallment() { + return fixedPrincipalPercentagePerInstallment; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java index 949a2f3e0..3df21b707 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java @@ -188,6 +188,9 @@ public class LoanProduct extends AbstractPersistableCustom { @Column(name = "is_equal_amortization", nullable = false) private boolean isEqualAmortization = false; + @Column(name = "fixed_principal_percentage_per_installment", scale = 2, precision = 5, nullable = true) + private BigDecimal fixedPrincipalPercentagePerInstallment; + public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, final List productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate, final List productRates) { @@ -346,6 +349,9 @@ public class LoanProduct extends AbstractPersistableCustom { ? command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM) : false; + BigDecimal fixedPrincipalPercentagePerInstallment = command + .bigDecimalValueOfParameterNamed(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName); + return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery, @@ -360,7 +366,7 @@ public class LoanProduct extends AbstractPersistableCustom { floatingRate, interestRateDifferential, minDifferentialLendingRate, maxDifferentialLendingRate, defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGapBetweenInstallments, maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup, - isEqualAmortization, productRates); + isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment); } @@ -595,7 +601,7 @@ public class LoanProduct extends AbstractPersistableCustom { Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed, final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments, final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization, - final List rates) { + final List rates, final BigDecimal fixedPrincipalPercentagePerInstallment) { this.fund = fund; this.transactionProcessingStrategy = transactionProcessingStrategy; this.name = name.trim(); @@ -673,6 +679,7 @@ public class LoanProduct extends AbstractPersistableCustom { this.syncExpectedWithDisbursementDate = syncExpectedWithDisbursementDate; this.canUseForTopup = canUseForTopup; this.isEqualAmortization = isEqualAmortization; + this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment; if (rates != null) { this.rates = rates; @@ -1093,6 +1100,14 @@ public class LoanProduct extends AbstractPersistableCustom { } } + if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName, + this.fixedPrincipalPercentagePerInstallment)) { + BigDecimal newValue = command + .bigDecimalValueOfParameterNamed(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName); + actualChanges.put(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName, newValue); + this.fixedPrincipalPercentagePerInstallment = newValue; + } + return actualChanges; } @@ -1443,4 +1458,7 @@ public class LoanProduct extends AbstractPersistableCustom { this.rates = rates; } + public BigDecimal getFixedPrincipalPercentagePerInstallment() { + return fixedPrincipalPercentagePerInstallment; + } } 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 a61e0aa31..c48938e26 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 @@ -42,7 +42,9 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; +import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; +import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; @@ -106,8 +108,8 @@ public final class LoanProductDataValidator { LoanProductConstants.recalculationRestFrequencyWeekdayParamName, LoanProductConstants.recalculationRestFrequencyNthDayParamName, LoanProductConstants.recalculationRestFrequencyOnDayParamName, LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, LoanProductConstants.allowCompoundingOnEodParamName, - LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, - LoanProductConstants.RATES_PARAM_NAME)); + LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, LoanProductConstants.RATES_PARAM_NAME, + LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName)); private static final String[] supportedloanConfigurableAttributes = { LoanProductConstants.amortizationTypeParamName, LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyIdParamName, @@ -515,6 +517,18 @@ public final class LoanProductDataValidator { .extractBigDecimalWithLocaleNamed(LoanProductConstants.principalThresholdForLastInstallmentParamName, element); baseDataValidator.reset().parameter(LoanProductConstants.principalThresholdForLastInstallmentParamName) .value(principalThresholdForLastInstallment).notLessThanMin(BigDecimal.ZERO).notGreaterThanMax(BigDecimal.valueOf(100)); + + BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName, element); + baseDataValidator.reset().parameter(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName) + .value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE).notGreaterThanMax(BigDecimal.valueOf(100)); + + if (!amortizationType.equals(AmortizationMethod.EQUAL_PRINCIPAL.getValue()) && fixedPrincipalPercentagePerInstallment != null) { + baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName).failWithCode( + "not.supported.principal.fixing.not.allowed.with.equal.installments", + "Principal fixing cannot be done with equal installment amortization"); + } + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.canDefineEmiAmountParamName, element)) { final Boolean canDefineInstallmentAmount = this.fromApiJsonHelper .extractBooleanNamed(LoanProductConstants.canDefineEmiAmountParamName, element); @@ -1063,9 +1077,9 @@ public final class LoanProductDataValidator { .integerZeroOrGreater(); } - // + Integer amortizationType = null; if (this.fromApiJsonHelper.parameterExists("amortizationType", element)) { - final Integer amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault()); + amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault()); baseDataValidator.reset().parameter("amortizationType").value(amortizationType).notNull().inMinMaxRange(0, 1); } @@ -1340,6 +1354,18 @@ public final class LoanProductDataValidator { .value(principalThresholdForLastInstallment).notNull().notLessThanMin(BigDecimal.ZERO) .notGreaterThanMax(BigDecimal.valueOf(100)); } + + BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName, element); + baseDataValidator.reset().parameter(LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName) + .value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE).notGreaterThanMax(BigDecimal.valueOf(100)); + + if (!AmortizationMethod.EQUAL_PRINCIPAL.getValue().equals(amortizationType) && fixedPrincipalPercentagePerInstallment != null) { + baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName).failWithCode( + "not.supported.principal.fixing.not.allowed.with.equal.installments", + "Principal fixing cannot be done with equal installment amortization"); + } + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.canDefineEmiAmountParamName, element)) { final Boolean canDefineInstallmentAmount = this.fromApiJsonHelper .extractBooleanNamed(LoanProductConstants.canDefineEmiAmountParamName, element); 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 0f20d4869..29a9b4766 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 @@ -214,6 +214,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "lpr.is_compounding_to_be_posted_as_transaction as isCompoundingToBePostedAsTransaction, " + "lpr.allow_compounding_on_eod as allowCompoundingOnEod, " + "lp.hold_guarantee_funds as holdGuaranteeFunds, " + "lp.principal_threshold_for_last_installment as principalThresholdForLastInstallment, " + + "lp.fixed_principal_percentage_per_installment fixedPrincipalPercentagePerInstallment, " + "lp.sync_expected_with_disbursement_date as syncExpectedWithDisbursementDate, " + "lpg.id as lpgId, lpg.mandatory_guarantee as mandatoryGuarantee, " + "lpg.minimum_guarantee_from_own_funds as minimumGuaranteeFromOwnFunds, lpg.minimum_guarantee_from_guarantor_funds as minimumGuaranteeFromGuarantor, " @@ -448,6 +449,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo } final BigDecimal principalThresholdForLastInstallment = rs.getBigDecimal("principalThresholdForLastInstallment"); + final BigDecimal fixedPrincipalPercentagePerInstallment = rs.getBigDecimal("fixedPrincipalPercentagePerInstallment"); final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = rs.getBoolean("accountMovesOutOfNPAOnlyOnArrearsCompletion"); final boolean syncExpectedWithDisbursementDate = rs.getBoolean("syncExpectedWithDisbursementDate"); @@ -471,7 +473,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap, maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates, - isRatesEnabled); + isRatesEnabled, fixedPrincipalPercentagePerInstallment); } } diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V372__fixed_principal_percentage.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V372__fixed_principal_percentage.sql new file mode 100644 index 000000000..a22a4ddae --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V372__fixed_principal_percentage.sql @@ -0,0 +1,21 @@ +-- +-- 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. +-- + +alter table m_product_loan add column fixed_principal_percentage_per_installment decimal(5,2) DEFAULT NULL; +alter table m_loan add column fixed_principal_percentage_per_installment decimal(5,2) DEFAULT NULL; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanFixedPrincipalPercentageAmortizationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanFixedPrincipalPercentageAmortizationTest.java new file mode 100644 index 000000000..4b317e239 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanFixedPrincipalPercentageAmortizationTest.java @@ -0,0 +1,350 @@ +/** + * 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 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.Arrays; +import java.util.HashMap; +import java.util.List; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.CollateralManagementHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.accounting.Account; +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.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class LoanFixedPrincipalPercentageAmortizationTest { + + private static final Logger LOG = LoggerFactory.getLogger(LoanFixedPrincipalPercentageAmortizationTest.class); + + private static final String ACCOUNTING_NONE = "1"; + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private LoanTransactionHelper loanTransactionHelper; + + @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.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + // this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec); + } + + @Test + public void checkLoanCreateAndDisburseFlowWithFixedPrincipalPercentage() { + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final Integer loanProductID = createLoanProduct(ACCOUNTING_NONE); + final Integer loanID = applyForLoanApplication(clientID, loanProductID, null, null, "100000.00"); + final ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, + loanID); + verifyLoanRepaymentScheduleForEqualPrincipal(loanSchedule); + } + + @Test + public void checkLoanCreateAndDisburseFlowWithFixedPrincipalPercentageWithPrincipalGrace() { + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final Integer loanProductID = createLoanProduct(ACCOUNTING_NONE); + final Integer loanID = applyForLoanApplicationWithPrincipalGrace(clientID, loanProductID, null, null, "100000.00"); + final ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, + loanID); + verifyLoanRepaymentScheduleForEqualPrincipalWithPrincipalGrace(loanSchedule); + } + + @Test + public void checkLoanCreateAndDisburseFlowWithFixedPrincipalPercentageAndFlatInterest() { + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final Integer loanProductID = createLoanProductWithFlatInterest(ACCOUNTING_NONE); + final Integer loanID = applyForLoanApplicationWithFlatInterest(clientID, loanProductID, null, null, "100000.00"); + final ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, + loanID); + verifyLoanRepaymentScheduleForEqualPrincipalAndFlatInterest(loanSchedule); + } + + private Integer createLoanProduct(final String accountingRule, final Account... accounts) { + LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); + LoanProductTestBuilder builder = new LoanProductTestBuilder() // + .withPrincipal("100000.00") // + .withNumberOfRepayments("13") // + .withRepaymentAfterEvery("1") // + .withRepaymentTypeAsMonth() // + .withinterestRatePerPeriod("1") // + .withInterestCalculationPeriodTypeAsDays().withInterestRateFrequencyTypeAsMonths() // + .withAmortizationTypeAsEqualPrincipalPayment() // This is required to fix the principal + .withPrinciplePercentagePerInstallment("5.00") // This fixes the principal at a fixed value till the + // second last EMI + .withInterestTypeAsDecliningBalance() // + .withAccounting(accountingRule, accounts); + + final String loanProductJSON = builder.build(null); + return this.loanTransactionHelper.getLoanProductId(loanProductJSON); + } + + private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, List charges, + final String savingsId, String principal) { + + List collaterals = new ArrayList<>(); + + final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(collateralId); + final Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(this.requestSpec, this.responseSpec, + String.valueOf(clientID), collateralId); + Assertions.assertNotNull(clientCollateralId); + addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1)); + + LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + final String loanApplicationJSON = new LoanApplicationTestBuilder() // + .withPrincipal(principal) // + .withLoanTermFrequency("13") // + .withLoanTermFrequencyAsMonths() // + .withNumberOfRepayments("13") // + .withRepaymentEveryAfter("1") // + .withRepaymentFrequencyTypeAsMonths() // + .withInterestRatePerPeriod("2") // + .withAmortizationTypeAsEqualInstallments() // + .withAmortizationTypeAsEqualPrincipalPayments() // This is required to fix the principal + .withPrinciplePercentagePerInstallment("5.00") // This fixes the principal at a fixed value till the + // second last EMI + .withInterestTypeAsDecliningBalance() // + .withInterestCalculationPeriodTypeAsDays() // + .withExpectedDisbursementDate("20 September 2011") // + .withSubmittedOnDate("20 September 2011") // + .withCollaterals(collaterals).withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId); + return this.loanTransactionHelper.getLoanId(loanApplicationJSON); + } + + private void addCollaterals(List collaterals, Integer collateralId, BigDecimal quantity) { + collaterals.add(collaterals(collateralId, quantity)); + } + + private HashMap collaterals(Integer collateralId, BigDecimal quantity) { + HashMap collateral = new HashMap(2); + collateral.put("clientCollateralId", collateralId.toString()); + collateral.put("quantity", quantity.toString()); + return collateral; + } + + private Integer applyForLoanApplicationWithPrincipalGrace(final Integer clientID, final Integer loanProductID, List charges, + final String savingsId, String principal) { + List collaterals = new ArrayList<>(); + + final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(collateralId); + final Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(this.requestSpec, this.responseSpec, + String.valueOf(clientID), collateralId); + Assertions.assertNotNull(clientCollateralId); + addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1)); + LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + final String loanApplicationJSON = new LoanApplicationTestBuilder() // + .withPrincipal(principal) // + .withLoanTermFrequency("19") // + .withLoanTermFrequencyAsMonths() // + .withNumberOfRepayments("19") // + .withRepaymentEveryAfter("1") // + .withRepaymentFrequencyTypeAsMonths() // + .withInterestRatePerPeriod("2") // + .withAmortizationTypeAsEqualInstallments() // + .withAmortizationTypeAsEqualPrincipalPayments() // This is required to fix the principal + .withPrinciplePercentagePerInstallment("5.00") // This fixes the principal at a fixed value till the + // second last EMI + .withPrincipalGrace("6").withInterestTypeAsDecliningBalance() // + .withInterestCalculationPeriodTypeAsDays() // + .withExpectedDisbursementDate("20 September 2011") // + .withSubmittedOnDate("20 September 2011") // + .withCollaterals(collaterals).withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId); + return this.loanTransactionHelper.getLoanId(loanApplicationJSON); + } + + private void verifyLoanRepaymentScheduleForEqualPrincipal(final ArrayList loanSchedule) { + LOG.info("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------"); + + assertEquals(new ArrayList<>(Arrays.asList(2011, 10, 20)), loanSchedule.get(1).get("dueDate"), + "Checking for Due Date for 1st Month"); + assertEquals(Float.parseFloat("5000"), loanSchedule.get(1).get("principalOriginalDue"), "Checking for Principal Due for 1st Month"); + assertEquals(Float.parseFloat("1972.60"), loanSchedule.get(1).get("interestOriginalDue"), + "Checking for Interest Due for 1st Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2011, 11, 20)), loanSchedule.get(2).get("dueDate"), + "Checking for Due Date for 2nd Month"); + assertEquals(Float.parseFloat("5000"), loanSchedule.get(2).get("principalDue"), "Checking for Principal Due for 2nd Month"); + assertEquals(Float.parseFloat("1936.44"), loanSchedule.get(2).get("interestOriginalDue"), + "Checking for Interest Due for 2nd Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2011, 12, 20)), loanSchedule.get(3).get("dueDate"), + "Checking for Due Date for 3rd Month"); + assertEquals(Float.parseFloat("5000"), loanSchedule.get(3).get("principalDue"), "Checking for Principal Due for 3rd Month"); + assertEquals(Float.parseFloat("1775.34"), loanSchedule.get(3).get("interestOriginalDue"), + "Checking for Interest Due for 3rd Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2012, 9, 20)), loanSchedule.get(12).get("dueDate"), + "Checking for Due Date for 12th Month"); + assertEquals(Float.parseFloat("5000 "), loanSchedule.get(12).get("principalDue"), "Checking for Principal Due for 12th Month"); + assertEquals(Float.parseFloat("917.26"), loanSchedule.get(12).get("interestOriginalDue"), + "Checking for Interest Due for 12th Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2012, 10, 20)), loanSchedule.get(13).get("dueDate"), + "Checking for Due Date for 13th Month - Last EMI"); + assertEquals(Float.parseFloat("40000"), loanSchedule.get(13).get("principalDue"), + "Checking for Principal Due for 13th Month - Last EMI"); + assertEquals(Float.parseFloat("789.04"), loanSchedule.get(13).get("interestOriginalDue"), + "Checking for Interest Due for 13th Month - Last EMI"); + + } + + private void verifyLoanRepaymentScheduleForEqualPrincipalWithPrincipalGrace(final ArrayList loanSchedule) { + LOG.info("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------"); + + assertEquals(new ArrayList<>(Arrays.asList(2011, 10, 20)), loanSchedule.get(1).get("dueDate"), + "Checking for Due Date for 1st Month"); + assertEquals(Integer.parseInt("0"), loanSchedule.get(1).get("principalOriginalDue"), "Checking for Principal Due for 1st Month"); + assertEquals(Float.parseFloat("1972.6"), loanSchedule.get(1).get("interestOriginalDue"), "Checking for Interest Due for 1st Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2012, 3, 20)), loanSchedule.get(6).get("dueDate"), + "Checking for Due Date for 6th Month"); + assertEquals(Integer.parseInt("0"), loanSchedule.get(6).get("principalDue"), "Checking for Principal Due for 6th Month"); + assertEquals(Float.parseFloat("1906.85"), loanSchedule.get(6).get("interestOriginalDue"), + "Checking for Interest Due for 6th Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2012, 4, 20)), loanSchedule.get(7).get("dueDate"), + "Checking for Due Date for 7th Month"); + assertEquals(Float.parseFloat("5000"), loanSchedule.get(7).get("principalDue"), "Checking for Principal Due for 7th Month"); + assertEquals(Float.parseFloat("2038.36"), loanSchedule.get(7).get("interestOriginalDue"), + "Checking for Interest Due for 7th Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2013, 3, 20)), loanSchedule.get(18).get("dueDate"), + "Checking for Due Date for 18th Month"); + assertEquals(Float.parseFloat("5000"), loanSchedule.get(18).get("principalDue"), "Checking for Principal Due for 18th Month"); + assertEquals(Float.parseFloat("828.49"), loanSchedule.get(18).get("interestOriginalDue"), + "Checking for Interest Due for 18th Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2013, 4, 20)), loanSchedule.get(19).get("dueDate"), + "Checking for Due Date for 19th Month - Last EMI"); + assertEquals(Float.parseFloat("40000"), loanSchedule.get(19).get("principalDue"), + "Checking for Principal Due for 19th Month - Last EMI"); + assertEquals(Float.parseFloat("815.34"), loanSchedule.get(19).get("interestOriginalDue"), + "Checking for Interest Due for 19th Month - Last EMI"); + + } + + private Integer createLoanProductWithFlatInterest(final String accountingRule, final Account... accounts) { + LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); + LoanProductTestBuilder builder = new LoanProductTestBuilder() // + .withPrincipal("100000.00") // + .withNumberOfRepayments("13") // + .withRepaymentAfterEvery("1") // + .withRepaymentTypeAsMonth() // + .withinterestRatePerPeriod("1") // + .withInterestCalculationPeriodTypeAsDays().withInterestRateFrequencyTypeAsMonths() // + .withAmortizationTypeAsEqualPrincipalPayment() // This is required to fix the principal + .withPrinciplePercentagePerInstallment("5.00") // This fixes the principal at a fixed value till the + // second last EMI + .withInterestTypeAsFlat() // + .withAccounting(accountingRule, accounts); + + final String loanProductJSON = builder.build(null); + return this.loanTransactionHelper.getLoanProductId(loanProductJSON); + } + + private Integer applyForLoanApplicationWithFlatInterest(final Integer clientID, final Integer loanProductID, List charges, + final String savingsId, String principal) { + LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + final String loanApplicationJSON = new LoanApplicationTestBuilder() // + .withPrincipal(principal) // + .withLoanTermFrequency("13") // + .withLoanTermFrequencyAsMonths() // + .withNumberOfRepayments("13") // + .withRepaymentEveryAfter("1") // + .withRepaymentFrequencyTypeAsMonths() // + .withInterestRatePerPeriod("2") // + .withAmortizationTypeAsEqualInstallments() // + .withAmortizationTypeAsEqualPrincipalPayments() // This is required to fix the principal + .withPrinciplePercentagePerInstallment("5.00") // This fixes the principal at a fixed value till the + // second last EMI + .withInterestTypeAsFlatBalance() // + .withInterestCalculationPeriodTypeAsDays() // + .withExpectedDisbursementDate("20 September 2011") // + .withSubmittedOnDate("20 September 2011") // + .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId); + return this.loanTransactionHelper.getLoanId(loanApplicationJSON); + } + + private void verifyLoanRepaymentScheduleForEqualPrincipalAndFlatInterest(final ArrayList loanSchedule) { + LOG.info("--------------------VERIFYING THE PRINCIPAL DUES,INTEREST DUE AND DUE DATE--------------------------"); + + assertEquals(new ArrayList<>(Arrays.asList(2011, 10, 20)), loanSchedule.get(1).get("dueDate"), + "Checking for Due Date for 1st Month"); + assertEquals(Float.parseFloat("5000"), loanSchedule.get(1).get("principalOriginalDue"), "Checking for Principal Due for 1st Month"); + assertEquals(Float.parseFloat("2002.95"), loanSchedule.get(1).get("interestOriginalDue"), + "Checking for Interest Due for 1st Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2011, 11, 20)), loanSchedule.get(2).get("dueDate"), + "Checking for Due Date for 2nd Month"); + assertEquals(Float.parseFloat("5000"), loanSchedule.get(2).get("principalDue"), "Checking for Principal Due for 2nd Month"); + assertEquals(Float.parseFloat("2002.95"), loanSchedule.get(2).get("interestOriginalDue"), + "Checking for Interest Due for 2nd Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2011, 12, 20)), loanSchedule.get(3).get("dueDate"), + "Checking for Due Date for 3rd Month"); + assertEquals(Float.parseFloat("5000"), loanSchedule.get(3).get("principalDue"), "Checking for Principal Due for 3rd Month"); + assertEquals(Float.parseFloat("2002.95"), loanSchedule.get(3).get("interestOriginalDue"), + "Checking for Interest Due for 3rd Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2012, 9, 20)), loanSchedule.get(12).get("dueDate"), + "Checking for Due Date for 12th Month"); + assertEquals(Float.parseFloat("5000 "), loanSchedule.get(12).get("principalDue"), "Checking for Principal Due for 12th Month"); + assertEquals(Float.parseFloat("2002.95"), loanSchedule.get(12).get("interestOriginalDue"), + "Checking for Interest Due for 12th Month"); + + assertEquals(new ArrayList<>(Arrays.asList(2012, 10, 20)), loanSchedule.get(13).get("dueDate"), + "Checking for Due Date for 13th Month - Last EMI"); + assertEquals(Float.parseFloat("40000"), loanSchedule.get(13).get("principalDue"), + "Checking for Principal Due for 13th Month - Last EMI"); + assertEquals(Float.parseFloat("2002.96"), loanSchedule.get(13).get("interestOriginalDue"), + "Checking for Interest Due for 13th Month - Last EMI"); + + } + +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java index 7a22d88c1..a0d17f85e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java @@ -76,6 +76,7 @@ public class LoanApplicationTestBuilder { private boolean syncDisbursementWithMeeting = false; private List> datatables = null; private List> approvalFormData = null; + private String fixedPrincipalPercentagePerInstallment; public String build(final String clientID, final String groupID, final String loanProductId, final String savingsID) { final HashMap map = new HashMap<>(); @@ -141,6 +142,7 @@ public class LoanApplicationTestBuilder { map.put("repaymentFrequencyType", this.repaymentFrequencyType); map.put("interestRatePerPeriod", this.interestRate); map.put("amortizationType", this.amortizationType); + map.put("fixedPrincipalPercentagePerInstallment", fixedPrincipalPercentagePerInstallment); map.put("interestType", this.interestType); map.put("interestCalculationPeriodType", this.interestCalculationPeriodType); map.put("transactionProcessingStrategyId", this.transactionProcessingID); @@ -375,4 +377,9 @@ public class LoanApplicationTestBuilder { this.datatables = datatables; return this; } + + public LoanApplicationTestBuilder withPrinciplePercentagePerInstallment(String fixedPrincipalPercentagePerInstallment) { + this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment; + return this; + } } 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 df0c593cd..ea99fc4f8 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 @@ -124,6 +124,7 @@ public class LoanProductTestBuilder { private Integer recalculationCompoundingFrequencyDayOfWeekType = null; private Integer recalculationRestFrequencyDayOfWeekType = null; private boolean syncExpectedWithDisbursementDate = false; + private String fixedPrincipalPercentagePerInstallment; public String build(final String chargeId) { final HashMap map = new HashMap<>(); @@ -149,6 +150,7 @@ public class LoanProductTestBuilder { map.put("interestRatePerPeriod", this.interestRatePerPeriod); map.put("interestRateFrequencyType", this.interestRateFrequencyType); map.put("amortizationType", this.amortizationType); + map.put("fixedPrincipalPercentagePerInstallment", fixedPrincipalPercentagePerInstallment); map.put("interestType", this.interestType); map.put("interestCalculationPeriodType", this.interestCalculationPeriodType); map.put("inArrearsTolerance", this.inArrearsTolerance); @@ -506,4 +508,9 @@ public class LoanProductTestBuilder { this.syncExpectedWithDisbursementDate = syncExpectedWithDisbursementDate; return this; } + + public LoanProductTestBuilder withPrinciplePercentagePerInstallment(String fixedPrincipalPercentagePerInstallment) { + this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment; + return this; + } }