/*
 * 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;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.fp.document.DisbursementVoucherDocument;
import org.kuali.kfs.gl.businessobject.Encumbrance;
import org.kuali.kfs.gl.service.EncumbranceService;
import org.kuali.kfs.krad.UserSession;
import org.kuali.kfs.krad.bo.Note;
import org.kuali.kfs.krad.dao.DocumentDao;
import org.kuali.kfs.krad.document.Document;
import org.kuali.kfs.krad.rules.rule.event.KualiDocumentEvent;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.KRADPropertyConstants;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.tem.TemConstants;
import org.kuali.kfs.module.tem.TemConstants.ExpenseType;
import org.kuali.kfs.module.tem.TemConstants.TravelDocTypes;
import org.kuali.kfs.module.tem.TemConstants.TravelParameters;
import org.kuali.kfs.module.tem.TemConstants.TravelReimbursementParameters;
import org.kuali.kfs.module.tem.TemConstants.TravelReimbursementStatusCodeKeys;
import org.kuali.kfs.module.tem.TemParameterConstants;
import org.kuali.kfs.module.tem.TemWorkflowConstants;
import org.kuali.kfs.module.tem.businessobject.ActualExpense;
import org.kuali.kfs.module.tem.businessobject.TravelAdvance;
import org.kuali.kfs.module.tem.businessobject.TripType;
import org.kuali.kfs.module.tem.document.service.TravelAuthorizationService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AmountTotaling;
import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.document.DocumentStatus;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
import org.kuali.rice.kim.api.identity.IdentityService;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kim.api.identity.principal.Principal;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.sql.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * Travel Reimbursement Document
 */
@Entity
@Table(name = "TEM_TRVL_REIMB_DOC_T")
public class TravelReimbursementDocument extends TEMReimbursementDocument implements AmountTotaling {

    public static Logger LOG = Logger.getLogger(TravelReimbursementDocument.class);
    private volatile static IdentityService identityService;

    @Column(name = "final_reimb_ind", nullable = false, length = 1)
    private Boolean finalReimbursement = Boolean.FALSE;
    private Boolean employeeCertification = Boolean.FALSE;
    private String contactName;
    private String contactPhoneNum;
    private String contactEmailAddress;
    private String contactCampusCode;
    private KualiDecimal perDiemAdjustment;
    private KualiDecimal travelAdvanceAmount = KualiDecimal.ZERO;
    @Transient
    private KualiDecimal reimbursableAmount = KualiDecimal.ZERO;
    @Transient
    private List<TravelAdvance> travelAdvances;

    public TravelReimbursementDocument() {
    }

    public Boolean getFinalReimbursement() {
        return finalReimbursement;
    }

    public void setFinalReimbursement(final Boolean finalReimbursement) {
        this.finalReimbursement = finalReimbursement;
    }

    @Column(name = "emp_cert_ind", nullable = false, length = 1)
    public Boolean getEmployeeCertification() {
        return employeeCertification;
    }

    public void setEmployeeCertification(final Boolean employeeCertification) {
        this.employeeCertification = employeeCertification;
    }

    public Boolean getNonEmployeeCertification() {
        return !employeeCertification;
    }

    public void setNonEmployeeCertification(final Boolean employeeCertification) {
        this.employeeCertification = !employeeCertification;
    }

    @Column(name = "con_nm", length = 40, nullable = false)
    public String getContactName() {
        return contactName;
    }

    public void setContactName(String contactName) {
        this.contactName = contactName;
    }

    @Column(name = "con_phone_nbr", length = 20, nullable = false)
    public String getContactPhoneNum() {
        return contactPhoneNum;
    }

    public void setContactPhoneNum(String contactPhoneNum) {
        this.contactPhoneNum = contactPhoneNum;
    }

    @Column(name = "con_email_addr", length = 40, nullable = true)
    public String getContactEmailAddress() {
        return contactEmailAddress;
    }

    public void setContactEmailAddress(String contactEmailAddress) {
        this.contactEmailAddress = contactEmailAddress;
    }

    @Column(name = "con_campus_cd", length = 2, nullable = true)
    public String getContactCampusCode() {
        return contactCampusCode;
    }

