/**
   * Decreases the registered error path, so that field highlighting can occur on the appropriate
   * object attribute
   *
   * @param errorPathPrefix
   */
  private void decreaseErrorPath(String errorPathPrefix) {
    MessageMap errorMap = GlobalVariables.getMessageMap();

    if (!StringUtils.isBlank(errorPathPrefix)) {
      errorMap.removeFromErrorPath(errorPathPrefix);
    }
  }
  /**
   * Validate non-ar/non-invoice line items on a PaymentApplicationDocument.
   *
   * @param nonInvoiced
   * @return
   */
  public static boolean validateNonInvoiced(
      NonInvoiced nonInvoiced,
      PaymentApplicationDocument paymentApplicationDocument,
      KualiDecimal totalFromControl)
      throws WorkflowException {
    MessageMap errorMap = GlobalVariables.getMessageMap();
    int originalErrorCount = errorMap.getErrorCount();

    //  validate the NonInvoiced BO
    String sNonInvoicedErrorPath = "nonInvoicedAddLine";
    errorMap.addToErrorPath(sNonInvoicedErrorPath);
    SpringContext.getBean(DictionaryValidationService.class).validateBusinessObject(nonInvoiced);
    errorMap.removeFromErrorPath(sNonInvoicedErrorPath);

    if (errorMap.getErrorCount() != originalErrorCount) {
      return false;
    }

    boolean isValid = true;

    // Required fields, so always validate these.
    nonInvoiced.refreshReferenceObject("account");
    if (ObjectUtils.isNull(nonInvoiced.getAccount())) {
      isValid &= false;
      putError(
          ArPropertyConstants.PaymentApplicationDocumentFields.NON_INVOICED_LINE_ACCOUNT,
          ArKeyConstants.PaymentApplicationDocumentErrors.NON_AR_ACCOUNT_INVALID,
          nonInvoiced.getAccountNumber());
    }
    isValid &=
        validateNonInvoicedLineItem(
            "chartOfAccountsCode",
            nonInvoiced.getChartOfAccountsCode(),
            Chart.class,
            ArPropertyConstants.PaymentApplicationDocumentFields.NON_INVOICED_LINE_CHART,
            ArKeyConstants.PaymentApplicationDocumentErrors.NON_AR_CHART_INVALID);
    isValid &=
        validateNonInvoicedLineItem(
            "accountNumber",
            nonInvoiced.getAccountNumber(),
            Account.class,
            ArPropertyConstants.PaymentApplicationDocumentFields.NON_INVOICED_LINE_ACCOUNT,
            ArKeyConstants.PaymentApplicationDocumentErrors.NON_AR_ACCOUNT_INVALID);
    isValid &=
        validateNonInvoicedLineItem(
            "financialObjectCode",
            nonInvoiced.getFinancialObjectCode(),
            ObjectCode.class,
            ArPropertyConstants.PaymentApplicationDocumentFields.NON_INVOICED_LINE_OBJECT,
            ArKeyConstants.PaymentApplicationDocumentErrors.NON_AR_OBJECT_CODE_INVALID);

    // Optional fields, so only validate if a value was entered.
    if (StringUtils.isNotBlank(nonInvoiced.getSubAccountNumber())) {
      isValid &=
          validateNonInvoicedLineItem(
              "subAccountNumber",
              nonInvoiced.getSubAccountNumber(),
              SubAccount.class,
              ArPropertyConstants.PaymentApplicationDocumentFields.NON_INVOICED_LINE_SUBACCOUNT,
              ArKeyConstants.PaymentApplicationDocumentErrors.NON_AR_SUB_ACCOUNT_INVALID);
    }
    if (StringUtils.isNotBlank(nonInvoiced.getFinancialSubObjectCode())) {
      isValid &=
          validateNonInvoicedLineItem(
              "financialSubObjectCode",
              nonInvoiced.getFinancialSubObjectCode(),
              SubObjectCode.class,
              ArPropertyConstants.PaymentApplicationDocumentFields.NON_INVOICED_LINE_SUBOBJECT,
              ArKeyConstants.PaymentApplicationDocumentErrors.NON_AR_SUB_OBJECT_CODE_INVALID);
    }
    if (StringUtils.isNotBlank(nonInvoiced.getProjectCode())) {
      isValid &=
          validateNonInvoicedLineItem(
              "code",
              nonInvoiced.getProjectCode(),
              ProjectCode.class,
              ArPropertyConstants.PaymentApplicationDocumentFields.NON_INVOICED_LINE_PROJECT,
              ArKeyConstants.PaymentApplicationDocumentErrors.NON_AR_PROJECT_CODE_INVALID);
    }

    isValid &=
        validateNonInvoicedLineAmount(nonInvoiced, paymentApplicationDocument, totalFromControl);

    return isValid;
  }
  /**
   * Validate budget rates. Applicable rates are mandatory
   *
   * @param budgetDocument
   * @return
   */
  protected boolean processBudgetRatesBusinessRule(BudgetDocument budgetDocument) {
    boolean valid = true;
    final int APPLICABLE_RATE_LENGTH_EXCEEDED = 1;
    final int APPLICABLE_RATE_NEGATIVE = -1;

    MessageMap errorMap = GlobalVariables.getMessageMap();
    int i = 0;
    for (BudgetRate budgetRate : budgetDocument.getBudget().getBudgetRates()) {
      String rateClassType = budgetRate.getRateClass().getRateClassTypeT().getDescription();
      String errorPath = "budgetProposalRate[" + rateClassType + "][" + i + "]";
      errorMap.addToErrorPath(errorPath);
      /* look for applicable rate */
      if (budgetRate.isApplicableRateNull()) {
        valid = false;
        errorMap.putError("applicableRate", KeyConstants.ERROR_REQUIRED_APPLICABLE_RATE);
      } else if (!ScaleTwoDecimal.isNumeric(budgetRate.getApplicableRate().toString())) {
        valid = false;
        errorMap.putError("applicableRate", KeyConstants.ERROR_APPLICABLE_RATE_NOT_NUMERIC);
      } else {
        switch (verifyApplicableRate(budgetRate.getApplicableRate())) {
          case APPLICABLE_RATE_LENGTH_EXCEEDED:
            valid = false;
            errorMap.putError(
                "applicableRate",
                KeyConstants.ERROR_APPLICABLE_RATE_LIMIT,
                Constants.APPLICABLE_RATE_LIMIT);
            break;
          case APPLICABLE_RATE_NEGATIVE:
            valid = false;
            errorMap.putError("applicableRate", KeyConstants.ERROR_APPLICABLE_RATE_NEGATIVE);
            break;
        }
      }
      errorMap.removeFromErrorPath(errorPath);
      i++;
    }

    i = 0;
    for (BudgetLaRate budgetLaRate : budgetDocument.getBudget().getBudgetLaRates()) {
      String rateClassType = "";
      if (ObjectUtils.isNotNull(budgetLaRate.getRateClass())
          && ObjectUtils.isNotNull(budgetLaRate.getRateClass().getRateClassTypeT())) {
        rateClassType = budgetLaRate.getRateClass().getRateClassTypeT().getDescription();
      }
      String errorPath = "budgetRate[" + rateClassType + "][" + i + "]";
      errorMap.addToErrorPath(errorPath);
      /* look for applicable rate */
      if (budgetLaRate.isApplicableRateNull()) {
        valid = false;
        errorMap.putError("applicableRate", KeyConstants.ERROR_REQUIRED_APPLICABLE_RATE);
      } else if (!ScaleTwoDecimal.isNumeric(budgetLaRate.getApplicableRate().toString())) {
        valid = false;
        errorMap.putError("applicableRate", KeyConstants.ERROR_APPLICABLE_RATE_NOT_NUMERIC);
      } else {
        switch (verifyApplicableRate(budgetLaRate.getApplicableRate())) {
          case APPLICABLE_RATE_LENGTH_EXCEEDED:
            valid = false;
            errorMap.putError(
                "applicableRate",
                KeyConstants.ERROR_APPLICABLE_RATE_LIMIT,
                Constants.APPLICABLE_RATE_LIMIT);
            break;
          case APPLICABLE_RATE_NEGATIVE:
            valid = false;
            errorMap.putError("applicableRate", KeyConstants.ERROR_APPLICABLE_RATE_NEGATIVE);
            break;
        }
      }
      errorMap.removeFromErrorPath(errorPath);
      i++;
    }

    return valid;
  }
  /**
   * Validate budget project income. costshare percentage must be between 0 and 999.99
   *
   * @param budgetDocument
   * @return
   */
  protected boolean processBudgetProjectIncomeBusinessRule(BudgetDocument budgetDocument) {
    boolean valid = true;
    MessageMap errorMap = GlobalVariables.getMessageMap();
    int i = 0;
    for (BudgetCostShare budgetCostShare : budgetDocument.getBudget().getBudgetCostShares()) {
      String errorPath = "budgetCostShare[" + i + "]";
      errorMap.addToErrorPath(errorPath);
      if (budgetCostShare.getSharePercentage() != null
          && (budgetCostShare.getSharePercentage().isLessThan(new ScaleTwoDecimal(0))
              || budgetCostShare.getSharePercentage().isGreaterThan(new ScaleTwoDecimal(100)))) {
        errorMap.putError("sharePercentage", KeyConstants.ERROR_COST_SHARE_PERCENTAGE);
        valid = false;
      }
      // check for duplicate fiscal year and source accounts on all unchecked cost shares
      if (i < budgetDocument.getBudget().getBudgetCostShareCount()) {
        for (int j = i + 1; j < budgetDocument.getBudget().getBudgetCostShareCount(); j++) {
          BudgetCostShare tmpCostShare = budgetDocument.getBudget().getBudgetCostShare(j);
          int thisFiscalYear =
              budgetCostShare.getProjectPeriod() == null
                  ? Integer.MIN_VALUE
                  : budgetCostShare.getProjectPeriod();
          int otherFiscalYear =
              tmpCostShare.getProjectPeriod() == null
                  ? Integer.MIN_VALUE
                  : tmpCostShare.getProjectPeriod();
          if (thisFiscalYear == otherFiscalYear
              && StringUtils.equalsIgnoreCase(
                  budgetCostShare.getSourceAccount(), tmpCostShare.getSourceAccount())) {
            errorMap.putError(
                "fiscalYear",
                KeyConstants.ERROR_COST_SHARE_DUPLICATE,
                thisFiscalYear == Integer.MIN_VALUE ? "" : thisFiscalYear + "",
                budgetCostShare.getSourceAccount() == null
                    ? "\"\""
                    : budgetCostShare.getSourceAccount());
            valid = false;
          }
        }
      }

      // validate project period stuff
      String currentField = "document.budget.budgetCostShare[" + i + "].projectPeriod";
      int numberOfProjectPeriods = budgetDocument.getBudget().getBudgetPeriods().size();
      boolean validationCheck =
          this.validateProjectPeriod(
              budgetCostShare.getProjectPeriod(), currentField, numberOfProjectPeriods);
      valid &= validationCheck;

      errorMap.removeFromErrorPath(errorPath);
      i++;
    }
    // check project income for values that are not greater than 0
    GlobalVariables.getMessageMap().removeFromErrorPath("budget");
    GlobalVariables.getMessageMap().addToErrorPath("budgets[0]");
    i = 0;
    for (BudgetProjectIncome budgetProjectIncome :
        budgetDocument.getBudget().getBudgetProjectIncomes()) {
      String errorPath = "budgetProjectIncomes[" + i + "]";
      errorMap.addToErrorPath(errorPath);
      if (budgetProjectIncome.getProjectIncome() == null
          || !budgetProjectIncome.getProjectIncome().isGreaterThan(new ScaleTwoDecimal(0.00))) {
        errorMap.putError("projectIncome", "error.projectIncome.negativeOrZero");
        valid = false;
      }
      errorMap.removeFromErrorPath(errorPath);
      i++;
    }
    GlobalVariables.getMessageMap().removeFromErrorPath("budgets[0]");
    GlobalVariables.getMessageMap().addToErrorPath("budget");

    return valid;
  }