validating disbursement date against expected disbursement date

This commit is contained in:
jinjurajan
2016-08-02 11:53:22 +05:30
committed by Nazeer Hussain Shaik
parent 8dfcd13641
commit 68f013abc2
10 changed files with 253 additions and 13 deletions
@@ -0,0 +1,117 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.integrationtests;
import java.util.HashMap;
import java.util.List;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
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;
@SuppressWarnings("rawtypes")
public class LoanDisbursalDateValidationTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private LoanTransactionHelper loanTransactionHelper;
private ResponseSpecification responseForbiddenError;
@Before
public void setup() {
Utils.initializeRESTAssured();
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);
this.responseForbiddenError = new ResponseSpecBuilder().expectStatusCode(403).build();
}
@Test
public void LoanApplicationValidateDisbursalDate() {
final String proposedAmount = "5000";
final String approveDate = "1 March 2014";
final String disbursalDate = "02 March 2014";
// CREATE CLIENT
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014");
System.out.println("---------------------------------CLIENT CREATED WITH ID---------------------------------------------------"
+ clientID);
// CREATE LOAN PRODUCT
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder()
.withSyncExpectedWithDisbursementDate(true).build(null));
System.out.println("----------------------------------LOAN PRODUCT CREATED WITH ID-------------------------------------------"
+ loanProductID);
// APPLY FOR LOAN
final Integer loanID = applyForLoanApplication(clientID, loanProductID, proposedAmount);
System.out.println("-----------------------------------LOAN CREATED WITH LOANID-------------------------------------------------"
+ loanID);
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
// VALIDATE THE LOAN STATUS
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------");
loanStatusHashMap = this.loanTransactionHelper.approveLoan(approveDate, loanID);
// VALIDATE THE LOAN IS APPROVED
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
// DISBURSE A LOAN
@SuppressWarnings("unchecked")
List<HashMap> disbursalError = (List<HashMap>) this.loanTransactionHelper.disburseLoan(disbursalDate, loanID, this.responseForbiddenError);
Assert.assertEquals("error.msg.actual.disbursement.date.does.not.match.with.expected.disbursal.date",
disbursalError.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
}
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String proposedAmount) {
final String loanApplication = new LoanApplicationTestBuilder()
.withPrincipal(proposedAmount).withLoanTermFrequency("5")
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("5")
.withRepaymentEveryAfter("1")
.withRepaymentFrequencyTypeAsMonths()
.withInterestRatePerPeriod("2")
.withExpectedDisbursementDate("1 March 2014")
.withSubmittedOnDate("26 February 2014").
build(clientID.toString(), loanProductID.toString(), null);
return this.loanTransactionHelper.getLoanId(loanApplication);
}
}
@@ -124,6 +124,7 @@ public class LoanProductTestBuilder {
private Integer recalculationRestFrequencyOnDayType = null; private Integer recalculationRestFrequencyOnDayType = null;
private Integer recalculationCompoundingFrequencyDayOfWeekType = null; private Integer recalculationCompoundingFrequencyDayOfWeekType = null;
private Integer recalculationRestFrequencyDayOfWeekType = null; private Integer recalculationRestFrequencyDayOfWeekType = null;
private boolean syncExpectedWithDisbursementDate = false;
public String build(final String chargeId) { public String build(final String chargeId) {
final HashMap<String, Object> map = new HashMap<>(); final HashMap<String, Object> map = new HashMap<>();
@@ -211,6 +212,8 @@ public class LoanProductTestBuilder {
map.put("minimumGap", minimumGap) ; map.put("minimumGap", minimumGap) ;
map.put("maximumGap", maximumGap) ; map.put("maximumGap", maximumGap) ;
} }
map.put("syncExpectedWithDisbursementDate",
this.syncExpectedWithDisbursementDate);
return new Gson().toJson(map); return new Gson().toJson(map);
} }
@@ -498,4 +501,10 @@ public class LoanProductTestBuilder {
this.maximumGap = maximumGap; this.maximumGap = maximumGap;
return this ; return this ;
} }
public LoanProductTestBuilder withSyncExpectedWithDisbursementDate(Boolean syncExpectedWithDisbursementDate) {
this.syncExpectedWithDisbursementDate =
syncExpectedWithDisbursementDate ;
return this ;
}
} }
@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.Utils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
@@ -176,6 +177,10 @@ public class LoanTransactionHelper {
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date, disburseAmt)); return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date, disburseAmt));
} }
public Object disburseLoan(final String date, final Integer loanID, ResponseSpecification responseValidationError) {
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), getDisburseLoanAsJSON(date, null), responseValidationError);
}
public HashMap disburseLoanToSavings(final String date, final Integer loanID) { public HashMap disburseLoanToSavings(final String date, final Integer loanID) {
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_TO_SAVINGS_COMMAND, loanID), getDisburseLoanAsJSON(date, null)); return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_TO_SAVINGS_COMMAND, loanID), getDisburseLoanAsJSON(date, null));
} }
@@ -493,6 +498,11 @@ public class LoanTransactionHelper {
return Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForLoanTransaction, jsonToBeSent, responseAttribute); return Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForLoanTransaction, jsonToBeSent, responseAttribute);
} }
private Object performLoanTransaction(final String postURLForLoanTransaction, final String jsonToBeSent, ResponseSpecification responseValidationError) {
return Utils.performServerPost(this.requestSpec, responseValidationError, postURLForLoanTransaction, jsonToBeSent, CommonConstants.RESPONSE_ERROR);
}
public Object adjustLoanTransaction(final Integer loanId, final Integer transactionId, final String date, public Object adjustLoanTransaction(final Integer loanId, final Integer transactionId, final String date,
final String transactionAmount, final String responseAttribute) { final String transactionAmount, final String responseAttribute) {
return adjustLoanTransaction(loanId, transactionId, getAdjustTransactionJSON(date, transactionAmount), responseAttribute); return adjustLoanTransaction(loanId, transactionId, getAdjustTransactionJSON(date, transactionAmount), responseAttribute);
@@ -0,0 +1,39 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
import org.joda.time.LocalDate;
/**
* {@link AbstractPlatformDomainRuleException} thrown when
* actual disbursement date does not match with
* expected disbursement date
*
*/
public class DateMismatchException extends AbstractPlatformDomainRuleException {
public DateMismatchException(final LocalDate actualDisbursementDate,
final LocalDate expectedDisbursedOnLocalDate) {
super("error.msg.actual.disbursement.date.does.not.match.with.expected.disbursal.date",
"Actual disbursement date (" + actualDisbursementDate + ") "
+ "should be equal to Expected disbursal date (" + expectedDisbursedOnLocalDate+ ")", actualDisbursementDate,
expectedDisbursedOnLocalDate, null);
}
}
@@ -142,6 +142,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementC
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException;
import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException; import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException; import org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException; import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException;
@@ -164,6 +165,7 @@ import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonV
import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer; import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
import org.apache.fineract.portfolio.loanproduct.data.LoanOverdueDTO; import org.apache.fineract.portfolio.loanproduct.data.LoanOverdueDTO;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException; import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException;
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException; import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
@@ -312,6 +314,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanEventApiJsonValidator.validateDisbursement(command.json(), isAccountTransfer); this.loanEventApiJsonValidator.validateDisbursement(command.json(), isAccountTransfer);
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
// validate ActualDisbursement Date Against Expected Disbursement Date
LoanProduct loanProduct = loan.loanProduct();
if(loanProduct.syncExpectedWithDisbursementDate()){
syncExpectedDateWithActualDisbursementDate(loan, actualDisbursementDate);
}
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
final LocalDate nextPossibleRepaymentDate = loan.getNextPossibleRepaymentDateForRescheduling(); final LocalDate nextPossibleRepaymentDate = loan.getNextPossibleRepaymentDateForRescheduling();
@@ -321,14 +331,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
checkForProductMixRestrictions(loan); checkForProductMixRestrictions(loan);
LocalDate recalculateFrom = null; LocalDate recalculateFrom = null;
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
// validate actual disbursement date against meeting date // validate actual disbursement date against meeting date
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue()); CalendarEntityType.LOANS.getValue());
if (loan.isSyncDisbursementWithMeeting()) { if (loan.isSyncDisbursementWithMeeting()) {
this.loanEventApiJsonValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance, this.loanEventApiJsonValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance,
scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), scheduleGeneratorDTO.getNumberOfdays()); scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), scheduleGeneratorDTO.getNumberOfdays());
} }
@@ -346,6 +354,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Boolean isPaymnetypeApplicableforDisbursementCharge = configurationDomainService final Boolean isPaymnetypeApplicableforDisbursementCharge = configurationDomainService
.isPaymnetypeApplicableforDisbursementCharge(); .isPaymnetypeApplicableforDisbursementCharge();
// Recalculate first repayment date based in actual disbursement date.
updateLoanCounters(loan, actualDisbursementDate); updateLoanCounters(loan, actualDisbursementDate);
Money amountBeforeAdjust = loan.getPrincpal(); Money amountBeforeAdjust = loan.getPrincpal();
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED); loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
@@ -571,6 +580,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final SingleDisbursalCommand singleLoanDisbursalCommand = disbursalCommand[i]; final SingleDisbursalCommand singleLoanDisbursalCommand = disbursalCommand[i];
final Loan loan = this.loanAssembler.assembleFrom(singleLoanDisbursalCommand.getLoanId()); final Loan loan = this.loanAssembler.assembleFrom(singleLoanDisbursalCommand.getLoanId());
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
// validate ActualDisbursement Date Against Expected Disbursement Date
LoanProduct loanProduct = loan.loanProduct();
if(loanProduct.syncExpectedWithDisbursementDate()){
syncExpectedDateWithActualDisbursementDate(loan, actualDisbursementDate);
}
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL, this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
@@ -586,7 +602,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// disbursement date and next available meeting dates // disbursement date and next available meeting dates
// assuming repayment schedule won't regenerate because expected // assuming repayment schedule won't regenerate because expected
// disbursement and actual disbursement happens on same date // disbursement and actual disbursement happens on same date
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED); loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
updateLoanCounters(loan, actualDisbursementDate); updateLoanCounters(loan, actualDisbursementDate);
boolean canDisburse = loan.canDisburse(actualDisbursementDate); boolean canDisburse = loan.canDisburse(actualDisbursementDate);
@@ -2949,4 +2964,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
private void syncExpectedDateWithActualDisbursementDate(final Loan loan, LocalDate actualDisbursementDate){
if(!loan.getExpectedDisbursedOnLocalDate().equals(actualDisbursementDate)){
throw new DateMismatchException(actualDisbursementDate,
loan.getExpectedDisbursedOnLocalDate());
}
}
} }
@@ -181,6 +181,7 @@ public class LoanProductData {
private final LoanProductGuaranteeData productGuaranteeData; private final LoanProductGuaranteeData productGuaranteeData;
private final Boolean accountMovesOutOfNPAOnlyOnArrearsCompletion; private final Boolean accountMovesOutOfNPAOnlyOnArrearsCompletion;
private LoanProductConfigurableAttributes allowAttributeOverrides; private LoanProductConfigurableAttributes allowAttributeOverrides;
private final boolean syncExpectedWithDisbursementDate;
/** /**
* Used when returning lookup information about loan product for dropdowns. * Used when returning lookup information about loan product for dropdowns.
@@ -255,6 +256,7 @@ public class LoanProductData {
final boolean canDefineInstallmentAmount = false; final boolean canDefineInstallmentAmount = false;
final Integer installmentAmountInMultiplesOf = null; final Integer installmentAmountInMultiplesOf = null;
final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
final boolean syncExpectedWithDisbursementDate = false;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -269,7 +271,8 @@ public class LoanProductData {
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf,
loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName,
interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap); isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate);
} }
@@ -344,6 +347,7 @@ public class LoanProductData {
final boolean canDefineInstallmentAmount = false; final boolean canDefineInstallmentAmount = false;
final Integer installmentAmountInMultiplesOf = null; final Integer installmentAmountInMultiplesOf = null;
final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
final boolean syncExpectedWithDisbursementDate = false;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -358,7 +362,8 @@ public class LoanProductData {
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf,
loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName,
interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap); isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate);
} }
@@ -440,6 +445,7 @@ public class LoanProductData {
final boolean canDefineInstallmentAmount = false; final boolean canDefineInstallmentAmount = false;
final Integer installmentAmountInMultiplesOf = null; final Integer installmentAmountInMultiplesOf = null;
final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
final boolean syncExpectedWithDisbursementDate = false;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -454,7 +460,9 @@ public class LoanProductData {
principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap); maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate);
} }
@@ -493,7 +501,8 @@ public class LoanProductData {
boolean isLinkedToFloatingInterestRates, Integer floatingRateId, String floatingRateName, BigDecimal interestRateDifferential, boolean isLinkedToFloatingInterestRates, Integer floatingRateId, String floatingRateName, BigDecimal interestRateDifferential,
BigDecimal minDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal minDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, BigDecimal maxDifferentialLendingRate,
boolean isFloatingInterestRateCalculationAllowed, final boolean isVariableInstallmentsAllowed, boolean isFloatingInterestRateCalculationAllowed, final boolean isVariableInstallmentsAllowed,
final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments) { final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments,
final boolean syncExpectedWithDisbursementDate) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.shortName = shortName; this.shortName = shortName;
@@ -595,6 +604,7 @@ public class LoanProductData {
this.canDefineInstallmentAmount = canDefineInstallmentAmount; this.canDefineInstallmentAmount = canDefineInstallmentAmount;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.preClosureInterestCalculationStrategyOptions = null; this.preClosureInterestCalculationStrategyOptions = null;
this.syncExpectedWithDisbursementDate = syncExpectedWithDisbursementDate;
} }
@@ -729,6 +739,7 @@ public class LoanProductData {
this.canDefineInstallmentAmount = productData.canDefineInstallmentAmount; this.canDefineInstallmentAmount = productData.canDefineInstallmentAmount;
this.installmentAmountInMultiplesOf = productData.installmentAmountInMultiplesOf; this.installmentAmountInMultiplesOf = productData.installmentAmountInMultiplesOf;
this.preClosureInterestCalculationStrategyOptions = preCloseInterestCalculationStrategyOptions; this.preClosureInterestCalculationStrategyOptions = preCloseInterestCalculationStrategyOptions;
this.syncExpectedWithDisbursementDate = productData.syncExpectedWithDisbursementDate;
} }
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) { private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -1077,4 +1088,9 @@ public class LoanProductData {
public Boolean getAllowPartialPeriodInterestCalcualtion() { public Boolean getAllowPartialPeriodInterestCalcualtion() {
return this.allowPartialPeriodInterestCalcualtion; return this.allowPartialPeriodInterestCalcualtion;
} }
public boolean syncExpectedWithDisbursementDate() {
return syncExpectedWithDisbursementDate;
}
} }
@@ -176,6 +176,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true, fetch=FetchType.EAGER) @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true, fetch=FetchType.EAGER)
private LoanProductVariableInstallmentConfig variableInstallmentConfig; private LoanProductVariableInstallmentConfig variableInstallmentConfig;
@Column(name = "sync_expected_with_disbursement_date")
private boolean syncExpectedWithDisbursementDate;
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) { final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) {
@@ -318,6 +321,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
final Integer installmentAmountInMultiplesOf = command final Integer installmentAmountInMultiplesOf = command
.integerValueOfParameterNamed(LoanProductConstants.installmentAmountInMultiplesOfParamName); .integerValueOfParameterNamed(LoanProductConstants.installmentAmountInMultiplesOfParamName);
final boolean syncExpectedWithDisbursementDate = command.booleanPrimitiveValueOfParameterNamed("syncExpectedWithDisbursementDate");
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal, return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery, annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery,
@@ -331,7 +336,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
installmentAmountInMultiplesOf, loanConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRate, installmentAmountInMultiplesOf, loanConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRate,
interestRateDifferential, minDifferentialLendingRate, maxDifferentialLendingRate, defaultDifferentialLendingRate, interestRateDifferential, minDifferentialLendingRate, maxDifferentialLendingRate, defaultDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGapBetweenInstallments, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGapBetweenInstallments,
maximumGapBetweenInstallments); maximumGapBetweenInstallments, syncExpectedWithDisbursementDate);
} }
@@ -562,7 +567,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
Boolean isLinkedToFloatingInterestRates, FloatingRate floatingRate, BigDecimal interestRateDifferential, Boolean isLinkedToFloatingInterestRates, FloatingRate floatingRate, BigDecimal interestRateDifferential,
BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate,
Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed, Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed,
final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments) { final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments,
final boolean syncExpectedWithDisbursementDate) {
this.fund = fund; this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy; this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim(); this.name = name.trim();
@@ -636,6 +642,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion; this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion;
this.canDefineInstallmentAmount = canDefineEmiAmount; this.canDefineInstallmentAmount = canDefineEmiAmount;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.syncExpectedWithDisbursementDate =
syncExpectedWithDisbursementDate;
} }
public MonetaryCurrency getCurrency() { public MonetaryCurrency getCurrency() {
@@ -859,6 +867,13 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.minimumDaysBetweenDisbursalAndFirstRepayment = newValue; this.minimumDaysBetweenDisbursalAndFirstRepayment = newValue;
} }
if(command.isChangeInBooleanParameterNamed("syncExpectedWithDisbursementDate"
, this.syncExpectedWithDisbursementDate)){
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("syncExpectedWithDisbursementDate");
actualChanges.put("syncExpectedWithDisbursementDate", newValue);
this.syncExpectedWithDisbursementDate = newValue;
}
/** /**
* Update interest recalculation settings * Update interest recalculation settings
*/ */
@@ -1155,7 +1170,15 @@ public class LoanProduct extends AbstractPersistable<Long> {
return borrowerCycleVariation; return borrowerCycleVariation;
} }
public Map<String, BigDecimal> fetchBorrowerCycleVariationsForCycleNumber(final Integer cycleNumber) { public boolean syncExpectedWithDisbursementDate() {
return syncExpectedWithDisbursementDate;
}
public void setSyncExpectedWithDisbursementDate(boolean syncExpectedWithDisbursementDate) {
this.syncExpectedWithDisbursementDate = syncExpectedWithDisbursementDate;
}
public Map<String, BigDecimal> fetchBorrowerCycleVariationsForCycleNumber(final Integer cycleNumber) {
Map<String, BigDecimal> borrowerCycleVariations = new HashMap<>(); Map<String, BigDecimal> borrowerCycleVariations = new HashMap<>();
borrowerCycleVariations.put(LoanProductConstants.principal, this.loanProductRelatedDetail.getPrincipal().getAmount()); borrowerCycleVariations.put(LoanProductConstants.principal, this.loanProductRelatedDetail.getPrincipal().getAmount());
borrowerCycleVariations.put(LoanProductConstants.interestRatePerPeriod, borrowerCycleVariations.put(LoanProductConstants.interestRatePerPeriod,
@@ -70,7 +70,7 @@ public final class LoanProductDataValidator {
"transactionProcessingStrategyId", "graceOnPrincipalPayment", "recurringMoratoriumOnPrincipalPeriods", "graceOnInterestPayment", "graceOnInterestCharged", "charges", "transactionProcessingStrategyId", "graceOnPrincipalPayment", "recurringMoratoriumOnPrincipalPeriods", "graceOnInterestPayment", "graceOnInterestCharged", "charges",
"accountingRule", "includeInBorrowerCycle", "startDate", "closeDate", "externalId", "isLinkedToFloatingInterestRates", "accountingRule", "includeInBorrowerCycle", "startDate", "closeDate", "externalId", "isLinkedToFloatingInterestRates",
"floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate", "defaultDifferentialLendingRate", "floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate", "defaultDifferentialLendingRate",
"maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed","recurringMoratoriumOnPrincipalPeriods", "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", "syncExpectedWithDisbursementDate",
LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(),
@@ -206,6 +206,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lpr.allow_compounding_on_eod as allowCompoundingOnEod, " + "lpr.allow_compounding_on_eod as allowCompoundingOnEod, "
+ "lp.hold_guarantee_funds as holdGuaranteeFunds, " + "lp.hold_guarantee_funds as holdGuaranteeFunds, "
+ "lp.principal_threshold_for_last_installment as principalThresholdForLastInstallment, " + "lp.principal_threshold_for_last_installment as principalThresholdForLastInstallment, "
+ "lp.sync_expected_with_disbursement_date as syncExpectedWithDisbursementDate, "
+ "lpg.id as lpgId, lpg.mandatory_guarantee as mandatoryGuarantee, " + "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, " + "lpg.minimum_guarantee_from_own_funds as minimumGuaranteeFromOwnFunds, lpg.minimum_guarantee_from_guarantor_funds as minimumGuaranteeFromGuarantor, "
+ "lp.account_moves_out_of_npa_only_on_arrears_completion as accountMovesOutOfNPAOnlyOnArrearsCompletion, " + "lp.account_moves_out_of_npa_only_on_arrears_completion as accountMovesOutOfNPAOnlyOnArrearsCompletion, "
@@ -441,6 +442,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final BigDecimal principalThresholdForLastInstallment = rs.getBigDecimal("principalThresholdForLastInstallment"); final BigDecimal principalThresholdForLastInstallment = rs.getBigDecimal("principalThresholdForLastInstallment");
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = rs.getBoolean("accountMovesOutOfNPAOnlyOnArrearsCompletion"); final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = rs.getBoolean("accountMovesOutOfNPAOnlyOnArrearsCompletion");
final boolean syncExpectedWithDisbursementDate = rs.getBoolean("syncExpectedWithDisbursementDate");
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -457,7 +459,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
installmentAmountInMultiplesOf, allowAttributeOverrides, isLinkedToFloatingInterestRates, floatingRateId, installmentAmountInMultiplesOf, allowAttributeOverrides, isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap,
maximumGap); maximumGap, syncExpectedWithDisbursementDate);
} }
} }
@@ -0,0 +1 @@
ALTER TABLE `m_product_loan`ADD COLUMN `sync_expected_with_disbursement_date` TINYINT NULL DEFAULT '0' AFTER `instalment_amount_in_multiples_of`;