public void validateForModify()

in fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java [879:1487]


    public void validateForModify(final JsonCommand command, final Loan loan) {
        String json = command.json();
        validateRequestBody(json);

        validateForSupportedParameters(json);

        if (!loan.isSubmittedAndPendingApproval()) {
            throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(loan.getId());
        }

        // If new loan product to be set
        LoanProduct loanProduct;
        final String productIdParamName = "productId";
        final Long productId = command.longValueOfParameterNamed(productIdParamName);
        if (productId == null || productId.equals(loan.getLoanProduct().getId())) {
            loanProduct = loan.getLoanProduct();
        } else {
            loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId));
        }

        Validator.validateOrThrow("loan", baseDataValidator -> {
            final JsonElement element = this.fromApiJsonHelper.parse(json);
            boolean atLeastOneParameterPassedForUpdate = false;

            Long clientId = loan.getClient() != null ? loan.getClient().getId() : null;
            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.clientIdParameterName, element)) {
                atLeastOneParameterPassedForUpdate = true;
                clientId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.clientIdParameterName, element);
                baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull()
                        .integerGreaterThanZero();
            }
            Client client = null;
            if (clientId != null) {
                client = this.clientRepository.findOneWithNotFoundDetection(clientId);
            }
            Long groupId = loan.getGroup() != null ? loan.getGroup().getId() : null;
            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.groupIdParameterName, element)) {
                atLeastOneParameterPassedForUpdate = true;
                groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element);
                baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull()
                        .integerGreaterThanZero();
            }
            Group group = null;
            if (groupId != null) {
                group = this.groupRepository.findOneWithNotFoundDetection(groupId);
            }

            if (productId != null) {
                atLeastOneParameterPassedForUpdate = true;
                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 = loan.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 = loan.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
                validateTransactionProcessingStrategy(transactionProcessingStrategy, loanProduct);
            }

            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);
            }

            Integer numberOfRepayments = loan.getNumberOfRepayments();
            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.numberOfRepaymentsParameterName, element)) {
                atLeastOneParameterPassedForUpdate = true;
                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 = loan.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 = loan.getLoanProductRelatedDetail().getInterestMethod().getValue();
                }
                if (InterestMethod.FLAT.getValue().equals(interestType)) {
                    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 = loan.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 = loan.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 = loan.getExpectedDisbursementDate();
            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();
            }

            Integer graceOnPrincipalPayment = loan.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
            // grace validation
            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.graceOnPrincipalPaymentParameterName, element)) {
                graceOnPrincipalPayment = this.fromApiJsonHelper
                        .extractIntegerWithLocaleNamed(LoanApiConstants.graceOnPrincipalPaymentParameterName, element);
                baseDataValidator.reset().parameter(LoanApiConstants.graceOnPrincipalPaymentParameterName).value(graceOnPrincipalPayment)
                        .zeroOrPositiveAmount();
            }

            Integer graceOnInterestPayment = loan.getLoanProductRelatedDetail().getGraceOnInterestPayment();
            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.graceOnInterestPaymentParameterName, element)) {
                graceOnInterestPayment = this.fromApiJsonHelper
                        .extractIntegerWithLocaleNamed(LoanApiConstants.graceOnInterestPaymentParameterName, element);
                baseDataValidator.reset().parameter(LoanApiConstants.graceOnInterestPaymentParameterName).value(graceOnInterestPayment)
                        .zeroOrPositiveAmount();
            }

            Integer graceOnInterestCharged = loan.getLoanProductRelatedDetail().getGraceOnInterestCharged();
            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.graceOnInterestChargedParameterName, element)) {
                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 (!loan.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);
            }

            validateLinkedSavingsAccount(element, baseDataValidator);

            // charges
            loanChargeApiJsonValidator.validateLoanCharges(element, loanProduct, baseDataValidator);

            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.isEmpty()) {
                                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.fixedEmiAmountParameterName, element)) {
                if (!(loanProduct.isCanDefineInstallmentAmount() || loanProduct.isMultiDisburseLoan())) {
                    List<String> unsupportedParameterList = new ArrayList<>();
                    unsupportedParameterList.add(LoanApiConstants.fixedEmiAmountParameterName);
                    throw new UnsupportedParameterException(unsupportedParameterList);
                }
                if (isEqualAmortization) {
                    throw new EqualAmortizationUnsupportedFeatureException("fixed.emi", "fixed emi");
                }
                final BigDecimal emiAnount = this.fromApiJsonHelper
                        .extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName, element);
                baseDataValidator.reset().parameter(LoanApiConstants.fixedEmiAmountParameterName).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.isCanUseForTopup() && 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();

                    LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName,
                            element);
                    if (submittedOnDate == null) {
                        submittedOnDate = loan.getSubmittedOnDate();
                    }
                    final Long loanIdToClose = command.longValueOfParameterNamed(LoanApiConstants.loanIdToClose);
                    final Loan loanToClose = this.loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, clientId);
                    if (loanToClose == null) {
                        throw new GeneralPlatformDomainRuleException(
                                "error.msg.loan.loanIdToClose.no.active.loan.associated.to.client.found",
                                "loanIdToClose is invalid, No Active Loan associated with the given Client ID found.");
                    }
                    if (loanToClose.isMultiDisburmentLoan()
                            && !loanToClose.getLoanProductRelatedDetail().isInterestRecalculationEnabled()) {
                        throw new GeneralPlatformDomainRuleException(
                                "error.msg.loan.topup.on.multi.tranche.loan.without.interest.recalculation.not.supported",
                                "Topup on loan with multi-tranche disbursal and without interest recalculation is not supported.");
                    }
                    final LocalDate disbursalDateOfLoanToClose = loanToClose.getDisbursementDate();
                    if (!DateUtils.isAfter(submittedOnDate, disbursalDateOfLoanToClose)) {
                        throw new GeneralPlatformDomainRuleException(
                                "error.msg.loan.submitted.date.should.be.after.topup.loan.disbursal.date",
                                "Submitted date of this loan application " + submittedOnDate
                                        + " should be after the disbursed date of loan to be closed " + disbursalDateOfLoanToClose);
                    }
                    if (!loanToClose.getCurrencyCode().equals(loanProduct.getCurrency().getCode())) {
                        throw new GeneralPlatformDomainRuleException("error.msg.loan.to.be.closed.has.different.currency",
                                "loanIdToClose is invalid, Currency code is different.");
                    }
                    final LocalDate lastUserTransactionOnLoanToClose = loanToClose.getLastUserTransactionDate();
                    if (DateUtils.isBefore(expectedDisbursementDate, lastUserTransactionOnLoanToClose)) {
                        throw new GeneralPlatformDomainRuleException(
                                "error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed",
                                "Disbursal date of this loan application " + expectedDisbursementDate
                                        + " should be after last transaction date of loan to be closed "
                                        + lastUserTransactionOnLoanToClose);
                    }
                    BigDecimal loanOutstanding = this.loanReadPlatformService
                            .retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, expectedDisbursementDate)
                            .getAmount();
                    final BigDecimal firstDisbursalAmount = getFirstDisbursalAmount(loan);
                    if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) {
                        throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed",
                                "Topup loan amount should be greater than outstanding amount of loan to be closed.");
                    }
                }
            }

            validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
            validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct);

            String loanScheduleProcessingType = loan.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 (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element)) {
                final Boolean isEnableInstallmentLevelDelinquency = this.fromApiJsonHelper
                        .extractBooleanNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element);
                baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY)
                        .value(isEnableInstallmentLevelDelinquency).validateForBooleanValue();
                if (loanProduct.getDelinquencyBucket() == null) {
                    if (isEnableInstallmentLevelDelinquency) {
                        baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY).failWithCode(
                                "can.be.enabled.for.loan.with.loan.product.having.valid.delinquency.bucket",
                                "Installment level delinquency cannot be enabled for a loan if Delinquency bucket is not configured for loan product");
                    }
                }
            }

            loanScheduleValidator.validateDownPaymentAttribute(loanProduct.getLoanProductRelatedDetail().isEnableDownPayment(), element);

            validateDisbursementDetails(loanProduct, element);
            validateSubmittedOnDate(element, loan.getSubmittedOnDate(), loan.getExpectedDisbursementDate(), loanProduct);
            validateClientOrGroup(client, group, productId);

            // validate if disbursement date is a holiday or a non-working day
            validateDisbursementDateIsOnNonWorkingDay(expectedDisbursementDate);
            final Long officeId = resolveOfficeId(client, group);
            validateDisbursementDateIsOnHoliday(expectedDisbursementDate, officeId);

            Integer recurringMoratoriumOnPrincipalPeriods = loan.getLoanProductRelatedDetail().getRecurringMoratoriumOnPrincipalPeriods();
            if (this.fromApiJsonHelper.parameterExists("recurringMoratoriumOnPrincipalPeriods", element)) {
                recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
                        .extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
            }
            validateBorrowerCycle(element, loanProduct, clientId, groupId, baseDataValidator);

            loanProductDataValidator.validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment,
                    graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);

            if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE, element)) {
                if (!LoanScheduleType.PROGRESSIVE.equals(loanProduct.getLoanProductRelatedDetail().getLoanScheduleType())) {
                    List<String> unsupportedParameterList = new ArrayList<>();
                    unsupportedParameterList.add(LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE);
                    throw new UnsupportedParameterException(unsupportedParameterList);
                }
            }
        });
    }