/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.module.tem.document.web.struts;

import com.lowagie.text.pdf.PdfCopy;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.SimpleBookmark;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.log4j.Logger;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.kns.util.KNSGlobalVariables;
import org.kuali.kfs.kns.web.struts.form.KualiDocumentFormBase;
import org.kuali.kfs.kns.web.struts.form.KualiForm;
import org.kuali.kfs.krad.bo.PersistableBusinessObject;
import org.kuali.kfs.krad.document.Document;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.service.KualiRuleService;
import org.kuali.kfs.krad.uif.field.LinkField;
import org.kuali.kfs.krad.util.ErrorMessage;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.KRADConstants;
import org.kuali.kfs.krad.util.KRADPropertyConstants;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.krad.util.UrlFactory;
import org.kuali.kfs.module.tem.TemConstants;
import org.kuali.kfs.module.tem.TemConstants.TravelCustomSearchLinks;
import org.kuali.kfs.module.tem.TemKeyConstants;
import org.kuali.kfs.module.tem.TemParameterConstants;
import org.kuali.kfs.module.tem.TemPropertyConstants;
import org.kuali.kfs.module.tem.businessobject.AccountingDistribution;
import org.kuali.kfs.module.tem.businessobject.AccountingDocumentRelationship;
import org.kuali.kfs.module.tem.businessobject.ActualExpense;
import org.kuali.kfs.module.tem.businessobject.GroupTraveler;
import org.kuali.kfs.module.tem.businessobject.HistoricalTravelExpense;
import org.kuali.kfs.module.tem.businessobject.ImportedExpense;
import org.kuali.kfs.module.tem.businessobject.PerDiem;
import org.kuali.kfs.module.tem.businessobject.PerDiemExpense;
import org.kuali.kfs.module.tem.businessobject.PrimaryDestination;
import org.kuali.kfs.module.tem.businessobject.SpecialCircumstances;
import org.kuali.kfs.module.tem.businessobject.TemDistributionAccountingLine;
import org.kuali.kfs.module.tem.businessobject.TemExpense;
import org.kuali.kfs.module.tem.businessobject.TemProfile;
import org.kuali.kfs.module.tem.businessobject.TemSourceAccountingLine;
import org.kuali.kfs.module.tem.businessobject.TravelerDetail;
import org.kuali.kfs.module.tem.document.TravelAuthorizationDocument;
import org.kuali.kfs.module.tem.document.TravelDocument;
import org.kuali.kfs.module.tem.document.TravelDocumentBase;
import org.kuali.kfs.module.tem.document.TravelReimbursementDocument;
import org.kuali.kfs.module.tem.document.authorization.ReturnToFiscalOfficerAuthorizer;
import org.kuali.kfs.module.tem.document.authorization.TravelArrangeableAuthorizer;
import org.kuali.kfs.module.tem.document.service.AccountingDocumentRelationshipService;
import org.kuali.kfs.module.tem.document.service.TravelDocumentService;
import org.kuali.kfs.module.tem.document.validation.event.AddGroupTravelLineEvent;
import org.kuali.kfs.module.tem.document.validation.event.RecalculateTripDetailTotalEvent;
import org.kuali.kfs.module.tem.document.validation.event.UpdateTripDetailsEvent;
import org.kuali.kfs.module.tem.document.web.bean.TravelMvcWrapperBean;
import org.kuali.kfs.module.tem.report.service.TravelReportService;
import org.kuali.kfs.module.tem.service.AccountingDistributionService;
import org.kuali.kfs.module.tem.service.PerDiemService;
import org.kuali.kfs.module.tem.service.TemProfileService;
import org.kuali.kfs.module.tem.service.TemRoleService;
import org.kuali.kfs.module.tem.service.TravelEncumbranceService;
import org.kuali.kfs.module.tem.service.TravelService;
import org.kuali.kfs.module.tem.service.TravelerService;
import org.kuali.kfs.module.tem.util.ExpenseUtils;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.businessobject.WireCharge;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.SegmentedLookupResultsService;
import org.kuali.kfs.sys.service.UniversityDateService;
import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase;
import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentFormBase;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.KewApiConstants;
import org.kuali.rice.kew.api.doctype.DocumentType;
import org.kuali.rice.kew.api.doctype.DocumentTypeService;
import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kim.impl.KIMPropertyConstants;
import org.springframework.web.util.HtmlUtils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import static org.kuali.kfs.module.tem.TemConstants.CERTIFICATION_STATEMENT_ATTRIBUTE;
import static org.kuali.kfs.module.tem.TemConstants.DELINQUENT_TEST_ATTRIBUTE;
import static org.kuali.kfs.module.tem.TemConstants.EMPLOYEE_TEST_ATTRIBUTE;
import static org.kuali.kfs.module.tem.TemConstants.FISCAL_OFFICER_TEST_ATTRIBUTE;
import static org.kuali.kfs.module.tem.TemConstants.PRIMARY_DESTINATION_LOOKUPABLE;
import static org.kuali.kfs.module.tem.TemConstants.RETURN_TO_FO_QUESTION;
import static org.kuali.kfs.module.tem.TemConstants.SHOW_ACCOUNT_DISTRIBUTION_ATTRIBUTE;
import static org.kuali.kfs.module.tem.TemConstants.SHOW_REPORTS_ATTRIBUTE;
import static org.kuali.kfs.module.tem.TemConstants.TRAVEL_ARRANGER_TEST_ATTRIBUTE;
import static org.kuali.kfs.module.tem.TemConstants.TRAVEL_MANAGER_TEST_ATTRIBUTE;
import static org.kuali.kfs.module.tem.TemConstants.TravelParameters.EMPLOYEE_CERTIFICATION_STATEMENT;
import static org.kuali.kfs.module.tem.TemConstants.TravelParameters.NON_EMPLOYEE_CERTIFICATION_STATEMENT;
import static org.kuali.kfs.module.tem.TemConstants.TravelReimbursementParameters.DISPLAY_ACCOUNTING_DISTRIBUTION_TAB_IND;
import static org.kuali.kfs.module.tem.TemConstants.TravelReimbursementParameters.FOREIGN_CURRENCY_URL;
import static org.kuali.kfs.module.tem.TemPropertyConstants.TRIP_INFO_UPDATE_TRIP_DTL;

/**
 * Common action class
 */
public abstract class TravelActionBase extends KualiAccountingDocumentActionBase {

    public static Logger LOG = Logger.getLogger(TravelActionBase.class);

    protected static final String[] methodToCallExclusionArray = {"recalculate", "calculate", "recalculateTripDetailTotal", "save", "route", "approve", "blanketApprove", "updatePerDiemExpenses"};

    protected volatile static PerDiemService perDiemService;
    protected volatile static DocumentTypeService documentTypeService;

    protected static final String ACCOUNTING_LINES_TOTALS_VALIDATION_BEAN = "TravelDocument-accountingLineTotalsValidation";


    @Override
    protected DocumentService getDocumentService() {
        return SpringContext.getBean(DocumentService.class);
    }

    protected TravelDocumentService getTravelDocumentService() {
        return SpringContext.getBean(TravelDocumentService.class);
    }

    protected TravelEncumbranceService getTravelEncumbranceService() {
        return SpringContext.getBean(TravelEncumbranceService.class);
    }

    protected TemRoleService getTemRoleService() {
        return SpringContext.getBean(TemRoleService.class);
    }

    public PersonService getPersonService() {
        return SpringContext.getBean(PersonService.class);
    }

    protected TravelReportService getTravelReportService() {
        return SpringContext.getBean(TravelReportService.class);
    }

    public AccountingDocumentRelationshipService getAccountingDocumentRelationshipService() {
        return SpringContext.getBean(AccountingDocumentRelationshipService.class);
    }

    protected TravelerService getTravelerService() {
        return SpringContext.getBean(TravelerService.class);
    }

    protected TravelService getTravelService() {
        return SpringContext.getBean(TravelService.class);
    }

    protected AccountingDistributionService getAccountingDistributionService() {
        return SpringContext.getBean(AccountingDistributionService.class);
    }

    protected TemProfileService getTemProfileService() {
        return SpringContext.getBean(TemProfileService.class);
    }

    protected PerDiemService getPerDiemService() {
        if (perDiemService == null) {
            perDiemService = SpringContext.getBean(PerDiemService.class);
        }
        return perDiemService;
    }

    protected DocumentTypeService getDocumentTypeService() {
        if (documentTypeService == null) {
            documentTypeService = SpringContext.getBean(DocumentTypeService.class);
        }
        return documentTypeService;
    }


