/** get the traveler profile associated with the given travel document */
 protected TemProfile getTravelProfile(TravelDocument travelDocument) {
   Integer travelProfileId = travelDocument.getProfileId();
   if (travelProfileId != null) {
     return this.getBusinessObjectService()
         .findBySinglePrimaryKey(TemProfile.class, travelProfileId);
   }
   return null;
 }
  /** build mail message object from the given travel document */
  @SuppressWarnings("null")
  protected MailMessage buildDocumentStatusChangeMailMessage(
      TravelDocument travelDocument,
      DocumentRouteStatusChange statusChangeDTO,
      NotificationPreference preference) {
    MailMessage mailMessage = new MailMessage();

    String senderEmailAddress = this.getNotificationSender();
    mailMessage.setFromAddress(senderEmailAddress);

    TravelerDetail traveler = travelDocument.getTraveler();
    String travelerEmailAddress = null;
    if (!ObjectUtils.isNull(traveler) && !ObjectUtils.isNull(travelDocument.getProfileId())) {
      TemProfile profile =
          SpringContext.getBean(TemProfileService.class)
              .findTemProfileById(travelDocument.getProfileId());
      travelerEmailAddress = profile.getEmailAddress();
    } else {
      travelerEmailAddress = traveler.getEmailAddress();
    }

    if (senderEmailAddress != null && travelerEmailAddress != null) {
      mailMessage.addToAddress(travelerEmailAddress);

      String notificationSubject = this.getNotificationSubject(preference);
      mailMessage.setSubject(notificationSubject);

      String notificationBody =
          this.buildNotificationBody(travelDocument, statusChangeDTO, preference);
      mailMessage.setMessage(notificationBody);

      return mailMessage;
    }

    return null;
  }
  /**
   * @see
   *     org.kuali.kfs.module.tem.service.TravelDocumentNotificationService#sendNotificationOnChange(org.kuali.kfs.module.tem.document.TravelDocument,
   *     org.kuali.rice.kew.dto.DocumentRouteStatusChange)
   */
  @Override
  public void sendNotificationOnChange(
      TravelDocument travelDocument, DocumentRouteStatusChange statusChangeDTO) {
    String documentTypeCode =
        travelDocument.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
    NotificationPreference preference = null;
    if (this.isNotificationEnabled()) {
      TemProfile travelProfile = this.getTravelProfile(travelDocument);
      String newRouteStatus = statusChangeDTO.getNewRouteStatus();

      if (travelProfile == null) {
        LOG.error("travelProfile is null.");
        return;
      }

      if (travelDocument instanceof TravelAuthorizationDocument) {
        if (!verifyDocumentTypeCodes(
            documentTypeCode, this.getEligibleTravelAuthorizationDocumentTypeCodes())) {
          return;
        }

        preference =
            getEmailNotificationPreference(
                preference,
                newRouteStatus,
                travelProfile.getNotifyTAFinal(),
                travelProfile.getNotifyTAStatusChange(),
                documentTypeCode);
      } else {
        // TR/ENT/RELO
        if (!verifyDocumentTypeCodes(
            documentTypeCode, this.getEligibleTravelExpenseDocumentTypeCodes())) {
          return;
        }

        preference =
            getEmailNotificationPreference(
                preference,
                newRouteStatus,
                travelProfile.getNotifyTERFinal(),
                travelProfile.getNotifyTERStatusChange(),
                documentTypeCode);
      }

      this.sendNotificationByPreference(travelDocument, statusChangeDTO, preference);
    }
  }
  /**
   * @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;
  }
  @Override
  public boolean validate(AttributedDocumentEvent event) {
    boolean rulePassed = true;
    TravelDocument travelDocument = (TravelDocument) event.getDocument();
    GlobalVariables.getMessageMap().addToErrorPath(KRADPropertyConstants.DOCUMENT);
    // Actual Expenses
    int counter = 0;
    for (ActualExpense actualExpense : travelDocument.getActualExpenses()) {
      String property = TemPropertyConstants.ACTUAL_EXPENSES + "[" + counter + "]";
      /*
       * Determine if the detail is an amount that doesn't go over the threshold
       */
      KualiDecimal total = actualExpense.getTotalDetailExpenseAmount();
      if (!total.isZero()) {
        if (total.isGreaterThan(actualExpense.getExpenseAmount())) {
          GlobalVariables.getMessageMap()
              .putError(
                  property + "." + TemPropertyConstants.EXPENSE_AMOUNT,
                  TemKeyConstants.ERROR_TEM_DETAIL_GREATER_THAN_EXPENSE);
          rulePassed = false;
        } else if (total.isLessThan(actualExpense.getExpenseAmount())) {
          GlobalVariables.getMessageMap()
              .putError(
                  property + "." + TemPropertyConstants.EXPENSE_AMOUNT,
                  TemKeyConstants.ERROR_TEM_DETAIL_LESS_THAN_EXPENSE);
          rulePassed = false;
        }
      }
      counter++;
    }

    // Imported Expenses
    counter = 0;
    for (ImportedExpense importedExpense : travelDocument.getImportedExpenses()) {
      String property = TemPropertyConstants.IMPORTED_EXPENSES + "[" + counter + "]";

      // Determine if the detail is an amount that doesn't go over the threshold
      KualiDecimal total = KualiDecimal.ZERO;
      for (TemExpense detail : importedExpense.getExpenseDetails()) {
        total = total.add(detail.getExpenseAmount());
      }
      if (!total.isZero()) {
        if (total.isGreaterThan(importedExpense.getExpenseAmount())) {
          GlobalVariables.getMessageMap()
              .putError(
                  property + "." + TemPropertyConstants.EXPENSE_AMOUNT,
                  TemKeyConstants.ERROR_TEM_DETAIL_GREATER_THAN_EXPENSE);
          rulePassed = false;
        } else if (total.isLessThan(importedExpense.getExpenseAmount())) {
          GlobalVariables.getMessageMap()
              .putError(
                  property + "." + TemPropertyConstants.EXPENSE_AMOUNT,
                  TemKeyConstants.ERROR_TEM_DETAIL_LESS_THAN_EXPENSE);
          rulePassed = false;
        }
      }
      counter++;
    }

    GlobalVariables.getMessageMap().clearErrorPath();

    return rulePassed;
  }