[FINERACT-1348] -balloon-payment-fixed-principal-percentage (#1702)

This commit is contained in:
Manoj
2021-10-11 17:32:56 +05:30
committed by GitHub
parent 899a0363c2
commit 72640bac6a
21 changed files with 603 additions and 62 deletions
@@ -135,4 +135,6 @@ public interface LoanApiConstants {
String applicationId = "applicationId";
String lastApplication = "lastApplication";
String fixedPrincipalPercentagePerInstallmentParamName = "fixedPrincipalPercentagePerInstallment";
}
@@ -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")
@@ -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() {
@@ -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);
}
/**
@@ -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();
}
@@ -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,
@@ -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;
@@ -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)) {
@@ -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);
}
@@ -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);
}
}
@@ -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";
@@ -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"));
@@ -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")
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
@@ -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);
}
}
@@ -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;
@@ -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");
}
}
@@ -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;
}
}
@@ -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;
}
}