    public void setContactCampusCode(String contactCampusCode) {
        this.contactCampusCode = contactCampusCode;
    }

    @Override
    public void setPerDiemAdjustment(final KualiDecimal perDiemAdjustment) {
        this.perDiemAdjustment = perDiemAdjustment;
    }

    @Override
    public KualiDecimal getPerDiemAdjustment() {
        return perDiemAdjustment;
    }

    /**
     * Gets the travelAdvanceAmount attribute. This is the travel advance amount applied on this TR.
     *
     * @return Returns the travelAdvanceAmount.
     */
    public KualiDecimal getTravelAdvanceAmount() {
        return travelAdvanceAmount;
    }

    /**
     * Sets the travelAdvanceAmount attribute value. This is the travel advance amount applied on this TR.
     *
     * @param travelAdvanceAmount The travelAdvanceAmount to set.
     */
    public void setTravelAdvanceAmount(KualiDecimal travelAdvanceAmount) {
        this.travelAdvanceAmount = travelAdvanceAmount;
    }

    public KualiDecimal getReimbursableAmount() {
        return reimbursableAmount;
    }

    public void setReimbursableAmount(KualiDecimal reimbursableAmount) {
        this.reimbursableAmount = reimbursableAmount;
    }

    /**
     * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#doRouteStatusChange(org.kuali.rice.kew.dto.DocumentRouteStatusChange)
     */
    @Override
    public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) {
        super.doRouteStatusChange(statusChangeEvent);

        LOG.debug("Handling route status change");

        if (DocumentStatus.PROCESSED.getCode().equals(statusChangeEvent.getNewRouteStatus())) {

            // 1. process the reimbursement on the TR
            try {
                // update the status to Dept approved
                updateAndSaveAppDocStatus(TravelReimbursementStatusCodeKeys.DEPT_APPROVED);
                getTravelReimbursementService().processCustomerReimbursement(this);
            } catch (Exception e) {
                LOG.error("Could not spawn CRM or DV on FINAL for travel id " + getTravelDocumentIdentifier());
                LOG.error(e.getMessage(), e);
            }

            try {
                final TravelAuthorizationDocument openAuthorization = getTravelReimbursementService().getRelatedOpenTravelAuthorizationDocument(this);
                List<Document> authorizations = getTravelDocumentService().getDocumentsRelatedTo(this, TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT);

                // 2. if there is an authorization and there isn't a TAC then we process dis-encumbrance
                if (openAuthorization != null) {
                    // check TAC existance
                    List<Document> relatedCloseDocuments = getTravelDocumentService().getDocumentsRelatedTo(openAuthorization, TravelDocTypes.TRAVEL_AUTHORIZATION_CLOSE_DOCUMENT);

                    // Trip is encumbrance and no TAC(TAC doc would have handled dis encumbrance) and this is the final TR...so it needs to spawn a TAC
                    if (relatedCloseDocuments.isEmpty() && getFinalReimbursement()) {
                        // save our GLPE's so that the TAC finds them when calculating encumbrances
                        getBusinessObjectService().save(getGeneralLedgerPendingEntries());
                        // store this so we can reset after we're finished
                        final String initiatorId = getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();
                        final Principal initiator = getIdentityService().getPrincipal(initiatorId);
                        GlobalVariables.doInNewGlobalVariables(new UserSession(initiator.getPrincipalName()), new Callable<Object>() {
                            @Override
                            public Object call() {
                                //close the authorization
                                getTravelAuthorizationService().closeAuthorization(openAuthorization, getTripDescription(), initiator.getPrincipalName(), getDocumentNumber());
                                return null;
                            }
                        });
                    }
                }
                // if open authorization is null, try to check if there is ANY authorization at all (may be it was closed manually; so its not opened)
                else if (!authorizations.isEmpty()) {
                    // authorization that is not opened; note to the TR document
                    Note newNote = getDocumentService().createNoteFromDocument(this, "TA is no longer Open; skip Dis-encumberance process.");
                    addNote(newNote);
                    getDocumentDao().save(this);
                }
            } catch (Exception e) {
                LOG.error("Could Add notes for annotation to TR doc #" + getDocumentNumber());
                LOG.error(e.getMessage(), e);
            }
        }
    }

    /**
     * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#toCopy()
     */
    @Override
    public void toCopy() throws WorkflowException {
        super.toCopy();

        getNotes().clear();
        addContactInformation();
        employeeCertification = Boolean.FALSE;

    }

    /**
     * Adds contact information of the initiator of the {@link TravelReimbursementDocument} instance
     * to the {@link TravelReimbursementDocument}. Initiator is determined as the current person in the
     * user session
     *
     * @param document to modify
     */
    public void addContactInformation() {
        final String initiatorName = GlobalVariables.getUserSession().getPrincipalName();
        final Person initiator = getPersonService().getPersonByPrincipalName(initiatorName);
        this.setContactName(initiator.getName());
        this.setContactPhoneNum(initiator.getPhoneNumber());
        this.setContactEmailAddress(initiator.getEmailAddress());
        this.setContactCampusCode(initiator.getCampusCode());
    }

    /**
     * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#initiateDocument()
     */
    @Override
    public void initiateDocument() {
        super.initiateDocument();
        setApplicationDocumentStatus(TravelReimbursementStatusCodeKeys.IN_PROCESS);
        getTravelPayment().setDocumentationLocationCode(getParameterService().getParameterValueAsString(TravelReimbursementDocument.class, TravelParameters.DOCUMENTATION_LOCATION_CODE,
            getParameterService().getParameterValueAsString(TemParameterConstants.TEM_DOCUMENT.class, TravelParameters.DOCUMENTATION_LOCATION_CODE)));
    }

    /**
     * Provides answers to the following splits: PurchaseWasReceived VendorIsEmployeeOrNonResidentAlien
     *
     * @see org.kuali.kfs.sys.document.FinancialSystemTransactionalDocumentBase#answerSplitNodeQuestion(java.lang.String)
     */
    @Override
    public boolean answerSplitNodeQuestion(String nodeName) throws UnsupportedOperationException {
        if (nodeName.equals(TemWorkflowConstants.DIVISION_APPROVAL_REQUIRED)) {
            return requiresDivisionApprovalRouting() && isNotAutomaticReimbursement();
        }
        if (nodeName.equals(TemWorkflowConstants.SPECIAL_REQUEST)) {
            return (requiresSpecialRequestReviewRouting() || (isDelinquent() && !hasDelinquencyException())) && isNotAutomaticReimbursement();
        }
        if (nodeName.equals(TemWorkflowConstants.INTL_TRAVEL)) {
            return requiresInternationalTravelReviewRouting() && isNotAutomaticReimbursement();
        }
        if (nodeName.equals(TemWorkflowConstants.TAX_MANAGER_APPROVAL_REQUIRED)) {
            return requiresTaxManagerApprovalRouting() && isNotAutomaticReimbursement();
        }
        if (StringUtils.equals(TemWorkflowConstants.REQUIRES_BUDGET_REVIEW, nodeName)) {
            return isBudgetReviewRequired();
        }
        if (nodeName.equals(TemWorkflowConstants.SEPARATION_OF_DUTIES)) {
            return requiresSeparationOfDutiesRouting() && isNotAutomaticReimbursement();
        }
        if (nodeName.equals(TemWorkflowConstants.REQUIRES_TRAVELER_REVIEW)) {
            return requiresTravelerApprovalRouting();
        }
        if (nodeName.equals(TemWorkflowConstants.REQUIRES_AWARD) || nodeName.equals(TemWorkflowConstants.REQUIRES_SUB_FUND)) {
            return isNotAutomaticReimbursement();
        }
        throw new UnsupportedOperationException("Cannot answer split question for this node you call \"" + nodeName + "\"");
    }

    /**
     * @return
     */
    private boolean isNotAutomaticReimbursement() {
        boolean enabled = getParameterService().getParameterValueAsBoolean(TravelReimbursementDocument.class, TemConstants.TravelReimbursementParameters.AUTOMATIC_APPROVALS_IND);
        if (!enabled) {
            return true;
        }
        if (!ObjectUtils.isNull(getTraveler()) && !StringUtils.equals(getTraveler().getTravelerTypeCode(), TemConstants.EMP_TRAVELER_TYP_CD)) {
            return true;
        }
        if (getActualExpenses() != null && getActualExpenses().size() > 0) {
            for (ActualExpense expense : getActualExpenses()) {
                if (expense.getExpenseTypeObjectCode() != null && expense.getExpenseTypeObjectCode().isReceiptRequired()
                    && getTravelExpenseService().isTravelExpenseExceedReceiptRequirementThreshold(expense)) {
                    return true;
                }
            }
        }

        final List<Document> authorizations = getTravelDocumentService().getDocumentsRelatedTo(this, TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT, TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_AMEND_DOCUMENT);
        if (authorizations != null && !authorizations.isEmpty()) {
            for (Document doc : authorizations) {
                TravelAuthorizationDocument auth = (TravelAuthorizationDocument) doc;
                if (!ObjectUtils.isNull(auth.getTravelAdvance()) && auth.shouldProcessAdvanceForDocument() && (KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_WIRE.equals(auth.getAdvanceTravelPayment().getPaymentMethodCode()) || KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_DRAFT.equals(auth.getAdvanceTravelPayment().getPaymentMethodCode()))) {
                    return true;
                }
            }
        }

        KualiDecimal trTotal = KualiDecimal.ZERO;
        List<AccountingLine> lines = getSourceAccountingLines();
        for (AccountingLine line : lines) {
            trTotal = trTotal.add(line.getAmount());
        }
        Map<String, String> fieldValues = new HashMap<String, String>();
        fieldValues.put(KRADPropertyConstants.CODE, this.getTripTypeCode());
        TripType tripType = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(TripType.class, fieldValues);
        KualiDecimal threshold = tripType.getAutoTravelReimbursementLimit();
        if (trTotal.isGreaterEqual(threshold)) {
            return true;
        }
        return false;
    }

    /**
     * @return true if the TR is delinquent, false otherwise
     */
    protected boolean isDelinquent() {
        return !StringUtils.isBlank(getDelinquentAction());
    }

    /**
     * @return true if this reimbursement has a delinquincy exception, false otherwise
     */
    protected boolean hasDelinquencyException() {
        if (getDelinquentTRException() != null && getDelinquentTRException().booleanValue()) {
            return true;
        }
        // didn't find it?  Then we need to look at our current TA
        final TravelAuthorizationDocument travelAuth = getTravelDocumentService().findCurrentTravelAuthorization(this);
        if (travelAuth != null && travelAuth.getDelinquentTRException() != null) {
            return travelAuth.getDelinquentTRException().booleanValue();
        }
        return false;
    }

    /**
     * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#requiresInternationalTravelReviewRouting()
     */
    @Override
    protected boolean requiresInternationalTravelReviewRouting() {
        return super.requiresInternationalTravelReviewRouting() && requiresDivisionApprovalRouting();
    }

    /**
     * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#requiresDivisionApprovalRouting()
     */
    @Override
    protected boolean requiresDivisionApprovalRouting() {
        boolean reqDivApproval = false;
        KualiDecimal trTotal = getTravelDocumentService().getTotalCumulativeReimbursements(this);
        KualiDecimal divApprovalMax = new KualiDecimal(getParameterService().getParameterValueAsString(TemParameterConstants.TEM_DOCUMENT.class, TravelParameters.CUMULATIVE_REIMBURSABLE_AMOUNT_WITHOUT_DIVISION_APPROVAL));
        return (trTotal.isGreaterThan(divApprovalMax)) && requiresAccountingReviewRouting();
    }

    /**
     * @return the current open amount of the encumbrance(s) created by the related travel authorizations
     */
    public KualiDecimal getTotalAuthorizedEncumbrance() {
        final KualiDecimal taTotal = getTravelDocumentService().getTotalAuthorizedEncumbrance(this);
        return taTotal;
    }

    /**
     * @return the total cumulative reimbursements so far
     */
    public KualiDecimal getTotalCumulativeReimbursements() {
        final KualiDecimal trTotal = getTravelDocumentService().getTotalCumulativeReimbursements(this);
        return trTotal;
    }

    /**
     * @return
     */
    private boolean requiresAccountingReviewRouting() {
        // start with getting the TA encumbrance amount
        String percent = getParameterService().getParameterValueAsString(TravelReimbursementDocument.class, TravelReimbursementParameters.REIMBURSEMENT_PERCENT_OVER_ENCUMBRANCE_AMOUNT);

        KualiDecimal taTotal = getTravelDocumentService().getTotalAuthorizedEncumbrance(this);
        if (taTotal.isLessEqual(KualiDecimal.ZERO)) {
            return false;
        }

        KualiDecimal trTotal = getTravelDocumentService().getTotalCumulativeReimbursements(this);
        if (trTotal.isLessEqual(KualiDecimal.ZERO)) {
            return false;
        }

        if (trTotal.isGreaterThan(taTotal)) {
            KualiDecimal subAmount = trTotal.subtract(taTotal);
            KualiDecimal percentOver = (subAmount.divide(taTotal)).multiply(new KualiDecimal(100));
            return (percentOver.isGreaterThan(new KualiDecimal(percent)));
        }
        return false;
    }

    /**
     * @return
     */
    public DocumentDao getDocumentDao() {
        return SpringContext.getBean(DocumentDao.class);
    }

    /**
     * Overridden to take out advances amount
     *
     * @see org.kuali.kfs.module.tem.document.TEMReimbursementDocument#getPaymentAmount()
     */
    @Override
    public KualiDecimal getPaymentAmount() {
        final KualiDecimal paymentAmountBeforeAdvances = super.getPaymentAmount();
        final KualiDecimal paymentAmountAfterAdvances = paymentAmountBeforeAdvances.subtract(getAdvancesTotal());
        if (paymentAmountAfterAdvances.isLessThan(KualiDecimal.ZERO)) {
            return KualiDecimal.ZERO;
        }
        return paymentAmountAfterAdvances;
    }

    /**
     * @return all travel advances associated with the trip this document is reimbursing
     */
    public List<TravelAdvance> getTravelAdvances() {
        if (travelAdvances == null) {
            travelAdvances = getTravelDocumentService().getTravelAdvancesForTrip(getTravelDocumentIdentifier());
        }
        return travelAdvances;
    }

    /**
     * @return
     */
    public KualiDecimal getAdvancesTotal() {
        KualiDecimal retval = KualiDecimal.ZERO;
        if (getTravelAdvanceAmount().isZero()) {
            retval = getTravelReimbursementService().getInvoiceAmount(this);

            // Note that the travelAdvanceAmount is not set here. It is only set when the APP doc is created.
        } else {
            retval = getTravelAdvanceAmount();
        }
        return retval;
    }

    /**
     * @see org.kuali.kfs.module.tem.document.TEMReimbursementDocument#getReimbursableGrandTotal()
     */
    @Override
    public KualiDecimal getReimbursableGrandTotal() {
        KualiDecimal grandTotal = getReimbursableTotal();

        KualiDecimal advancesTotal = getAdvancesTotal();
        if (advancesTotal.isGreaterThan(grandTotal)) {
            return KualiDecimal.ZERO; // if advances are greater than what is being reimbursed, then the grand total is a big fat goose egg.  With two equally sized goose eggs after the decimal point.
        }
        final KualiDecimal reimbursableGrandTotal = grandTotal.subtract(getAdvancesTotal());
        final KualiDecimal reimbursableGrandTotalAfterExpenseLimit = applyExpenseLimit(reimbursableGrandTotal);
        return reimbursableGrandTotalAfterExpenseLimit;
    }

    /**
     * @see org.kuali.kfs.module.tem.document.TravelDocument#getReportPurpose()
     */
    @Override
    public String getReportPurpose() {
        return getTripDescription();
    }

    /**
     * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#populateVendorPayment(org.kuali.kfs.fp.document.DisbursementVoucherDocument)
     */
    @Override
    public void populateVendorPayment(DisbursementVoucherDocument disbursementVoucherDocument) {
        super.populateVendorPayment(disbursementVoucherDocument);
        String locationCode = getParameterService().getParameterValueAsString(TravelReimbursementDocument.class, TravelParameters.DOCUMENTATION_LOCATION_CODE, getParameterService().getParameterValueAsString(TemParameterConstants.TEM_DOCUMENT.class, TravelParameters.DOCUMENTATION_LOCATION_CODE));
        String startDate = new SimpleDateFormat("MM/dd/yyyy").format(this.getTripBegin());
        String endDate = new SimpleDateFormat("MM/dd/yyyy").format(this.getTripEnd());
        String checkStubText = this.getTravelDocumentIdentifier() + ", " + this.getPrimaryDestinationName() + ", " + startDate + " - " + endDate;

        disbursementVoucherDocument.setDisbursementVoucherDocumentationLocationCode(locationCode);
        disbursementVoucherDocument.setDisbVchrCheckStubText(checkStubText);
    }

    /**
     * Retrieves all encumbrances associated with this trip and adds up their amounts
     *
     * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#getEncumbranceTotal()
     */
    @Transient
    @Override
    public KualiDecimal getEncumbranceTotal() {
        KualiDecimal encTotal = KualiDecimal.ZERO;
        final List<Encumbrance> encumbrances = getTravelEncumbranceService().getEncumbrancesForTrip(getTravelDocumentIdentifier(), getDocumentNumber());
        for (Encumbrance enc : encumbrances) {
            encTotal = encTotal.add(enc.getAccountLineEncumbranceAmount());
        }
        return encTotal;
    }

    /**
     * Returns TRCA
     *
     * @see org.kuali.kfs.module.tem.document.TEMReimbursementDocument#getAchCheckDocumentType()
     */
    @Override
    public String getAchCheckDocumentType() {
        return TemConstants.TravelDocTypes.TRAVEL_REIMBURSEMENT_CHECK_ACH_DOCUMENT;
    }

    /**
     * Returns TRWF
     *
     * @see org.kuali.kfs.module.tem.document.TEMReimbursementDocument#getWireTransferOrForeignDraftDocumentType()
     */
    @Override
    public String getWireTransferOrForeignDraftDocumentType() {
        return TemConstants.TravelDocTypes.TRAVEL_REIMBURSEMENT_WIRE_OR_FOREIGN_DRAFT_DOCUMENT;
    }

    /**
     * Generate TR disencumbrance glpe's
     *
     * @see org.kuali.kfs.module.tem.document.TEMReimbursementDocument#generateDocumentGeneralLedgerPendingEntries(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
     */
    @Override
    public boolean generateDocumentGeneralLedgerPendingEntries(GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
        boolean result = super.generateDocumentGeneralLedgerPendingEntries(sequenceHelper);
        if (isTripGenerateEncumbrance()) {
            getTravelEncumbranceService().disencumberTravelReimbursementFunds(this, sequenceHelper);
        }
        getTravelReimbursementService().generateEntriesForAdvances(this, sequenceHelper);
        return result;
    }

    /**
     * Overridden to handle the explicit entries generated for travel advance clearing and crediting accounting lines
     *
     * @see org.kuali.kfs.module.tem.document.TEMReimbursementDocument#customizeExplicitGeneralLedgerPendingEntry(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry)
     */
    @Override
    public void customizeExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry) {
        super.customizeExplicitGeneralLedgerPendingEntry(postable, explicitEntry);
        if (postable instanceof AccountingLine) {
            final AccountingLine accountingLine = (AccountingLine) postable;
            if (TemConstants.TRAVEL_ADVANCE_CLEARING_LINE_TYPE_CODE.equals(accountingLine.getFinancialDocumentLineTypeCode())) {
                explicitEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
                explicitEntry.setFinancialDocumentTypeCode(TemConstants.TravelDocTypes.TRAVEL_REIMBURSEMENT_TRAVEL_ADVANCES_DOCUMENT);
            } else if (TemConstants.TRAVEL_ADVANCE_CREDITING_LINE_TYPE_CODE.equals(accountingLine.getFinancialDocumentLineTypeCode())) {
                explicitEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
                explicitEntry.setFinancialDocumentTypeCode(TemConstants.TravelDocTypes.TRAVEL_REIMBURSEMENT_TRAVEL_ADVANCES_DOCUMENT);
            }
        }
    }

    /**
     * This method returns total expense amount minus the non-reimbursable.  Overridden to remove manually adjusted per diem if it exists
     *
     * @return
     */
    @Override
    public KualiDecimal getApprovedAmount() {
        KualiDecimal total = KualiDecimal.ZERO;
        for (ExpenseType expense : EnumSet.allOf(ExpenseType.class)) {
            KualiDecimal expenseAmount = getTravelExpenseService().getExpenseServiceByType(expense).getAllExpenseTotal(this, false);
            if (expenseAmount == null) {
                expenseAmount = KualiDecimal.ZERO;
            }
            total = expenseAmount.add(total);
            if (expense.equals(ExpenseType.perDiem) && getPerDiemAdjustment() != null && getPerDiemAdjustment().isPositive() && expenseAmount.isPositive()) {
                total = total.subtract(getPerDiemAdjustment());
            }
        }
        return total;
    }

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

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

    protected TravelAuthorizationService getTravelAuthorizationService() {
        return SpringContext.getBean(TravelAuthorizationService.class);
    }

    protected EncumbranceService getEncumbranceService() {
        return SpringContext.getBean(EncumbranceService.class);
    }

    protected GeneralLedgerPendingEntryService getGeneralLedgerPendingEntryService() {
        return SpringContext.getBean(GeneralLedgerPendingEntryService.class);
    }

    /**
     * @return the default implementation of IdentityService
     */
    protected IdentityService getIdentityService() {
        if (identityService == null) {
            identityService = SpringContext.getBean(IdentityService.class);
        }
        return identityService;
    }

    /**
     * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#getDisapprovedAppDocStatusMap()
     */
    @Override
    public Map<String, String> getDisapprovedAppDocStatusMap() {
        return TravelReimbursementStatusCodeKeys.getDisapprovedAppDocStatusMap();
    }

    @Override
    protected String generateDescription() {
        DateTimeService dateTimeService = SpringContext.getBean(DateTimeService.class);
        String description = super.generateDescription();

        if (getTripEnd() != null && getTripBegin() != null) {
            boolean preTripReimbursement = false;
            try {
                Date tripEnd = dateTimeService.convertToSqlDate(getTripEnd());
                Date tripBegin = dateTimeService.convertToSqlDate(getTripBegin());
                Date currentDate = dateTimeService.getCurrentSqlDate();
                preTripReimbursement = tripBegin.compareTo(currentDate) >= 0 && tripEnd.compareTo(currentDate) >= 0 ? true : false;
            } catch (ParseException pe) {
                LOG.error("Error while parsing dates ", pe);
            }

            final boolean preTrip = getParameterService().getParameterValueAsBoolean(TravelReimbursementDocument.class, TemConstants.TravelReimbursementParameters.PRETRIP_REIMBURSEMENT_IND, false);

            if (preTrip && preTripReimbursement) {
                return postpendPreTripToDescription(description);
            }
        }

        return description;
    }

    /**
     * Adds (Pre-Trip) to the end of the given String (presumably the document description), and then makes sure it will fit within the doc description's max length
     *
     * @param description the description to add (Pre-Trip) to
     * @return the fitted String
     */
    protected String postpendPreTripToDescription(String description) {
        final String postPendedDescription = TemConstants.TRAVEL_REIMBURSEMENT_PRETRIP_DESCRIPTION_TEXT + description;
        final int maxLength = getDataDictionaryService().getAttributeMaxLength(getDocumentHeader().getClass(), KFSPropertyConstants.DOCUMENT_DESCRIPTION);
        final String fittedDescription = (postPendedDescription.length() > maxLength) ? postPendedDescription.substring(0, maxLength) : postPendedDescription;
        return fittedDescription;
    }

    /**
     * Checks the check stub text for the payment
     *
     * @see org.kuali.kfs.module.tem.document.TravelDocumentBase#prepareForSave(org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent)
     */
    @Override
    public void prepareForSave(KualiDocumentEvent event) {
        super.prepareForSave(event);
        getTravelPayment().setCheckStubText(getTravelDocumentIdentifier() + " " + StringUtils.defaultString(getTripDescription()) + " " + getTripBegin());
    }
}
