FINERACT-1515 Allow multi-disbursements without setting up expected tranche details
This commit is contained in:
committed by
Aleksandar Vidakovic
parent
2bb8ae2f16
commit
de64de876c
+88
-36
@@ -121,6 +121,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureExcept
|
|||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataNotAllowedException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException;
|
import org.apache.fineract.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||||
@@ -1330,8 +1331,10 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updateLoanScheduleDependentDerivedFields() {
|
public void updateLoanScheduleDependentDerivedFields() {
|
||||||
this.expectedMaturityDate = Date.from(determineExpectedMaturityDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
|
if (this.getLoanRepaymentScheduleInstallmentsSize() > 0) {
|
||||||
this.actualMaturityDate = Date.from(determineExpectedMaturityDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
|
this.expectedMaturityDate = Date.from(determineExpectedMaturityDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||||
|
this.actualMaturityDate = Date.from(determineExpectedMaturityDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLoanSummaryDerivedFields() {
|
private void updateLoanSummaryDerivedFields() {
|
||||||
@@ -1620,9 +1623,16 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
}
|
}
|
||||||
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
|
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
|
||||||
|
|
||||||
if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
|
if (loanProduct.isDisallowExpectedDisbursements()) {
|
||||||
final String errorMessage = "For this loan product, disbursement details must be provided";
|
if (disbursementDataArray != null) {
|
||||||
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
final String errorMessage = "For this loan product, disbursement details are not allowed";
|
||||||
|
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
|
||||||
|
final String errorMessage = "For this loan product, disbursement details must be provided";
|
||||||
|
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (disbursementDataArray.size() > loanProduct.maxTrancheCount()) {
|
if (disbursementDataArray.size() > loanProduct.maxTrancheCount()) {
|
||||||
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
|
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
|
||||||
@@ -2224,31 +2234,19 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
LocalDate expecteddisbursementDate = command.localDateValueOfParameterNamed("expectedDisbursementDate");
|
LocalDate expecteddisbursementDate = command.localDateValueOfParameterNamed("expectedDisbursementDate");
|
||||||
|
|
||||||
BigDecimal approvedLoanAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName);
|
BigDecimal approvedLoanAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName);
|
||||||
|
|
||||||
if (approvedLoanAmount != null) {
|
if (approvedLoanAmount != null) {
|
||||||
|
compareApprovedToProposedPrincipal(approvedLoanAmount);
|
||||||
|
|
||||||
// Approved amount has to be less than or equal to principal
|
/*
|
||||||
// amount demanded
|
* All the calculations are done based on the principal amount, so it is necessary to set principal
|
||||||
|
* amount to approved amount
|
||||||
|
*/
|
||||||
|
this.approvedPrincipal = approvedLoanAmount;
|
||||||
|
|
||||||
if (approvedLoanAmount.compareTo(this.proposedPrincipal) < 0) {
|
this.loanRepaymentScheduleDetail.setPrincipal(approvedLoanAmount);
|
||||||
|
actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount);
|
||||||
this.approvedPrincipal = approvedLoanAmount;
|
actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount);
|
||||||
|
actualChanges.put(LoanApiConstants.disbursementNetDisbursalAmountParameterName, netDisbursalAmount);
|
||||||
/*
|
|
||||||
* All the calculations are done based on the principal amount, so it is necessary to set principal
|
|
||||||
* amount to approved amount
|
|
||||||
*/
|
|
||||||
|
|
||||||
this.loanRepaymentScheduleDetail.setPrincipal(approvedLoanAmount);
|
|
||||||
|
|
||||||
actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount);
|
|
||||||
actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount);
|
|
||||||
actualChanges.put(LoanApiConstants.disbursementNetDisbursalAmountParameterName, netDisbursalAmount);
|
|
||||||
} else if (approvedLoanAmount.compareTo(this.proposedPrincipal) > 0) {
|
|
||||||
final String errorMessage = "Loan approved amount can't be greater than loan amount demanded.";
|
|
||||||
throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded",
|
|
||||||
errorMessage, this.proposedPrincipal, approvedLoanAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update disbursement details */
|
/* Update disbursement details */
|
||||||
if (disbursementDataArray != null) {
|
if (disbursementDataArray != null) {
|
||||||
@@ -2259,10 +2257,16 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
recalculateAllCharges();
|
recalculateAllCharges();
|
||||||
|
|
||||||
if (loanProduct.isMultiDisburseLoan()) {
|
if (loanProduct.isMultiDisburseLoan()) {
|
||||||
|
if (loanProduct.isDisallowExpectedDisbursements()) {
|
||||||
if (this.disbursementDetails.isEmpty()) {
|
if (!disbursementDetails.isEmpty()) {
|
||||||
final String errorMessage = "For this loan product, disbursement details must be provided";
|
final String errorMessage = "For this loan product, disbursement details are not allowed";
|
||||||
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (disbursementDetails.isEmpty()) {
|
||||||
|
final String errorMessage = "For this loan product, disbursement details must be provided";
|
||||||
|
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.disbursementDetails.size() > loanProduct.maxTrancheCount()) {
|
if (this.disbursementDetails.size() > loanProduct.maxTrancheCount()) {
|
||||||
@@ -2313,6 +2317,39 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return actualChanges;
|
return actualChanges;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void compareApprovedToProposedPrincipal(BigDecimal approvedLoanAmount) {
|
||||||
|
|
||||||
|
if (this.loanProduct().isDisallowExpectedDisbursements() && this.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||||
|
BigDecimal maxApprovedLoanAmount = getOverAppliedMax();
|
||||||
|
if (approvedLoanAmount.compareTo(maxApprovedLoanAmount) > 0) {
|
||||||
|
final String errorMessage = "Loan approved amount can't be greater than maximum applied loan amount calculation.";
|
||||||
|
throw new InvalidLoanStateTransitionException("approval",
|
||||||
|
"amount.can't.be.greater.than.maximum.applied.loan.amount.calculation", errorMessage, approvedLoanAmount,
|
||||||
|
maxApprovedLoanAmount);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (approvedLoanAmount.compareTo(this.proposedPrincipal) > 0) {
|
||||||
|
final String errorMessage = "Loan approved amount can't be greater than loan amount demanded.";
|
||||||
|
throw new InvalidLoanStateTransitionException("approval", "amount.can't.be.greater.than.loan.amount.demanded", errorMessage,
|
||||||
|
this.proposedPrincipal, approvedLoanAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal getOverAppliedMax() {
|
||||||
|
BigDecimal maxAmount = null;
|
||||||
|
if (this.getLoanProduct().getOverAppliedCalculationType().equals("percentage")) {
|
||||||
|
BigDecimal overAppliedNumber = BigDecimal.valueOf(getLoanProduct().getOverAppliedNumber());
|
||||||
|
BigDecimal x = overAppliedNumber.divide(BigDecimal.valueOf(100));
|
||||||
|
BigDecimal totalPercentage = BigDecimal.valueOf(1).add(x);
|
||||||
|
maxAmount = this.proposedPrincipal.multiply(totalPercentage);
|
||||||
|
} else {
|
||||||
|
maxAmount = this.proposedPrincipal.add(BigDecimal.valueOf(getLoanProduct().getOverAppliedNumber()));
|
||||||
|
}
|
||||||
|
return maxAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> undoApproval(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
public Map<String, Object> undoApproval(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||||
@@ -2542,11 +2579,7 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
totalAmount = totalAmount.add(disbursementDetails.principal());
|
totalAmount = totalAmount.add(disbursementDetails.principal());
|
||||||
}
|
}
|
||||||
this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
|
this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount);
|
||||||
if (totalAmount.compareTo(this.approvedPrincipal) > 0) {
|
compareDisbursedToApprovedOrProposedPrincipal(disburseAmount.getAmount(), totalAmount);
|
||||||
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved principal ";
|
|
||||||
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", principalDisbursed,
|
|
||||||
this.approvedPrincipal);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount());
|
this.loanRepaymentScheduleDetail.setPrincipal(this.loanRepaymentScheduleDetail.getPrincipal().minus(diff).getAmount());
|
||||||
}
|
}
|
||||||
@@ -2559,6 +2592,25 @@ public class Loan extends AbstractPersistableCustom {
|
|||||||
return disburseAmount;
|
return disburseAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void compareDisbursedToApprovedOrProposedPrincipal(BigDecimal disbursedAmount, BigDecimal totalDisbursed) {
|
||||||
|
|
||||||
|
if (this.loanProduct().isDisallowExpectedDisbursements() && this.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||||
|
BigDecimal maxDisbursedAmount = getOverAppliedMax();
|
||||||
|
if (disbursedAmount.compareTo(maxDisbursedAmount) > 0) {
|
||||||
|
final String errorMessage = "Loan disbursal amount can't be greater than maximum applied loan amount calculation.";
|
||||||
|
throw new InvalidLoanStateTransitionException("disbursal",
|
||||||
|
"amount.can't.be.greater.than.maximum.applied.loan.amount.calculation", errorMessage, disbursedAmount,
|
||||||
|
maxDisbursedAmount);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (totalDisbursed.compareTo(this.approvedPrincipal) > 0) {
|
||||||
|
final String errorMsg = "Loan can't be disbursed,disburse amount is exceeding approved principal ";
|
||||||
|
throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", totalDisbursed,
|
||||||
|
this.approvedPrincipal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ChangedTransactionDetail reprocessTransactionForDisbursement() {
|
private ChangedTransactionDetail reprocessTransactionForDisbursement() {
|
||||||
ChangedTransactionDetail changedTransactionDetail = null;
|
ChangedTransactionDetail changedTransactionDetail = null;
|
||||||
if (this.loanProduct.isMultiDisburseLoan()) {
|
if (this.loanProduct.isMultiDisburseLoan()) {
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public class MultiDisbursementDataNotAllowedException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public MultiDisbursementDataNotAllowedException(final String entity, final String defaultUserMessage,
|
||||||
|
final Object... defaultUserMessageArgs) {
|
||||||
|
super("error.msg." + entity + ".not.allowed", defaultUserMessage, defaultUserMessageArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+13
-4
@@ -75,6 +75,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionProcessin
|
|||||||
import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
|
import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollaterals;
|
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollaterals;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataNotAllowedException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||||
@@ -200,13 +201,21 @@ public class LoanAssembler {
|
|||||||
}
|
}
|
||||||
BigDecimal maxOutstandingLoanBalance = null;
|
BigDecimal maxOutstandingLoanBalance = null;
|
||||||
if (loanProduct.isMultiDisburseLoan()) {
|
if (loanProduct.isMultiDisburseLoan()) {
|
||||||
disbursementDetails = this.loanUtilService.fetchDisbursementData(element.getAsJsonObject());
|
|
||||||
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
|
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
|
||||||
maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
|
maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
|
||||||
element, locale);
|
element, locale);
|
||||||
if (disbursementDetails.isEmpty()) {
|
|
||||||
final String errorMessage = "For this loan product, disbursement details must be provided";
|
disbursementDetails = this.loanUtilService.fetchDisbursementData(element.getAsJsonObject());
|
||||||
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
if (loanProduct.isDisallowExpectedDisbursements()) {
|
||||||
|
if (!disbursementDetails.isEmpty()) {
|
||||||
|
final String errorMessage = "For this loan product, disbursement details are not allowed";
|
||||||
|
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (disbursementDetails.isEmpty()) {
|
||||||
|
final String errorMessage = "For this loan product, disbursement details must be provided";
|
||||||
|
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disbursementDetails.size() > loanProduct.maxTrancheCount()) {
|
if (disbursementDetails.size() > loanProduct.maxTrancheCount()) {
|
||||||
|
|||||||
+36
-8
@@ -173,6 +173,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursement
|
|||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataNotAllowedException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
|
import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
|
||||||
@@ -373,6 +374,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||||
|
if (loan.loanProduct().isDisallowExpectedDisbursements()) {
|
||||||
|
// create artificial 'tranche/expected disbursal' as current disburse code expects it for multi-disbursal
|
||||||
|
// products
|
||||||
|
final Date artificialExpectedDate = Date
|
||||||
|
.from(loan.getExpectedDisbursedOnLocalDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||||
|
LoanDisbursementDetails disbursementDetail = new LoanDisbursementDetails(artificialExpectedDate, null,
|
||||||
|
loan.getDisbursedAmount(), null);
|
||||||
|
disbursementDetail.updateLoan(loan);
|
||||||
|
loan.getDisbursementDetails().add(disbursementDetail);
|
||||||
|
}
|
||||||
|
|
||||||
// Get disbursedAmount
|
// Get disbursedAmount
|
||||||
final BigDecimal disbursedAmount = loan.getDisbursedAmount();
|
final BigDecimal disbursedAmount = loan.getDisbursedAmount();
|
||||||
@@ -2900,17 +2911,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate) {
|
private void validateMultiDisbursementData(final JsonCommand command, LocalDate expectedDisbursementDate,
|
||||||
|
boolean isDisallowExpectedDisbursements) {
|
||||||
final String json = command.json();
|
final String json = command.json();
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
|
||||||
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
|
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
|
||||||
if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
|
|
||||||
final String errorMessage = "For this loan product, disbursement details must be provided";
|
if (isDisallowExpectedDisbursements) {
|
||||||
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
if (disbursementDataArray != null) {
|
||||||
|
final String errorMessage = "For this loan product, disbursement details are not allowed";
|
||||||
|
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
|
||||||
|
final String errorMessage = "For this loan product, disbursement details must be provided";
|
||||||
|
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element);
|
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("approvedLoanAmount", element);
|
||||||
|
|
||||||
loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate,
|
loanApplicationCommandFromApiJsonHelper.validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate,
|
||||||
@@ -2952,15 +2973,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
final String errorMessage = "cannot.modify.tranches.if.loan.is.pendingapproval.closed.overpaid.writtenoff";
|
final String errorMessage = "cannot.modify.tranches.if.loan.is.pendingapproval.closed.overpaid.writtenoff";
|
||||||
throw new LoanMultiDisbursementException(errorMessage);
|
throw new LoanMultiDisbursementException(errorMessage);
|
||||||
}
|
}
|
||||||
validateMultiDisbursementData(command, expectedDisbursementDate);
|
validateMultiDisbursementData(command, expectedDisbursementDate, loan.loanProduct().isDisallowExpectedDisbursements());
|
||||||
|
|
||||||
this.validateForAddAndDeleteTranche(loan);
|
this.validateForAddAndDeleteTranche(loan);
|
||||||
|
|
||||||
loan.updateDisbursementDetails(command, actualChanges);
|
loan.updateDisbursementDetails(command, actualChanges);
|
||||||
|
|
||||||
if (loan.getDisbursementDetails().isEmpty()) {
|
if (loan.loanProduct().isDisallowExpectedDisbursements()) {
|
||||||
final String errorMessage = "For this loan product, disbursement details must be provided";
|
if (!loan.getDisbursementDetails().isEmpty()) {
|
||||||
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
final String errorMessage = "For this loan product, disbursement details are not allowed";
|
||||||
|
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (loan.getDisbursementDetails().isEmpty()) {
|
||||||
|
final String errorMessage = "For this loan product, disbursement details must be provided";
|
||||||
|
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loan.getDisbursementDetails().size() > loan.loanProduct().maxTrancheCount()) {
|
if (loan.getDisbursementDetails().size() > loan.loanProduct().maxTrancheCount()) {
|
||||||
|
|||||||
+28
@@ -191,6 +191,18 @@ public class LoanProduct extends AbstractPersistableCustom {
|
|||||||
@Column(name = "fixed_principal_percentage_per_installment", scale = 2, precision = 5, nullable = true)
|
@Column(name = "fixed_principal_percentage_per_installment", scale = 2, precision = 5, nullable = true)
|
||||||
private BigDecimal fixedPrincipalPercentagePerInstallment;
|
private BigDecimal fixedPrincipalPercentagePerInstallment;
|
||||||
|
|
||||||
|
@Column(name = "disallow_expected_disbursements", nullable = false)
|
||||||
|
private boolean disallowExpectedDisbursements;
|
||||||
|
|
||||||
|
@Column(name = "allow_approved_disbursed_amounts_over_applied", nullable = false)
|
||||||
|
private boolean allowApprovedDisbursedAmountsOverApplied;
|
||||||
|
|
||||||
|
@Column(name = "over_applied_calculation_type", nullable = true)
|
||||||
|
private String overAppliedCalculationType;
|
||||||
|
|
||||||
|
@Column(name = "over_applied_number", nullable = true)
|
||||||
|
private Integer overAppliedNumber;
|
||||||
|
|
||||||
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,
|
||||||
final List<Rate> productRates) {
|
final List<Rate> productRates) {
|
||||||
@@ -1461,4 +1473,20 @@ public class LoanProduct extends AbstractPersistableCustom {
|
|||||||
public BigDecimal getFixedPrincipalPercentagePerInstallment() {
|
public BigDecimal getFixedPrincipalPercentagePerInstallment() {
|
||||||
return fixedPrincipalPercentagePerInstallment;
|
return fixedPrincipalPercentagePerInstallment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDisallowExpectedDisbursements() {
|
||||||
|
return disallowExpectedDisbursements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowApprovedDisbursedAmountsOverApplied() {
|
||||||
|
return allowApprovedDisbursedAmountsOverApplied;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOverAppliedCalculationType() {
|
||||||
|
return overAppliedCalculationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOverAppliedNumber() {
|
||||||
|
return overAppliedNumber;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,6 @@
|
|||||||
<include file="parts/0003_postgresql_specific_initial_data.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0003_postgresql_specific_initial_data.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0004_camelcase_column_renaming.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0004_camelcase_column_renaming.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0005_savings_transaction_reversal.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0005_savings_transaction_reversal.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0006_product_loan_disallow_expected_disbursements.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0007_product_loan_higher_than_applied_loan_amount_management.xml" relativeToChangelogFile="true"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||||
|
<changeSet author="fineract" id="1">
|
||||||
|
<addColumn tableName="m_product_loan">
|
||||||
|
<column name="disallow_expected_disbursements" type="BOOLEAN" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||||
|
<changeSet author="fineract" id="1">
|
||||||
|
<addColumn tableName="m_product_loan">
|
||||||
|
<column name="allow_approved_disbursed_amounts_over_applied" type="BOOLEAN" defaultValueBoolean="false">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="over_applied_calculation_type" type="VARCHAR(10)"/>
|
||||||
|
<column defaultValueComputed="NULL" name="over_applied_number" type="INT"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
||||||
Reference in New Issue
Block a user