MIFOSX-1584 Added business logic for minimum number of days between disbursal day and first repayment date
This commit is contained in:
+201
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* 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.integrationtests;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mifosplatform.integrationtests.common.CalendarHelper;
|
||||
import org.mifosplatform.integrationtests.common.ClientHelper;
|
||||
import org.mifosplatform.integrationtests.common.CommonConstants;
|
||||
import org.mifosplatform.integrationtests.common.GroupHelper;
|
||||
import org.mifosplatform.integrationtests.common.Utils;
|
||||
import org.mifosplatform.integrationtests.common.loans.LoanApplicationTestBuilder;
|
||||
import org.mifosplatform.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.mifosplatform.integrationtests.common.loans.LoanStatusChecker;
|
||||
import org.mifosplatform.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.mifosplatform.integrationtests.common.savings.SavingsAccountHelper;
|
||||
|
||||
import com.jayway.restassured.builder.RequestSpecBuilder;
|
||||
import com.jayway.restassured.builder.ResponseSpecBuilder;
|
||||
import com.jayway.restassured.http.ContentType;
|
||||
import com.jayway.restassured.specification.RequestSpecification;
|
||||
import com.jayway.restassured.specification.ResponseSpecification;
|
||||
|
||||
/**
|
||||
* Test the creation, approval and rejection of a loan reschedule request
|
||||
**/
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
public class MinimumDaysBetweenDisbursalAndFirstRepaymentTest {
|
||||
|
||||
private ResponseSpecification responseSpec;
|
||||
private ResponseSpecification responseSpecForStatusCode403;
|
||||
private RequestSpecification requestSpec;
|
||||
private LoanTransactionHelper loanTransactionHelper;
|
||||
private Integer clientId;
|
||||
private Integer groupId;
|
||||
private Integer groupCalendarId;
|
||||
private Integer loanProductId;
|
||||
private Integer loanId;
|
||||
private String loanPrincipalAmount = "100000.00";
|
||||
private String numberOfRepayments = "12";
|
||||
private String interestRatePerPeriod = "18";
|
||||
private String groupActivationDate = "1 August 2014";
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Utils.initializeRESTAssured();
|
||||
}
|
||||
|
||||
/*
|
||||
* MinimumDaysBetweenDisbursalAndFirstRepayment is set to 7 days and days
|
||||
* between disbursal date and first repayment is set as 7. system should
|
||||
* allow to create this loan and allow to disburse
|
||||
*/
|
||||
@Test
|
||||
public void createLoanEntity_WITH_DAY_BETWEEN_DISB_DATE_AND_REPAY_START_DATE_GREATER_THAN_MIN_DAY_CRITERIA() {
|
||||
|
||||
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
// create all required entities
|
||||
this.createRequiredEntities();
|
||||
|
||||
final String disbursalDate = "4 September 2014";
|
||||
final String firstRepaymentDate = "11 September 2014";
|
||||
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount)
|
||||
.withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsWeeks().withNumberOfRepayments(numberOfRepayments)
|
||||
.withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments()
|
||||
.withInterestCalculationPeriodTypeAsDays().withInterestRatePerPeriod(interestRatePerPeriod)
|
||||
.withRepaymentFrequencyTypeAsWeeks().withSubmittedOnDate(disbursalDate).withExpectedDisbursementDate(disbursalDate)
|
||||
.withPrincipalGrace("2").withInterestGrace("2").withFirstRepaymentDate(firstRepaymentDate)
|
||||
.build(this.clientId.toString(), this.loanProductId.toString(), null);
|
||||
|
||||
this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
|
||||
// Test for loan account is created
|
||||
Assert.assertNotNull(this.loanId);
|
||||
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
|
||||
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
|
||||
|
||||
// Test for loan account is created, can be approved
|
||||
this.loanTransactionHelper.approveLoan(disbursalDate, this.loanId);
|
||||
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
|
||||
// Test for loan account approved can be disbursed
|
||||
this.loanTransactionHelper.disburseLoan(disbursalDate, this.loanId);
|
||||
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId);
|
||||
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* MinimumDaysBetweenDisbursalAndFirstRepayment is set to 7 days and days
|
||||
* between disbursal date and first repayment is set as 7. system should
|
||||
* allow to create this loan and allow to disburse
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void createLoanEntity_WITH_DAY_BETWEEN_DISB_DATE_AND_REPAY_START_DATE_LESS_THAN_MIN_DAY_CRITERIA() {
|
||||
|
||||
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||
this.responseSpecForStatusCode403 = new ResponseSpecBuilder().expectStatusCode(403).build();
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||
// create all required entities
|
||||
this.createRequiredEntities();
|
||||
|
||||
// loanTransactionHelper is reassigned to accept 403 status code from
|
||||
// server
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode403);
|
||||
|
||||
final String disbursalDate = "4 September 2014";
|
||||
final String firstRepaymentDate = "5 September 2014";
|
||||
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount)
|
||||
.withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsWeeks().withNumberOfRepayments(numberOfRepayments)
|
||||
.withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments()
|
||||
.withInterestCalculationPeriodTypeAsDays().withInterestRatePerPeriod(interestRatePerPeriod)
|
||||
.withRepaymentFrequencyTypeAsWeeks().withSubmittedOnDate(disbursalDate).withExpectedDisbursementDate(disbursalDate)
|
||||
.withPrincipalGrace("2").withInterestGrace("2").withFirstRepaymentDate(firstRepaymentDate)
|
||||
.build(this.clientId.toString(), this.loanProductId.toString(), null);
|
||||
|
||||
List<HashMap> error = (List<HashMap>) this.loanTransactionHelper.createLoanAccount(loanApplicationJSON,
|
||||
CommonConstants.RESPONSE_ERROR);
|
||||
assertEquals("validation.msg.loan.days.between.first.repayment.and.disbursal.are.less than.minimum.number.of.days.required", error
|
||||
.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the client, loan product, and loan entities
|
||||
**/
|
||||
private void createRequiredEntities() {
|
||||
final String minimumDaysBetweenDisbursalAndFirstRepayment = "7"; // &
|
||||
// days
|
||||
this.createGroupEntityWithCalendar();
|
||||
this.createClientEntity();
|
||||
this.associateClientToGroup(this.groupId, this.clientId);
|
||||
this.createLoanProductEntity(minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Associate client to the group
|
||||
*/
|
||||
|
||||
private void associateClientToGroup(final Integer groupId, final Integer clientId) {
|
||||
GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupId.toString(), clientId.toString());
|
||||
GroupHelper.verifyGroupMembers(this.requestSpec, this.responseSpec, groupId, clientId);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new group
|
||||
*/
|
||||
|
||||
private void createGroupEntityWithCalendar() {
|
||||
this.groupId = GroupHelper.createGroup(this.requestSpec, this.responseSpec, this.groupActivationDate);
|
||||
GroupHelper.verifyGroupCreatedOnServer(this.requestSpec, this.responseSpec, this.groupId);
|
||||
|
||||
final String startDate = this.groupActivationDate;
|
||||
final String frequency = "2"; // 2:Weekly
|
||||
final String interval = "1"; // Every one week
|
||||
final String repeatsOnDay = "1"; // 1:Monday
|
||||
|
||||
this.groupCalendarId = CalendarHelper.createMeetingCalendarForGroup(this.requestSpec, this.responseSpec, this.groupId, startDate,
|
||||
frequency, interval, repeatsOnDay);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new client
|
||||
**/
|
||||
private void createClientEntity() {
|
||||
this.clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, this.clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new loan product
|
||||
**/
|
||||
private void createLoanProductEntity(final String minimumDaysBetweenDisbursalAndFirstRepayment) {
|
||||
final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount)
|
||||
.withNumberOfRepayments(numberOfRepayments).withinterestRatePerPeriod(interestRatePerPeriod)
|
||||
.withInterestRateFrequencyTypeAsYear()
|
||||
.withMinimumDaysBetweenDisbursalAndFirstRepayment(minimumDaysBetweenDisbursalAndFirstRepayment).build(null);
|
||||
this.loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.integrationtests.common;
|
||||
|
||||
import static com.jayway.restassured.path.json.JsonPath.from;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.jayway.restassured.specification.RequestSpecification;
|
||||
import com.jayway.restassured.specification.ResponseSpecification;
|
||||
|
||||
public class CalendarHelper {
|
||||
|
||||
private static final String BASE_URL = "/mifosng-provider/api/v1/";
|
||||
private static final String PARENT_ENTITY_NAME = "groups/";
|
||||
private static final String ENITY_NAME = "/calendars";
|
||||
|
||||
public static Integer createMeetingCalendarForGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||
final Integer groupId, final String startDate, final String frequency, final String interval, final String repeatsOnDay) {
|
||||
|
||||
System.out.println("---------------------------------CREATING A MEETING CALENDAR FOR THE GROUP------------------------------");
|
||||
|
||||
final String CALENDAR_RESOURCE_URL = BASE_URL + PARENT_ENTITY_NAME + groupId + ENITY_NAME + "?" + Utils.TENANT_IDENTIFIER;
|
||||
|
||||
System.out.println(CALENDAR_RESOURCE_URL);
|
||||
|
||||
return Utils.performServerPost(requestSpec, responseSpec, CALENDAR_RESOURCE_URL, getTestCalendarAsJSON(frequency, interval, repeatsOnDay, startDate),
|
||||
"resourceId");
|
||||
}
|
||||
|
||||
public static String getTestCalendarAsJSON(final String frequency, final String interval,final String repeatsOnDay,
|
||||
final String startDate) {
|
||||
|
||||
final HashMap<String, String> map = new HashMap<>();
|
||||
map.put("dateFormat", "dd MMMM yyyy");
|
||||
map.put("locale", "en");
|
||||
map.put("frequency", frequency);
|
||||
map.put("interval", interval);
|
||||
map.put("repeating", "true");
|
||||
map.put("repeatsOnDay", repeatsOnDay );
|
||||
map.put("title", Utils.randomNameGenerator("groups_CollectionMeeting", 4));
|
||||
map.put("typeId", "1");
|
||||
map.put("startDate", startDate);
|
||||
System.out.println("map : " + map);
|
||||
return new Gson().toJson(map);
|
||||
}
|
||||
|
||||
public static void verifyCalendarCreatedOnServer(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||
final Integer generatedGroupId, final Integer generatedCalendarId) {
|
||||
System.out.println("------------------------------CHECK CALENDAR DETAILS------------------------------------\n");
|
||||
final String CLIENT_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupId + "?associations=all&" + Utils.TENANT_IDENTIFIER;
|
||||
final String responseCalendarDetailsinJSON = Utils.performServerGet(requestSpec, responseSpec, CLIENT_URL, "collectionMeetingCalendar");
|
||||
final Integer responseCalendarId = from(responseCalendarDetailsinJSON).get("id");
|
||||
assertEquals("ERROR IN CREATING THE CALENDAR", generatedCalendarId, responseCalendarId);
|
||||
}
|
||||
}
|
||||
+8
@@ -47,6 +47,7 @@ public class LoanApplicationTestBuilder {
|
||||
private List<HashMap> disbursementData = null;
|
||||
private List<HashMap> charges = new ArrayList<HashMap>();
|
||||
private String recalculationRestFrequencyDate = null;
|
||||
private String repaymentsStartingFromDate = null;
|
||||
|
||||
public String build(final String ID, final String loanProductId, final String savingsID) {
|
||||
|
||||
@@ -73,6 +74,8 @@ public class LoanApplicationTestBuilder {
|
||||
map.put("expectedDisbursementDate", this.expectedDisbursmentDate);
|
||||
map.put("submittedOnDate", this.submittedOnDate);
|
||||
map.put("loanType", this.loanType);
|
||||
map.put("repaymentsStartingFromDate", this.repaymentsStartingFromDate);
|
||||
|
||||
if (charges != null) {
|
||||
map.put("charges", charges);
|
||||
}
|
||||
@@ -240,4 +243,9 @@ public class LoanApplicationTestBuilder {
|
||||
this.recalculationRestFrequencyDate = recalculationRestFrequencyDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanApplicationTestBuilder withFirstRepaymentDate(final String firstRepaymentDate) {
|
||||
this.repaymentsStartingFromDate = firstRepaymentDate;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -87,6 +87,7 @@ public class LoanProductTestBuilder {
|
||||
private String recalculationRestFrequencyType = "1";
|
||||
private String recalculationRestFrequencyInterval = "0";
|
||||
private String recalculationRestFrequencyDate = null;
|
||||
private String minimumDaysBetweenDisbursalAndFirstRepayment = "0";
|
||||
|
||||
public String build(final String chargeId) {
|
||||
final HashMap<String, Object> map = new HashMap<>();
|
||||
@@ -120,6 +121,7 @@ public class LoanProductTestBuilder {
|
||||
map.put("minPrincipal", this.minPrincipal);
|
||||
map.put("maxPrincipal", this.maxPrincipal);
|
||||
map.put("overdueDaysForNPA", this.overdueDaysForNPA);
|
||||
map.put("minimumDaysBetweenDisbursalAndFirstRepayment", this.minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
if (multiDisburseLoan) {
|
||||
map.put("multiDisburseLoan", this.multiDisburseLoan);
|
||||
map.put("maxTrancheCount", this.maxTrancheCount);
|
||||
@@ -377,4 +379,8 @@ public class LoanProductTestBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanProductTestBuilder withMinimumDaysBetweenDisbursalAndFirstRepayment(final String minimumDaysBetweenDisbursalAndFirstRepayment){
|
||||
this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -49,6 +49,10 @@ public class LoanTransactionHelper {
|
||||
return Utils.performServerPost(this.requestSpec, this.responseSpec, APPLY_LOAN_URL, loanApplicationJSON, "loanId");
|
||||
}
|
||||
|
||||
public Object createLoanAccount(final String loanApplicationJSON, final String responseAttribute) {
|
||||
return Utils.performServerPost(this.requestSpec, this.responseSpec, APPLY_LOAN_URL, loanApplicationJSON, responseAttribute);
|
||||
}
|
||||
|
||||
public Integer updateLoan(final Integer id, final String loanApplicationJSON) {
|
||||
return Utils.performServerPut(this.requestSpec, this.responseSpec, "/mifosng-provider/api/v1/loans/" + id
|
||||
+ "?" + Utils.TENANT_IDENTIFIER, loanApplicationJSON, "loanId");
|
||||
|
||||
+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