MIFOSX-1844 : added config to manage last transaction

This commit is contained in:
Pramod Nuthakki
2015-01-20 17:03:06 +05:30
parent 69d1edff32
commit d9db7d5313
10 changed files with 167 additions and 82 deletions
@@ -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());
}
/**
@@ -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)) {
@@ -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,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);
}
@@ -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;
@@ -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;
}
}
@@ -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);
@@ -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 });
@@ -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;