MIFOSX-1844 : added config to manage last transaction
This commit is contained in:
+9
-5
@@ -1928,7 +1928,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
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<Long> {
|
||||
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<Long> {
|
||||
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<Long> {
|
||||
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<Long> {
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+20
-11
@@ -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<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
|
||||
final List<LoanTermVariationsData> 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<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
|
||||
final List<LoanTermVariationsData> emiAmountVariations, final LocalDate interestChargedFromDate) {
|
||||
final List<LoanTermVariationsData> 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<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
|
||||
final List<LoanTermVariationsData> 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)) {
|
||||
|
||||
+3
-1
@@ -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<DisbursementData> fetchDisbursementData(final JsonObject command) {
|
||||
|
||||
+5
@@ -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);
|
||||
}
|
||||
|
||||
+12
-4
@@ -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<String, Object> 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;
|
||||
|
||||
+26
-2
@@ -136,6 +136,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
@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<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
|
||||
|
||||
@@ -225,6 +228,14 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
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<Long> {
|
||||
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<Long> {
|
||||
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<Long> {
|
||||
this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
this.holdGuaranteeFunds = holdGuarantorFunds;
|
||||
this.loanProductGuaranteeDetails = loanProductGuaranteeDetails;
|
||||
this.principalThresholdForLastInstalment = principalThresholdForLastInstalment;
|
||||
}
|
||||
|
||||
public MonetaryCurrency getCurrency() {
|
||||
@@ -737,6 +749,14 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
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<Long> {
|
||||
return this.shortName;
|
||||
}
|
||||
|
||||
public BigDecimal getPrincipalThresholdForLastInstalment() {
|
||||
return this.principalThresholdForLastInstalment;
|
||||
}
|
||||
|
||||
}
|
||||
+14
-1
@@ -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);
|
||||
|
||||
|
||||
+56
-54
@@ -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 });
|
||||
|
||||
+8
@@ -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;
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user