blob: 2c1251f4914a866e141b6aaa1915cd27304c7973 [file] [log] [blame]
/**
* 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.serialization;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagementRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollateralQuantity;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollaterals;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.domain.AdvancedPaymentAllocationsValidator;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule;
import org.apache.fineract.portfolio.loanproduct.exception.EqualAmortizationUnsupportedFeatureException;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor
@Component
public final class LoanApplicationCommandFromApiJsonHelper {
/**
* The parameters supported for this command.
*/
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList(LoanApiConstants.dateFormatParameterName,
LoanApiConstants.localeParameterName, LoanApiConstants.idParameterName, LoanApiConstants.clientIdParameterName,
LoanApiConstants.groupIdParameterName, LoanApiConstants.loanTypeParameterName, LoanApiConstants.productIdParameterName,
LoanApiConstants.principalParamName, LoanApiConstants.totalLoanParamName, LoanApiConstants.parentAccountParamName,
LoanApiConstants.loanTermFrequencyParameterName, LoanApiConstants.loanTermFrequencyTypeParameterName,
LoanApiConstants.numberOfRepaymentsParameterName, LoanApiConstants.repaymentEveryParameterName,
LoanApiConstants.repaymentFrequencyTypeParameterName, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, LoanApiConstants.interestRatePerPeriodParameterName,
LoanApiConstants.amortizationTypeParameterName, LoanApiConstants.amortizationTypeOptionsParameterName,
LoanApiConstants.interestTypeParameterName, LoanApiConstants.isFloatingInterestRate, LoanApiConstants.interestRateDifferential,
LoanApiConstants.interestCalculationPeriodTypeParameterName,
LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME,
LoanApiConstants.interestRateFrequencyTypeParameterName, LoanApiConstants.expectedDisbursementDateParameterName,
LoanApiConstants.repaymentsStartingFromDateParameterName, LoanApiConstants.graceOnPrincipalPaymentParameterName,
LoanApiConstants.graceOnInterestPaymentParameterName, LoanApiConstants.graceOnInterestChargedParameterName,
LoanApiConstants.interestChargedFromDateParameterName, LoanApiConstants.submittedOnDateParameterName,
LoanApiConstants.submittedOnNoteParameterName, LoanApiConstants.accountNoParameterName,
LoanApiConstants.externalIdParameterName, LoanApiConstants.fundIdParameterName, LoanApiConstants.loanOfficerIdParameterName, // optional
LoanApiConstants.loanPurposeIdParameterName, LoanApiConstants.inArrearsToleranceParameterName,
LoanApiConstants.chargesParameterName, LoanApiConstants.collateralParameterName, // optional
LoanApiConstants.transactionProcessingStrategyCodeParameterName, // settings
LoanApiConstants.calendarIdParameterName, // optional
LoanApiConstants.syncDisbursementWithMeetingParameterName, // optional
LoanApiConstants.linkAccountIdParameterName, LoanApiConstants.disbursementDataParameterName,
LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName,
LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME,
LoanApiConstants.createStandingInstructionAtDisbursementParameterName, LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose,
LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, LoanProductConstants.RATES_PARAM_NAME,
LoanApiConstants.applicationId, // glim specific
LoanApiConstants.lastApplication, // glim specific
LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
LoanApiConstants.DISALLOW_EXPECTED_DISBURSEMENTS, LoanApiConstants.FRAUD_ATTRIBUTE_NAME,
LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH));
public static final String LOANAPPLICATION_UNDO = "loanapplication.undo";
private final FromJsonHelper fromApiJsonHelper;
private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper;
private final ClientCollateralManagementRepositoryWrapper clientCollateralManagementRepositoryWrapper;
private final LoanChargeApiJsonValidator loanChargeApiJsonValidator;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final AdvancedPaymentAllocationsValidator advancedPaymentAllocationsValidator;
public void validateForCreate(final String json, final boolean isMeetingMandatoryForJLGLoans, final LoanProduct loanProduct) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanTypeStr).notNull();
if (!StringUtils.isBlank(loanTypeStr)) {
final AccountType loanType = AccountType.fromName(loanTypeStr);
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanType.getValue()).inMinMaxRange(1, 4);
final Long clientId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.clientIdParameterName, element);
final Long groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element);
if (loanType.isIndividualAccount()) {
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull().longGreaterThanZero();
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId)
.mustBeBlankWhenParameterProvided(LoanApiConstants.clientIdParameterName, clientId);
}
if (loanType.isGroupAccount()) {
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull().longGreaterThanZero();
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId)
.mustBeBlankWhenParameterProvided(LoanApiConstants.groupIdParameterName, groupId);
}
if (loanType.isJLGAccount()) {
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull()
.integerGreaterThanZero();
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull().longGreaterThanZero();
// if it is JLG loan that must have meeting details
if (isMeetingMandatoryForJLGLoans) {
final Long calendarId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.calendarIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.calendarIdParameterName).value(calendarId).notNull()
.integerGreaterThanZero();
// if it is JLG loan then must have a value for
// syncDisbursement passed in
final Boolean syncDisbursement = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.syncDisbursementWithMeetingParameterName, element);
if (syncDisbursement == null) {
baseDataValidator.reset().parameter(LoanApiConstants.syncDisbursementWithMeetingParameterName)
.value(syncDisbursement).trueOrFalseRequired(false);
}
}
}
}
boolean isEqualAmortization = false;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isEqualAmortizationParam, element);
baseDataValidator.reset().parameter(LoanApiConstants.isEqualAmortizationParam).value(isEqualAmortization).ignoreIfNull()
.validateForBooleanValue();
if (isEqualAmortization && loanProduct.isInterestRecalculationEnabled()) {
throw new EqualAmortizationUnsupportedFeatureException("interest.recalculation", "interest recalculation");
}
}
BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName)
.value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE).notGreaterThanMax(BigDecimal.valueOf(100));
final Long productId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.productIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.productIdParameterName).value(productId).notNull().integerGreaterThanZero();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.accountNoParameterName, element)) {
final String accountNo = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.accountNoParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.accountNoParameterName).value(accountNo).ignoreIfNull()
.notExceedingLengthOf(20);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.externalIdParameterName, element)) {
final String externalId = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.externalIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.externalIdParameterName).value(externalId).ignoreIfNull()
.notExceedingLengthOf(100);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.fundIdParameterName, element)) {
final Long fundId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.fundIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fundIdParameterName).value(fundId).ignoreIfNull().integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanOfficerIdParameterName, element)) {
final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanOfficerIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanOfficerIdParameterName).value(loanOfficerId).ignoreIfNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanPurposeIdParameterName, element)) {
final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanPurposeIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanPurposeIdParameterName).value(loanPurposeId).ignoreIfNull()
.integerGreaterThanZero();
}
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.principalParamName, element);
baseDataValidator.reset().parameter(LoanApiConstants.principalParamName).value(principal).notNull().positiveAmount();
final Integer loanTermFrequency = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.loanTermFrequencyParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyParameterName).value(loanTermFrequency).notNull()
.integerGreaterThanZero();
final Integer loanTermFrequencyType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(LoanApiConstants.loanTermFrequencyTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyTypeParameterName).value(loanTermFrequencyType).notNull()
.inMinMaxRange(0, 3);
final Integer numberOfRepayments = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.numberOfRepaymentsParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.numberOfRepaymentsParameterName).value(numberOfRepayments).notNull()
.integerGreaterThanZero();
final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanApiConstants.repaymentEveryParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentEveryParameterName).value(repaymentEvery).notNull()
.integerGreaterThanZero();
final Integer repaymentEveryType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(LoanApiConstants.repaymentFrequencyTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentFrequencyTypeParameterName).value(repaymentEveryType).notNull()
.inMinMaxRange(0, 3);
CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, element, this.fromApiJsonHelper);
final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(LoanApiConstants.interestTypeParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1);
final Integer interestCalculationPeriodType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(LoanApiConstants.interestCalculationPeriodTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestCalculationPeriodTypeParameterName)
.value(interestCalculationPeriodType).notNull().inMinMaxRange(0, 1);
if (loanProduct.isLinkedToFloatingInterestRate()) {
if (isEqualAmortization) {
throw new EqualAmortizationUnsupportedFeatureException("floating.interest.rate", "floating interest rate");
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRatePerPeriodParameterName, element)) {
baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).failWithCode(
"not.supported.loanproduct.linked.to.floating.rate",
"interestRatePerPeriod param is not supported, selected Loan Product is linked with floating interest rate.");
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate,
element);
if (isFloatingInterestRate != null && isFloatingInterestRate
&& !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()) {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode(
"true.not.supported.for.selected.loanproduct",
"isFloatingInterestRate value of true not supported for selected Loan Product.");
}
} else {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).trueOrFalseRequired(false);
}
if (interestType != null && interestType.equals(InterestMethod.FLAT.getValue())) {
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).failWithCode(
"should.be.0.for.selected.loan.product",
"interestType should be DECLINING_BALANCE for selected Loan Product as it is linked to floating rates.");
}
final String interestRateDifferentialParameterName = LoanApiConstants.interestRateDifferential;
final BigDecimal interestRateDifferential = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(interestRateDifferentialParameterName, element);
baseDataValidator.reset().parameter(interestRateDifferentialParameterName).value(interestRateDifferential).notNull()
.zeroOrPositiveAmount().inMinAndMaxAmountRange(loanProduct.getFloatingRates().getMinDifferentialLendingRate(),
loanProduct.getFloatingRates().getMaxDifferentialLendingRate());
} else {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode(
"not.supported.loanproduct.not.linked.to.floating.rate",
"isFloatingInterestRate param is not supported, selected Loan Product is not linked with floating interest rate.");
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRateDifferential, element)) {
baseDataValidator.reset().parameter(LoanApiConstants.interestRateDifferential).failWithCode(
"not.supported.loanproduct.not.linked.to.floating.rate",
"interestRateDifferential param is not supported, selected Loan Product is not linked with floating interest rate.");
}
final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRatePerPeriodParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull()
.zeroOrPositiveAmount();
}
final Integer amortizationType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(LoanApiConstants.amortizationTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.amortizationTypeParameterName).value(amortizationType).notNull()
.inMinMaxRange(0, 1);
if (!AmortizationMethod.EQUAL_PRINCIPAL.getValue().equals(amortizationType) && fixedPrincipalPercentagePerInstallment != null) {
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName).failWithCode(
"not.supported.principal.fixing.not.allowed.with.equal.installments",
"Principal fixing cannot be done with equal installment amortization");
}
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName).value(expectedDisbursementDate)
.notNull();
// grace validation
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnPrincipalPaymentParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnPrincipalPaymentParameterName).value(graceOnPrincipalPayment)
.zeroOrPositiveAmount();
final Integer graceOnInterestPayment = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnInterestPaymentParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnInterestPaymentParameterName).value(graceOnInterestPayment)
.zeroOrPositiveAmount();
final Integer graceOnInterestCharged = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnInterestChargedParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnInterestChargedParameterName).value(graceOnInterestCharged)
.zeroOrPositiveAmount();
final Integer graceOnArrearsAgeing = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element);
baseDataValidator.reset().parameter(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME).value(graceOnArrearsAgeing)
.zeroOrPositiveAmount();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestChargedFromDateParameterName, element)) {
final LocalDate interestChargedFromDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.interestChargedFromDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestChargedFromDateParameterName).value(interestChargedFromDate)
.ignoreIfNull().notNull();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.repaymentsStartingFromDateParameterName, element)) {
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.repaymentsStartingFromDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate)
.ignoreIfNull().notNull();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.inArrearsToleranceParameterName, element)) {
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.inArrearsToleranceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.inArrearsToleranceParameterName).value(inArrearsTolerance).ignoreIfNull()
.zeroOrPositiveAmount();
}
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName,
element);
if (submittedOnDate == null) {
baseDataValidator.reset().parameter(LoanApiConstants.submittedOnDateParameterName).value(submittedOnDate).notNull();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.submittedOnNoteParameterName, element)) {
final String submittedOnNote = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.submittedOnNoteParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.submittedOnNoteParameterName).value(submittedOnNote).ignoreIfNull()
.notExceedingLengthOf(500);
}
final String transactionProcessingStrategy = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName)
.value(transactionProcessingStrategy).notNull();
if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(loanProduct.getTransactionProcessingStrategyCode())
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategy)) {
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName).failWithCode(
"strategy.cannot.be.advanced.payment.allocation.if.not.configured",
"Loan transaction processing strategy cannot be Advanced Payment Allocation Strategy if it's not configured on loan product");
}
// Validating whether the processor is existing
loanRepaymentScheduleTransactionProcessorFactory.determineProcessor(transactionProcessingStrategy);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.linkAccountIdParameterName, element)) {
final Long linkAccountId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.linkAccountIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.linkAccountIdParameterName).value(linkAccountId).ignoreIfNull()
.longGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.createStandingInstructionAtDisbursementParameterName, element)) {
final Boolean createStandingInstructionAtDisbursement = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.createStandingInstructionAtDisbursementParameterName, element);
final Long linkAccountId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.linkAccountIdParameterName, element);
if (createStandingInstructionAtDisbursement) {
baseDataValidator.reset().parameter(LoanApiConstants.linkAccountIdParameterName).value(linkAccountId).notNull()
.longGreaterThanZero();
}
}
// charges
if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.chargesParameterName, element)) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.get(LoanApiConstants.chargesParameterName).isJsonArray()) {
final Type arrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
final Set<String> supportedParameters = new HashSet<>(
Arrays.asList(LoanApiConstants.idParameterName, LoanApiConstants.chargeIdParameterName,
LoanApiConstants.amountParameterName, LoanApiConstants.chargeTimeTypeParameterName,
LoanApiConstants.chargeCalculationTypeParameterName, LoanApiConstants.dueDateParamName));
final JsonArray array = topLevelJsonElement.get(LoanApiConstants.chargesParameterName).getAsJsonArray();
for (int i = 1; i <= array.size(); i++) {
final JsonObject loanChargeElement = array.get(i - 1).getAsJsonObject();
final String arrayObjectJson = this.fromApiJsonHelper.toJson(loanChargeElement);
this.fromApiJsonHelper.checkForUnsupportedParameters(arrayObjectParameterTypeOfMap, arrayObjectJson,
supportedParameters);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.chargeIdParameterName,
loanChargeElement);
baseDataValidator.reset().parameter(LoanApiConstants.chargesParameterName)
.parameterAtIndexArray(LoanApiConstants.chargeIdParameterName, i).value(chargeId).notNull()
.integerGreaterThanZero();
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.amountParameterName,
loanChargeElement, locale);
baseDataValidator.reset().parameter(LoanApiConstants.chargesParameterName)
.parameterAtIndexArray(LoanApiConstants.amountParameterName, i).value(amount).notNull().positiveAmount();
this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.dueDateParamName, loanChargeElement, dateFormat, locale);
}
}
}
/**
* TODO: Add collaterals for other loan accounts if needed. For now it's only applicable for individual
* accounts. (loanType.isJLG() || loanType.isGLIM())
*/
if (!StringUtils.isBlank(loanTypeStr)) {
final AccountType loanType = AccountType.fromName(loanTypeStr);
// collateral
if (loanType.isIndividualAccount() && element.isJsonObject()
&& this.fromApiJsonHelper.parameterExists(LoanApiConstants.collateralParameterName, element)) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.get(LoanApiConstants.collateralParameterName).isJsonArray()) {
final Type collateralParameterTypeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
final Set<String> supportedParameters = new HashSet<>(
Arrays.asList(LoanApiConstants.clientCollateralIdParameterName, LoanApiConstants.quantityParameterName));
final JsonArray array = topLevelJsonElement.get(LoanApiConstants.collateralParameterName).getAsJsonArray();
for (int i = 1; i <= array.size(); i++) {
final JsonObject collateralItemElement = array.get(i - 1).getAsJsonObject();
final String collateralJson = this.fromApiJsonHelper.toJson(collateralItemElement);
this.fromApiJsonHelper.checkForUnsupportedParameters(collateralParameterTypeOfMap, collateralJson,
supportedParameters);
final Long clientCollateralId = this.fromApiJsonHelper
.extractLongNamed(LoanApiConstants.clientCollateralIdParameterName, collateralItemElement);
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName)
.parameterAtIndexArray(LoanApiConstants.clientCollateralIdParameterName, i).value(clientCollateralId)
.notNull().integerGreaterThanZero();
final BigDecimal quantity = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.quantityParameterName,
collateralItemElement, locale);
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName)
.parameterAtIndexArray(LoanApiConstants.quantityParameterName, i).value(quantity).notNull()
.positiveAmount();
final ClientCollateralManagement clientCollateralManagement = this.clientCollateralManagementRepositoryWrapper
.getCollateral(clientCollateralId);
if (clientCollateralId != null && BigDecimal.valueOf(0).compareTo(clientCollateralManagement.getQuantity()) >= 0) {
throw new InvalidAmountOfCollateralQuantity(clientCollateralManagement.getQuantity());
}
}
} else {
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName).expectedArrayButIsNot();
}
}
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.emiAmountParameterName, element)) {
if (!(loanProduct.canDefineInstallmentAmount() || loanProduct.isMultiDisburseLoan())) {
List<String> unsupportedParameterList = new ArrayList<>();
unsupportedParameterList.add(LoanApiConstants.emiAmountParameterName);
throw new UnsupportedParameterException(unsupportedParameterList);
}
if (isEqualAmortization) {
throw new EqualAmortizationUnsupportedFeatureException("fixed.emi", "fixed emi");
}
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.maxOutstandingBalanceParameterName, element)) {
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance)
.ignoreIfNull().positiveAmount();
}
if (loanProduct.canUseForTopup() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.isTopup, element)) {
final Boolean isTopup = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isTopup, element);
baseDataValidator.reset().parameter(LoanApiConstants.isTopup).value(isTopup).validateForBooleanValue();
if (isTopup != null && isTopup) {
final Long loanId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanIdToClose, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanIdToClose).value(loanId).notNull().longGreaterThanZero();
}
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.datatables, element)) {
final JsonArray datatables = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.datatables, element);
baseDataValidator.reset().parameter(LoanApiConstants.datatables).value(datatables).notNull().jsonArrayNotEmpty();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.daysInYearTypeParameterName, element)) {
final Integer daysInYearType = this.fromApiJsonHelper.extractIntegerNamed(LoanApiConstants.daysInYearTypeParameterName, element,
Locale.getDefault());
baseDataValidator.reset().parameter(LoanApiConstants.daysInYearTypeParameterName).value(daysInYearType).notNull()
.isOneOfTheseValues(1, 360, 364, 365);
}
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
String loanScheduleProcessingType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleProcessingType().name();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, element)) {
loanScheduleProcessingType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE,
element);
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE).value(loanScheduleProcessingType)
.isOneOfEnumValues(LoanScheduleProcessingType.class);
}
if (LoanScheduleProcessingType.VERTICAL.equals(LoanScheduleProcessingType.valueOf(loanScheduleProcessingType))
&& !AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategy)) {
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE).failWithCode(
"supported.only.with.advanced.payment.allocation.strategy",
"Vertical repayment schedule processing is only available with `Advanced payment allocation` strategy");
}
List<LoanProductPaymentAllocationRule> allocationRules = loanProduct.getPaymentAllocationRules();
if (LoanScheduleProcessingType.HORIZONTAL.name().equals(loanScheduleProcessingType)
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategy)) {
advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(allocationRules);
}
validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
public void validateForModify(final String json, final LoanProduct loanProduct, final Loan existingLoanApplication) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final JsonElement element = this.fromApiJsonHelper.parse(json);
boolean atLeastOneParameterPassedForUpdate = false;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.clientIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Long clientId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.clientIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull().integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.groupIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Long groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull().integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.productIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Long productId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.productIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.productIdParameterName).value(productId).notNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.accountNoParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final String accountNo = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.accountNoParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.accountNoParameterName).value(accountNo).notBlank()
.notExceedingLengthOf(20);
}
boolean isEqualAmortization = existingLoanApplication.getLoanProductRelatedDetail().isEqualAmortization();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, element);
baseDataValidator.reset().parameter(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM).value(isEqualAmortization).ignoreIfNull()
.validateForBooleanValue();
if (isEqualAmortization && loanProduct.isInterestRecalculationEnabled()) {
throw new EqualAmortizationUnsupportedFeatureException("interest.recalculation", "interest recalculation");
}
}
BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName)
.value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE).notGreaterThanMax(BigDecimal.valueOf(100));
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.externalIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final String externalId = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.externalIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.externalIdParameterName).value(externalId).ignoreIfNull()
.notExceedingLengthOf(100);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.fundIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Long fundId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.fundIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fundIdParameterName).value(fundId).ignoreIfNull().integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanOfficerIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanOfficerIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanOfficerIdParameterName).value(loanOfficerId).ignoreIfNull()
.integerGreaterThanZero();
}
String transactionProcessingStrategy = existingLoanApplication.getTransactionProcessingStrategyCode();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
transactionProcessingStrategy = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName)
.value(transactionProcessingStrategy).notNull();
// Validating whether the processor is existing
loanRepaymentScheduleTransactionProcessorFactory.determineProcessor(transactionProcessingStrategy);
}
if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(loanProduct.getTransactionProcessingStrategyCode())
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategy)) {
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName).failWithCode(
"strategy.cannot.be.advanced.payment.allocation.if.not.configured",
"Loan transaction processing strategy cannot be Advanced Payment Allocation Strategy if it's not configured on loan product");
}
BigDecimal principal = null;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.principalParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.principalParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.principalParameterName).value(principal).notNull().positiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.inArrearsToleranceParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.inArrearsToleranceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.inArrearsToleranceParameterName).value(inArrearsTolerance).ignoreIfNull()
.zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanTermFrequencyParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Integer loanTermFrequency = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.loanTermFrequencyParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyParameterName).value(loanTermFrequency).notNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanTermFrequencyTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Integer loanTermFrequencyType = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.loanTermFrequencyTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyTypeParameterName).value(loanTermFrequencyType).notNull()
.inMinMaxRange(0, 3);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.numberOfRepaymentsParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Integer numberOfRepayments = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.numberOfRepaymentsParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.numberOfRepaymentsParameterName).value(numberOfRepayments).notNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.repaymentEveryParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Integer repaymentEvery = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.repaymentEveryParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentEveryParameterName).value(repaymentEvery).notNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.repaymentFrequencyTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Integer repaymentEveryType = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.repaymentFrequencyTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentFrequencyTypeParameterName).value(repaymentEveryType).notNull()
.inMinMaxRange(0, 3);
}
CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, element, this.fromApiJsonHelper);
Integer interestType = null;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanApiConstants.interestTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0,
1);
}
if (loanProduct.isLinkedToFloatingInterestRate()) {
if (isEqualAmortization) {
throw new EqualAmortizationUnsupportedFeatureException("floating.interest.rate", "floating interest rate");
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRatePerPeriodParameterName, element)) {
baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).failWithCode(
"not.supported.loanproduct.linked.to.floating.rate",
"interestRatePerPeriod param is not supported, selected Loan Product is linked with floating interest rate.");
}
Boolean isFloatingInterestRate = existingLoanApplication.getIsFloatingInterestRate();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate, element);
atLeastOneParameterPassedForUpdate = true;
}
if (isFloatingInterestRate != null) {
if (isFloatingInterestRate && !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()) {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode(
"true.not.supported.for.selected.loanproduct",
"isFloatingInterestRate value of true not supported for selected Loan Product.");
}
} else {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).trueOrFalseRequired(false);
}
if (interestType == null) {
interestType = existingLoanApplication.getLoanProductRelatedDetail().getInterestMethod().getValue();
}
if (interestType != null && interestType.equals(InterestMethod.FLAT.getValue())) {
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).failWithCode(
"should.be.0.for.selected.loan.product",
"interestType should be DECLINING_BALANCE for selected Loan Product as it is linked to floating rates.");
}
BigDecimal interestRateDifferential = existingLoanApplication.getInterestRateDifferential();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRateDifferentialParameterName, element)) {
interestRateDifferential = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element);
atLeastOneParameterPassedForUpdate = true;
}
baseDataValidator.reset().parameter(LoanApiConstants.interestRateDifferentialParameterName).value(interestRateDifferential)
.notNull().zeroOrPositiveAmount().inMinAndMaxAmountRange(loanProduct.getFloatingRates().getMinDifferentialLendingRate(),
loanProduct.getFloatingRates().getMaxDifferentialLendingRate());
} else {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode(
"not.supported.loanproduct.not.linked.to.floating.rate",
"isFloatingInterestRate param is not supported, selected Loan Product is not linked with floating interest rate.");
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRateDifferential, element)) {
baseDataValidator.reset().parameter(LoanApiConstants.interestRateDifferential).failWithCode(
"not.supported.loanproduct.not.linked.to.floating.rate",
"interestRateDifferential param is not supported, selected Loan Product is not linked with floating interest rate.");
}
BigDecimal interestRatePerPeriod = existingLoanApplication.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRatePerPeriodParameterName, element)) {
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRatePerPeriodParameterName, element);
atLeastOneParameterPassedForUpdate = true;
}
baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull()
.zeroOrPositiveAmount();
}
Integer interestCalculationPeriodType = loanProduct.getLoanProductRelatedDetail().getInterestCalculationPeriodMethod().getValue();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestCalculationPeriodTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
interestCalculationPeriodType = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.interestCalculationPeriodTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestCalculationPeriodTypeParameterName)
.value(interestCalculationPeriodType).notNull().inMinMaxRange(0, 1);
}
Integer amortizationType = null;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.amortizationTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanApiConstants.amortizationTypeParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.amortizationTypeParameterName).value(amortizationType).notNull()
.inMinMaxRange(0, 1);
}
if (!AmortizationMethod.EQUAL_PRINCIPAL.getValue().equals(amortizationType) && fixedPrincipalPercentagePerInstallment != null) {
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName).failWithCode(
"not.supported.principal.fixing.not.allowed.with.equal.installments",
"Principal fixing cannot be done with equal installment amortization");
}
LocalDate expectedDisbursementDate = null;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.expectedDisbursementDateParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final String expectedDisbursementDateStr = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.expectedDisbursementDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName).value(expectedDisbursementDateStr)
.notBlank();
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName).value(expectedDisbursementDate)
.notNull();
}
// grace validation
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.graceOnPrincipalPaymentParameterName, element)) {
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnPrincipalPaymentParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnPrincipalPaymentParameterName).value(graceOnPrincipalPayment)
.zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.graceOnInterestPaymentParameterName, element)) {
final Integer graceOnInterestPayment = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnInterestPaymentParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnInterestPaymentParameterName).value(graceOnInterestPayment)
.zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.graceOnInterestChargedParameterName, element)) {
final Integer graceOnInterestCharged = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnInterestChargedParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnInterestChargedParameterName).value(graceOnInterestCharged)
.zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element)) {
final Integer graceOnArrearsAgeing = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element);
baseDataValidator.reset().parameter(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME).value(graceOnArrearsAgeing)
.zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestChargedFromDateParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final LocalDate interestChargedFromDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.interestChargedFromDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestChargedFromDateParameterName).value(interestChargedFromDate)
.ignoreIfNull();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.repaymentsStartingFromDateParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.repaymentsStartingFromDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate)
.ignoreIfNull();
if (!existingLoanApplication.getLoanTermVariations().isEmpty()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("cannot.modify.application.due.to.variable.installments");
}
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.submittedOnDateParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.submittedOnDateParameterName).value(submittedOnDate).notNull();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.submittedOnNoteParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final String submittedOnNote = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.submittedOnNoteParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.submittedOnNoteParameterName).value(submittedOnNote).ignoreIfNull()
.notExceedingLengthOf(500);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.submittedOnNoteParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Long linkAccountId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.linkAccountIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.linkAccountIdParameterName).value(linkAccountId).ignoreIfNull()
.longGreaterThanZero();
}
// charges
if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.chargesParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.get(LoanApiConstants.chargesParameterName).isJsonArray()) {
final Type arrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
final Set<String> supportedParameters = new HashSet<>(
Arrays.asList(LoanApiConstants.idParameterName, LoanApiConstants.chargeIdParameterName,
LoanApiConstants.amountParameterName, LoanApiConstants.chargeTimeTypeParameterName,
LoanApiConstants.chargeCalculationTypeParameterName, LoanApiConstants.dueDateParamName));
final JsonArray array = topLevelJsonElement.get(LoanApiConstants.chargesParameterName).getAsJsonArray();
for (int i = 1; i <= array.size(); i++) {
final JsonObject loanChargeElement = array.get(i - 1).getAsJsonObject();
final String arrayObjectJson = this.fromApiJsonHelper.toJson(loanChargeElement);
this.fromApiJsonHelper.checkForUnsupportedParameters(arrayObjectParameterTypeOfMap, arrayObjectJson,
supportedParameters);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.chargeIdParameterName,
loanChargeElement);
baseDataValidator.reset().parameter(LoanApiConstants.chargesParameterName)
.parameterAtIndexArray(LoanApiConstants.chargeIdParameterName, i).value(chargeId).notNull()
.integerGreaterThanZero();
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.amountParameterName,
loanChargeElement, locale);
baseDataValidator.reset().parameter(LoanApiConstants.chargesParameterName)
.parameterAtIndexArray(LoanApiConstants.amountParameterName, i).value(amount).notNull().positiveAmount();
this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.dueDateParamName, loanChargeElement, dateFormat, locale);
}
}
}
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanTypeStr).notNull();
if (!StringUtils.isBlank(loanTypeStr)) {
final AccountType loanType = AccountType.fromName(loanTypeStr);
if (loanType.isInvalid()) {
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanType.getValue())
.isOneOfEnumValues(AccountType.class);
}
if (!loanType.isInvalid() && loanType.isIndividualAccount()) {
// collateral
final String collateralParameterName = LoanApiConstants.collateralParameterName;
if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(collateralParameterName, element)) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.get(LoanApiConstants.collateralParameterName).isJsonArray()) {
final Type collateralParameterTypeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
final Set<String> supportedParameters = new HashSet<>(Arrays.asList(LoanApiConstants.idParameterName,
LoanApiConstants.clientCollateralIdParameterName, LoanApiConstants.quantityParameterName));
final JsonArray array = topLevelJsonElement.get(LoanApiConstants.collateralParameterName).getAsJsonArray();
if (array.size() > 0) {
BigDecimal totalAmount = BigDecimal.ZERO;
for (int i = 1; i <= array.size(); i++) {
final JsonObject collateralItemElement = array.get(i - 1).getAsJsonObject();
final String collateralJson = this.fromApiJsonHelper.toJson(collateralItemElement);
this.fromApiJsonHelper.checkForUnsupportedParameters(collateralParameterTypeOfMap, collateralJson,
supportedParameters);
final Long id = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.idParameterName,
collateralItemElement);
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName)
.parameterAtIndexArray(LoanApiConstants.idParameterName, i).value(id).ignoreIfNull();
final Long clientCollateralId = this.fromApiJsonHelper
.extractLongNamed(LoanApiConstants.clientCollateralIdParameterName, collateralItemElement);
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName)
.parameterAtIndexArray(LoanApiConstants.clientCollateralIdParameterName, i)
.value(clientCollateralId).notNull().integerGreaterThanZero();
final BigDecimal quantity = this.fromApiJsonHelper
.extractBigDecimalNamed(LoanApiConstants.quantityParameterName, collateralItemElement, locale);
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName)
.parameterAtIndexArray(LoanApiConstants.quantityParameterName, i).value(quantity).notNull()
.positiveAmount();
if (clientCollateralId != null || quantity != null) {
BigDecimal baseAmount = this.clientCollateralManagementRepositoryWrapper
.getCollateral(clientCollateralId).getCollaterals().getBasePrice();
BigDecimal pctToBase = this.clientCollateralManagementRepositoryWrapper
.getCollateral(clientCollateralId).getCollaterals().getPctToBase();
BigDecimal total = baseAmount.multiply(pctToBase).multiply(quantity);
totalAmount = totalAmount.add(total);
}
}
if (principal != null && principal.compareTo(totalAmount) > 0) {
throw new InvalidAmountOfCollaterals(totalAmount);
}
}
} else {
baseDataValidator.reset().parameter(collateralParameterName).expectedArrayButIsNot();
}
}
}
}
boolean meetingIdRequired = false;
// validate syncDisbursement
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.syncDisbursementWithMeetingParameterName, element)) {
final Boolean syncDisbursement = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.syncDisbursementWithMeetingParameterName, element);
if (syncDisbursement == null) {
baseDataValidator.reset().parameter(LoanApiConstants.syncDisbursementWithMeetingParameterName).value(syncDisbursement)
.trueOrFalseRequired(false);
} else if (syncDisbursement.booleanValue()) {
meetingIdRequired = true;
}
}
// if disbursement is synced then must have a meeting (calendar)
if (meetingIdRequired || this.fromApiJsonHelper.parameterExists(LoanApiConstants.calendarIdParameterName, element)) {
final Long calendarId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.calendarIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.calendarIdParameterName).value(calendarId).notNull()
.integerGreaterThanZero();
}
if (!atLeastOneParameterPassedForUpdate) {
final Object forceError = null;
baseDataValidator.reset().anyOfNotNull(forceError);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.emiAmountParameterName, element)) {
if (!(loanProduct.canDefineInstallmentAmount() || loanProduct.isMultiDisburseLoan())) {
List<String> unsupportedParameterList = new ArrayList<>();
unsupportedParameterList.add(LoanApiConstants.emiAmountParameterName);
throw new UnsupportedParameterException(unsupportedParameterList);
}
if (isEqualAmortization) {
throw new EqualAmortizationUnsupportedFeatureException("fixed.emi", "fixed emi");
}
final BigDecimal emiAnount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAnount).ignoreIfNull().positiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.maxOutstandingBalanceParameterName, element)) {
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance)
.ignoreIfNull().positiveAmount();
}
if (loanProduct.canUseForTopup() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.isTopup, element)) {
final Boolean isTopup = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isTopup, element);
baseDataValidator.reset().parameter(LoanApiConstants.isTopup).value(isTopup).ignoreIfNull().validateForBooleanValue();
if (isTopup != null && isTopup) {
final Long loanId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanIdToClose, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanIdToClose).value(loanId).notNull().longGreaterThanZero();
}
}
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct);
String loanScheduleProcessingType = existingLoanApplication.getLoanRepaymentScheduleDetail().getLoanScheduleProcessingType().name();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, element)) {
loanScheduleProcessingType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE,
element);
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE).value(loanScheduleProcessingType)
.ignoreIfNull().isOneOfEnumValues(LoanScheduleProcessingType.class);
}
if (LoanScheduleProcessingType.VERTICAL.equals(LoanScheduleProcessingType.valueOf(loanScheduleProcessingType))
&& !AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategy)) {
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE).failWithCode(
"supported.only.with.advanced.payment.allocation.strategy",
"Vertical repayment schedule processing is only available with `Advanced payment allocation` strategy");
}
List<LoanProductPaymentAllocationRule> allocationRules = loanProduct.getPaymentAllocationRules();
if (LoanScheduleProcessingType.HORIZONTAL.name().equals(loanScheduleProcessingType)
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategy)) {
advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(allocationRules);
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
public void validateForUndo(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Set<String> undoSupportedParameters = new HashSet<>(List.of(LoanApiConstants.noteParamName));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(LOANAPPLICATION_UNDO);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String note = "note";
if (this.fromApiJsonHelper.parameterExists(note, element)) {
final String noteText = this.fromApiJsonHelper.extractStringNamed(note, element);
baseDataValidator.reset().parameter(note).value(noteText).notExceedingLengthOf(1000);
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final BigDecimal minPrincipal = loanProduct.getMinPrincipalAmount().getAmount();
final BigDecimal maxPrincipal = loanProduct.getMaxPrincipalAmount().getAmount();
final String principalParameterName = LoanApiConstants.principalParameterName;
if (this.fromApiJsonHelper.parameterExists(principalParameterName, element)) {
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element);
baseDataValidator.reset().parameter(principalParameterName).value(principal).notNull().positiveAmount()
.inMinAndMaxAmountRange(minPrincipal, maxPrincipal);
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
public void validateLoanTermAndRepaidEveryValues(final Integer loanTermFrequency, final Integer loanTermFrequencyType,
final Integer numberOfRepayments, final Integer repaymentEvery, final Integer repaymentEveryType, final Loan loan) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
this.apiJsonHelper.validateSelectedPeriodFrequencyTypeIsTheSame(dataValidationErrors, loanTermFrequency, loanTermFrequencyType,
numberOfRepayments, repaymentEvery, repaymentEveryType);
/**
* For multi-disbursal loans where schedules are auto-generated based on a fixed EMI, ensure the number of
* repayments is within the permissible range defined by the loan product
**/
if (loan.getFixedEmiAmount() != null) {
Integer minimumNoOfRepayments = loan.loanProduct().getMinNumberOfRepayments();
Integer maximumNoOfRepayments = loan.loanProduct().getMaxNumberOfRepayments();
Integer actualNumberOfRepayments = loan.getLoanRepaymentScheduleInstallmentsSize();
// validate actual number of repayments is > minimum number of
// repayments
if (minimumNoOfRepayments != null && minimumNoOfRepayments != 0 && actualNumberOfRepayments < minimumNoOfRepayments) {
final ApiParameterError error = ApiParameterError.generalError(
"validation.msg.loan.numberOfRepayments.lesser.than.minimumNumberOfRepayments",
"The total number of calculated repayments for this loan " + actualNumberOfRepayments
+ " is lesser than the allowed minimum of " + minimumNoOfRepayments,
actualNumberOfRepayments, minimumNoOfRepayments);
dataValidationErrors.add(error);
}
// validate actual number of repayments is < maximum number of
// repayments
if (maximumNoOfRepayments != null && maximumNoOfRepayments != 0 && actualNumberOfRepayments > maximumNoOfRepayments) {
final ApiParameterError error = ApiParameterError.generalError(
"validation.msg.loan.numberOfRepayments.greater.than.maximumNumberOfRepayments",
"The total number of calculated repayments for this loan " + actualNumberOfRepayments
+ " is greater than the allowed maximum of " + maximumNoOfRepayments,
actualNumberOfRepayments, maximumNoOfRepayments);
dataValidationErrors.add(error);
}
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
public void validatelinkedSavingsAccount(final SavingsAccount savingsAccount, final Loan loanApplication) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
if (savingsAccount.isNotActive()) {
final ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.linked.savings.account.is.not.active",
"Linked Savings account with id:" + savingsAccount.getId() + " is not in active state", "linkAccountId",
savingsAccount.getId());
dataValidationErrors.add(error);
} else if (!loanApplication.getClientId().equals(savingsAccount.clientId())) {
final ApiParameterError error = ApiParameterError.parameterError(
"validation.msg.loan.linked.savings.account.not.belongs.to.same.client",
"Linked Savings account with id:" + savingsAccount.getId() + " is not belongs to the same client", "linkAccountId",
savingsAccount.getId());
dataValidationErrors.add(error);
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
private void validateDisbursementsAreDatewiseOrdered(JsonElement element, final DataValidatorBuilder baseDataValidator) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final JsonArray variationArray = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName,
element);
if (variationArray != null) {
for (int i = 0; i < variationArray.size(); i++) {
final JsonObject jsonObject1 = variationArray.get(i).getAsJsonObject();
if (jsonObject1.has(LoanApiConstants.expectedDisbursementDateParameterName)) {
LocalDate date1 = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName,
jsonObject1, dateFormat, locale);
for (int j = i + 1; j < variationArray.size(); j++) {
final JsonObject jsonObject2 = variationArray.get(j).getAsJsonObject();
if (jsonObject2.has(LoanApiConstants.expectedDisbursementDateParameterName)) {
LocalDate date2 = this.fromApiJsonHelper.extractLocalDateNamed(
LoanApiConstants.expectedDisbursementDateParameterName, jsonObject2, dateFormat, locale);
if (DateUtils.isAfter(date1, date2)) {
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName)
.failWithCode(LoanApiConstants.DISBURSEMENT_DATES_NOT_IN_ORDER);
}
}
}
}
}
}
}
public void validateLoanMultiDisbursementDate(final JsonElement element, final DataValidatorBuilder baseDataValidator,
LocalDate expectedDisbursement, BigDecimal totalPrincipal) {
this.validateDisbursementsAreDatewiseOrdered(element, baseDataValidator);
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.disbursementDataParameterName, element) && expectedDisbursement != null
&& totalPrincipal != null) {
BigDecimal tatalDisbursement = BigDecimal.ZERO;
final JsonArray variationArray = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName,
element);
List<LocalDate> expectedDisbursementDates = new ArrayList<>();
if (variationArray != null && variationArray.size() > 0) {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) {
boolean isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isEqualAmortizationParam,
element);
if (isEqualAmortization) {
throw new EqualAmortizationUnsupportedFeatureException("tranche.disbursal", "tranche disbursal");
}
}
int i = 0;
do {
final JsonObject jsonObject = variationArray.get(i).getAsJsonObject();
LocalDate expectedDisbursementDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, jsonObject, dateFormat, locale);
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName)
.parameterAtIndexArray(LoanApiConstants.expectedDisbursementDateParameterName, i)
.value(expectedDisbursementDate).notNull();
if (i == 0 && expectedDisbursementDate != null && !expectedDisbursement.equals(expectedDisbursementDate)) {
baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName)
.failWithCode(LoanApiConstants.DISBURSEMENT_DATE_START_WITH_ERROR);
} else if (i > 0 && expectedDisbursementDate != null
&& DateUtils.isBefore(expectedDisbursementDate, expectedDisbursement)) {
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName)
.failWithCode(LoanApiConstants.DISBURSEMENT_DATE_BEFORE_ERROR);
}
if (expectedDisbursementDate != null && expectedDisbursementDates.contains(expectedDisbursementDate)) {
baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName)
.failWithCode(LoanApiConstants.DISBURSEMENT_DATE_UNIQUE_ERROR);
}
expectedDisbursementDates.add(expectedDisbursementDate);
BigDecimal principal = this.fromApiJsonHelper
.extractBigDecimalNamed(LoanApiConstants.disbursementPrincipalParameterName, jsonObject, locale);
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName)
.parameterAtIndexArray(LoanApiConstants.disbursementPrincipalParameterName, i).value(principal).notBlank();
if (principal != null) {
tatalDisbursement = tatalDisbursement.add(principal);
}
i++;
} while (i < variationArray.size());
if (tatalDisbursement.compareTo(totalPrincipal) > 0) {
baseDataValidator.reset().parameter(LoanApiConstants.disbursementPrincipalParameterName)
.failWithCode(LoanApiConstants.APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES);
}
final Integer interestType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(LoanApiConstants.interestTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType).ignoreIfNull()
.integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
}
}
}
public void validateLoanForInterestRecalculation(final Loan loan) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
loanChargeApiJsonValidator.validateLoanCharges(loan.getActiveCharges(), dataValidationErrors);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
public void validateLoanForCollaterals(final Loan loan, final BigDecimal total) {
String errorCode;
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
if (loan.getProposedPrincipal().compareTo(total) >= 0) {
errorCode = LoanApiConstants.LOAN_COLLATERAL_TOTAL_VALUE_SHOULD_BE_SUFFICIENT;
baseDataValidator.reset().parameter(LoanApiConstants.collateralsParameterName).failWithCode(errorCode);
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
private void validatePartialPeriodSupport(final Integer interestCalculationPeriodType, final DataValidatorBuilder baseDataValidator,
final JsonElement element, final LoanProduct loanProduct) {
if (interestCalculationPeriodType != null) {
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod
.fromInt(interestCalculationPeriodType);
boolean considerPartialPeriodUpdates = interestCalculationPeriodMethod.isDaily() ? interestCalculationPeriodMethod.isDaily()
: loanProduct.getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalcualtion();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME,
element)) {
final Boolean considerPartialInterestEnabled = this.fromApiJsonHelper
.extractBooleanNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, element);
baseDataValidator.reset().parameter(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME)
.value(considerPartialInterestEnabled).notNull().isOneOfTheseValues(true, false);
boolean considerPartialPeriods = considerPartialInterestEnabled != null && considerPartialInterestEnabled;
if (interestCalculationPeriodMethod.isDaily()) {
if (considerPartialPeriods) {
baseDataValidator.reset().parameter(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME)
.failWithCode("not.supported.for.daily.calcualtions");
}
} else {
considerPartialPeriodUpdates = considerPartialPeriods;
}
}
if (!considerPartialPeriodUpdates) {
if (loanProduct.isInterestRecalculationEnabled()) {
baseDataValidator.reset().parameter(LoanProductConstants.IS_INTEREST_RECALCULATION_ENABLED_PARAMETER_NAME)
.failWithCode("not.supported.for.selected.interest.calcualtion.type");
}
if (loanProduct.isMultiDisburseLoan()) {
baseDataValidator.reset().parameter(LoanProductConstants.MULTI_DISBURSE_LOAN_PARAMETER_NAME)
.failWithCode("not.supported.for.selected.interest.calcualtion.type");
}
if (loanProduct.allowVariabeInstallments()) {
baseDataValidator.reset().parameter(LoanProductConstants.allowVariableInstallmentsParamName)
.failWithCode("not.supported.for.selected.interest.calcualtion.type");
}
if (loanProduct.isLinkedToFloatingInterestRate()) {
baseDataValidator.reset().parameter("isLinkedToFloatingInterestRates")
.failWithCode("not.supported.for.selected.interest.calcualtion.type");
}
}
}
}
}