diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index f83d56d68..37cce1b5f 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -11397,7 +11397,14 @@ Request Body:
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
index 302636b98..6d17123f0 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
@@ -1928,7 +1928,7 @@ public class Loan extends AbstractPersistable {
actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount);
actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount);
- } else if(approvedLoanAmount.compareTo(this.proposedPrincipal) == 1){
+ } else if (approvedLoanAmount.compareTo(this.proposedPrincipal) == 1) {
final String errorMessage = "Loan approved amount can't be greater than loan amount demanded.";
throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded",
errorMessage, this.proposedPrincipal, approvedLoanAmount);
@@ -2316,7 +2316,8 @@ public class Loan extends AbstractPersistable {
loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(),
getExpectedFirstRepaymentOnDate(), scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(),
this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData,
- this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate());
+ this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(),
+ this.loanProduct.getPrincipalThresholdForLastInstalment());
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(),
scheduleGeneratorDTO.getHolidayDetailDTO());
@@ -4649,7 +4650,8 @@ public class Loan extends AbstractPersistable {
calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail,
this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance,
loanVariationTermsData, getInterestChargedFromDate(), this.loanInterestRecalculationDetails,
- calendarInstanceForInterestRecalculation, recalculationFrequencyType);
+ calendarInstanceForInterestRecalculation, recalculationFrequencyType,
+ this.loanProduct.getPrincipalThresholdForLastInstalment());
return loanApplicationTerms;
}
@@ -4692,7 +4694,8 @@ public class Loan extends AbstractPersistable {
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(null, loanTermFrequency,
loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null,
getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount,
- disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate());
+ disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(),
+ this.loanProduct.getPrincipalThresholdForLastInstalment());
installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(),
LocalDate.now(), getInterestChargedFromDate(), loanApplicationTerms, mc, charges());
@@ -4893,7 +4896,8 @@ public class Loan extends AbstractPersistable {
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType,
dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate,
inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData,
- maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate);
+ maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate,
+ this.loanProduct.getPrincipalThresholdForLastInstalment());
}
/**
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index 4aefa7775..37527700c 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -132,6 +132,8 @@ public final class LoanApplicationTerms {
private final RecalculationFrequencyType recalculationFrequencyType;
+ private final BigDecimal principalThresholdForLastInstalment;
+
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType,
@@ -144,7 +146,8 @@ public final class LoanApplicationTerms {
final Money inArrearsTolerance, final boolean multiDisburseLoan, final BigDecimal emiAmount,
final List disbursementDatas, final BigDecimal maxOutstandingBalance,
final List emiAmountVariations, final Integer graceOnArrearsAgeing,
- final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled) {
+ final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled,
+ BigDecimal principalThresholdForLastInstalment) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
@@ -157,7 +160,7 @@ public final class LoanApplicationTerms {
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
disbursementDatas, maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance,
- recalculationFrequencyType);
+ recalculationFrequencyType, principalThresholdForLastInstalment);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -166,7 +169,8 @@ public final class LoanApplicationTerms {
final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance,
final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount,
final List disbursementDatas, final BigDecimal maxOutstandingBalance,
- final List emiAmountVariations, final LocalDate interestChargedFromDate) {
+ final List emiAmountVariations, final LocalDate interestChargedFromDate,
+ final BigDecimal principalThresholdForLastInstalment) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -201,7 +205,8 @@ public final class LoanApplicationTerms {
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance,
multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations,
loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
- rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType);
+ rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType,
+ principalThresholdForLastInstalment);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -211,7 +216,7 @@ public final class LoanApplicationTerms {
final BigDecimal emiAmount, final List disbursementDatas, final BigDecimal maxOutstandingBalance,
final List emiAmountVariations, final LocalDate interestChargedFromDate,
final LoanInterestRecalculationDetails interestRecalculationDetails, final CalendarInstance restCalendarInstance,
- final RecalculationFrequencyType recalculationFrequencyType) {
+ final RecalculationFrequencyType recalculationFrequencyType, final BigDecimal principalThresholdForLastInstalment) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -248,7 +253,8 @@ public final class LoanApplicationTerms {
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(),
daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod,
- interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType);
+ interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType,
+ principalThresholdForLastInstalment);
}
public static LoanApplicationTerms assembleFrom(final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType,
@@ -266,7 +272,7 @@ public final class LoanApplicationTerms {
applicationTerms.graceOnArrearsAgeing, applicationTerms.daysInMonthType, applicationTerms.daysInYearType,
applicationTerms.interestRecalculationEnabled, applicationTerms.rescheduleStrategyMethod,
applicationTerms.interestRecalculationCompoundingMethod, applicationTerms.restCalendarInstance,
- applicationTerms.recalculationFrequencyType);
+ applicationTerms.recalculationFrequencyType, applicationTerms.principalThresholdForLastInstalment);
}
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -283,7 +289,8 @@ public final class LoanApplicationTerms {
final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType,
final boolean isInterestRecalculationEnabled, final LoanRescheduleStrategyMethod rescheduleStrategyMethod,
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod,
- final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType) {
+ final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType,
+ final BigDecimal principalThresholdForLastInstalment) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
@@ -324,6 +331,7 @@ public final class LoanApplicationTerms {
this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
this.restCalendarInstance = restCalendarInstance;
this.recalculationFrequencyType = recalculationFrequencyType;
+ this.principalThresholdForLastInstalment = principalThresholdForLastInstalment;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -336,10 +344,11 @@ public final class LoanApplicationTerms {
// paid too much principal, subtract amount that overpays from
// principal paid for period.
adjusted = principalForPeriod.minus(totalPrincipalRemaining.abs());
- } else if (multiDisburseLoan) {
+ } else if (this.fixedEmiAmount != null) {
final Money difference = this.principal.minus(totalCumulativePrincipalToDate);
- final Money halfPrincipalVal = principalForPeriod.dividedBy(2, RoundingMode.HALF_DOWN);
- if (difference.isLessThan(halfPrincipalVal)) {
+ final Money principalThreshold = principalForPeriod.multipliedBy(this.principalThresholdForLastInstalment).dividedBy(100,
+ RoundingMode.HALF_EVEN);
+ if (difference.isLessThan(principalThreshold)) {
adjusted = principalForPeriod.plus(difference.abs());
}
} else if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
index 9d6d36004..08e961184 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
@@ -249,6 +249,8 @@ public class LoanScheduleAssembler {
final DaysInYearType daysInYearType = loanProduct.fetchDaysInYearType();
final boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
+
+ final BigDecimal principalThresholdForLastInstalment = loanProduct.getPrincipalThresholdForLastInstalment();
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod,
@@ -256,7 +258,7 @@ public class LoanScheduleAssembler {
principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate,
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney,
loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData,
- graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled);
+ graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, principalThresholdForLastInstalment);
}
private List fetchDisbursementData(final JsonObject command) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java
index 59a85aa99..ba0a01102 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java
@@ -5,6 +5,8 @@
*/
package org.mifosplatform.portfolio.loanproduct;
+import java.math.BigDecimal;
+
public interface LoanProductConstants {
public static final String useBorrowerCycleParameterName = "useBorrowerCycle";
@@ -78,4 +80,7 @@ public interface LoanProductConstants {
public static final String minimumGuaranteeFromOwnFundsParamName = "minimumGuaranteeFromOwnFunds";
public static final String minimumGuaranteeFromGuarantorParamName = "minimumGuaranteeFromGuarantor";
+ public static final String principalThresholdForLastInstalmentParamName = "principalThresholdForLastInstalment";
+ public static final BigDecimal DEFAULT_PRINCIPAL_THRESHOLD_FOR_MULTI_DISBURSE_LOAN = BigDecimal.valueOf(50);
+ public static final BigDecimal DEFAULT_PRINCIPAL_THRESHOLD_FOR_SINGLE_DISBURSE_LOAN = BigDecimal.valueOf(0);
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java
index 8d2d27f1c..e6e8234b7 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java
@@ -134,6 +134,7 @@ public class LoanProductData {
private final Boolean multiDisburseLoan;
private final Integer maxTrancheCount;
private final BigDecimal outstandingLoanBalance;
+ private final BigDecimal principalThresholdForLastInstalment;
private final Boolean holdGuaranteeFunds;
private final LoanProductGuaranteeData productGuaranteeData;
@@ -187,6 +188,7 @@ public class LoanProductData {
final BigDecimal outstandingLoanBalance = null;
final LoanProductGuaranteeData productGuaranteeData = null;
final Boolean holdGuaranteeFunds = false;
+ final BigDecimal principalThresholdForLastInstalment = null;
final EnumOptionData daysInMonthType = null;
final EnumOptionData daysInYearType = null;
@@ -202,7 +204,8 @@ public class LoanProductData {
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
- interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData);
+ interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
+ principalThresholdForLastInstalment);
}
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
@@ -258,6 +261,7 @@ public class LoanProductData {
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
final Boolean holdGuaranteeFunds = false;
final LoanProductGuaranteeData productGuaranteeData = null;
+ final BigDecimal principalThresholdForLastInstalment = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -267,7 +271,8 @@ public class LoanProductData {
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
- interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData);
+ interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
+ principalThresholdForLastInstalment);
}
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
@@ -330,6 +335,7 @@ public class LoanProductData {
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
final Boolean holdGuaranteeFunds = false;
final LoanProductGuaranteeData productGuaranteeData = null;
+ final BigDecimal principalThresholdForLastInstalment = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -340,7 +346,7 @@ public class LoanProductData {
principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle,
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment,
- holdGuaranteeFunds, productGuaranteeData);
+ holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstalment);
}
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map accountingMappings,
@@ -372,7 +378,7 @@ public class LoanProductData {
final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType,
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds,
- LoanProductGuaranteeData loanProductGuaranteeData) {
+ final LoanProductGuaranteeData loanProductGuaranteeData, final BigDecimal principalThresholdForLastInstalment) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -445,6 +451,7 @@ public class LoanProductData {
this.interestRecalculationData = interestRecalculationData;
this.holdGuaranteeFunds = holdGuaranteeFunds;
this.productGuaranteeData = loanProductGuaranteeData;
+ this.principalThresholdForLastInstalment = principalThresholdForLastInstalment;
this.daysInMonthTypeOptions = null;
this.daysInYearTypeOptions = null;
@@ -548,6 +555,7 @@ public class LoanProductData {
this.interestRecalculationData = productData.interestRecalculationData;
this.holdGuaranteeFunds = productData.holdGuaranteeFunds;
this.productGuaranteeData = productData.productGuaranteeData;
+ this.principalThresholdForLastInstalment = productData.principalThresholdForLastInstalment;
this.daysInMonthTypeOptions = daysInMonthTypeOptions;
this.daysInYearTypeOptions = daysInYearTypeOptions;
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java
index 2e49759cc..6c335e106 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java
@@ -136,6 +136,9 @@ public class LoanProduct extends AbstractPersistable {
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
private LoanProductGuaranteeDetails loanProductGuaranteeDetails;
+ @Column(name = "principal_threshold_for_last_instalment", scale = 2, precision = 5, nullable = false)
+ private BigDecimal principalThresholdForLastInstalment;
+
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
@@ -225,6 +228,14 @@ public class LoanProduct extends AbstractPersistable {
loanProductGuaranteeDetails = LoanProductGuaranteeDetails.createFrom(command);
}
+ BigDecimal principalThresholdForLastInstalment = command
+ .bigDecimalValueOfParameterNamed(LoanProductConstants.principalThresholdForLastInstalmentParamName);
+
+ if (principalThresholdForLastInstalment == null) {
+ principalThresholdForLastInstalment = multiDisburseLoan ? LoanProductConstants.DEFAULT_PRINCIPAL_THRESHOLD_FOR_MULTI_DISBURSE_LOAN
+ : LoanProductConstants.DEFAULT_PRINCIPAL_THRESHOLD_FOR_SINGLE_DISBURSE_LOAN;
+ }
+
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType,
@@ -233,7 +244,7 @@ public class LoanProduct extends AbstractPersistable {
startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount,
outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment,
- holdGuarantorFunds, loanProductGuaranteeDetails);
+ holdGuarantorFunds, loanProductGuaranteeDetails, principalThresholdForLastInstalment);
}
@@ -458,7 +469,7 @@ public class LoanProduct extends AbstractPersistable {
final boolean isInterestRecalculationEnabled,
final LoanProductInterestRecalculationDetails productInterestRecalculationDetails,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds,
- final LoanProductGuaranteeDetails loanProductGuaranteeDetails) {
+ final LoanProductGuaranteeDetails loanProductGuaranteeDetails, final BigDecimal principalThresholdForLastInstalment) {
this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim();
@@ -510,6 +521,7 @@ public class LoanProduct extends AbstractPersistable {
this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
this.holdGuaranteeFunds = holdGuarantorFunds;
this.loanProductGuaranteeDetails = loanProductGuaranteeDetails;
+ this.principalThresholdForLastInstalment = principalThresholdForLastInstalment;
}
public MonetaryCurrency getCurrency() {
@@ -737,6 +749,14 @@ public class LoanProduct extends AbstractPersistable {
this.loanProductGuaranteeDetails.update(command, actualChanges);
}
+ if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.principalThresholdForLastInstalmentParamName,
+ this.principalThresholdForLastInstalment)) {
+ BigDecimal newValue = command
+ .bigDecimalValueOfParameterNamed(LoanProductConstants.principalThresholdForLastInstalmentParamName);
+ actualChanges.put(LoanProductConstants.principalThresholdForLastInstalmentParamName, newValue);
+ this.principalThresholdForLastInstalment = newValue;
+ }
+
return actualChanges;
}
@@ -989,4 +1009,8 @@ public class LoanProduct extends AbstractPersistable {
return this.shortName;
}
+ public BigDecimal getPrincipalThresholdForLastInstalment() {
+ return this.principalThresholdForLastInstalment;
+ }
+
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index a4815145d..ecc59d2f9 100755
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -73,7 +73,7 @@ public final class LoanProductDataValidator {
LoanProductConstants.recalculationRestFrequencyTypeParameterName,
LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, LoanProductConstants.mandatoryGuaranteeParamName,
LoanProductConstants.holdGuaranteeFundsParamName, LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
- LoanProductConstants.minimumGuaranteeFromOwnFundsParamName));
+ LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, LoanProductConstants.principalThresholdForLastInstalmentParamName));
private final FromJsonHelper fromApiJsonHelper;
@@ -325,6 +325,11 @@ public final class LoanProductDataValidator {
}
}
+ BigDecimal principalThresholdForLastInstalment = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+ LoanProductConstants.principalThresholdForLastInstalmentParamName, element);
+ baseDataValidator.reset().parameter(LoanProductConstants.principalThresholdForLastInstalmentParamName)
+ .value(principalThresholdForLastInstalment).notLessThanMin(BigDecimal.ZERO).notGreaterThanMax(BigDecimal.valueOf(100));
+
// accounting related data validation
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4);
@@ -748,6 +753,14 @@ public final class LoanProductDataValidator {
}
}
+ if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.principalThresholdForLastInstalmentParamName, element)) {
+ BigDecimal principalThresholdForLastInstalment = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
+ LoanProductConstants.principalThresholdForLastInstalmentParamName, element);
+ baseDataValidator.reset().parameter(LoanProductConstants.principalThresholdForLastInstalmentParamName)
+ .value(principalThresholdForLastInstalment).notNull().notLessThanMin(BigDecimal.ZERO)
+ .notGreaterThanMax(BigDecimal.valueOf(100));
+ }
+
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).ignoreIfNull().inMinMaxRange(1, 4);
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
index 817ca3d3b..064171076 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
@@ -86,13 +86,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
String sql = "select " + rm.loanProductSchema();
- // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
- String inClause = mifosEntityAccessUtil.
- getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
- MifosEntityType.LOAN_PRODUCT);
- if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
- sql += " where lp.id in ( " + inClause + " ) ";
- }
+ // Check if branch specific products are enabled. If yes, fetch only
+ // products mapped to current user's office
+ String inClause = mifosEntityAccessUtil
+ .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
+ if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+ sql += " where lp.id in ( " + inClause + " ) ";
+ }
return this.jdbcTemplate.query(sql, rm, new Object[] {});
}
@@ -109,18 +109,18 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final LoanProductLookupMapper rm = new LoanProductLookupMapper();
String sql = "select ";
- if(activeOnly) {
+ if (activeOnly) {
sql += rm.activeOnlySchema();
} else {
sql += rm.schema();
}
- // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
- String inClause = mifosEntityAccessUtil.
- getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
- MifosEntityType.LOAN_PRODUCT);
- if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
- if(activeOnly) {
+ // Check if branch specific products are enabled. If yes, fetch only
+ // products mapped to current user's office
+ String inClause = mifosEntityAccessUtil
+ .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
+ if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+ if (activeOnly) {
sql += " and id in ( " + inClause + " )";
} else {
sql += " where id in ( " + inClause + " ) ";
@@ -164,6 +164,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lpr.rest_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, "
+ "lpr.rest_freqency_date as restFrequencyDate, "
+ "lp.hold_guarantee_funds as holdGuaranteeFunds, "
+ + "lp.principal_threshold_for_last_instalment as principalThresholdForLastInstalment, "
+ "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, "
+ "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId "
@@ -307,6 +308,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
minimumGuaranteeFromGuarantor);
}
+ final BigDecimal principalThresholdForLastInstalment = rs.getBigDecimal("principalThresholdForLastInstalment");
+
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
@@ -316,7 +319,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
- interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData);
+ interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData,
+ principalThresholdForLastInstalment);
}
}
@@ -390,13 +394,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
String sql = "select " + rm.loanProductSchema() + " where lp.currency_code='" + currencyCode + "'";
- // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
- String inClause = mifosEntityAccessUtil.
- getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
- MifosEntityType.LOAN_PRODUCT);
- if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
- sql += " and id in ( " + inClause + " ) ";
- }
+ // Check if branch specific products are enabled. If yes, fetch only
+ // products mapped to current user's office
+ String inClause = mifosEntityAccessUtil
+ .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
+ if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+ sql += " and id in ( " + inClause + " ) ";
+ }
return this.jdbcTemplate.query(sql, rm, new Object[] {});
}
@@ -410,13 +414,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
String sql = "Select " + rm.productMixSchema();
- // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
- String inClause = mifosEntityAccessUtil.
- getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
- MifosEntityType.LOAN_PRODUCT);
- if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
- sql += " and lp.id in ( " + inClause + " ) ";
- }
+ // Check if branch specific products are enabled. If yes, fetch only
+ // products mapped to current user's office
+ String inClause = mifosEntityAccessUtil
+ .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
+ if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+ sql += " and lp.id in ( " + inClause + " ) ";
+ }
return this.jdbcTemplate.query(sql, rm, new Object[] {});
}
@@ -429,24 +433,23 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final LoanProductLookupMapper rm = new LoanProductLookupMapper();
String sql = "Select " + rm.restrictedProductsSchema() + " where pm.product_id=? ";
- // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
- String inClause1 = mifosEntityAccessUtil.
- getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
- MifosEntityType.LOAN_PRODUCT);
- if ( (inClause1 != null) && (!(inClause1.trim().isEmpty())) ) {
- sql += " and rp.id in ( " + inClause1 + " ) ";
- }
+ // Check if branch specific products are enabled. If yes, fetch only
+ // products mapped to current user's office
+ String inClause1 = mifosEntityAccessUtil
+ .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
+ if ((inClause1 != null) && (!(inClause1.trim().isEmpty()))) {
+ sql += " and rp.id in ( " + inClause1 + " ) ";
+ }
- sql += " UNION Select "
- + rm.derivedRestrictedProductsSchema() + " where pm.restricted_product_id=?";
+ sql += " UNION Select " + rm.derivedRestrictedProductsSchema() + " where pm.restricted_product_id=?";
- // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
- String inClause2 = mifosEntityAccessUtil.
- getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
- MifosEntityType.LOAN_PRODUCT);
- if ( (inClause2 != null) && (!(inClause2.trim().isEmpty())) ) {
- sql += " and lp.id in ( " + inClause2 + " ) ";
- }
+ // Check if branch specific products are enabled. If yes, fetch only
+ // products mapped to current user's office
+ String inClause2 = mifosEntityAccessUtil
+ .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
+ if ((inClause2 != null) && (!(inClause2.trim().isEmpty()))) {
+ sql += " and lp.id in ( " + inClause2 + " ) ";
+ }
return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId });
}
@@ -460,16 +463,15 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
String sql = "Select " + rm.schema() + " where ";
- // Check if branch specific products are enabled. If yes, fetch only products mapped to current user's office
- String inClause = mifosEntityAccessUtil.
- getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(
- MifosEntityType.LOAN_PRODUCT);
- if ( (inClause != null) && (!(inClause.trim().isEmpty())) ) {
- sql += " lp.id in ( " + inClause + " ) and ";
- }
+ // Check if branch specific products are enabled. If yes, fetch only
+ // products mapped to current user's office
+ String inClause = mifosEntityAccessUtil
+ .getSQLWhereClauseForProductIDsForUserOffice_ifGlobalConfigEnabled(MifosEntityType.LOAN_PRODUCT);
+ if ((inClause != null) && (!(inClause.trim().isEmpty()))) {
+ sql += " lp.id in ( " + inClause + " ) and ";
+ }
- sql += "lp.id not in ("
- + "Select pm.restricted_product_id from m_product_mix pm where pm.product_id=? " + "UNION "
+ sql += "lp.id not in (" + "Select pm.restricted_product_id from m_product_mix pm where pm.product_id=? " + "UNION "
+ "Select pm.product_id from m_product_mix pm where pm.restricted_product_id=?)";
return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId });
diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V237__add_threshold_config_for_last_instalment.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V237__add_threshold_config_for_last_instalment.sql
new file mode 100755
index 000000000..fbb84999c
--- /dev/null
+++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V237__add_threshold_config_for_last_instalment.sql
@@ -0,0 +1,8 @@
+ALTER TABLE `m_product_loan`
+ ADD COLUMN `principal_threshold_for_last_instalment` DECIMAL(5,2) NOT NULL DEFAULT '50';
+
+update m_product_loan pl set pl.principal_threshold_for_last_instalment = 0 where pl.allow_multiple_disbursals = 0;
+
+
+
+
\ No newline at end of file