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
+14 -4
View File
@@ -11397,7 +11397,14 @@ Request Body:
<td class=fielddesc>The minimum number of days allowed between a Loan disbursal and its first repayment.
</td>
</tr>
<tr class=alt>
<td>principalThresholdForLastInstalment</td>
</tr>
<tr>
<td class=fielddesc>Field represents percentage of current instalment principal amount for comparing against principal outstanding to add another repayment instalment. If the outstanding principal amount is less then calculated amount, remaining outstanding amount will be added to current instalment. Default value for multi disburse loan is 50% and non-multi disburse loan is 0%
</td>
</tr>
@@ -11641,7 +11648,8 @@ GET https://DomainName/api/v1/loanproducts
"id": 2,
"code": "accountingRuleType.cash",
"value": "CASH BASED"
}
},
"principalThresholdForLastInstalment":0
}
]
</code>
@@ -12719,7 +12727,8 @@ GET https://DomainName/api/v1/loanproducts/{productId}
}
},
"multiDisburseLoan":true,"maxTrancheCount":3,"outstandingLoanBalance":36000.000000,
"overdueDaysForNPA":2
"overdueDaysForNPA":2,
"principalThresholdForLastInstalment":50
]
}
</code>
@@ -12763,7 +12772,7 @@ GET https://DomainName/api/v1/loanproducts/{productId}
numberOfRepaymentVariationsForBorrowerCycle,
interestRateVariationsForBorrowerCycle,
multiDisburseLoan,maxTrancheCount,
outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds
outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds, principalThresholdForLastInstalment
</td>
</tr>
</table>
@@ -12883,7 +12892,8 @@ Request Body:
"daysInMonthType":1,
"daysInYearType":1,
"isInterestRecalculationEnabled":"false",
"holdGuaranteeFunds":"false"
"holdGuaranteeFunds":"false",
"principalThresholdForLastInstalment":20
}
</code>
<code class="method-response">
@@ -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;