You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by av...@apache.org on 2021/10/11 12:03:06 UTC
[fineract] branch develop updated: [FINERACT-1348]
-balloon-payment-fixed-principal-percentage (#1702)
This is an automated email from the ASF dual-hosted git repository.
avikg pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 72640ba [FINERACT-1348] -balloon-payment-fixed-principal-percentage (#1702)
72640ba is described below
commit 72640bac6a8d537bcddddd5d162f35f97d4d7233
Author: Manoj <56...@users.noreply.github.com>
AuthorDate: Mon Oct 11 17:32:56 2021 +0530
[FINERACT-1348] -balloon-payment-fixed-principal-percentage (#1702)
---
.../loanaccount/api/LoanApiConstants.java | 2 +
.../loanaccount/api/LoansApiResourceSwagger.java | 7 +
.../loanaccount/data/LoanAccountData.java | 52 ++-
.../portfolio/loanaccount/domain/Loan.java | 30 +-
.../loanschedule/domain/LoanApplicationTerms.java | 39 ++-
.../service/LoanScheduleAssembler.java | 5 +-
...alculateLoanScheduleQueryFromApiJsonHelper.java | 2 +-
.../LoanApplicationCommandFromApiJsonHelper.java | 31 +-
.../loanaccount/service/LoanAssembler.java | 10 +-
.../service/LoanReadPlatformServiceImpl.java | 5 +-
.../loanproduct/LoanProductConstants.java | 1 +
.../loanproduct/api/LoanProductsApiResource.java | 5 +-
.../api/LoanProductsApiResourceSwagger.java | 7 +
.../loanproduct/data/LoanProductData.java | 24 +-
.../portfolio/loanproduct/domain/LoanProduct.java | 22 +-
.../serialization/LoanProductDataValidator.java | 34 +-
.../LoanProductReadPlatformServiceImpl.java | 4 +-
.../core_db/V372__fixed_principal_percentage.sql | 21 ++
...anFixedPrincipalPercentageAmortizationTest.java | 350 +++++++++++++++++++++
.../common/loans/LoanApplicationTestBuilder.java | 7 +
.../common/loans/LoanProductTestBuilder.java | 7 +
21 files changed, 603 insertions(+), 62 deletions(-)
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 045c3fc..0bac357 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 ad1db14..dfc05cb 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 d157649..61accd2 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<DatatableData> datatables = null;
private final Boolean isEqualAmortization;
+ private final BigDecimal fixedPrincipalPercentagePerInstallment;
// Rate
private final List<RateData> 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<RateData> 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<RateData> 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<RateData> 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<LoanTransactionData> 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<LoanProductData> 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<Long, Integer> 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<LoanAccountSummaryData> clientActiveLoanOptions, final boolean isTopup, final Long closureLoanId,
final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization, final List<RateData> 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 3d3643c..9f93db5 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<Rate> 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<LoanCollateralManagement> collateral, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
- final BigDecimal interestRateDifferential, final List<Rate> rates) {
+ final BigDecimal interestRateDifferential, final List<Rate> 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<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
- final BigDecimal interestRateDifferential, final List<Rate> rates) {
+ final BigDecimal interestRateDifferential, final List<Rate> 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<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
- final BigDecimal interestRateDifferential, final List<Rate> rates) {
+ final BigDecimal interestRateDifferential, final List<Rate> 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<LoanCharge> loanCharges, final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting,
final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails,
final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
- final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates) {
+ final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> 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 cd30c1e..6386d37 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<LoanTermVariationsData> 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<LoanTermVariationsData> 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 e217fc8..d41e0e3 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 6f3a607..a057418 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 db04506..26d216d 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 805bf34..06514eb 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 2de00d3..5f37345 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 b5ec60f..f937ae1 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 f2a8c8e..b07cba3 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<String> 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 0f389ef..6d18aaf 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 82b5b50..6aaf01e 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<RateData> rateOptions, Collection<RateData> rates, final boolean isRatesEnabled) {
+ Collection<RateData> rateOptions, Collection<RateData> 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 949a2f3..3df21b7 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<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate,
final List<Rate> 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<Rate> rates) {
+ final List<Rate> 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 a61e0aa..c48938e 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 0f20d48..29a9b47 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 0000000..a22a4dd
--- /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 0000000..4b317e2
--- /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<HashMap> 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<HashMap> 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<HashMap> 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<HashMap> charges,
+ final String savingsId, String principal) {
+
+ List<HashMap> 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<HashMap> collaterals, Integer collateralId, BigDecimal quantity) {
+ collaterals.add(collaterals(collateralId, quantity));
+ }
+
+ private HashMap<String, String> collaterals(Integer collateralId, BigDecimal quantity) {
+ HashMap<String, String> collateral = new HashMap<String, String>(2);
+ collateral.put("clientCollateralId", collateralId.toString());
+ collateral.put("quantity", quantity.toString());
+ return collateral;
+ }
+
+ private Integer applyForLoanApplicationWithPrincipalGrace(final Integer clientID, final Integer loanProductID, List<HashMap> charges,
+ final String savingsId, String principal) {
+ List<HashMap> 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<HashMap> 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<HashMap> 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<HashMap> 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<HashMap> 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 7a22d88..a0d17f8 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<HashMap<String, Object>> datatables = null;
private List<Map<String, Object>> approvalFormData = null;
+ private String fixedPrincipalPercentagePerInstallment;
public String build(final String clientID, final String groupID, final String loanProductId, final String savingsID) {
final HashMap<String, Object> 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 df0c593..ea99fc4 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<String, Object> 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;
+ }
}