MIFOSX-1584 Added business logic for minimum number of days between disbursal day and first repayment date
This commit is contained in:
+4
@@ -130,6 +130,10 @@ public class DataValidatorBuilder {
|
||||
}
|
||||
|
||||
/*** FIXME: Vishwas, why does this method have a parameter? Seems wrong ***/
|
||||
/*
|
||||
* This method is not meant for validation, if you have mandatory boolean param and
|
||||
* if it has invalid value or value not passed then call this method, this method is always used with input as false
|
||||
*/
|
||||
public DataValidatorBuilder trueOrFalseRequired(final boolean trueOfFalseFieldProvided) {
|
||||
if (!trueOfFalseFieldProvided && !this.ignoreNullValue) {
|
||||
final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".")
|
||||
|
||||
+9
-1
@@ -58,7 +58,15 @@ public enum PeriodFrequencyType {
|
||||
}
|
||||
|
||||
public boolean isYearly() {
|
||||
return this.value.equals(PeriodFrequencyType.MONTHS.getValue());
|
||||
return this.value.equals(PeriodFrequencyType.YEARS.getValue());
|
||||
}
|
||||
|
||||
public boolean isWeekly() {
|
||||
return this.value.equals(PeriodFrequencyType.WEEKS.getValue());
|
||||
}
|
||||
|
||||
public boolean isDaily() {
|
||||
return this.value.equals(PeriodFrequencyType.DAYS.getValue());
|
||||
}
|
||||
|
||||
public boolean isInvalid() {
|
||||
|
||||
+3
-3
@@ -9,9 +9,9 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRul
|
||||
|
||||
public class LoanDisbursalException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public LoanDisbursalException(final String cureentProduct, final String restrictedProduct) {
|
||||
super("error.msg.loan.disbursal.failed", "This loan could not be disbursed as `" + cureentProduct + "` and `" + restrictedProduct
|
||||
+ "` are not allowed to co-exist", new Object[] { cureentProduct, restrictedProduct });
|
||||
public LoanDisbursalException(final String currentProduct, final String restrictedProduct) {
|
||||
super("error.msg.loan.disbursal.failed", "This loan could not be disbursed as `" + currentProduct + "` and `" + restrictedProduct
|
||||
+ "` are not allowed to co-exist", new Object[] { currentProduct, restrictedProduct });
|
||||
}
|
||||
|
||||
public LoanDisbursalException(final String defaultUserMessage, final String entity, final Object... defaultUserMessageArgs) {
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
/**
|
||||
* {@link AbstractPlatformDomainRuleException} thrown when a number of days between disbursal date and firstRepayment is less than
|
||||
* minimumDaysBetweenDisbursalAndFirstRepayment
|
||||
*
|
||||
*/
|
||||
public class MinimumDaysBetweenDisbursalAndFirstRepaymentRuleException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public MinimumDaysBetweenDisbursalAndFirstRepaymentRuleException(final String defaultUserMessage,
|
||||
final Object... defaultUserMessageArgs) {
|
||||
super("validation.msg.loan.days.between.first.repayment.and.disbursal.are.less than.minimum.number.of.days.required",
|
||||
defaultUserMessage, defaultUserMessageArgs);
|
||||
}
|
||||
}
|
||||
+144
-27
@@ -28,6 +28,7 @@ import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
|
||||
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
|
||||
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
|
||||
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
|
||||
@@ -50,6 +51,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.MinimumDaysBetweenDisbursalAndFirstRepaymentRuleException;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
@@ -160,44 +162,97 @@ public class LoanScheduleAssembler {
|
||||
|
||||
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element);
|
||||
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element);
|
||||
LocalDate calculatedRepaymentsStartingFromDate = repaymentsStartingFromDate;
|
||||
LocalDate calculatedRepaymentsStartingFromDate = null;
|
||||
|
||||
final Boolean synchDisbursement = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
|
||||
final Long calendarId = this.fromApiJsonHelper.extractLongNamed("calendarId", element);
|
||||
Calendar calendar = null;
|
||||
if ((synchDisbursement != null && synchDisbursement.booleanValue()) || (calendarId != null && calendarId != 0)) {
|
||||
calendar = this.calendarRepository.findOne(calendarId);
|
||||
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
|
||||
|
||||
final String loanTypeParameterName = "loanType";
|
||||
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
|
||||
|
||||
final AccountType loanType = AccountType.fromName(loanTypeStr);
|
||||
|
||||
|
||||
// validate repayment frequency and interval with meeting frequency
|
||||
// and interval
|
||||
final PeriodFrequencyType meetingPeriodFrequency = CalendarUtils.getMeetingPeriodFrequencyType(calendar.getRecurrence());
|
||||
validateRepaymentFrequencyIsSameAsMeetingFrequency(meetingPeriodFrequency.getValue(), repaymentFrequencyType,
|
||||
CalendarUtils.getInterval(calendar.getRecurrence()), repaymentEvery);
|
||||
/*
|
||||
* If it is JLG loan then make sure loan frequency is same as
|
||||
* Group/Center meeting frequency or multiple of it. TODO: Check should
|
||||
* be either same frequency or loan freq is multiple of center/group
|
||||
* meeting freq multiples
|
||||
*/
|
||||
if (loanType.isJLGAccount()) {
|
||||
if ((synchDisbursement != null && synchDisbursement.booleanValue())) {
|
||||
calendar = this.calendarRepository.findOne(calendarId);
|
||||
if (calendar == null) { throw new CalendarNotFoundException(calendarId); }
|
||||
final PeriodFrequencyType meetingPeriodFrequency = CalendarUtils.getMeetingPeriodFrequencyType(calendar.getRecurrence());
|
||||
validateRepaymentFrequencyIsSameAsMeetingFrequency(meetingPeriodFrequency.getValue(), repaymentFrequencyType,
|
||||
CalendarUtils.getInterval(calendar.getRecurrence()), repaymentEvery);
|
||||
}
|
||||
}
|
||||
|
||||
if (synchDisbursement != null && synchDisbursement.booleanValue()) {
|
||||
validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar);
|
||||
/*
|
||||
* If user has not passed the first repayments date then only system
|
||||
* will derive it based on loan is individual/group loan or JLG loan.
|
||||
*/
|
||||
Boolean isRepaymentsStartingFromDateDerived = false;
|
||||
if (repaymentsStartingFromDate == null) {
|
||||
calculatedRepaymentsStartingFromDate = deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
|
||||
repaymentPeriodFrequencyType, loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar);
|
||||
isRepaymentsStartingFromDateDerived = true;
|
||||
}
|
||||
|
||||
// if calendar is attached (not null) then reschedule repayment dates
|
||||
// according to meeting
|
||||
if (null != calendar) {
|
||||
// TODO : AA - Is it require to reset repaymentsStartingFromDate or
|
||||
// set only if it is not provided (or null)
|
||||
// Currently provided repaymentsStartingFromDate takes precedence
|
||||
// over system generated next meeting date
|
||||
if (calculatedRepaymentsStartingFromDate == null) {
|
||||
// FIXME: AA - Possibility of having next meeting date
|
||||
// immediately after disbursement date,
|
||||
// need to have minimum number of days gap between disbursement
|
||||
// and first repayment date.
|
||||
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
|
||||
calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, expectedDisbursementDate,
|
||||
repaymentEvery, frequency);
|
||||
} else {// validate user provided repaymentsStartFromDate
|
||||
/*
|
||||
* If it is JLG loan, then make sure first repayment falls on meeting
|
||||
* date
|
||||
*/
|
||||
if (loanType.isJLGAccount()) {
|
||||
if (isRepaymentsStartingFromDateDerived) {
|
||||
validateRepaymentsStartDateWithMeetingDates(calculatedRepaymentsStartingFromDate, calendar);
|
||||
} else {
|
||||
validateRepaymentsStartDateWithMeetingDates(repaymentsStartingFromDate, calendar);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If it is JLG loan and disbursement is synced on meeting, make sure
|
||||
* disbursement date on meeting date
|
||||
*/
|
||||
if (loanType.isJLGAccount()) {
|
||||
if (synchDisbursement != null && synchDisbursement.booleanValue()) {
|
||||
validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If minimum days between disbursal and first repayment is set, then
|
||||
* validate the rule
|
||||
*/
|
||||
if (isRepaymentsStartingFromDateDerived) {
|
||||
validateMinimumDaysBetweenDisbursalAndFirstRepayment(expectedDisbursementDate, calculatedRepaymentsStartingFromDate,
|
||||
loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment());
|
||||
} else {
|
||||
validateMinimumDaysBetweenDisbursalAndFirstRepayment(expectedDisbursementDate, repaymentsStartingFromDate,
|
||||
loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment());
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* If first repayment is not derived and instead directly taken from
|
||||
* input then copy the value of repaymentsStartingFromDate to
|
||||
* calculatedRepaymentsStartingFromDate
|
||||
*
|
||||
* TODO: The reason for this bad code is, LoanApplicationTerms.assembleFrom
|
||||
* takes both repaymentsStartingFromDate and calculatedRepaymentsStartingFromDate
|
||||
* and derived data has precedence over input date,
|
||||
*
|
||||
* FIXME:LoanApplicationTerms.assembleFrom should take only one
|
||||
* "first repayment date" than taking two dates with same purpose.
|
||||
*
|
||||
*/
|
||||
if (!isRepaymentsStartingFromDateDerived) {
|
||||
calculatedRepaymentsStartingFromDate = repaymentsStartingFromDate;
|
||||
}
|
||||
|
||||
// grace details
|
||||
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
|
||||
final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
|
||||
@@ -424,4 +479,66 @@ public class LoanScheduleAssembler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate deriveFirstRepaymentDate(final AccountType loanType, final Integer repaymentEvery,
|
||||
final LocalDate expectedDisbursementDate, final PeriodFrequencyType repaymentPeriodFrequencyType,
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar){
|
||||
|
||||
LocalDate derivedFirstRepayment = null;
|
||||
LocalDate dateBasedOnRepaymentFrequency = null;
|
||||
final LocalDate dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment = expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
|
||||
if (loanType.isIndividualAccount() || loanType.isGroupAccount()) {
|
||||
// Derive the first repayment date as greater date among (disbursement date + plus frequency) or
|
||||
// (disbursement date + minimum between disbursal and first repayment )
|
||||
if (repaymentPeriodFrequencyType.isDaily()) {
|
||||
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusDays(repaymentEvery);
|
||||
}
|
||||
else if(repaymentPeriodFrequencyType.isWeekly()){
|
||||
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusWeeks(repaymentEvery);
|
||||
}
|
||||
else if (repaymentPeriodFrequencyType.isMonthly()) {
|
||||
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusMonths(repaymentEvery);
|
||||
}
|
||||
else if (repaymentPeriodFrequencyType.isYearly()){
|
||||
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusYears(repaymentEvery);
|
||||
}
|
||||
derivedFirstRepayment = dateBasedOnRepaymentFrequency.isAfter(dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment)?dateBasedOnRepaymentFrequency:dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
}
|
||||
else if(loanType.isJLGAccount()){
|
||||
|
||||
final LocalDate refernceDateForCalculatingFirstRepaymentDate = expectedDisbursementDate;
|
||||
derivedFirstRepayment = deriveFirstRepaymentDateForJLGLoans(repaymentEvery, expectedDisbursementDate,refernceDateForCalculatingFirstRepaymentDate,
|
||||
repaymentPeriodFrequencyType,minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
|
||||
|
||||
}
|
||||
|
||||
return derivedFirstRepayment;
|
||||
}
|
||||
|
||||
private LocalDate deriveFirstRepaymentDateForJLGLoans(final Integer repaymentEvery,
|
||||
final LocalDate expectedDisbursementDate, final LocalDate refernceDateForCalculatingFirstRepaymentDate,
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType,final Integer minimumDaysBetweenDisbursalAndFirstRepayment,
|
||||
final Calendar calendar){
|
||||
|
||||
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
|
||||
final LocalDate derivedFirstRepayment = CalendarUtils.getFirstRepaymentMeetingDate(calendar, refernceDateForCalculatingFirstRepaymentDate,
|
||||
repaymentEvery, frequency);
|
||||
final LocalDate minimumFirstRepaymentDate = expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment
|
||||
:deriveFirstRepaymentDateForJLGLoans(repaymentEvery, expectedDisbursementDate,derivedFirstRepayment, repaymentPeriodFrequencyType,
|
||||
minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
|
||||
}
|
||||
|
||||
private void validateMinimumDaysBetweenDisbursalAndFirstRepayment(final LocalDate disbursalDate,
|
||||
final LocalDate firstRepaymentDate, final Integer minimumDaysBetweenDisbursalAndFirstRepayment){
|
||||
|
||||
final LocalDate minimumFirstRepaymentDate = disbursalDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
if(firstRepaymentDate.isBefore(minimumFirstRepaymentDate)){
|
||||
final String errorMessage = "Number of days between disbursal date (" + disbursalDate + ") and first repayment date ("
|
||||
+ firstRepaymentDate + ") can't be less than minimumDaysBetweenDisbursalAndFirstRepayment ("
|
||||
+ minimumDaysBetweenDisbursalAndFirstRepayment + ")." ;
|
||||
throw new MinimumDaysBetweenDisbursalAndFirstRepaymentRuleException(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-19
@@ -111,6 +111,19 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
if (loanType.isJLGAccount()) {
|
||||
baseDataValidator.reset().parameter("clientId").value(clientId).notNull().integerGreaterThanZero();
|
||||
baseDataValidator.reset().parameter("groupId").value(groupId).notNull().longGreaterThanZero();
|
||||
|
||||
// if it is JLG loan then must have a meeting details (calendar)
|
||||
final String calendarIdParameterName = "calendarId";
|
||||
final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParameterName, element);
|
||||
baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).notNull().integerGreaterThanZero();
|
||||
|
||||
// if it is JLG loan then must have a syncDisbursement
|
||||
final String syncDisbursementParameterName = "syncDisbursementWithMeeting";
|
||||
final Boolean syncDisbursement = this.fromApiJsonHelper.extractBooleanNamed(syncDisbursementParameterName, element);
|
||||
|
||||
if (syncDisbursement == null) {
|
||||
baseDataValidator.reset().parameter(syncDisbursementParameterName).value(syncDisbursement).trueOrFalseRequired(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -331,25 +344,6 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
}
|
||||
}
|
||||
|
||||
boolean meetingIdRequired = false;
|
||||
// validate syncDisbursement
|
||||
final String syncDisbursementParameterName = "syncDisbursementWithMeeting";
|
||||
if (this.fromApiJsonHelper.parameterExists(syncDisbursementParameterName, element)) {
|
||||
final Boolean syncDisbursement = this.fromApiJsonHelper.extractBooleanNamed(syncDisbursementParameterName, element);
|
||||
if (syncDisbursement == null) {
|
||||
baseDataValidator.reset().parameter(syncDisbursementParameterName).value(syncDisbursement).trueOrFalseRequired(false);
|
||||
} else if (syncDisbursement.booleanValue()) {
|
||||
meetingIdRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
final String calendarIdParameterName = "calendarId";
|
||||
// if disbursement is synced then must have a meeting (calendar)
|
||||
if (meetingIdRequired || this.fromApiJsonHelper.parameterExists(calendarIdParameterName, element)) {
|
||||
final Long calendarId = this.fromApiJsonHelper.extractLongNamed(calendarIdParameterName, element);
|
||||
baseDataValidator.reset().parameter(calendarIdParameterName).value(calendarId).notNull().integerGreaterThanZero();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.emiAmountParameterName, element)) {
|
||||
final BigDecimal emiAnount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
|
||||
element);
|
||||
|
||||
+1
-1
@@ -59,8 +59,8 @@ public interface LoanProductConstants {
|
||||
public static final String outstandingLoanBalanceParameterName = "outstandingLoanBalance";
|
||||
|
||||
public static final String graceOnArrearsAgeingParameterName = "graceOnArrearsAgeing";
|
||||
|
||||
public static final String overdueDaysForNPAParameterName = "overdueDaysForNPA";
|
||||
public static final String minimumDaysBetweenDisbursalAndFirstRepayment = "minimumDaysBetweenDisbursalAndFirstRepayment";
|
||||
|
||||
// Interest recalculation related
|
||||
public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled";
|
||||
|
||||
+12
-4
@@ -83,6 +83,7 @@ public class LoanProductData {
|
||||
private final EnumOptionData daysInYearType;
|
||||
private final boolean isInterestRecalculationEnabled;
|
||||
private final LoanProductInterestRecalculationData interestRecalculationData;
|
||||
private final Integer minimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
|
||||
// charges
|
||||
private final Collection<ChargeData> charges;
|
||||
@@ -186,6 +187,7 @@ public class LoanProductData {
|
||||
final EnumOptionData daysInYearType = null;
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
final LoanProductInterestRecalculationData interestRecalculationData = null;
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
@@ -195,7 +197,7 @@ public class LoanProductData {
|
||||
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
|
||||
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData);
|
||||
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
}
|
||||
|
||||
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
|
||||
@@ -248,6 +250,7 @@ public class LoanProductData {
|
||||
final EnumOptionData daysInYearType = null;
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
final LoanProductInterestRecalculationData interestRecalculationData = null;
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
@@ -257,7 +260,7 @@ public class LoanProductData {
|
||||
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
|
||||
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData);
|
||||
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
}
|
||||
|
||||
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
|
||||
@@ -317,6 +320,8 @@ public class LoanProductData {
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
final LoanProductInterestRecalculationData interestRecalculationData = LoanProductInterestRecalculationData
|
||||
.sensibleDefaultsForNewLoanProductCreation();
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
|
||||
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
@@ -326,7 +331,7 @@ public class LoanProductData {
|
||||
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId,
|
||||
principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle,
|
||||
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
|
||||
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData);
|
||||
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
}
|
||||
|
||||
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
|
||||
@@ -356,7 +361,8 @@ public class LoanProductData {
|
||||
Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations, Boolean multiDisburseLoan,
|
||||
Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing,
|
||||
final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType,
|
||||
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData) {
|
||||
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData,
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.shortName = shortName;
|
||||
@@ -394,6 +400,7 @@ public class LoanProductData {
|
||||
this.closeDate = closeDate;
|
||||
this.status = status;
|
||||
this.externalId = externalId;
|
||||
this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
|
||||
this.chargeOptions = null;
|
||||
this.penaltyOptions = null;
|
||||
@@ -508,6 +515,7 @@ public class LoanProductData {
|
||||
this.multiDisburseLoan = productData.multiDisburseLoan;
|
||||
this.maxTrancheCount = productData.maxTrancheCount;
|
||||
this.outstandingLoanBalance = productData.outstandingLoanBalance;
|
||||
this.minimumDaysBetweenDisbursalAndFirstRepayment = productData.minimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
|
||||
this.amortizationTypeOptions = amortizationTypeOptions;
|
||||
this.interestTypeOptions = interestTypeOptions;
|
||||
|
||||
+22
-2
@@ -121,6 +121,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
|
||||
@Column(name = "overdue_days_for_npa", nullable = true)
|
||||
private Integer overdueDaysForNPA;
|
||||
|
||||
@Column(name = "min_days_between_disbursal_and_first_repayment", nullable = true)
|
||||
private Integer minimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
|
||||
@@ -164,6 +167,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
final Integer graceOnPrincipalPayment = command.integerValueOfParameterNamed("graceOnPrincipalPayment");
|
||||
final Integer graceOnInterestPayment = command.integerValueOfParameterNamed("graceOnInterestPayment");
|
||||
final Integer graceOnInterestCharged = command.integerValueOfParameterNamed("graceOnInterestCharged");
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = command
|
||||
.integerValueOfParameterNamed("minimumDaysBetweenDisbursalAndFirstRepayment");
|
||||
|
||||
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
|
||||
final boolean includeInBorrowerCycle = command.booleanPrimitiveValueOfParameterNamed("includeInBorrowerCycle");
|
||||
@@ -213,7 +218,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
graceOnInterestCharged, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle,
|
||||
startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount,
|
||||
outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationSettings);
|
||||
isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
|
||||
}
|
||||
|
||||
@@ -430,7 +435,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations, final boolean multiDisburseLoan,
|
||||
final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing,
|
||||
final Integer overdueDaysForNPA, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType,
|
||||
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationDetails productInterestRecalculationDetails) {
|
||||
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationDetails productInterestRecalculationDetails,
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment) {
|
||||
this.fund = fund;
|
||||
this.transactionProcessingStrategy = transactionProcessingStrategy;
|
||||
this.name = name.trim();
|
||||
@@ -479,6 +485,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
this.loanProducTrancheDetails = new LoanProductTrancheDetails(multiDisburseLoan, maxTrancheCount, outstandingLoanBalance);
|
||||
this.overdueDaysForNPA = overdueDaysForNPA;
|
||||
this.productInterestRecalculationDetails = productInterestRecalculationDetails;
|
||||
this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
}
|
||||
|
||||
public MonetaryCurrency getCurrency() {
|
||||
@@ -650,6 +657,15 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
this.overdueDaysForNPA = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment,
|
||||
this.minimumDaysBetweenDisbursalAndFirstRepayment)) {
|
||||
final Integer newValue = command
|
||||
.integerValueOfParameterNamed(LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
actualChanges.put(LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, newValue);
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.minimumDaysBetweenDisbursalAndFirstRepayment = newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update interest recalculation settings
|
||||
*/
|
||||
@@ -793,6 +809,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
return this.loanProductRelatedDetail.isInterestRecalculationEnabled();
|
||||
}
|
||||
|
||||
public Integer getMinimumDaysBetweenDisbursalAndFirstRepayment(){
|
||||
return this.minimumDaysBetweenDisbursalAndFirstRepayment == null?0:this.minimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
}
|
||||
|
||||
public LoanProductBorrowerCycleVariations fetchLoanProductBorrowerCycleVariationById(Long id) {
|
||||
LoanProductBorrowerCycleVariations borrowerCycleVariation = null;
|
||||
for (LoanProductBorrowerCycleVariations cycleVariation : this.borrowerCycleVariations) {
|
||||
|
||||
+10
-2
@@ -70,7 +70,8 @@ public final class LoanProductDataValidator {
|
||||
LoanProductConstants.interestRecalculationCompoundingMethodParameterName,
|
||||
LoanProductConstants.recalculationRestFrequencyDateParamName,
|
||||
LoanProductConstants.recalculationRestFrequencyIntervalParameterName,
|
||||
LoanProductConstants.recalculationRestFrequencyTypeParameterName));
|
||||
LoanProductConstants.recalculationRestFrequencyTypeParameterName,
|
||||
LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@@ -103,7 +104,14 @@ public final class LoanProductDataValidator {
|
||||
final Long fundId = this.fromApiJsonHelper.extractLongNamed("fundId", element);
|
||||
baseDataValidator.reset().parameter("fundId").value(fundId).ignoreIfNull().integerGreaterThanZero();
|
||||
}
|
||||
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists("minimumDaysBetweenDisbursalAndFirstRepayment", element)) {
|
||||
final Long minimumDaysBetweenDisbursalAndFirstRepayment = this.fromApiJsonHelper.extractLongNamed(
|
||||
"minimumDaysBetweenDisbursalAndFirstRepayment", element);
|
||||
baseDataValidator.reset().parameter("minimumDaysBetweenDisbursalAndFirstRepayment")
|
||||
.value(minimumDaysBetweenDisbursalAndFirstRepayment).ignoreIfNull().integerGreaterThanZero();
|
||||
}
|
||||
|
||||
final Boolean includeInBorrowerCycle = this.fromApiJsonHelper.extractBooleanNamed("includeInBorrowerCycle", element);
|
||||
baseDataValidator.reset().parameter("includeInBorrowerCycle").value(includeInBorrowerCycle).ignoreIfNull()
|
||||
.validateForBooleanValue();
|
||||
|
||||
+3
-1
@@ -120,6 +120,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
+ "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod,"
|
||||
+ "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, lp.min_number_of_repayments as minNumberOfRepayments, lp.max_number_of_repayments as maxNumberOfRepayments, "
|
||||
+ "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,lp.grace_on_arrears_ageing as graceOnArrearsAgeing,lp.overdue_days_for_npa as overdueDaysForNPA, "
|
||||
+ "lp.min_days_between_disbursal_and_first_repayment As minimumDaysBetweenDisbursalAndFirstRepayment, "
|
||||
+ "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, "
|
||||
+ "lp.accounting_type as accountingType, lp.include_in_borrower_cycle as includeInBorrowerCycle,lp.use_borrower_cycle as useBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate, "
|
||||
+ "lp.allow_multiple_disbursals as multiDisburseLoan, lp.max_disbursals as maxTrancheCount, lp.max_outstanding_loan_balance as outstandingLoanBalance, "
|
||||
@@ -172,6 +173,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
final Integer graceOnInterestCharged = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestCharged");
|
||||
final Integer graceOnArrearsAgeing = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnArrearsAgeing");
|
||||
final Integer overdueDaysForNPA = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "overdueDaysForNPA");
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = JdbcSupport.getInteger(rs,"minimumDaysBetweenDisbursalAndFirstRepayment");
|
||||
|
||||
final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType");
|
||||
final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId);
|
||||
@@ -263,7 +265,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
|
||||
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData);
|
||||
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `m_product_loan`
|
||||
ADD COLUMN `min_days_between_disbursal_and_first_repayment` INT(3) NULL AFTER `interest_recalculation_enabled`;
|
||||
Reference in New Issue
Block a user