/**
  * The sum of all CORP accounting lines on the document
  *
  * @see
  *     org.kuali.kfs.sys.batch.service.PaymentSourceToExtractService#getPaymentAmount(org.kuali.kfs.sys.document.PaymentSource)
  */
 @Override
 public KualiDecimal getPaymentAmount(TEMReimbursementDocument document) {
   KualiDecimal amount = KualiDecimal.ZERO;
   for (TemSourceAccountingLine line :
       (List<TemSourceAccountingLine>) document.getSourceAccountingLines()) {
     if (StringUtils.equals(line.getCardType(), TemConstants.TRAVEL_TYPE_CORP)) {
       amount = amount.add(line.getAmount());
     }
   }
   return amount;
 }
 /**
  * Determines if a) the document is at the PaymentMethod route node; and b) the accountingLine is
  * for a travel advance
  *
  * @param document a travel document
  * @param accountingLine the accounting line to validate
  * @return true if the advance payment/payment method is correct and accessibility should not be
  *     checked; false otherwise
  */
 protected boolean isAdvancePaymentMethodException(
     Document document, TemSourceAccountingLine accountingLine) {
   return StringUtils.equals(
           TemConstants.TRAVEL_ADVANCE_ACCOUNTING_LINE_TYPE_CODE,
           accountingLine.getFinancialDocumentLineTypeCode())
       && document
           .getDocumentHeader()
           .getWorkflowDocument()
           .getCurrentNodeNames()
           .contains(KFSConstants.RouteLevelNames.PAYMENT_METHOD);
 }
 /**
  * Only build account details for corporate card accounting lines
  *
  * @see
  *     org.kuali.kfs.module.tem.document.service.TravelPaymentsHelperService#buildGenericPaymentAccountDetails(java.util.List)
  */
 protected List<PaymentAccountDetail> buildPaymentAccountDetails(
     List<? extends AccountingLine> accountingLines) {
   List<PaymentAccountDetail> details = new ArrayList<PaymentAccountDetail>();
   for (AccountingLine al : accountingLines) {
     final TemSourceAccountingLine accountingLine = (TemSourceAccountingLine) al;
     if (StringUtils.equals(accountingLine.getCardType(), TemConstants.TRAVEL_TYPE_CORP)) {
       PaymentAccountDetail pad = new PaymentAccountDetail();
       pad.setFinChartCode(accountingLine.getChartOfAccountsCode());
       pad.setAccountNbr(accountingLine.getAccountNumber());
       if (!StringUtils.isBlank(accountingLine.getSubAccountNumber())) {
         pad.setSubAccountNbr(accountingLine.getSubAccountNumber());
       } else {
         pad.setSubAccountNbr(KFSConstants.getDashSubAccountNumber());
       }
       pad.setFinObjectCode(accountingLine.getFinancialObjectCode());
       if (!StringUtils.isBlank(accountingLine.getFinancialSubObjectCode())) {
         pad.setFinSubObjectCode(accountingLine.getFinancialSubObjectCode());
       } else {
         pad.setFinSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
       }
       if (!StringUtils.isBlank(accountingLine.getOrganizationReferenceId())) {
         pad.setOrgReferenceId(accountingLine.getOrganizationReferenceId());
       }
       if (!StringUtils.isBlank(accountingLine.getProjectCode())) {
         pad.setProjectCode(accountingLine.getProjectCode());
       } else {
         pad.setProjectCode(KFSConstants.getDashProjectCode());
       }
       pad.setAccountNetAmount(accountingLine.getAmount());
       details.add(pad);
     }
   }
   return details;
 }
  /**
   * Builds the PaymentDetail for the given reimbursable travel & entertainment document
   *
   * @param document the reimbursable travel & entertainment document to create a payment for
   * @param processRunDate the date when the extraction is occurring
   * @return a PaymentDetail to add to the PaymentGroup
   */
  protected PaymentDetail buildPaymentDetail(
      TEMReimbursementDocument document, Date processRunDate) {
    if (LOG.isDebugEnabled()) {
      LOG.debug("buildPaymentDetail() started");
    }

    PaymentDetail pd = new PaymentDetail();
    if (StringUtils.isNotEmpty(document.getDocumentHeader().getOrganizationDocumentNumber())) {
      pd.setOrganizationDocNbr(document.getDocumentHeader().getOrganizationDocumentNumber());
    }
    pd.setCustPaymentDocNbr(document.getDocumentNumber());
    pd.setInvoiceDate(new java.sql.Date(processRunDate.getTime()));
    pd.setOrigInvoiceAmount(getPaymentAmount(document));
    pd.setInvTotDiscountAmount(KualiDecimal.ZERO);
    pd.setInvTotOtherCreditAmount(KualiDecimal.ZERO);
    pd.setInvTotOtherDebitAmount(KualiDecimal.ZERO);
    pd.setInvTotShipAmount(KualiDecimal.ZERO);
    pd.setNetPaymentAmount(getPaymentAmount(document));
    pd.setPrimaryCancelledPayment(Boolean.FALSE);
    pd.setFinancialDocumentTypeCode(getAchCheckDocumentType(document));
    pd.setFinancialSystemOriginCode(KFSConstants.ORIGIN_CODE_KUALI);
    pd.setPurchaseOrderNbr(document.getTravelDocumentIdentifier());
    pd.setOrganizationDocNbr(document.getTravelDocumentIdentifier());

    int line = 0;
    PaymentNoteText pnt = new PaymentNoteText();
    pnt.setCustomerNoteLineNbr(new KualiInteger(line++));
    final String travelerId =
        (!ObjectUtils.isNull(document.getTemProfile())
            ? (!ObjectUtils.isNull(document.getTemProfile().getPrincipal())
                ? document.getTemProfile().getPrincipal().getPrincipalName()
                : document.getTemProfile().getCustomerNumber())
            : document.getDocumentNumber()); // they got this far without a payee id?  that really
    // probably shouldn't happen
    pnt.setCustomerNoteText(
        "Info: " + travelerId + " " + document.getTemProfile().getPhoneNumber());
    pd.addNote(pnt);

    final String text = document.getDocumentHeader().getDocumentDescription();
    if (!StringUtils.isBlank(text)) {
      pnt = getPaymentSourceHelperService().buildNoteForCheckStubText(text, line);
      if (LOG.isDebugEnabled()) {
        LOG.debug("Creating check stub text note: " + pnt.getCustomerNoteText());
      }
      pd.addNote(pnt);
    }
    // Handle accounts, but only corporate card accounting lines
    List<TemSourceAccountingLine> corporateCardLines = new ArrayList<TemSourceAccountingLine>();
    for (TemSourceAccountingLine accountingLine :
        (List<TemSourceAccountingLine>) document.getSourceAccountingLines()) {
      if (StringUtils.equals(TemConstants.TRAVEL_TYPE_CORP, accountingLine.getCardType())) {
        corporateCardLines.add(accountingLine);
      }
    }
    final List<PaymentAccountDetail> paymentAccounts =
        buildPaymentAccountDetails(corporateCardLines);
    for (PaymentAccountDetail pad : paymentAccounts) {
      pd.addAccountDetail(pad);
    }

    return pd;
  }
  /**
   * @see
   *     org.kuali.kfs.sys.document.validation.Validation#validate(org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent)
   */
  @SuppressWarnings("rawtypes")
  @Override
  public boolean validate(AttributedDocumentEvent event) {
    final Person currentUser = GlobalVariables.getUserSession().getPerson();
    TemSourceAccountingLine line = null;
    if (event instanceof UpdateAccountingLineEvent) {
      line =
          (TemSourceAccountingLine) ((UpdateAccountingLineEvent) event).getUpdatedAccountingLine();
    } else {
      line = (TemSourceAccountingLine) ((AccountingLineEvent) event).getAccountingLine();
    }
    List<String> holdErrors = new ArrayList<String>();
    holdErrors.addAll(GlobalVariables.getMessageMap().getErrorPath());
    GlobalVariables.getMessageMap().clearErrorPath();
    TravelDocument travelDocument = (TravelDocument) event.getDocument();

    final boolean canUpdate =
        isAtTravelNode(event.getDocument().getDocumentHeader().getWorkflowDocument())
            || isAdvancePaymentMethodException(
                event.getDocument(),
                line); // Are we at the travel node?  If so, there's a chance that accounting lines
                       // changed; if they did, that
    // was a permission granted to the travel manager so we should allow it.  Also, if we're at
    // PaymentMethod and the line is an advance accounting line, that's allowed to

    boolean valid = true;
    String errorPath = TemPropertyConstants.NEW_SOURCE_ACCTG_LINE;
    for (TemSourceAccountingLine sourceLine :
        (List<TemSourceAccountingLine>) travelDocument.getSourceAccountingLines()) {
      if (line.equals(sourceLine)) {
        errorPath =
            "document."
                + TemPropertyConstants.SOURCE_ACCOUNTING_LINE
                + "["
                + travelDocument.getSourceAccountingLines().indexOf(line)
                + "]";
        break;
      }
    }

    // Test added accounting lines for null values and if there is an access change.
    valid = getTravelDocumentService().validateSourceAccountingLines(travelDocument, false);

    if ((!travelDocument
            .getAppDocStatus()
            .equalsIgnoreCase(TemConstants.TRAVEL_DOC_APP_DOC_STATUS_INIT))
        && (!travelDocument
            .getAppDocStatus()
            .equalsIgnoreCase(TemConstants.TravelAuthorizationStatusCodeKeys.IN_PROCESS))
        && (!travelDocument
            .getAppDocStatus()
            .equalsIgnoreCase(TemConstants.TravelAuthorizationStatusCodeKeys.CHANGE_IN_PROCESS))) {
      if (!line.getAccount()
              .getAccountFiscalOfficerUser()
              .getPrincipalId()
              .equals(currentUser.getPrincipalId())
          && !canUpdate) {
        GlobalVariables.getMessageMap()
            .putError(
                KFSPropertyConstants.ACCOUNT_NUMBER,
                TemKeyConstants.ERROR_TA_FISCAL_OFFICER_ACCOUNT,
                line.getAccountNumber());
        return false;
      }
    }
    GlobalVariables.getMessageMap().addToErrorPath(errorPath);

    // skip accounting line validation for TA
    if (!(event.getDocument() instanceof TravelAuthorizationDocument)) {
      if (ObjectUtils.isNotNull(line.getObjectTypeCode())) {
        // check to make sure they're the same
        List<AccountingDistribution> list =
            getAccountingDistributionService().buildDistributionFrom(travelDocument);
        List<AccountingLineDistributionKey> distributionList =
            new ArrayList<AccountingLineDistributionKey>();
        List<String> expectedObjectCodes = new ArrayList<String>();
        for (AccountingDistribution dist : list) {
          distributionList.add(
              new AccountingLineDistributionKey(dist.getObjectCode(), dist.getCardType()));
          expectedObjectCodes.add(dist.getObjectCode());
        }
        final String expectedObjectCodesString = StringUtils.join(expectedObjectCodes, ", ");

        if (!distributionList.contains(
            new AccountingLineDistributionKey(line.getFinancialObjectCode(), line.getCardType()))) {
          GlobalVariables.getMessageMap()
              .putError(
                  TravelAuthorizationFields.FIN_OBJ_CD,
                  TemKeyConstants.ERROR_TEM_ACCOUNTING_LINES_OBJECT_CODE_CARD_TYPE,
                  line.getFinancialObjectCode(),
                  line.getCardType(),
                  expectedObjectCodesString);
          valid &= false;
        }
      }
    }

    if (line.getAmount().isLessEqual(KualiDecimal.ZERO) && !travelDocument.getBlanketTravel()) {
      GlobalVariables.getMessageMap()
          .putError(
              KFSPropertyConstants.AMOUNT,
              KFSKeyConstants.ERROR_CUSTOM,
              "Amount must be greater than zero.");
      valid &= false;
    }

    if (valid) {
      // Fly America validation
      TravelDocument document = (TravelDocument) event.getDocument();
      List<TemExpense> allExpenses = new ArrayList<TemExpense>();
      allExpenses.addAll(document.getImportedExpenses());
      allExpenses.addAll(document.getActualExpenses());
      if (allExpenses.size() > 0) {
        boolean hasAttachment = false;
        boolean showFlyAmerica = false;
        for (Note note : document.getNotes()) {
          if (note.getAttachment() != null) {
            hasAttachment = true;
            break;
          }
        }
        boolean isCGEnabled =
            getParameterService()
                .getParameterValueAsBoolean(
                    KFSConstants.CoreModuleNamespaces.CHART,
                    KFSConstants.RouteLevelNames.ACCOUNT,
                    KFSConstants.ChartApcParms.ACCOUNT_FUND_GROUP_DENOTES_CG);
        if (isCGEnabled) {
          for (TemExpense expense : allExpenses) {
            if (expense.getExpenseTypeCode().equals(TemConstants.ExpenseTypes.AIRFARE)) {
              Map<String, Object> fieldValues = new HashMap<String, Object>();
              fieldValues.put(KRADPropertyConstants.CODE, TemConstants.ExpenseTypes.AIRFARE);
              fieldValues.put(KRADPropertyConstants.NAME, expense.getTravelCompanyCodeName());
              TravelCompanyCode travelCompany =
                  getBusinessObjectService().findByPrimaryKey(TravelCompanyCode.class, fieldValues);
              if (travelCompany != null && travelCompany.isForeignCompany()) {
                String financialObjectCode =
                    expense.getExpenseTypeObjectCode() != null
                        ? expense.getExpenseTypeObjectCode().getFinancialObjectCode()
                        : null;
                if (travelDocument instanceof TravelAuthorizationDocument
                    && expense instanceof ActualExpense) {
                  if (document.getTripType() != null) {
                    financialObjectCode = document.getTripType().getEncumbranceObjCode();
                  }
                }
                if (financialObjectCode != null
                    && financialObjectCode.equals(line.getFinancialObjectCode())) {
                  String cg =
                      getParameterService()
                          .getParameterValueAsString(
                              KFSConstants.CoreModuleNamespaces.CHART,
                              KFSConstants.RouteLevelNames.ACCOUNT,
                              KFSConstants.ChartApcParms.ACCOUNT_CG_DENOTING_VALUE);
                  if (line.getAccount() == null) {
                    line.refreshReferenceObject(KFSPropertyConstants.ACCOUNT);
                  }
                  if (line.getAccount().getSubFundGroup() == null) {
                    line.refreshReferenceObject(KFSPropertyConstants.SUB_FUND_GROUP);
                  }
                  if (line.getAccount().getSubFundGroup().getFundGroupCode().equals(cg)) {
                    showFlyAmerica = true;
                  }
                }
              }
            }
          }
        }

        // Fly America error has been triggered, determine what accounting line to show it on.
        if (showFlyAmerica && !hasAttachment) {
          boolean newLine = true;
          for (TemSourceAccountingLine sourceLine :
              (List<TemSourceAccountingLine>) travelDocument.getSourceAccountingLines()) {
            if (line.equals(sourceLine)) {
              newLine = false;
            }
          }
          // if line is a new accounting line or a current one being saved/submitted in the
          // document.
          // figure out where the new accounting line will be added and set the error to that line #
          if (newLine) {
            GlobalVariables.getMessageMap().clearErrorPath();

            int newIndex =
                document
                        .getSourceAccountingLine(document.getSourceAccountingLines().size() - 1)
                        .getSequenceNumber()
                    + 1;
            errorPath =
                "document." + TemPropertyConstants.SOURCE_ACCOUNTING_LINE + "[" + newIndex + "]";
            GlobalVariables.getMessageMap().addToErrorPath(errorPath);
          }

          GlobalVariables.getMessageMap()
              .putError(
                  KFSPropertyConstants.ACCOUNT_NUMBER, TemKeyConstants.ERROR_ACCOUNTING_LINE_CG);
        }
      }
    }

    GlobalVariables.getMessageMap().clearErrorPath();
    GlobalVariables.getMessageMap().getErrorPath().addAll(holdErrors);

    return valid;
  }