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 <[email protected]>
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;
+ }
}