    /**
     * When the approver only wants the accounting lines to be changed but the trip information is acceptable, routes the document
     * back to the Account Node (Fiscal Officer Reviewer) and removes the fiscal officer approvals and the approvals that are beyond
     * the Account node. The fiscal officer should be able to select the accounts outside of their organization like the PCDO
     * document.
     */
    public ActionForward returnToFiscalOfficer(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        LOG.debug("Returning to Fiscal Officer");
        final QuestionHandler<TravelDocument> questionHandler = getQuestionHandler(RETURN_TO_FO_QUESTION);

        final Inquisitive<TravelDocument, ActionForward> inq = new StrutsInquisitor<TravelDocument, TravelFormBase, TravelActionBase>(mapping, (TravelFormBase) form, this, request, response);

        if (inq.wasQuestionAsked()) {
            return questionHandler.handleResponse(inq);
        }

        return questionHandler.askQuestion(inq);
    }

    protected <T> T getQuestionHandler(final String question) {
        final T retval = (T) SpringContext.getService(question);

        return retval;
    }

    /**
     * For use with a specific set of methods of this class that create new purchase order-derived document types in response to
     * user actions, including <code>amendTa</code>. It employs the question framework to ask the user for a response before
     * creating and routing the new document. The response should consist of a note detailing a reason, and either yes or no. This
     * method can be better understood if it is noted that it will be gone through twice (via the question framework); when each
     * question is originally asked, and again when the yes/no response is processed, for confirmation.
     *
     * @param mapping      These are boiler-plate.
     * @param form         "
     * @param request      "
     * @param response     "
     * @param questionType A string identifying the type of question being asked.
     * @param confirmType  A string identifying which type of question is being confirmed.
     * @param documentType A string, the type of document to create
     * @param notePrefix   A string to appear before the note in the BO Notes tab
     * @param messageType  A string to appear on the PO once the question framework is done, describing the action taken
     * @param operation    A string, the verb to insert in the original question describing the action to be taken
     * @return An ActionForward
     * @throws Exception
     */
    protected ActionForward askQuestionsAndPerformDocumentAction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String questionType, String confirmType, String documentType, String notePrefix, String messageType, String operation) throws Exception {
        LOG.debug("askQuestionsAndPerformDocumentAction started.");
        TravelFormBase trForm = (TravelFormBase) form;
        TravelDocument document = trForm.getTravelDocument();

        return null;
    }

    @Override
    public ActionForward route(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        recalculateTripDetailTotalOnly(mapping, form, request, response);

        TravelFormBase travelForm = (TravelFormBase) form;

        if (requiresCalculate(travelForm)) {
            GlobalVariables.getMessageMap().putError(KFSConstants.DOCUMENT_ERRORS, TemKeyConstants.ERROR_REQUIRES_CALCULATE, new String[]{"routing"});

            return mapping.findForward(KFSConstants.MAPPING_BASIC);
        }

        ActionForward forward = super.route(mapping, travelForm, request, response);
        getTravelDocumentService().attachImportedExpenses(travelForm.getTravelDocument());

        return forward;
    }

    /**
     * Determines whether or not someone can return the document to fiscal officer
     *
     * @param form
     */
    protected void setCanReturnToFisicalOfficer(TravelFormBase form) {
        boolean can = form.getTravelDocument().canReturn();

        if (can) {
            ReturnToFiscalOfficerAuthorizer documentAuthorizer = getDocumentAuthorizer(form);
            can = documentAuthorizer.canReturnToFisicalOfficer(form.getTravelDocument(), GlobalVariables.getUserSession().getPerson());
        }

        form.setCanReturn(can);
    }

    @Override
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        GlobalVariables.getUserSession().removeObject(TemPropertyConstants.TemProfileProperties.PROFILE_ID);
        GlobalVariables.getUserSession().removeObject(KFSPropertyConstants.DOCUMENT_TYPE_CODE);
        TravelFormBase travelFormBase = (TravelFormBase) form;
        final String methodToCall = travelFormBase.getMethodToCall();
        final TravelDocument document = (TravelDocument) travelFormBase.getDocument();

        //set PerDiem categories and breakdown
        getPerDiemService().setPerDiemCategoriesAndBreakdown(travelFormBase);
        travelFormBase.setDisplayNonEmployeeForm(!isEmployee(document.getTraveler()));

        if (!StringUtils.isEmpty(methodToCall) && !methodToCall.equalsIgnoreCase("docHandler")) {
            // Setting the certification statement in the case that there is a validation error
            request.setAttribute(CERTIFICATION_STATEMENT_ATTRIBUTE, getCertificationStatement(document));
            request.setAttribute(EMPLOYEE_TEST_ATTRIBUTE, isEmployee(document.getTraveler()));
            request.setAttribute(TRAVEL_ARRANGER_TEST_ATTRIBUTE, setTravelArranger((TravelFormBase) form));
            request.setAttribute(TRAVEL_MANAGER_TEST_ATTRIBUTE, setTravelManager((TravelFormBase) form));
            request.setAttribute(FISCAL_OFFICER_TEST_ATTRIBUTE, setFiscalOfficer((TravelFormBase) form));
            request.setAttribute(DELINQUENT_TEST_ATTRIBUTE, document.getDelinquentAction());
        }

        if (document.shouldRefreshExpenseTypeObjectCode()) {
            document.refreshExpenseTypeObjectCodesForExpenses();
        }

        ExpenseUtils.calculateMileage(document, document.getActualExpenses());
        updateActualExpenseConversionRates(document);
        populateForeignCurrencyUrl(travelFormBase);

        final ActionForward retval = super.execute(mapping, form, request, response);
        request.setAttribute(CERTIFICATION_STATEMENT_ATTRIBUTE, getCertificationStatement(document));
        request.setAttribute(EMPLOYEE_TEST_ATTRIBUTE, isEmployee(document.getTraveler()));
        request.setAttribute(TRAVEL_ARRANGER_TEST_ATTRIBUTE, setTravelArranger((TravelFormBase) form));
        request.setAttribute(TRAVEL_MANAGER_TEST_ATTRIBUTE, setTravelManager((TravelFormBase) form));
        request.setAttribute(FISCAL_OFFICER_TEST_ATTRIBUTE, setFiscalOfficer((TravelFormBase) form));
        request.setAttribute(DELINQUENT_TEST_ATTRIBUTE, document.getDelinquentAction());

        populateAgencyLinks(travelFormBase);

        if (!StringUtils.isBlank(document.getDocumentNumber())) {
            final Map<String, List<Document>> relatedDocuments = getTravelDocumentService().getDocumentsRelatedTo(document); //we don't have a doc number yet...there's no related documents
            travelFormBase.setRelatedDocuments(relatedDocuments);
            travelFormBase.setDistribution(getAccountingDistributionService().buildDistributionFrom(travelFormBase.getTravelDocument())); // and likely, there's nothing to distribute either
        }

        return retval;
    }

    /**
     * Overridden to recalculate per diems and actual expenses in case per diem rate or mileage rates changed; if
     *
     * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
     */
    @Override
    protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
        super.loadDocument(kualiDocumentFormBase);
        TravelDocument travelDoc = ((TravelFormBase) kualiDocumentFormBase).getTravelDocument();
        if (travelDoc.getDocumentHeader().getWorkflowDocument().isEnroute() || travelDoc.getDocumentHeader().getWorkflowDocument().isCompletionRequested()) { // only update background rates if the document is still enroute
            if (travelDoc.getActualExpenses() != null && !travelDoc.getActualExpenses().isEmpty()) {
                ExpenseUtils.calculateMileage(travelDoc, travelDoc.getActualExpenses());
            }
        }
    }

    /**
     * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#refresh(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    @SuppressWarnings("rawtypes")
    public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        super.refresh(mapping, form, request, response);
        TravelFormBase travelForm = (TravelFormBase) form;

        Collection<PersistableBusinessObject> rawValues = null;
        Map<String, Set<String>> segmentedSelection = new HashMap<String, Set<String>>();

        // If multiple asset lookup was used to select the assets, then....
        if (StringUtils.equals(KFSConstants.MULTIPLE_VALUE, travelForm.getRefreshCaller())) {
            String lookupResultsSequenceNumber = travelForm.getLookupResultsSequenceNumber();

            if (StringUtils.isNotBlank(lookupResultsSequenceNumber)) {
                // actually returning from a multiple value lookup
                Set<String> selectedIds = SpringContext.getBean(SegmentedLookupResultsService.class).retrieveSetOfSelectedObjectIds(lookupResultsSequenceNumber, GlobalVariables.getUserSession().getPerson().getPrincipalId());
                for (String selectedId : selectedIds) {
                    String selectedObjId = StringUtils.substringBefore(selectedId, ".");
                    String selectedMonthData = StringUtils.substringAfter(selectedId, ".");

                    if (!segmentedSelection.containsKey(selectedObjId)) {
                        segmentedSelection.put(selectedObjId, new HashSet<String>());
                    }
                    segmentedSelection.get(selectedObjId).add(selectedMonthData);
                }
                // Retrieving selected data from table.
                LOG.debug("Asking segmentation service for object ids " + segmentedSelection.keySet());
                rawValues = SpringContext.getBean(SegmentedLookupResultsService.class).retrieveSelectedResultBOs(lookupResultsSequenceNumber, segmentedSelection.keySet(), HistoricalTravelExpense.class, GlobalVariables.getUserSession().getPerson().getPrincipalId());
                List<ImportedExpense> newImportedExpenses = ExpenseUtils.convertHistoricalToImportedExpense((List) rawValues, travelForm.getTravelDocument());
                AddImportedExpenseEvent event = new AddImportedExpenseEvent();
                for (ImportedExpense newImportedExpense : newImportedExpenses) {
                    travelForm.setNewImportedExpenseLine(newImportedExpense);
                    event.update(null, travelForm);
                }
                if (rawValues != null && rawValues.size() > 0) {
                    this.save(mapping, travelForm, request, response);
                    KNSGlobalVariables.getMessageList().add(TemKeyConstants.INFO_TEM_IMPORT_DOCUMENT_SAVE);
                }
            }
        }

        // set wire charge message in form
        ((TravelFormBase) form).setWireChargeMessage(retrieveWireChargeMessage());

        final String refreshCaller = ((KualiForm) form).getRefreshCaller();
        if (!StringUtils.isBlank(refreshCaller)) {
            final TravelDocument document = ((TravelFormBase) form).getTravelDocument();

            if (TemConstants.TRAVELER_PROFILE_DOC_LOOKUPABLE.equals(refreshCaller)) {
                performRequesterRefresh(document, (TravelFormBase) form, request);
            }
        }

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    @Override
    public ActionForward performLookup(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase travelForm = (TravelFormBase) form;
        // parse out the important strings from our methodToCall parameter
        String fullParameter = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
        validateLookupInquiryFullParameter(request, form, fullParameter);
        String boClassName = StringUtils.substringBetween(fullParameter, KRADConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL, KRADConstants.METHOD_TO_CALL_BOPARM_RIGHT_DEL);
        if (!StringUtils.isBlank(boClassName)
            && boClassName.equals(HistoricalTravelExpense.class.getName())) {
            TravelDocument document = travelForm.getTravelDocument();
            boolean success = true;
            if (document.getTemProfileId() == null) {
                String identifier = TemConstants.documentProfileNames().get(document.getFinancialDocumentTypeCode());

                GlobalVariables.getMessageMap().putError(KRADPropertyConstants.DOCUMENT + "." + KIMPropertyConstants.Person.FIRST_NAME, TemKeyConstants.ERROR_TEM_IMPORT_EXPENSES_PROFILE_MISSING, identifier);
                success = false;

            }
            if (!success) {
                return mapping.findForward(KFSConstants.MAPPING_BASIC);
            }
            travelForm.setRefreshCaller(KFSConstants.MULTIPLE_VALUE);
            GlobalVariables.getUserSession().addObject(TemPropertyConstants.TemProfileProperties.PROFILE_ID, document.getTemProfileId());
            GlobalVariables.getUserSession().addObject(TemPropertyConstants.ARRANGER_PROFILE_ID, getTemProfileService().findTemProfileByPrincipalId(GlobalVariables.getUserSession().getPrincipalId()).getProfileId());
            GlobalVariables.getUserSession().addObject(KFSPropertyConstants.DOCUMENT_TYPE_CODE, document.getFinancialDocumentTypeCode());
        }

        return super.performLookup(mapping, form, request, response);
    }

    /**
     * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#docHandler(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward docHandler(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final ActionForward retval = super.docHandler(mapping, form, request, response);
        final TravelFormBase travelForm = (TravelFormBase) form;
        final TravelDocument travelDocument = travelForm.getTravelDocument();

        initializeNewActualExpenseLines(travelForm.getNewActualExpenseLines(), travelDocument.getActualExpenses());

        //update TA and TR's custom primary destinations
        if ((travelDocument instanceof TravelAuthorizationDocument) || travelDocument instanceof TravelReimbursementDocument) {
            updateCustomPrimaryDestination(travelDocument);
        }

        return retval;
    }

    /**
     * Update the custom primary destination values and indicator
     * <p>
     * This is only applicable to TR and TA docs, but we will keep it in the action base
     *
     * @param travelDocument
     */
    protected void updateCustomPrimaryDestination(TravelDocument travelDocument) {
        //refresh and setup the custom primary destinations values and indicator
        if (travelDocument.getPrimaryDestinationId() != null && travelDocument.getPrimaryDestinationId().intValue() == TemConstants.CUSTOM_PRIMARY_DESTINATION_ID) {
            PrimaryDestination destination = travelDocument.getPrimaryDestination();
            destination.setPrimaryDestinationName(travelDocument.getPrimaryDestinationName());
            destination.setCounty(travelDocument.getPrimaryDestinationCounty());
            destination.getRegion().setRegionName(travelDocument.getPrimaryDestinationCountryState());
            travelDocument.setPrimaryDestinationIndicator(true);
        }
    }

    protected void initializeNewActualExpenseLines(List<ActualExpense> newActualExpenseLines, List<ActualExpense> actualExpenses) {
        if (actualExpenses != null && !actualExpenses.isEmpty()) {
            if (newActualExpenseLines == null) {
                newActualExpenseLines = new ArrayList<ActualExpense>();
            }

            newActualExpenseLines.clear();

            for (ActualExpense actualExpense : actualExpenses) {
                ActualExpense newActualExpenseLine = new ActualExpense();
                newActualExpenseLine.setExpenseDate(actualExpense.getExpenseDate());
                newActualExpenseLine.setExpenseTypeObjectCodeId(actualExpense.getExpenseTypeObjectCodeId());
                newActualExpenseLine.setExpenseParentId(actualExpense.getId());

                newActualExpenseLines.add(newActualExpenseLine);
            }
        }
    }

    /**
     * This method will be called when user clicks on Destination not found button on UI under Traveler information section, and
     * enables state code and county fields by setting primary destination indicator to true.
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return ActionForward
     * @throws Exception
     */
    public ActionForward enablePrimaryDestinationFields(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase baseForm = (TravelFormBase) form;
        TravelDocumentBase document = (TravelDocumentBase) baseForm.getDocument();

        document.setPrimaryDestinationIndicator(true);
        document.setPrimaryDestinationId(TemConstants.CUSTOM_PRIMARY_DESTINATION_ID);
        // initialize as new primary dest object.
        if (document.getPrimaryDestination() != null) {
            document.getPrimaryDestination().setId(TemConstants.CUSTOM_PRIMARY_DESTINATION_ID);
            document.getPrimaryDestination().setVersionNumber(null);
            document.getPrimaryDestination().setObjectId(null);
        } else {
            document.setPrimaryDestination(new PrimaryDestination());
            document.getPrimaryDestination().setId(TemConstants.CUSTOM_PRIMARY_DESTINATION_ID);
            document.getPrimaryDestination().setPrimaryDestinationName(document.getPrimaryDestinationName());
            document.getPrimaryDestination().getRegion().setRegionName(document.getPrimaryDestinationCountryState());
            document.getPrimaryDestination().setCounty(document.getPrimaryDestinationCounty());
            document.getPrimaryDestination().getRegion().setTripTypeCode(document.getTripTypeCode());
            document.getPrimaryDestination().getRegion().setTripType(document.getTripType());
        }

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    protected String getCertificationStatement(final TravelDocument document) {
        if (isEmployee(document.getTraveler())) {
            return getEmployeeCertificationStatement();
        }

        return getNonEmployeeCertificationStatement();
    }

    protected String getEmployeeCertificationStatement() {
        return getCertificationStatementFrom(EMPLOYEE_CERTIFICATION_STATEMENT);
    }

    protected String getNonEmployeeCertificationStatement() {
        return getCertificationStatementFrom(NON_EMPLOYEE_CERTIFICATION_STATEMENT);
    }

    protected String getCertificationStatementFrom(final String parameter) {
        return getParameterService().getParameterValueAsString(TemParameterConstants.TEM_DOCUMENT.class, parameter);
    }

    /**
     * Uses the {@link TravelerService} to determine if the given {@link TravelerDetail} is that of an employee or not
     *
     * @param traveler is a {@link TravelerDetail}
     * @return true if employee or false otherwise
     * @see @see org.kuali.kfs.module.tem.service.TravelerService#isEmployee(org.kuali.kfs.module.tem.businessobject.TravelerDetail)
     */
    protected boolean isEmployee(final TravelerDetail traveler) {
        if (traveler == null) {
            return false;
        }

        return getTravelerService().isEmployee(traveler);
    }

    /**
     * Do initialization for a new {@link TravelDocument}
     *
     * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#createDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
     */
    @Override
    protected void createDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
        super.createDocument(kualiDocumentFormBase);
        final TravelDocument document = (TravelDocument) kualiDocumentFormBase.getDocument();
        document.initiateDocument();

        final List<SpecialCircumstances> specialCircumstances = getTravelDocumentService().findActiveSpecialCircumstances(document.getDocumentHeader().getDocumentNumber(), kualiDocumentFormBase.getDocTypeName());
        document.setSpecialCircumstances(specialCircumstances);
    }

    /**
     * @param form
     * @return
     * @throws Exception
     */
    @SuppressWarnings("rawtypes")
    public <T> T newMvcDelegate(final ActionForm form) throws Exception {
        T retval = (T) Proxy.newProxyInstance(getClass().getClassLoader(),
            new Class[]{getMvcWrapperInterface()},
            new TravelMvcWrapperInvocationHandler(form));
        return retval;
    }

    /**
     * Forces inheriting classes to define their own MVC Wrappers
     *
     * @return some {@link Class} instance for an interface that inherits from the {@link TravelMvcWrapperBean}. That's right. It
     * has to be one of those.
     */
    protected abstract Class<? extends TravelMvcWrapperBean> getMvcWrapperInterface();

    /**
     * Checks if calculation is required. Currently it is required when it has not already been calculated and if the user can
     * perform calculate
     *
     * @return true if calculation is required, false otherwise
     */
    protected boolean requiresCalculate(TravelFormBase travelForm) {
        boolean requiresCalculate = true;

        requiresCalculate = !travelForm.isCalculated();

        return requiresCalculate;
    }

    /**
     * Uses generics to get whatever the authorizer is for the attached {@link KualiDocumentFormBase}
     *
     * @return some authorizer connected to the {@link Document} in <code>form</code>
     */
    protected <T> T getDocumentAuthorizer(final KualiDocumentFormBase form) {
        return (T) getDocumentHelperService().getDocumentAuthorizer(form.getDocument());
    }

    protected Integer getLineNumberFromParameter(String parameterKey) {
        int beginIndex = parameterKey.lastIndexOf("[") + 1;
        int endIndex = parameterKey.lastIndexOf("]");
        return Integer.parseInt(parameterKey.substring(beginIndex, endIndex));
    }

    public ActionForward updatePerDiemExpenses(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase reqForm = (TravelFormBase) form;
        TravelDocument document = reqForm.getTravelDocument();
        ParameterService paramService = SpringContext.getBean(ParameterService.class);

        if (SpringContext.getBean(KualiRuleService.class).applyRules(new UpdateTripDetailsEvent(TRIP_INFO_UPDATE_TRIP_DTL, reqForm.getDocument()))) {
            getTravelDocumentService().updatePerDiemItemsFor(document, document.getPerDiemExpenses(), document.getPrimaryDestinationId(), document.getTripBegin(), document.getTripEnd());
        }
        reqForm.getNewSourceLine().setAmount(this.getAccountingLineAmountToFillIn(reqForm));


        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward clearPerDiemExpenses(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase reqForm = (TravelFormBase) form;
        TravelDocument document = reqForm.getTravelDocument();
        document.setPerDiemExpenses(new ArrayList<PerDiemExpense>());
        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward copyDownPerDiemExpenses(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase reqForm = (TravelFormBase) form;
        TravelDocument document = reqForm.getTravelDocument();

        int copyIndex = getSelectedLine(request);
        getTravelDocumentService().copyDownPerDiemExpense(document, copyIndex, document.getPerDiemExpenses());

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward addActualExpenseLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final TravelFormBase travelForm = (TravelFormBase) form;
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);

        travelForm.getObservable().notifyObservers(mvcWrapper);

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward addActualExpenseDetailLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final TravelFormBase travelForm = (TravelFormBase) form;
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);

        travelForm.getObservable().notifyObservers(new Object[]{mvcWrapper, getSelectedLine(request)});

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward deleteActualExpenseLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final TravelFormBase travelForm = (TravelFormBase) form;
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);

        travelForm.getObservable().notifyObservers(new Object[]{mvcWrapper, getSelectedLine(request)});

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward deleteActualExpenseDetailLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final TravelFormBase travelForm = (TravelFormBase) form;
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);
        int[] lineNumbers = getSelectedDetailLine(request);
        travelForm.getObservable().notifyObservers(new Object[]{mvcWrapper, lineNumbers[0], lineNumbers[1]});

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * @param request
     * @param response
     * @param reportFile
     * @param fileName
     * @throws IOException
     */
    @SuppressWarnings("rawtypes")
    protected void displayPDF(HttpServletRequest request, HttpServletResponse response, File reportFile, StringBuilder fileName) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        String contentDisposition = "";
        try {
            ArrayList master = new ArrayList();
            PdfCopy writer = null;

            // create a reader for the document
            String reportName = reportFile.getAbsolutePath();
            PdfReader reader = new PdfReader(reportName);
            reader.consolidateNamedDestinations();

            // retrieve the total number of pages
            int n = reader.getNumberOfPages();
            List bookmarks = SimpleBookmark.getBookmark(reader);
            if (bookmarks != null) {
                master.addAll(bookmarks);
            }

            // step 1: create a document-object
            com.lowagie.text.Document pdfDoc = new com.lowagie.text.Document(reader.getPageSizeWithRotation(1));
            // step 2: create a writer that listens to the document
            writer = new PdfCopy(pdfDoc, baos);
            // step 3: open the document
            pdfDoc.open();
            // step 4: add content
            PdfImportedPage page;
            for (int i = 0; i < n; ) {
                ++i;
                page = writer.getImportedPage(reader, i);
                writer.addPage(page);
            }
            writer.freeReader(reader);
            if (!master.isEmpty()) {
                writer.setOutlines(master);
            }
            // step 5: we close the document
            pdfDoc.close();

            StringBuffer sbContentDispValue = new StringBuffer();
            String useJavascript = request.getParameter("useJavascript");
            if (useJavascript == null || useJavascript.equalsIgnoreCase("false")) {
                sbContentDispValue.append("attachment");
            } else {
                sbContentDispValue.append("inline");
            }
            sbContentDispValue.append("; filename=");
            sbContentDispValue.append(fileName);

            contentDisposition = sbContentDispValue.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }

        response.setContentType("application/pdf");
        response.setHeader("Content-Disposition", contentDisposition);
        response.setHeader("Expires", "0");
        response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
        response.setHeader("Pragma", "public");
        response.setContentLength(baos.size());

        // write to output
        ServletOutputStream sos;
        sos = response.getOutputStream();
        baos.writeTo(sos);
        sos.flush();
        sos.close();
    }

    public ActionForward customPerDiemExpenses(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase reqForm = (TravelFormBase) form;
        TravelDocument document = reqForm.getTravelDocument();

        int copyIndex = getSelectedLine(request);

        PerDiemExpense expense = document.getPerDiemExpenses().get(copyIndex);

        // now copy info over to expense
        expense.setPrimaryDestinationId(TemConstants.CUSTOM_PRIMARY_DESTINATION_ID);
        expense.setPrimaryDestination("");
        expense.setCountryState("");
        expense.setCounty("");
        expense.setLodging(KualiDecimal.ZERO);
        expense.setIncidentalsValue(KualiDecimal.ZERO);
        expense.setBreakfastValue(KualiDecimal.ZERO);
        expense.setLunchValue(KualiDecimal.ZERO);
        expense.setDinnerValue(KualiDecimal.ZERO);

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward refreshAfterPrimaryDestinationLookup(ActionMapping mapping, TravelFormBase reqForm, HttpServletRequest request) {
        String refreshCaller = reqForm.getRefreshCaller();

        TravelDocument document = reqForm.getTravelDocument();

        boolean isPrimaryDestinationLookupable = PRIMARY_DESTINATION_LOOKUPABLE.equals(refreshCaller);

        // if a cancel occurred on address lookup we need to reset the payee id and type, rest of fields will still have correct
        // information
        if (refreshCaller == null) {
            return null;
        }

        // do not execute the further refreshing logic if the refresh caller is not a per diem lookupable
        if (!isPrimaryDestinationLookupable) {
            return null;
        }

        Map parameters = request.getParameterMap();
        Set<String> parameterKeys = parameters.keySet();
        for (String parameterKey : parameterKeys) {
            if (StringUtils.containsIgnoreCase(parameterKey, TemPropertyConstants.PER_DIEM_EXPENSES)) {
                // its one of the numbered lines, lets
                int estimateLineNum = getLineNumberFromParameter(parameterKey);
                PerDiemExpense expense = document.getPerDiemExpenses().get(estimateLineNum);

                String[] priDestId = (String[]) parameters.get(parameterKey);
                PerDiem perDiem = null;
                if (expense.getPrimaryDestinationId() != TemConstants.CUSTOM_PRIMARY_DESTINATION_ID && expense.getPrimaryDestinationId().equals(new Integer(priDestId[0]))) {
                    perDiem = getPerDiemService().getPerDiem(expense.getPrimaryDestinationId(), expense.getMileageDate(), document.getEffectiveDateForPerDiem(expense.getMileageDate()));
                } else {
                    perDiem = getPerDiemService().getPerDiem(new Integer(priDestId[0]), expense.getMileageDate(), document.getEffectiveDateForPerDiem(expense.getMileageDate()));
                }

                // now copy info over to estimate
                if (perDiem != null) {
                    expense.setPrimaryDestinationId(perDiem.getPrimaryDestinationId());
                    expense.setPrimaryDestination(perDiem.getPrimaryDestination().getPrimaryDestinationName());
                    expense.setCountryState(perDiem.getPrimaryDestination().getRegion().getRegionName());
                    expense.setCounty(perDiem.getPrimaryDestination().getCounty());
                    final boolean shouldProrate = document.isOnTripBegin(expense) || document.isOnTripEnd(expense);
                    getTravelDocumentService().setPerDiemMealsAndIncidentals(expense, perDiem, document.getTripType(), document.getTripEnd(), shouldProrate);
                    expense.setLodging(perDiem.getLodging());
                }
                return null;
            }
        }

        Integer primaryDestinationId = document.getPrimaryDestinationId();
        if (primaryDestinationId == null) {
            if (document.getPrimaryDestination().getId() != null) {
                document.setPerDiemExpenses(new ArrayList<PerDiemExpense>());
            }
        } else if (document.getPrimaryDestination().getId() != null) {
            if (primaryDestinationId.intValue() != document.getPrimaryDestination().getId()) {
                document.setPerDiemExpenses(new ArrayList<PerDiemExpense>());
            }
        }

        if (isPrimaryDestinationLookupable) {
            PrimaryDestination primaryDestination = new PrimaryDestination();
            primaryDestination.setId(primaryDestinationId);
            document.setPrimaryDestinationIndicator(false);
            primaryDestination = (PrimaryDestination) SpringContext.getBean(BusinessObjectService.class).retrieve(primaryDestination);

            document.setPrimaryDestination(primaryDestination);
            document.setPrimaryDestinationId(primaryDestination.getId());
            document.setTripType(primaryDestination.getRegion().getTripType());
            document.setTripTypeCode(primaryDestination.getRegion().getTripTypeCode().toUpperCase());
            return null;
        }

        return null;
    }

    /**
     * Just a passthru {@link InvocationHandler}. It's used when creating a proxy, to access methods in a class without knowing what
     * that class really is. This allows us to put a facade layer on top of whatever MVC we use; hence, the name
     * {@link TravelMvcWrapperInvocationHandler}
     */
    class TravelMvcWrapperInvocationHandler<MvcClass> implements InvocationHandler {
        private MvcClass mvcObj;

        public TravelMvcWrapperInvocationHandler(final MvcClass mvcObj) {
            this.mvcObj = mvcObj;
        }

        @Override
        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Exception {
            return method.invoke(mvcObj, args);
        }
    }


    protected KualiDecimal getAccountingLineAmountToFillIn(TravelFormBase travelReqForm) {
        KualiDecimal amount = KualiDecimal.ZERO;

        TravelDocument travelDocument = travelReqForm.getTravelDocument();
        KualiDecimal amountToBePaid = travelDocument.getTotalAccountLineAmount();

        final List<TemSourceAccountingLine> accountingLines = travelDocument.getSourceAccountingLines();

        KualiDecimal accountingTotal = KualiDecimal.ZERO;
        for (TemSourceAccountingLine accountingLine : accountingLines) {
            accountingTotal = accountingTotal.add(accountingLine.getAmount());
        }

        if (!ObjectUtils.isNull(amountToBePaid) && amountToBePaid.isGreaterEqual(accountingTotal)) {
            amount = amountToBePaid.subtract(accountingTotal);
        }

        if (travelDocument.getExpenseLimit() != null && travelDocument.getExpenseLimit().isPositive()) {
            if (accountingTotal.isGreaterEqual(travelDocument.getExpenseLimit())) {
                return KualiDecimal.ZERO; // the accounting line total is greater than or equal to the expense limit, there's no more expense limit to spend
            }
            if (amount.isGreaterThan(travelDocument.getExpenseLimit())) {
                return travelDocument.getExpenseLimit().subtract(accountingTotal); // the amount to be paid - accounting total is still greater than the expense limit; so the amount we can actually pay is the expense limit - the accounting total
            }
            // we're under the expense limit; let's just return amount
        }

        return amount;
    }


    /**
     * This method calculates trip detail total for both TA and TR
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public ActionForward recalculateTripDetailTotal(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        KNSGlobalVariables.getMessageList().remove(new ErrorMessage(TemKeyConstants.MESSAGE_RECALCULATE_SUCCESSFUL));

        // check to make sure all the number fields in the Trip Detail Estimates section are not negative
        TravelFormBase travelReqForm = (TravelFormBase) form;
        TravelDocumentBase travelReqDoc = (TravelDocumentBase) travelReqForm.getDocument();

        // Added to set values to zero when personal box checked to display correct values and totals
        if (SpringContext.getBean(KualiRuleService.class).applyRules(new RecalculateTripDetailTotalEvent("", travelReqForm.getDocument()))) {
            List<PerDiemExpense> estimates = travelReqDoc.getPerDiemExpenses();
            if (estimates != null && !estimates.isEmpty()) {
                getTravelDocumentService().updatePerDiemItemsFor(travelReqDoc, estimates, null, travelReqDoc.getTripBegin(), travelReqDoc.getTripEnd());
            }
        }

        if (!travelReqDoc.isValidExpenses()) {
            return mapping.findForward(KFSConstants.MAPPING_BASIC);
        }

        travelReqForm.getNewSourceLine().setAmount(this.getAccountingLineAmountToFillIn(travelReqForm));

        // forcing a refresh does the same as recalculation (unless we change how we do it)
        LOG.debug("Recalculating travel auth document " + travelReqDoc.getTravelDocumentIdentifier());
        travelReqForm.setCalculated(true);

        KNSGlobalVariables.getMessageList().add(TemKeyConstants.MESSAGE_RECALCULATE_SUCCESSFUL);

        showAccountDistribution(request, travelReqForm.getDocument());

        // Flag to display reports (TR, ENT, RELO)
        request.setAttribute(SHOW_REPORTS_ATTRIBUTE, !travelReqDoc.getDocumentHeader().getWorkflowDocument().isInitiated());

        if (travelReqDoc.getReimbursableTotal() != null && travelReqDoc.getExpenseLimit() != null && travelReqDoc.getReimbursableTotal().isGreaterThan(travelReqDoc.getExpenseLimit())) {
            GlobalVariables.getMessageMap().putWarning(KFSConstants.DOCUMENT_PROPERTY_NAME + "." + TemPropertyConstants.TRVL_AUTH_TOTAL_ESTIMATE, KFSKeyConstants.ERROR_CUSTOM, "Travel expense is exceeding the expense limit.");
        }

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    protected void showAccountDistribution(HttpServletRequest request, Document document) {
        if (document instanceof TravelDocumentBase) {
            final boolean showAccountDistribution = getParameterService().getParameterValueAsBoolean(document.getClass(), DISPLAY_ACCOUNTING_DISTRIBUTION_TAB_IND);
            request.setAttribute(SHOW_ACCOUNT_DISTRIBUTION_ATTRIBUTE, showAccountDistribution);
        }
    }

    /**
     * This method wraps recalculateTripDetailTotal and removes the success message
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public ActionForward recalculateTripDetailTotalOnly(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // call recalculateTripDetailTotal where all the magic happens
        ActionForward actionForward = recalculateTripDetailTotal(mapping, form, request, response);
        KNSGlobalVariables.getMessageList().remove(new ErrorMessage(TemKeyConstants.MESSAGE_RECALCULATE_SUCCESSFUL));

        return actionForward;
    }

    /**
     * This method determines if the user is a travel arranger for the document
     *
     * @return true if they are a travel arranger
     */
    protected boolean setTravelArranger(TravelFormBase form) {
        // TODO shouldn't this be an "edit transactional document" permission?

        TravelDocument travelDocument = form.getTravelDocument();

        // don't set "travelArranger" attribute if this isn't the base document
        if (!StringUtils.isBlank(travelDocument.getTravelDocumentIdentifier())) {
            final TravelDocument baseTravelDocument = getTravelDocumentService().getParentTravelDocument(travelDocument.getTravelDocumentIdentifier());
            if (baseTravelDocument != null && !StringUtils.equals(baseTravelDocument.getDocumentNumber(), travelDocument.getDocumentNumber())) {
                return false; // this isn't the base document...so we can't change the traveler
            }
        }

        TemProfile profile = null;

        //default to nulls
        String profileId = null;
        String homeDepartment = null;

        if (ObjectUtils.isNotNull(travelDocument.getTemProfileId())) {
            profile = getBusinessObjectService().findBySinglePrimaryKey(TemProfile.class, travelDocument.getTemProfileId());
            if (ObjectUtils.isNotNull(profile)) {
                homeDepartment = profile.getHomeDepartment();
                profileId = profile.getProfileId().toString();
            }
        }
        return getTemRoleService().isTravelArranger(GlobalVariables.getUserSession().getPerson(), homeDepartment, profileId, travelDocument.getDocumentTypeName());
    }

    /**
     * This method determines if the user is a travel manager
     *
     * @return true if they are a travel manager
     */
    protected boolean setTravelManager(TravelFormBase reqForm) {
        // Find if they have the role
        return getTravelDocumentService().isTravelManager(GlobalVariables.getUserSession().getPerson());
    }

    /**
     * This method determines if the user is a fiscal officer
     *
     * @return true if they are a fiscal officer
     */
    protected boolean setFiscalOfficer(TravelFormBase reqForm) {
        // Find if they have the role

        // checking this is causing a RuntimeException when the workflow document is null (when generating a report).
        // apparently even checking if a workflow document is null will cause it to send a RuntimeException - hence the try catch.
        boolean workflowCheck = false;
        try {
            List<RouteNodeInstance> nodes = reqForm.getWorkflowDocument().getRouteNodeInstances();
            for (RouteNodeInstance routeNode : nodes) {
                if (routeNode.getName().equals(KFSConstants.RouteLevelNames.ACCOUNT)) {
                    workflowCheck = true;
                }
            }
            workflowCheck &= reqForm.getWorkflowDocument().isEnroute();
        } catch (RuntimeException e) {
            // Do not propagate this exception.
            LOG.info("Could not retrieve the workflow document. This is most likely normal and ok in this case.");
        }

        return getTravelDocumentService().isResponsibleForAccountsOn(reqForm.getTravelDocument(), GlobalVariables.getUserSession().getPerson().getPrincipalId()) && workflowCheck;
    }

    protected List<String> getCalculateIgnoreList() {
        return Arrays.asList(methodToCallExclusionArray);
    }

    /**
     * This method adds a new related document into the travel request document
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public ActionForward addRelatedDocumentLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase travelReqForm = (TravelFormBase) form;

        AccountingDocumentRelationship adr = travelReqForm.getNewAccountingDocumentRelationship();
        if (adr.getRelDocumentNumber() == null || !NumberUtils.isDigits(adr.getRelDocumentNumber())) {
            GlobalVariables.getMessageMap().putError(
                String.format("%s.%s",
                    TemKeyConstants.TRVL_RELATED_DOCUMENT,
                    TemPropertyConstants.TRVL_RELATED_DOCUMENT_NUM),
                TemKeyConstants.ERROR_TRVL_RELATED_DOCUMENT_REQUIRED);
        } else {
            if (getDocumentService().documentExists(adr.getRelDocumentNumber())) {
                adr.setDocumentNumber(travelReqForm.getDocument().getDocumentNumber());
                adr.setPrincipalId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
                getAccountingDocumentRelationshipService().save(adr);
                travelReqForm.setNewAccountingDocumentRelationship(new AccountingDocumentRelationship());
            } else {
                GlobalVariables.getMessageMap().putError(
                    String.format("%s.%s",
                        TemKeyConstants.TRVL_RELATED_DOCUMENT,
                        TemPropertyConstants.TRVL_RELATED_DOCUMENT_NUM),
                    TemKeyConstants.ERROR_TRVL_RELATED_DOCUMENT_NOT_FOUND, new String[]{adr.getRelDocumentNumber()});
            }
        }

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward deleteRelatedDocumentLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase travelReqForm = (TravelFormBase) form;
        String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
        if (StringUtils.isNotBlank(parameterName)) {
            String relDocumentNumber = StringUtils.substringBetween(parameterName, "deleteRelatedDocumentLine.", ".");
            List<AccountingDocumentRelationship> adrList = getAccountingDocumentRelationshipService().find(new AccountingDocumentRelationship(travelReqForm.getDocument().getDocumentNumber(), relDocumentNumber));

            if (adrList != null && adrList.size() == 1) {
                if (adrList.get(0).getPrincipalId().equals(GlobalVariables.getUserSession().getPerson().getPrincipalId())) {
                    getAccountingDocumentRelationshipService().delete(adrList.get(0));
                }
            }
        }

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * This method adds a new group traveler into the travel document
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public ActionForward addGroupTravelerLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase travelReqForm = (TravelFormBase) form;
        TravelDocumentBase travelReqDoc = (TravelDocumentBase) travelReqForm.getDocument();

        GroupTraveler newGroupTravelerLine = travelReqForm.getNewGroupTravelerLine();
        boolean rulePassed = true;
        // check any business rules
        rulePassed &= SpringContext.getBean(KualiRuleService.class).applyRules(new AddGroupTravelLineEvent("newGroupTravelerLine", travelReqForm.getDocument(), newGroupTravelerLine));

        if (rulePassed) {
            travelReqDoc.addGroupTravelerLine(newGroupTravelerLine);
            travelReqForm.setNewGroupTravelerLine(new GroupTraveler());
        }

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }


    /**
     * This method removes a group traveler from this collection
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public ActionForward deleteGroupTravelerLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase travelReqForm = (TravelFormBase) form;
        TravelDocumentBase travelReqDoc = (TravelDocumentBase) travelReqForm.getDocument();

        int deleteIndex = getLineToDelete(request);
        travelReqDoc.getGroupTravelers().remove(deleteIndex);

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * Import group travel to the document from a spreadsheet.
     *
     * @param mapping  An ActionMapping
     * @param form     An ActionForm
     * @param request  The HttpServletRequest
     * @param response The HttpServletResponse
     * @return An ActionForward
     * @throws Exception
     */
    public ActionForward uploadGroupTravelerImportFile(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final TravelFormBase travelForm = (TravelFormBase) form;
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);

        travelForm.getObservable().notifyObservers(new Object[]{mvcWrapper, new String(travelForm.getGroupTravelerImportFile().getFileData())});

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward payDVToVendor(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final String url = getKualiConfigurationService().getPropertyValueAsString(KFSConstants.APPLICATION_URL_KEY) + "/" + TravelCustomSearchLinks.DV_URL + HtmlUtils.htmlEscape(((TravelFormBase) form).getTravelDocument().getDocumentNumber());
        return new ActionForward(url, true);
    }

    public ActionForward addImportedExpenseLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final TravelFormBase travelForm = (TravelFormBase) form;
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);

        travelForm.getObservable().notifyObservers(mvcWrapper);

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward addImportedExpenseDetailLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final TravelFormBase travelForm = (TravelFormBase) form;
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);

        travelForm.getObservable().notifyObservers(new Object[]{mvcWrapper, getSelectedLine(request)});

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward deleteImportedExpenseLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final TravelFormBase travelForm = (TravelFormBase) form;
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);

        travelForm.getObservable().notifyObservers(new Object[]{mvcWrapper, getSelectedLine(request)});

        //remove the imported expense accounting line if import expense list is empty
        checkImportedExpenseAccountingLine(travelForm.getTravelDocument());
        this.save(mapping, travelForm, request, response);
        KNSGlobalVariables.getMessageList().add(TemKeyConstants.INFO_TEM_IMPORT_DOCUMENT_SAVE);

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward deleteImportedExpenseDetailLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final TravelFormBase travelForm = (TravelFormBase) form;
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);
        int[] lineNumbers = getSelectedDetailLine(request);
        travelForm.getObservable().notifyObservers(new Object[]{mvcWrapper, lineNumbers[0], lineNumbers[1]});

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    protected int[] getSelectedDetailLine(HttpServletRequest request) {
        int[] selectedLines = new int[2];
        String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
        if (StringUtils.isNotBlank(parameterName)) {
            String lineNumbers = StringUtils.substringBetween(parameterName, ".line", ".");
            selectedLines[0] = Integer.parseInt(lineNumbers.split("-")[0]);
            selectedLines[1] = Integer.parseInt(lineNumbers.split("-")[1]);
        }

        return selectedLines;
    }

    /**
     * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#deleteSourceLine(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward deleteSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ActionForward forward = super.deleteSourceLine(mapping, form, request, response);
        TravelFormBase travelForm = (TravelFormBase) form;
        travelForm.setAnchor(TemConstants.SOURCE_ANCHOR);
        travelForm.setDistribution(new ArrayList<AccountingDistribution>());
        travelForm.setDistribution(getAccountingDistributionService().buildDistributionFrom(travelForm.getTravelDocument()));

        return forward;
    }

    public ActionForward deleteDistributionLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase travelForm = (TravelFormBase) form;
        travelForm.setAnchor(TemConstants.DISTRIBUTION_ANCHOR);
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);
        int deleteIndex = getLineToDelete(request);
        travelForm.getObservable().notifyObservers(new Object[]{mvcWrapper, deleteIndex});

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward insertDistributionLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase travelForm = (TravelFormBase) form;
        TravelDocument travelDocument = travelForm.getTravelDocument();
        travelForm.getAccountDistributionnewSourceLine().setCardType(travelDocument.getDefaultAccountingLineCardAgencyType());
        travelForm.setAnchor(TemConstants.DISTRIBUTION_ANCHOR);
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);

        travelForm.getObservable().notifyObservers(mvcWrapper);

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#deleteAccountingLine(boolean,
     * org.kuali.kfs.sys.web.struts.KualiAccountingDocumentFormBase, int)
     */
    @Override
    protected void deleteAccountingLine(boolean isSource, KualiAccountingDocumentFormBase financialDocumentForm, int deleteIndex) {
        financialDocumentForm.setAnchor(TemConstants.SOURCE_ANCHOR);
        super.deleteAccountingLine(isSource, financialDocumentForm, deleteIndex);
    }

    /**
     * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#insertAccountingLine(boolean,
     * org.kuali.kfs.sys.web.struts.KualiAccountingDocumentFormBase, org.kuali.kfs.sys.businessobject.AccountingLine)
     */
    @Override
    protected void insertAccountingLine(boolean isSource, KualiAccountingDocumentFormBase financialDocumentForm, AccountingLine line) {
        financialDocumentForm.setAnchor(TemConstants.SOURCE_ANCHOR);
        super.insertAccountingLine(isSource, financialDocumentForm, line);
    }

    public ActionForward selectAllDistributions(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        request.setAttribute("anchoraccountingDistributionAnchor", "anchoraccountingDistributionAnchor");
        TravelFormBase travelForm = (TravelFormBase) form;
        int index = getSelectedLine(request);
        boolean selected = false;
        if (index == Integer.parseInt(TemConstants.SELECT_ALL_INDEX)) {
            selected = true;
        }
        for (AccountingDistribution accountingDistribution : travelForm.getDistribution()) {
            accountingDistribution.setSelected(selected);
        }

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * Action method that handles setting up the distribution of expenses. Expenses are distributed by object code across
     * {@link AccountingLine} instances. When a distribution is setup, the relevant information is added to the new
     * {@link AccountingLine} on the {@link TravelReimbursementForm}
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return {@link ActionForward}
     */
    protected void initializeAssignAccounts(TravelFormBase travelForm) {
        TravelDocument travelDocument = travelForm.getTravelDocument();
        TemDistributionAccountingLine newLine = getAccountingDistributionService().distributionToDistributionAccountingLine(travelForm.getDistribution());
        travelForm.setAccountDistributionsourceAccountingLines(new ArrayList<TemDistributionAccountingLine>());
        travelForm.setAccountDistributionnextSourceLineNumber(new Integer(1));
        newLine.setAccountLinePercent(new BigDecimal(100));
        newLine.setCardType(travelDocument.getDefaultAccountingLineCardAgencyType());

        if (!ObjectUtils.isNull(travelDocument.getTemProfile())) {
            newLine.setChartOfAccountsCode(travelDocument.getTemProfile().getDefaultChartCode());
            newLine.setAccountNumber(travelDocument.getTemProfile().getDefaultAccount());
            newLine.setSubAccountNumber(travelDocument.getTemProfile().getDefaultSubAccount());
            newLine.setProjectCode(travelDocument.getTemProfile().getDefaultProjectCode());
        }

        travelForm.setAccountDistributionnewSourceLine(newLine);
    }

    public ActionForward distributeAccountingLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        final TravelFormBase travelForm = (TravelFormBase) form;
        travelForm.setAnchor(TemConstants.DISTRIBUTION_ANCHOR);
        final TravelMvcWrapperBean mvcWrapper = newMvcDelegate(form);

        travelForm.getObservable().notifyObservers(mvcWrapper);

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    public ActionForward resetAccountingLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TravelFormBase travelForm = (TravelFormBase) form;
        TravelDocument document = travelForm.getTravelDocument();
        document.setNextSourceLineNumber(new Integer(1));
        document.setSourceAccountingLines(new ArrayList<TemSourceAccountingLine>());
        travelForm.setDistribution(new ArrayList<AccountingDistribution>());
        travelForm.setDistribution(getAccountingDistributionService().buildDistributionFrom(document));
        travelForm.setAnchor(TemConstants.SOURCE_ANCHOR);

        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#approve(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward approve(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        recalculateTripDetailTotalOnly(mapping, form, request, response);

        ActionForward forward = super.approve(mapping, form, request, response);

        return forward;
    }

    /**
     * is this document in a final workflow state
     *
     * @param reqForm
     * @return
     */
    protected boolean isFinal(TravelFormBase reqForm) {
        return reqForm.getTransactionalDocument().getDocumentHeader().getWorkflowDocument().isFinal();
    }

    /**
     * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#disapprove(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward disapprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // Document is disapproved. Free up imported expenses
        TravelFormBase travelForm = (TravelFormBase) form;
        TravelDocument document = travelForm.getTravelDocument();
        getTravelDocumentService().detachImportedExpenses(document);

        return super.disapprove(mapping, form, request, response);
    }

    /**
     * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#cancel(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward cancel(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ActionForward forward = super.cancel(mapping, form, request, response);

        if (forward.getName() != null && forward.getName().equals(KRADConstants.MAPPING_PORTAL)) {
            // Document is cancelled. Free up imported expenses
            TravelFormBase travelForm = (TravelFormBase) form;
            TravelDocument document = travelForm.getTravelDocument();

            getTravelDocumentService().detachImportedExpenses(document);
        }

        return forward;
    }

    /**
     * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#close(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward close(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ActionForward forward = super.close(mapping, form, request, response);
        TravelFormBase travelForm = (TravelFormBase) form;
        TravelDocument document = travelForm.getTravelDocument();

        if (forward.getName() != null && forward.getName().equals(KRADConstants.MAPPING_PORTAL)) {
            // Document is not saved. Free up imported expenses.
            if (document.getDocumentHeader().getWorkflowDocument().isInitiated()) {
                getTravelDocumentService().detachImportedExpenses(document);
            }
        }

        return forward;
    }

    /**
     * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#save(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        recalculateTripDetailTotalOnly(mapping, form, request, response);

        TravelFormBase travelForm = (TravelFormBase) form;
        TravelDocument document = travelForm.getTravelDocument();
        ActionForward forward = super.save(mapping, form, request, response);

        return forward;
    }

    /**
     * Remove the document accounting lines which were tied to import expenses
     *
     * @param document
     */
    public void checkImportedExpenseAccountingLine(TravelDocument document) {
        //remove imported expenses accounting line if there is no import expense
        if (!document.getSourceAccountingLines().isEmpty() && document.getImportedExpenses().isEmpty()) {
            List<SourceAccountingLine> filteredAccountingLine = new ArrayList<SourceAccountingLine>();

            //get the travel card type lists
            List<String> cardTypes = getTravelService().getTravelCardTypes();
            for (TemSourceAccountingLine line : (List<TemSourceAccountingLine>) document.getSourceAccountingLines()) {
                //filter out any source accounting line that is of travel card type
                if (!cardTypes.contains(line.getCardType())) {
                    filteredAccountingLine.add(line);
                }
            }
            document.setSourceAccountingLines(filteredAccountingLine);
        }
    }

    /**
     * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#blanketApprove(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward blanketApprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        recalculateTripDetailTotalOnly(mapping, form, request, response);

        TravelFormBase travelForm = (TravelFormBase) form;
        TravelDocument document = travelForm.getTravelDocument();
        ActionForward forward = super.blanketApprove(mapping, form, request, response);
        getTravelDocumentService().attachImportedExpenses(document);

        return forward;
    }

    /**
     * is this document in a processed workflow state?
     *
     * @param reqForm
     * @return
     */
    protected boolean isProcessed(TravelFormBase reqForm) {
        return reqForm.getTransactionalDocument().getDocumentHeader().getWorkflowDocument().isProcessed();
    }

    protected void refreshRelatedDocuments(TravelFormBase form) {
        Map<String, List<Document>> relatedDocuments;
        try {
            relatedDocuments = getTravelDocumentService().getDocumentsRelatedTo(form.getTravelDocument());
            form.setRelatedDocuments(relatedDocuments);
            form.setRelatedDocumentNotes(null);
            form.getRelatedDocumentNotes();
        } catch (WorkflowException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * @return the message associated with this wire charge
     */
    protected String retrieveWireChargeMessage() {
        String message = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(KFSKeyConstants.MESSAGE_PAYMENT_WIRE_CHARGE);
        WireCharge wireCharge = new WireCharge();
        wireCharge.setUniversityFiscalYear(SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear());

        wireCharge = (WireCharge) SpringContext.getBean(BusinessObjectService.class).retrieve(wireCharge);
        Object[] args = {wireCharge.getDomesticChargeAmt(), wireCharge.getForeignChargeAmt()};

        return MessageFormat.format(message, args);
    }

    /**
     * Determines whether or not someone can calculate a travel document
     *
     * @param authForm
     */
    protected void setCanCalculate(TravelFormBase form) {
        boolean can = !(isFinal(form) || isProcessed(form));

        if (can) {
            TravelArrangeableAuthorizer documentAuthorizer = getDocumentAuthorizer(form);
            can = documentAuthorizer.canCalculate(form.getTravelDocument(), GlobalVariables.getUserSession().getPerson());
        }

        form.setCanCalculate(can);
    }

    /**
     * Lookups the url for the currency url link on expense lines
     *
     * @param form the form this action is acting on
     */
    protected void populateForeignCurrencyUrl(TravelFormBase form) {
        final String currencyUrl = getParameterService().getParameterValueAsString(TemParameterConstants.TEM_DOCUMENT.class, FOREIGN_CURRENCY_URL);
        form.setForeignCurrencyUrl(currencyUrl);
    }

    /**
     * Populates the agency links on the form
     *
     * @param form the form to populate links on
     */
    protected void populateAgencyLinks(TravelFormBase form) {
        final List<LinkField> agencyLinks = getTravelDocumentService().getAgencyLinks(form.getTravelDocument());
        form.setAgencyLinks(agencyLinks);
        final boolean shouldDisplay = getKualiConfigurationService().getPropertyValueAsBoolean(TemKeyConstants.ENABLE_AGENCY_SITES_URL);
        form.setShouldDisplayAgencyLinks(shouldDisplay);
    }

    /**
     * Goes through the actual expenses on the document and pushes the conversion rate of the parent expense down to the details
     *
     * @param doc the travel document to update
     */
    protected void updateActualExpenseConversionRates(TravelDocument doc) {
        for (ActualExpense expense : doc.getActualExpenses()) {
            if (expense.getExpenseDetails() != null && !expense.getExpenseDetails().isEmpty()) {
                for (TemExpense detail : expense.getExpenseDetails()) {
                    detail.setCurrencyRate(expense.getCurrencyRate());
                }
            }
        }
    }

    /**
     * Builds a relationship between the document we're creating and the one we're basing the document off of
     *
     * @param progenitorDocument the original document
     * @param createdDocument    the document we're creating now, based on that document
     * @return the accounting document relationship between the two documents
     */
    protected AccountingDocumentRelationship buildRelationshipToProgenitorDocument(TravelDocument progenitorDocument, TravelDocument createdDocument) {
        final String progenitorType = progenitorDocument.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
        final String createdType = createdDocument.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
        AccountingDocumentRelationship relationship = new AccountingDocumentRelationship(progenitorDocument.getDocumentNumber(), createdDocument.getDocumentNumber(), progenitorType + " - " + createdType);
        relationship.setPrincipalId(GlobalVariables.getUserSession().getPrincipalId());
        return relationship;
    }

    /**
     * Disables any per diem meals which have matching hosted or group actual expenses and displays messages about that
     *
     * @param document the TA or TR to disable per diem expenses on
     */
    protected void disablePerDiemExpenes(TravelDocument document) {
        for (ActualExpense actualExpense : document.getActualExpenses()) {
            getTravelDocumentService().disableDuplicateExpenses(document, actualExpense);
        }

        //Display any messages
        Iterator<String> it = document.getDisabledProperties().keySet().iterator();
        while (it.hasNext()) {
            String key = it.next();
            GlobalVariables.getMessageMap().putInfo(key, TemKeyConstants.MESSAGE_GENERIC, document.getDisabledProperties().get(key));
        }
    }

    /**
     * Performs necessary updates after the requester on the travel document was updated
     *
     * @param document the document to update
     * @param request  the current web request
     */
    protected abstract void performRequesterRefresh(TravelDocument document, TravelFormBase travelForm, HttpServletRequest request);

    /**
     * Updates new accounting lines on the document with default info from the profile if applicable
     *
     * @param form    the form holding new accounting lines
     * @param profile the profile to switch the lines over to the default accounting information of
     */
    protected void updateAccountsWithNewProfile(TravelFormBase form, TemProfile profile) {
        if (profile != null) {
            // update new source accounting line
            if (form.getNewSourceLine() != null) {
                TemSourceAccountingLine acctLine = (TemSourceAccountingLine) form.getNewSourceLine();
                acctLine.setChartOfAccountsCode(profile.getDefaultChartCode());
                acctLine.setAccountNumber(profile.getDefaultAccount());
                acctLine.setSubAccountNumber(profile.getDefaultSubAccount());
                acctLine.setProjectCode(profile.getDefaultProjectCode());
            }

            // update new distribution line
            if (form.getAccountDistributionnewSourceLine() != null) {
                TemDistributionAccountingLine acctLine = form.getAccountDistributionnewSourceLine();
                acctLine.setChartOfAccountsCode(profile.getDefaultChartCode());
                acctLine.setAccountNumber(profile.getDefaultAccount());
                acctLine.setSubAccountNumber(profile.getDefaultSubAccount());
                acctLine.setProjectCode(profile.getDefaultProjectCode());
            }
        }
    }

    /**
     * looks up the resolved url for the given document type
     *
     * @param documentType the document type to check
     * @return the resolved url
     */
    protected String getResolvedUrlForDocumentType(String documentType) {
        final DocumentType docType = getDocumentTypeService().getDocumentTypeByName(documentType);
        return docType.getResolvedDocumentHandlerUrl();
    }

    /**
     * Builds a new reimburesment url for the given travel document
     *
     * @param travelDoc the travel document to create a new reimbursement for
     * @return the url to redirect to to create a new reimbursement
     */
    protected String buildNewReimbursementUrl(TravelDocument travelDoc) {
        Properties props = new Properties();
        props.put(TemPropertyConstants.TRAVEL_DOCUMENT_IDENTIFIER, travelDoc.getTravelDocumentIdentifier());
        props.put(KFSConstants.DOCUMENT_TYPE_NAME, TemConstants.TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT);
        props.put(KFSConstants.PARAMETER_COMMAND, KewApiConstants.INITIATE_COMMAND);

        final String docUrlBase = getResolvedUrlForDocumentType(TemConstants.TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT);
        final String url = UrlFactory.parameterizeUrl(docUrlBase, props);
        return url;
    }
}
