org.kuali.kfs.module.tem.service.impl.TravelEncumbranceServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.kfs.module.tem.service.impl.TravelEncumbranceServiceImpl.java

Source

/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 * 
 * Copyright 2005-2014 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.service.impl;

import java.sql.Date;
import java.text.ParseException;
import java.util.ArrayList;
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.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.coa.businessobject.AccountingPeriod;
import org.kuali.kfs.coa.businessobject.BalanceType;
import org.kuali.kfs.coa.businessobject.OffsetDefinition;
import org.kuali.kfs.coa.service.AccountingPeriodService;
import org.kuali.kfs.coa.service.BalanceTypeService;
import org.kuali.kfs.gl.batch.service.EncumbranceCalculator;
import org.kuali.kfs.gl.businessobject.Encumbrance;
import org.kuali.kfs.gl.service.EncumbranceService;
import org.kuali.kfs.integration.ar.AccountsReceivableModuleService;
import org.kuali.kfs.module.tem.TemConstants;
import org.kuali.kfs.module.tem.TemConstants.TravelDocTypes;
import org.kuali.kfs.module.tem.TemKeyConstants;
import org.kuali.kfs.module.tem.TemPropertyConstants;
import org.kuali.kfs.module.tem.businessobject.HeldEncumbranceEntry;
import org.kuali.kfs.module.tem.businessobject.TemSourceAccountingLine;
import org.kuali.kfs.module.tem.businessobject.TravelAdvance;
import org.kuali.kfs.module.tem.businessobject.TripType;
import org.kuali.kfs.module.tem.document.TravelAuthorizationAmendmentDocument;
import org.kuali.kfs.module.tem.document.TravelAuthorizationCloseDocument;
import org.kuali.kfs.module.tem.document.TravelAuthorizationDocument;
import org.kuali.kfs.module.tem.document.TravelDocument;
import org.kuali.kfs.module.tem.document.TravelReimbursementDocument;
import org.kuali.kfs.module.tem.document.service.TravelDocumentService;
import org.kuali.kfs.module.tem.service.TravelEncumbranceService;
import org.kuali.kfs.pdp.PdpConstants;
import org.kuali.kfs.pdp.PdpPropertyConstants;
import org.kuali.kfs.pdp.businessobject.PaymentDetail;
import org.kuali.kfs.pdp.service.PaymentMaintenanceService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLineBase;
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.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.businessobject.SystemOptions;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.validation.impl.AccountingDocumentRuleBaseConstants.GENERAL_LEDGER_PENDING_ENTRY_CODE;
import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
import org.kuali.kfs.sys.service.OptionsService;
import org.kuali.kfs.sys.service.UniversityDateService;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.krad.bo.Note;
import org.kuali.rice.krad.document.Document;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentService;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;

public class TravelEncumbranceServiceImpl implements TravelEncumbranceService {

    protected static Logger LOG = Logger.getLogger(TravelEncumbranceServiceImpl.class);

    protected BusinessObjectService businessObjectService;
    protected TravelDocumentService travelDocumentService;
    protected DocumentService documentService;
    protected EncumbranceService encumbranceService;
    protected EncumbranceCalculator encumbranceCalculator;
    protected GeneralLedgerPendingEntryService generalLedgerPendingEntryService;
    protected volatile AccountsReceivableModuleService accountsReceivableModuleService;
    protected PaymentMaintenanceService paymentMaintenanceService;
    protected PersonService personService;
    protected ConfigurationService configurationService;
    protected BalanceTypeService balanceTypeService;
    protected DateTimeService dateTimeService;
    protected UniversityDateService universityDateService;
    protected AccountingPeriodService accountingPeriodService;
    protected OptionsService optionsService;

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#liquidateEncumbranceForCancelTA(org.kuali.kfs.module.tem.document.TravelAuthorizationDocument)
     */
    @Override
    public void liquidateEncumbranceForCancelTA(TravelAuthorizationDocument travelAuthDocument) {
        //perform base on trip type
        if (travelAuthDocument.getTripType().isGenerateEncumbrance()) {
            // let's remove any associated held encumbrance entries
            deleteHeldEncumbranceEntriesForTrip(travelAuthDocument.getTravelDocumentIdentifier());

            travelAuthDocument.refreshReferenceObject(KFSPropertyConstants.GENERAL_LEDGER_PENDING_ENTRIES);
            //start GLPE sequence from the current GLPEs
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper = new GeneralLedgerPendingEntrySequenceHelper(
                    travelAuthDocument.getGeneralLedgerPendingEntries().size() + 1);

            deletePendingEntriesForTripCancellation(travelAuthDocument.getTravelDocumentIdentifier());

            // Get encumbrances for the document
            final Map<String, Object> criteria = new HashMap<String, Object>();
            criteria.put(KFSPropertyConstants.DOCUMENT_NUMBER, travelAuthDocument.getTravelDocumentIdentifier());

            final Iterator<Encumbrance> encumbranceIterator = encumbranceService.findOpenEncumbrance(criteria,
                    false);
            while (encumbranceIterator.hasNext()) {
                liquidateEncumbrance(encumbranceIterator.next(), sequenceHelper, travelAuthDocument, true);
            }
        }
    }

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#updateEncumbranceObjectCode(org.kuali.kfs.module.tem.document.TravelAuthorizationDocument, org.kuali.kfs.sys.businessobject.SourceAccountingLine)
     */
    @Override
    public void updateEncumbranceObjectCode(TravelAuthorizationDocument travelAuthDocument,
            SourceAccountingLine line) {
        // Accounting Line default the Encumbrance Object Code based on trip type, otherwise default object code to blank
        TripType tripType = travelAuthDocument.getTripType();
        line.setFinancialObjectCode(ObjectUtils.isNotNull(tripType) ? tripType.getEncumbranceObjCode() : "");
    }

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#getEncumbranceBalanceTypeByTripType(org.kuali.kfs.module.tem.document.TravelDocument)
     */
    @Override
    public String getEncumbranceBalanceTypeByTripType(TravelDocument document) {
        document.refreshReferenceObject(TemPropertyConstants.TRIP_TYPE);

        TripType tripType = document.getTripType();
        return ObjectUtils.isNotNull(tripType) ? StringUtils.defaultString(tripType.getEncumbranceBalanceType())
                : "";
    }

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#liquidateEncumbrance(org.kuali.kfs.gl.businessobject.Encumbrance, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, org.kuali.kfs.module.tem.document.TravelDocument, boolean)
     */
    @Override
    public void liquidateEncumbrance(final Encumbrance encumbrance,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper, TravelDocument document,
            boolean approveImmediately) {
        if (encumbrance.getAccountLineEncumbranceOutstandingAmount().isGreaterThan(KualiDecimal.ZERO)) {
            GeneralLedgerPendingEntry pendingEntry = this.setupPendingEntry(encumbrance, sequenceHelper, document);
            if (approveImmediately) {
                pendingEntry
                        .setFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.APPROVED);
            }
            sequenceHelper.increment();
            GeneralLedgerPendingEntry offsetEntry = this.setupOffsetEntry(encumbrance, sequenceHelper, document,
                    pendingEntry);
            if (approveImmediately) {
                offsetEntry
                        .setFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.APPROVED);
            }
            sequenceHelper.increment();

            final KualiDecimal amount = encumbrance.getAccountLineEncumbranceOutstandingAmount();
            pendingEntry.setTransactionLedgerEntryAmount(amount);
            offsetEntry.setTransactionLedgerEntryAmount(amount);
            document.addPendingEntry(pendingEntry);
            document.addPendingEntry(offsetEntry);
        }
    }

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#setupPendingEntry(org.kuali.kfs.gl.businessobject.Encumbrance, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, org.kuali.kfs.module.tem.document.TravelDocument)
     */
    @Override
    public GeneralLedgerPendingEntry setupPendingEntry(Encumbrance encumbrance,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper, TravelDocument document) {
        final GeneralLedgerPendingEntrySourceDetail sourceDetail = convertTo(document, encumbrance);

        GeneralLedgerPendingEntry pendingEntry = new GeneralLedgerPendingEntry();
        generalLedgerPendingEntryService.populateExplicitGeneralLedgerPendingEntry(document, sourceDetail,
                sequenceHelper, pendingEntry);
        updateEncumbranceEntry(encumbrance, document, pendingEntry);
        return pendingEntry;
    }

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#setupOffsetEntry(org.kuali.kfs.gl.businessobject.Encumbrance, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, org.kuali.kfs.module.tem.document.TravelDocument, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry)
     */
    @Override
    public GeneralLedgerPendingEntry setupOffsetEntry(Encumbrance encumbrance,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper, TravelDocument document,
            GeneralLedgerPendingEntry pendingEntry) {

        GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(pendingEntry);
        generalLedgerPendingEntryService.populateOffsetGeneralLedgerPendingEntry(
                pendingEntry.getUniversityFiscalYear(), pendingEntry, sequenceHelper, offsetEntry);
        updateEncumbranceEntry(encumbrance, document, offsetEntry);
        return offsetEntry;
    }

    /**
     * Using encumbrance information to preset the GLPE entry
     *
     * @param encumbrance
     * @param document
     * @param entry
     */
    private void updateEncumbranceEntry(Encumbrance encumbrance, TravelDocument document,
            GeneralLedgerPendingEntry entry) {
        String balanceType = getEncumbranceBalanceTypeByTripType(document);

        entry.setTransactionEncumbranceUpdateCode(KFSConstants.ENCUMB_UPDT_REFERENCE_DOCUMENT_CD);
        entry.setFinancialBalanceTypeCode(balanceType);
        entry.setFinancialDocumentApprovedCode(GENERAL_LEDGER_PENDING_ENTRY_CODE.NO);
        entry.setReferenceFinancialDocumentTypeCode(encumbrance.getDocumentTypeCode());
        entry.setReferenceFinancialSystemOriginationCode(encumbrance.getOriginCode());
    }

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#disencumberTravelAuthorizationClose(org.kuali.kfs.module.tem.document.TravelAuthorizationCloseDocument, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, java.util.List)
     */
    @Override
    public void disencumberTravelAuthorizationClose(TravelAuthorizationCloseDocument document,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
            List<GeneralLedgerPendingEntry> reimbursementPendingEntries) {

        //Get rid of all pending entries relating to encumbrance.
        clearAuthorizationEncumbranceGLPE(document);
        // let's remove any associated held encumbrance entries
        deleteHeldEncumbranceEntriesForTrip(document.getTravelDocumentIdentifier());

        final List<Encumbrance> encumbrances = getEncumbrancesForTrip(document.getTravelDocumentIdentifier(), null);

        applyReimbursementEntriesToEncumbrances(encumbrances, reimbursementPendingEntries);

        // Create encumbrance map based on account numbers
        int counter = document.getGeneralLedgerPendingEntries().size() + 1;
        for (Encumbrance encumbrance : encumbrances) {
            liquidateEncumbrance(encumbrance, sequenceHelper, document, false);
        }
    }

    /**
     * Find both posted and pending encumbrances associated with a trip
     * @param travelDocumentIdentifier the trip id, which acts as the document id of the encumbrance
     * @param skipDocumentNumber if not null, pending entries with the given document number will be skipped in the calculation
     * @return an Iterator of encumbrances
     */
    @Override
    @Transactional
    public List<Encumbrance> getEncumbrancesForTrip(String travelDocumentIdentifier, String skipDocumentNumber) {
        if (StringUtils.isBlank(travelDocumentIdentifier)) {
            return new ArrayList<Encumbrance>(); // there's no trip.  So don't bother looking up encumbrances
        }
        Map<String, Object> criteria = new HashMap<String, Object>();
        criteria.put(KFSPropertyConstants.DOCUMENT_NUMBER, travelDocumentIdentifier);
        Iterator<Encumbrance> encumbranceIterator = encumbranceService.findOpenEncumbrance(criteria, false);

        // now return single iterator
        List<Encumbrance> allEncumbrances = new ArrayList<Encumbrance>();
        while (encumbranceIterator.hasNext()) {
            allEncumbrances.add(encumbranceIterator.next());
        }

        // now get glpes which would create encumbrance
        Iterator<GeneralLedgerPendingEntry> pendingEntriesIterator = getPendingEntriesForTrip(
                travelDocumentIdentifier);
        while (pendingEntriesIterator.hasNext()) {
            final GeneralLedgerPendingEntry pendingEntry = pendingEntriesIterator.next();
            if (!StringUtils.equals(skipDocumentNumber, pendingEntry.getDocumentNumber())) {
                applyEntryToEncumbrances(allEncumbrances, pendingEntry);
            }
        }

        List<GeneralLedgerPendingEntry> heldEncumbranceEntries = findHeldEncumbranceEntriesForTrip(
                travelDocumentIdentifier);
        for (GeneralLedgerPendingEntry heldEntry : heldEncumbranceEntries) {
            if (!StringUtils.equals(skipDocumentNumber, heldEntry.getDocumentNumber())) {
                applyEntryToEncumbrances(allEncumbrances, heldEntry);
            }
        }

        return allEncumbrances;
    }

    /**
     * Retrieves the pending entries associated with the given travel document identifier
     * @param travelDocumentIdentifier the id of the trip/travel document
     * @return an Iterator of pending entries for that trip
     */
    protected Iterator<GeneralLedgerPendingEntry> getPendingEntriesForTrip(String travelDocumentIdentifier) {
        Map<String, String> glpeCriteria = new HashMap<String, String>();
        glpeCriteria.put(KFSPropertyConstants.DOCUMENT_NUMBER, travelDocumentIdentifier);
        Iterator<GeneralLedgerPendingEntry> pendingEntriesIterator = generalLedgerPendingEntryService
                .findPendingLedgerEntriesForEncumbrance(glpeCriteria, true); // find all approved entries with the criteria
        return pendingEntriesIterator;
    }

    /**
     * Retrieves all held encumbrance entries for the given trip, converting them to pending entries before sending them back
     * @param tripId the travel document identifier to look up held encumbrance entries for
     * @return a List of GLPEs created from held encumbrance entries
     */
    protected List<GeneralLedgerPendingEntry> findHeldEncumbranceEntriesForTrip(String tripId) {
        Map<String, String> heeCriteria = new HashMap<String, String>();
        heeCriteria.put(TemPropertyConstants.TRAVEL_DOCUMENT_IDENTIFIER, tripId);
        List<GeneralLedgerPendingEntry> entries = new ArrayList<GeneralLedgerPendingEntry>();
        Collection<HeldEncumbranceEntry> retrievedEntries = businessObjectService
                .findMatching(HeldEncumbranceEntry.class, heeCriteria);
        for (HeldEncumbranceEntry heeEntry : retrievedEntries) {
            GeneralLedgerPendingEntry glpe = convertHeldEncumbranceEntryToPendingEntry(heeEntry);
            entries.add(glpe);
        }
        return entries;
    }

    /**
     * Applies a single pending entry to the list of encumbrances if possible
     * @param allEncumbrances list of encumbrances to be updated
     * @param pendingEntry the pending entry to apply
     */
    protected void applyEntryToEncumbrances(List<Encumbrance> allEncumbrances,
            GeneralLedgerPendingEntry pendingEntry) {
        Encumbrance encumbrance = getEncumbranceCalculator().findEncumbrance(allEncumbrances, pendingEntry); // thank you, dear genius who extracted EncumbranceCalculator!
        if (encumbrance != null) {
            getEncumbranceCalculator().updateEncumbrance(pendingEntry, encumbrance);
        }
    }

    /**
     * Applies the pending entries from the TR - if they exist - to the given encumbrances
     * @param encumbrances the encumbrances to apply entries to
     * @param reimbursementPendingEntries the pending entries from the reimbursement - which may be null
     */
    protected void applyReimbursementEntriesToEncumbrances(List<Encumbrance> encumbrances,
            List<GeneralLedgerPendingEntry> reimbursementPendingEntries) {
        final Set<String> processedPendingEntryKeys = buildPendingEntryKeys(
                retrieveAllPendingEntriesForTravelDocumentIds(
                        getUniqueTravelDocumentIds(reimbursementPendingEntries)));
        if (reimbursementPendingEntries != null && !reimbursementPendingEntries.isEmpty()) {
            for (GeneralLedgerPendingEntry pendingEntry : reimbursementPendingEntries) {
                if (!pendingEntry.isTransactionEntryOffsetIndicator()) {
                    final String pendingEntryKey = buildPendingEntryKey(pendingEntry);
                    if (!processedPendingEntryKeys.contains(pendingEntryKey)) { // we need to avoid processing TR entries twice
                        applyEntryToEncumbrances(encumbrances, pendingEntry);
                    }
                }
            }
        }
    }

    /**
     * Finds a Set of the unique travel document ids in the given list of pending entries
     * @param pendingEntries general ledger pending entries to find
     * @return a Set of the unique ids
     */
    protected Set<String> getUniqueTravelDocumentIds(List<GeneralLedgerPendingEntry> pendingEntries) {
        Set<String> travelDocIds = new HashSet<String>();
        if (!CollectionUtils.isEmpty(pendingEntries)) {
            for (GeneralLedgerPendingEntry pendingEntry : pendingEntries) {
                if (!StringUtils.isBlank(pendingEntry.getReferenceFinancialDocumentNumber())) {
                    travelDocIds.add(pendingEntry.getReferenceFinancialDocumentNumber());
                }
            }
        }
        return travelDocIds;
    }

    /**
     * Retrieves all pending entries associated with all the of the trips specified with the given travel document ids
     * @param travelDocumentIds a Set of unique travel document ids to find pending entries for
     * @return an Iterator to list over all pending entries
     */
    protected Iterator<GeneralLedgerPendingEntry> retrieveAllPendingEntriesForTravelDocumentIds(
            Set<String> travelDocumentIds) {
        List<GeneralLedgerPendingEntry> allPendingEntries = new ArrayList<GeneralLedgerPendingEntry>();
        for (String travelDocumentId : travelDocumentIds) {
            Iterator<GeneralLedgerPendingEntry> currentPendingEntries = getPendingEntriesForTrip(travelDocumentId);
            //CollectionUtils.addAll(allPendingEntries, currentPendingEntries);
            while (currentPendingEntries.hasNext()) {
                allPendingEntries.add(currentPendingEntries.next());
            }
        }
        return allPendingEntries.iterator();
    }

    /**
     * Builds pending entry keys for each of the given pending entries
     * @param pendingEntries an Iterator of pending entries
     * @return a Set of unique keys for each of the pending entries
     */
    protected Set<String> buildPendingEntryKeys(Iterator<GeneralLedgerPendingEntry> pendingEntries) {
        Set<String> pendingEntryKeys = new HashSet<String>();
        while (pendingEntries.hasNext()) {
            GeneralLedgerPendingEntry pendingEntry = pendingEntries.next();
            pendingEntryKeys.add(buildPendingEntryKey(pendingEntry));
        }
        return pendingEntryKeys;
    }

    /**
     * Builds a key from the given pending entry based on the primary key fields of General Ledger Pending Entry
     * @param pendingEntry the pending entry to build a key from
     * @return a String key for the given pending entry
     */
    protected String buildPendingEntryKey(GeneralLedgerPendingEntry pendingEntry) {
        StringBuilder key = new StringBuilder();
        key.append(pendingEntry.getFinancialSystemOriginationCode());
        key.append('-');
        key.append(pendingEntry.getDocumentNumber());
        key.append('-');
        key.append(pendingEntry.getTransactionLedgerEntrySequenceNumber());
        return key.toString();
    }

    /**
     * This method adjusts the encumbrance for a TAA document.
     *
     * @param taDoc The document who pending entries need to be disencumbered.
     */
    @Override
    public void adjustEncumbranceForAmendment(TravelAuthorizationAmendmentDocument document,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
        if (document.getTripType().isGenerateEncumbrance()) {
            Map<String, Encumbrance> encumbranceMap = new HashMap<String, Encumbrance>();
            final List<Encumbrance> encumbrances = getEncumbrancesForTrip(document.getTravelDocumentIdentifier(),
                    document.getDocumentNumber());

            // Create encumbrance map based on account numbers
            for (Encumbrance encumbrance : encumbrances) {
                final String key = buildEncumbranceKey(encumbrance);
                encumbranceMap.put(key, encumbrance);
            }

            //Adjust current encumbrances with the new amounts If new pending entry is found in encumbrance map, create a pending
            // entry to balance the difference by either crediting or debiting. If not found just continue on to be processed as
            // normal.
            Iterator<GeneralLedgerPendingEntry> pendingEntriesIterator = document.getGeneralLedgerPendingEntries()
                    .iterator();
            while (pendingEntriesIterator.hasNext()) {
                GeneralLedgerPendingEntry pendingEntry = pendingEntriesIterator.next();

                if (!StringUtils.defaultString(pendingEntry.getOrganizationReferenceId())
                        .contains(TemConstants.IMPORTED_FLAG)) {
                    final String key = buildEncumbranceKey(pendingEntry);
                    Encumbrance encumbrance = encumbranceMap.get(key);

                    //If encumbrance found, find and calculate difference. If the difference is zero don't add to new list of glpe's If
                    // encumbrance is not found and glpe is not an offset glpe, add it and it's offset to the new list
                    if (encumbrance != null) {
                        KualiDecimal difference = encumbrance.getAccountLineEncumbranceOutstandingAmount()
                                .subtract(pendingEntry.getTransactionLedgerEntryAmount());
                        if (difference.isGreaterThan(KualiDecimal.ZERO)) {
                            if (!pendingEntry.isTransactionEntryOffsetIndicator()) {
                                pendingEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
                            } else {
                                pendingEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
                            }
                            pendingEntry.setTransactionLedgerEntryAmount(difference);
                            GeneralLedgerPendingEntry offset = pendingEntriesIterator.next();
                            offset.setTransactionLedgerEntryAmount(difference);
                        } else if (difference.isLessEqual(KualiDecimal.ZERO)) {
                            difference = difference.negated();
                            pendingEntry.setTransactionLedgerEntryAmount(difference);
                            GeneralLedgerPendingEntry offset = pendingEntriesIterator.next();
                            offset.setTransactionLedgerEntryAmount(difference);
                        }
                    }
                }
            }

            //Loop through and remove encumbrances from map. This is done here because there is a possibility of pending entries
            //with the same account number. Also, filter out 0 amount entries
            List<GeneralLedgerPendingEntry> continuingPendingEntries = new ArrayList<GeneralLedgerPendingEntry>();
            for (GeneralLedgerPendingEntry pendingEntry : document.getGeneralLedgerPendingEntries()) {
                if (!StringUtils.defaultString(pendingEntry.getOrganizationReferenceId())
                        .contains(TemConstants.IMPORTED_FLAG)
                        && !pendingEntry.isTransactionEntryOffsetIndicator()) {
                    final String key = buildEncumbranceKey(pendingEntry);
                    encumbranceMap.remove(key);
                }
                if (!pendingEntry.getTransactionLedgerEntryAmount().equals(KualiDecimal.ZERO)) {
                    continuingPendingEntries.add(pendingEntry);
                }
            }
            document.setGeneralLedgerPendingEntries(continuingPendingEntries);

            //Find any remaining encumbrances that no longer should exist in the new TAA.
            if (!encumbranceMap.isEmpty()) {
                for (final Encumbrance encumbrance : encumbranceMap.values()) {
                    liquidateEncumbrance(encumbrance, sequenceHelper, document, false);
                }
            }

        }
    }

    /**
     * Builds a key to represent an encumbrance
     * @param e the encumbrance to build a Map key for
     * @return the key for the encumbrance
     */
    protected String buildEncumbranceKey(Encumbrance e) {
        StringBuilder key = new StringBuilder();
        key.append(e.getAccountNumber());
        key.append(e.getSubAccountNumber());
        key.append(e.getObjectCode());
        key.append(e.getSubObjectCode());
        key.append(e.getDocumentNumber());
        return key.toString();
    }

    /**
     * Builds a key to represent an encumbrance, based on the general ledger pending entry which would update it
     * @param pendingEntry the pending entry which would update an encumbrance
     * @return the key representing the encumbrance
     */
    protected String buildEncumbranceKey(GeneralLedgerPendingEntry pendingEntry) {
        StringBuilder key = new StringBuilder();
        key.append(pendingEntry.getAccountNumber());
        key.append(pendingEntry.getSubAccountNumber());
        key.append(pendingEntry.getFinancialObjectCode());
        key.append(pendingEntry.getFinancialSubObjectCode());
        key.append(pendingEntry.getReferenceFinancialDocumentNumber());
        return key.toString();
    }

    /**
     * Find All related TA, TAA glpe's. Make sure they are not offsets(???) and not the current doc (this will be
     * previous document)
     *
     * Rather than deal with the complicated math of taking the old document's glpe's into account, just remove them
     * so they will never be picked up by the jobs and placed into encumbrance.  (Already processed document should
     * already have its GLPE scrubbed and
     *
     *
     * NOTE: this is really meant to prepare for TAC and TAA to remove the encumbrance entries.  However, if we remove
     * everything from previous TA, TAA document (what about TR?)
     *
     *  In case of TAC - deleting the TA/TAA entries are fine (we should probably note in the doc encumbrance were removed
     *  and liquidated by TAC) when there is no TR - if there are TRs, we need to look for the processed TR's dis-encumbrance
     *
     * Once there is TR in route, no more TA/TAA can be amend
     *
     * @param travelAuthDocument        The document being processed.  Should only be a TAA or TAC.
     */
    @Override
    public void processRelatedDocuments(TravelAuthorizationDocument travelAuthDocument) {
        List<Document> relatedDocs = travelDocumentService.getDocumentsRelatedTo(travelAuthDocument,
                TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT, TravelDocTypes.TRAVEL_AUTHORIZATION_AMEND_DOCUMENT);

        for (final Document tempDocument : relatedDocs) {
            if (!travelAuthDocument.getDocumentNumber().equals(tempDocument.getDocumentNumber())) {
                /*
                 * New for M3 - Skip glpe's created for imported expenses.
                 */
                for (GeneralLedgerPendingEntry glpe : travelAuthDocument.getGeneralLedgerPendingEntries()) {
                    if (glpe != null && glpe.getOrganizationReferenceId() != null
                            && !glpe.getOrganizationReferenceId().contains(TemConstants.IMPORTED_FLAG)) {
                        businessObjectService.delete(glpe);
                    }
                }
            }
        }
    }

    /**
     * Remove all the GLPE entries from the TAA documents (encumbrance adjust if exists), also annotated with note
     *
     * @param travelAuthDocument
     */
    public void clearAuthorizationEncumbranceGLPE(TravelAuthorizationCloseDocument travelAuthCloseDocument) {
        final List<String> encumbranceBalanceTypes = harvestCodesFromEncumbranceBalanceTypes();

        List<Document> relatedDocs = travelDocumentService.getDocumentsRelatedTo(travelAuthCloseDocument,
                TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT, TravelDocTypes.TRAVEL_AUTHORIZATION_AMEND_DOCUMENT);

        for (Document document : relatedDocs) {
            TravelAuthorizationDocument authorizationDocument = (TravelAuthorizationDocument) document;

            String docType = document instanceof TravelAuthorizationDocument
                    ? TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT
                    : TravelDocTypes.TRAVEL_AUTHORIZATION_AMEND_DOCUMENT;

            boolean hasRemovedGLPE = false;
            String note = String.format("TA Close Document # %s has cleared encumbrance GLPEs in %s # %s",
                    travelAuthCloseDocument.getDocumentNumber(), docType, document.getDocumentNumber());

            //if it has been processed and there are GLPEs, remove those which are encumbrance balance type
            if (travelDocumentService.isTravelAuthorizationProcessed(authorizationDocument)) {
                for (GeneralLedgerPendingEntry glpe : authorizationDocument.getGeneralLedgerPendingEntries()) {
                    if (encumbranceBalanceTypes.contains(glpe.getFinancialBalanceTypeCode())) {
                        businessObjectService.delete(glpe);
                        hasRemovedGLPE = true;
                    }
                }

                if (hasRemovedGLPE) {
                    try {
                        Note clearedGLPENote = documentService.createNoteFromDocument(authorizationDocument, note);
                        authorizationDocument.addNote(clearedGLPENote);
                        businessObjectService.save(authorizationDocument);
                    } catch (Exception ex) {
                        LOG.warn(ex.getMessage(), ex);
                    }
                }
            }
        }
    }

    /**
     * @return returns only the codes from encumbrance balance types
     */
    protected List<String> harvestCodesFromEncumbranceBalanceTypes() {
        List<String> balanceTypeCodes = new ArrayList<String>();
        Collection<BalanceType> encumbranceBalanceTypes = getBalanceTypeService().getAllEncumbranceBalanceTypes();
        for (BalanceType encumbranceBalanceType : encumbranceBalanceTypes) {
            balanceTypeCodes.add(encumbranceBalanceType.getCode());
        }
        return balanceTypeCodes;
    }

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#disencumberTravelReimbursementFunds(org.kuali.kfs.module.tem.document.TravelReimbursementDocument, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
     */
    @Override
    public void disencumberTravelReimbursementFunds(TravelReimbursementDocument travelReimbursementDocument,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
        List<Encumbrance> tripEncumbrances = getEncumbrancesForTrip(
                travelReimbursementDocument.getTravelDocumentIdentifier(), null);
        final List<TemSourceAccountingLine> travelReimbursementLines = travelReimbursementDocument
                .getSourceAccountingLines();
        final List<TemSourceAccountingLine> smooshedReimbursementLines = travelDocumentService
                .smooshAccountingLinesToSubAccount(travelReimbursementLines);
        for (TemSourceAccountingLine accountingLine : smooshedReimbursementLines) {
            Encumbrance encumbrance = findMatchingEncumbrance(accountingLine, tripEncumbrances);
            if (encumbrance != null && encumbrance.getAccountLineEncumbranceOutstandingAmount().isPositive()) {
                GeneralLedgerPendingEntry pendingEntry = setupPendingEntry(encumbrance, sequenceHelper,
                        travelReimbursementDocument);
                pendingEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE); // the disencumbrance should be a credit
                sequenceHelper.increment();
                GeneralLedgerPendingEntry offsetEntry = setupOffsetEntry(encumbrance, sequenceHelper,
                        travelReimbursementDocument, pendingEntry);
                sequenceHelper.increment();

                final KualiDecimal disencumbranceAmount = (encumbrance.getAccountLineEncumbranceOutstandingAmount()
                        .isLessThan(accountingLine.getAmount()))
                                ? encumbrance.getAccountLineEncumbranceOutstandingAmount()
                                : accountingLine.getAmount();
                pendingEntry.setTransactionLedgerEntryAmount(disencumbranceAmount);
                offsetEntry.setTransactionLedgerEntryAmount(disencumbranceAmount);
                travelReimbursementDocument.addPendingEntry(pendingEntry);
                travelReimbursementDocument.addPendingEntry(offsetEntry);

                encumbrance.setAccountLineEncumbranceClosedAmount(
                        encumbrance.getAccountLineEncumbranceClosedAmount().add(disencumbranceAmount));
            }
        }
    }

    /**
     * Given a List of open encumbrances for a trip, find the encumbrance which matches the accounting line     *
     * @param accountingLine the accounting line to find a matching encumbrance for
     * @param encumbrances the open encumbrances for this trip
     * @return the encumbrance matching the accounting line, or null if no matching encumbrance was found
     */
    protected Encumbrance findMatchingEncumbrance(TemSourceAccountingLine accountingLine,
            List<Encumbrance> encumbrances) {
        for (Encumbrance encumbrance : encumbrances) {
            if (StringUtils.equals(accountingLine.getChartOfAccountsCode(), encumbrance.getChartOfAccountsCode())
                    && StringUtils.equals(accountingLine.getAccountNumber(), encumbrance.getAccountNumber())
                    && (StringUtils.equals(accountingLine.getSubAccountNumber(), encumbrance.getSubAccountNumber())
                            || StringUtils.equals(KFSConstants.getDashSubAccountNumber(),
                                    encumbrance.getSubAccountNumber()))) {
                return encumbrance;
            }
        }
        return null;
    }

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#setupPendingEntry(org.kuali.kfs.sys.businessobject.AccountingLineBase, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, org.kuali.kfs.module.tem.document.TravelDocument)
     */
    @Override
    public GeneralLedgerPendingEntry setupPendingEntry(GeneralLedgerPendingEntrySourceDetail line,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper, TravelDocument document) {
        GeneralLedgerPendingEntry pendingEntry = new GeneralLedgerPendingEntry();

        String balanceType = "";
        document.refreshReferenceObject(TemPropertyConstants.TRIP_TYPE);
        TripType tripType = document.getTripType();
        if (ObjectUtils.isNotNull(tripType)) {
            balanceType = tripType.getEncumbranceBalanceType();
        }
        generalLedgerPendingEntryService.populateExplicitGeneralLedgerPendingEntry(document, line, sequenceHelper,
                pendingEntry);
        pendingEntry.setTransactionEncumbranceUpdateCode(KFSConstants.ENCUMB_UPDT_REFERENCE_DOCUMENT_CD);
        pendingEntry.setReferenceFinancialDocumentNumber(document.getTravelDocumentIdentifier());
        pendingEntry.setReferenceFinancialDocumentTypeCode(document.getFinancialDocumentTypeCode());
        pendingEntry.setFinancialBalanceTypeCode(balanceType);
        pendingEntry.setFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.APPROVED);
        pendingEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
        pendingEntry.setReferenceFinancialSystemOriginationCode(KFSConstants.ORIGIN_CODE_KUALI);

        return pendingEntry;
    }

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#setupOffsetEntry(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, org.kuali.kfs.module.tem.document.TravelDocument, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry)
     */
    @Override
    public GeneralLedgerPendingEntry setupOffsetEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
            TravelDocument document, GeneralLedgerPendingEntry pendingEntry) {
        String balanceType = "";
        document.refreshReferenceObject(TemPropertyConstants.TRIP_TYPE);
        TripType tripType = document.getTripType();
        if (ObjectUtils.isNotNull(tripType)) {
            balanceType = tripType.getEncumbranceBalanceType();
        }

        GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(pendingEntry);
        generalLedgerPendingEntryService.populateOffsetGeneralLedgerPendingEntry(
                pendingEntry.getUniversityFiscalYear(), pendingEntry, sequenceHelper, offsetEntry);
        offsetEntry.setTransactionEncumbranceUpdateCode(KFSConstants.ENCUMB_UPDT_REFERENCE_DOCUMENT_CD);
        offsetEntry.setReferenceFinancialDocumentTypeCode(document.getFinancialDocumentTypeCode());
        offsetEntry.setFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.APPROVED);
        offsetEntry.setFinancialBalanceTypeCode(balanceType);
        offsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
        offsetEntry.setReferenceFinancialSystemOriginationCode(
                pendingEntry.getReferenceFinancialSystemOriginationCode());

        return offsetEntry;
    }

    /**
     * @see org.kuali.kfs.module.tem.document.service.TravelDocumentService#convertTo(Encumbrance)
     */
    @SuppressWarnings("deprecation")
    @Override
    public GeneralLedgerPendingEntrySourceDetail convertTo(final TravelDocument document,
            final Encumbrance encumbrance) {

        AccountingLineBase accountLine = new SourceAccountingLine();
        accountLine.setChartOfAccountsCode(encumbrance.getChartOfAccountsCode());
        accountLine.setAccountNumber(encumbrance.getAccountNumber());
        accountLine.setAccount(encumbrance.getAccount());
        accountLine.setDocumentNumber(document.getDocumentNumber());
        accountLine.setFinancialObjectCode(encumbrance.getObjectCode());
        accountLine.setObjectCode(encumbrance.getFinancialObject());
        accountLine.setReferenceNumber(encumbrance.getDocumentNumber());
        accountLine.setSubAccountNumber(encumbrance.getSubAccountNumber());
        accountLine.setFinancialSubObjectCode(encumbrance.getSubObjectCode());
        accountLine.setFinancialDocumentLineDescription(encumbrance.getTransactionEncumbranceDescription());
        accountLine.setAmount(encumbrance.getAccountLineEncumbranceOutstandingAmount());
        accountLine.setPostingYear(encumbrance.getUniversityFiscalYear());
        accountLine.setBalanceTypeCode(encumbrance.getBalanceTypeCode());
        return accountLine;
    }

    /**
     * To be safe, we look up all document numbers associated with a trip (ie, look up any TA or TAA associated with the trip), and then remove all GLPEs associated with those document entries
     * Then lookup any invoices created for this trip, remove those glpe's, and finally find any checks associated with the trip and remove those pending entries
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#deletePendingEntriesForTrip(java.lang.String)
     */
    @Override
    public void deletePendingEntriesForTripCancellation(String travelDocumentIdentifier) {
        final Person kfsSystemUser = getPersonService().getPersonByPrincipalName(KFSConstants.SYSTEM_USER);

        List<String> tripDocumentNumbers = travelDocumentService
                .findAuthorizationDocumentNumbers(travelDocumentIdentifier);
        for (String documentNumber : tripDocumentNumbers) {
            generalLedgerPendingEntryService.delete(documentNumber); // delete glpe's if they exist
            cancelPaymentDetailForDocument(documentNumber, kfsSystemUser);
        }

        getAccountsReceivableModuleService().cancelInvoicesForTrip(travelDocumentIdentifier,
                travelDocumentService.getOrgOptions());
    }

    /**
     * Cancels the PDP payment detail for the document, if it exists (ie, the authorization would need to be extracted after it after it had an advance to extract...)
     * @param documentNumber the document number of the authorization to cancel payment details of
     * @param kfsSystemUser the KFS system user, responsible for cancelling the payment
     */
    protected void cancelPaymentDetailForDocument(String documentNumber, Person kfsSystemUser) {
        Map<String, String> keyMap = new HashMap<String, String>();
        keyMap.put(PdpPropertyConstants.PaymentDetail.PAYMENT_CUSTOMER_DOC_NUMBER, documentNumber);
        keyMap.put(
                PdpPropertyConstants.PaymentDetail.PAYMENT_GROUP + "."
                        + PdpPropertyConstants.PaymentGroup.PAYMENT_GROUP_PAYMENT_STATUS_CODE,
                PdpConstants.PaymentStatusCodes.OPEN); // only try to cancel open payments

        final Collection<PaymentDetail> paymentDetails = businessObjectService.findMatching(PaymentDetail.class,
                keyMap);
        if (paymentDetails != null && !paymentDetails.isEmpty()) {
            for (PaymentDetail paymentDetail : paymentDetails) {
                getPaymentMaintenanceService().cancelPendingPayment(paymentDetail.getPaymentGroupId().intValue(),
                        paymentDetail.getId().intValue(), getConfigurationService().getPropertyValueAsString(
                                TemKeyConstants.TA_MESSAGE_ADVANCE_PAYMENT_CANCELLED),
                        kfsSystemUser);
            }
        }
    }

    /**
     *
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#convertPendingEntryToHeldEncumbranceEntry(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry)
     */
    @Override
    public HeldEncumbranceEntry convertPendingEntryToHeldEncumbranceEntry(GeneralLedgerPendingEntry glpe) {
        HeldEncumbranceEntry hee = new HeldEncumbranceEntry();
        hee.setDocumentNumber(glpe.getDocumentNumber());
        hee.setTransactionLedgerEntrySequenceNumber(glpe.getTransactionLedgerEntrySequenceNumber());
        hee.setTravelDocumentIdentifier(glpe.getReferenceFinancialDocumentNumber());
        hee.setChartOfAccountsCode(glpe.getChartOfAccountsCode());
        hee.setAccountNumber(glpe.getAccountNumber());
        hee.setSubAccountNumber(glpe.getSubAccountNumber());
        hee.setFinancialObjectCode(glpe.getFinancialObjectCode());
        hee.setFinancialSubObjectCode(glpe.getFinancialSubObjectCode());
        hee.setFinancialBalanceTypeCode(glpe.getFinancialBalanceTypeCode());
        hee.setTransactionLedgerEntryDescription(glpe.getTransactionLedgerEntryDescription());
        hee.setTransactionLedgerEntryAmount(glpe.getTransactionLedgerEntryAmount());
        hee.setTransactionDebitCreditCode(glpe.getTransactionDebitCreditCode());
        hee.setProjectCode(glpe.getProjectCode());
        hee.setFinancialDocumentTypeCode(glpe.getFinancialDocumentTypeCode());
        hee.setOrganizationReferenceId(glpe.getOrganizationReferenceId());
        hee.setAcctSufficientFundsFinObjCd(glpe.getAcctSufficientFundsFinObjCd());
        hee.setTransactionEntryOffsetIndicator(glpe.isTransactionEntryOffsetIndicator());
        hee.setTransactionEntryProcessedTs(glpe.getTransactionEntryProcessedTs());
        return hee;
    }

    /**
     * Returns null if the accounting period to post to does not yet exist
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#convertHeldEncumbranceEntryToPendingEntry(org.kuali.kfs.module.tem.businessobject.HeldEncumbranceEntry)
     */
    @Override
    public GeneralLedgerPendingEntry convertHeldEncumbranceEntryToPendingEntry(HeldEncumbranceEntry hee) {
        final AccountingPeriod postingAccountingPeriod = getPostingAccountingPeriodForHeldEncumbrance(hee);
        if (postingAccountingPeriod == null) {
            return null;
        }
        GeneralLedgerPendingEntry glpe = new GeneralLedgerPendingEntry();
        glpe.setFinancialSystemOriginationCode(KFSConstants.ORIGIN_CODE_KUALI);
        glpe.setDocumentNumber(hee.getDocumentNumber());
        glpe.setTransactionLedgerEntrySequenceNumber(hee.getTransactionLedgerEntrySequenceNumber());
        glpe.setChartOfAccountsCode(hee.getChartOfAccountsCode());
        glpe.setAccountNumber(hee.getAccountNumber());
        glpe.setSubAccountNumber(hee.getSubAccountNumber());
        glpe.setFinancialObjectCode(hee.getFinancialObjectCode());
        glpe.setFinancialSubObjectCode(hee.getFinancialSubObjectCode());
        glpe.setFinancialBalanceTypeCode(hee.getFinancialBalanceTypeCode());
        if (ObjectUtils.isNull(hee.getFinancialObject())) {
            hee.refreshReferenceObject(KFSPropertyConstants.FINANCIAL_OBJECT);
        }
        if (!ObjectUtils.isNull(hee.getFinancialObject())) {
            glpe.setFinancialObjectTypeCode(hee.getFinancialObject().getFinancialObjectTypeCode());
        }
        glpe.setUniversityFiscalYear(postingAccountingPeriod.getUniversityFiscalYear());
        glpe.setUniversityFiscalPeriodCode(postingAccountingPeriod.getUniversityFiscalPeriodCode());
        glpe.setTransactionDate(getDateTimeService().getCurrentSqlDate());
        glpe.setFinancialDocumentTypeCode(TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT);
        glpe.setOrganizationDocumentNumber(hee.getTravelDocumentIdentifier());
        glpe.setTransactionLedgerEntryDescription(hee.getTransactionLedgerEntryDescription());
        glpe.setTransactionLedgerEntryAmount(hee.getTransactionLedgerEntryAmount());
        glpe.setReferenceFinancialDocumentNumber(hee.getTravelDocumentIdentifier());
        glpe.setReferenceFinancialDocumentTypeCode(TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT);
        glpe.setReferenceFinancialSystemOriginationCode(KFSConstants.ORIGIN_CODE_KUALI);
        glpe.setTransactionEncumbranceUpdateCode(KFSConstants.ENCUMB_UPDT_REFERENCE_DOCUMENT_CD);
        glpe.setFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.APPROVED);
        glpe.setTransactionDebitCreditCode(hee.getTransactionDebitCreditCode());
        glpe.setProjectCode(hee.getProjectCode());
        glpe.setFinancialDocumentTypeCode(hee.getFinancialDocumentTypeCode());
        glpe.setOrganizationReferenceId(hee.getOrganizationReferenceId());
        glpe.setAcctSufficientFundsFinObjCd(hee.getAcctSufficientFundsFinObjCd());
        glpe.setTransactionEntryOffsetIndicator(hee.isTransactionEntryOffsetIndicator());
        glpe.setTransactionEntryProcessedTs(hee.getTransactionEntryProcessedTs());
        return glpe;
    }

    /**
     * The posting accounting period of the held encumbrance is the FIRST period of the fiscal year of the END DATE of the current TA related to the held encumbrance
     * @return the AccountingPeriod of the posting account
     */
    protected AccountingPeriod getPostingAccountingPeriodForHeldEncumbrance(HeldEncumbranceEntry hee) {
        if (doesHeldEncumbranceRepresentAuthorization(hee)) {
            final TravelAuthorizationDocument originalTravelAuthorization = getTravelAuthForTripId(
                    hee.getTravelDocumentIdentifier());
            if (originalTravelAuthorization == null) {
                LOG.warn("Could not find travel authorization for trip id " + hee.getTravelDocumentIdentifier()
                        + " which is strange because we're looking for the travel authorization which created a TEM held encumbrance entry");
            } else {
                final TravelAuthorizationDocument currentTravelAuthorization = travelDocumentService
                        .findCurrentTravelAuthorization(originalTravelAuthorization);
                final java.sql.Date tripEnd = new java.sql.Date(currentTravelAuthorization.getTripEnd().getTime());
                final Integer tripEndFiscalYear = getUniversityDateService().getFiscalYear(tripEnd);
                if (tripEndFiscalYear == null) {
                    LOG.info("Could not yet release TEM held encumbrance entry " + hee.getDocumentNumber()
                            + " sequence: " + hee.getTransactionLedgerEntrySequenceNumber()
                            + " because the fiscal year for the trip end does not yet exist.");
                } else {
                    final AccountingPeriod firstAccountingPeriodOfEncumbranceFiscalYear = getFirstAccountingPeriodOfFiscalYear(
                            tripEndFiscalYear);
                    return firstAccountingPeriodOfEncumbranceFiscalYear;
                }
            }
        } else if (doesHeldEncumbranceRepresentAdvance(hee)) {
            final TravelAdvance advance = getTravelAdvanceForDocumentNumber(hee.getDocumentNumber());
            if (advance != null) {
                final Integer dueDateFiscalYear = getUniversityDateService().getFiscalYear(advance.getDueDate());
                final AccountingPeriod firstAccountingPeriodOfDueDateFiscalYear = getFirstAccountingPeriodOfFiscalYear(
                        dueDateFiscalYear);
                return firstAccountingPeriodOfDueDateFiscalYear;
            }
        }
        return null;
    }

    /**
     * Returns the first accounting period of the given fiscal year
     * @param fiscalYear the fiscal year to find the first accounting period for
     * @return the first accounting period of the given fiscal year
     */
    protected AccountingPeriod getFirstAccountingPeriodOfFiscalYear(Integer fiscalYear) {
        final java.util.Date firstDateOfFiscalYear = getUniversityDateService()
                .getFirstDateOfFiscalYear(fiscalYear);
        final AccountingPeriod firstAccountingPeriodOfFiscalYear = getAccountingPeriodService()
                .getByDate(new java.sql.Date(firstDateOfFiscalYear.getTime()));
        return firstAccountingPeriodOfFiscalYear;
    }

    /**
     * Get the travel authorization associated with the given trip id
     * @param tripId the trip id to find an authorization for
     * @return the travel authorization document if found, null otherwise
     */
    protected TravelAuthorizationDocument getTravelAuthForTripId(String tripId) {
        Map<String, Object> fieldValues = new HashMap<String, Object>();
        fieldValues.put(TemPropertyConstants.TRAVEL_DOCUMENT_IDENTIFIER, tripId);
        Collection<TravelAuthorizationDocument> travelAuthDocs = businessObjectService
                .findMatching(TravelAuthorizationDocument.class, fieldValues);
        TravelAuthorizationDocument travelAuth = null;
        for (TravelAuthorizationDocument currAuth : travelAuthDocs) {
            travelAuth = currAuth; // we should only be in this loop once or never
        }
        return travelAuth;
    }

    /**
     * Turns held encumbrances into glpes and approves and saves them if the glpe can be successfully converted (ie, the new fiscal year exists)
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#releaseHeldEncumbrances()
     */
    @Override
    public void releaseHeldEncumbrances() {
        Map<String, Boolean> releaseCache = new HashMap<String, Boolean>();
        Map<String, TravelAuthorizationDocument> documentCache = new HashMap<String, TravelAuthorizationDocument>();

        Collection<HeldEncumbranceEntry> allHeldEntries = businessObjectService.findAll(HeldEncumbranceEntry.class);
        List<HeldEncumbranceEntry> entriesToDelete = new ArrayList<HeldEncumbranceEntry>();
        List<GeneralLedgerPendingEntry> entriesToSave = new ArrayList<GeneralLedgerPendingEntry>();

        for (HeldEncumbranceEntry heldEntry : allHeldEntries) {
            if (shouldReleaseEntry(heldEntry, releaseCache)) {
                GeneralLedgerPendingEntry glpe = convertHeldEncumbranceEntryToPendingEntry(heldEntry);
                if (glpe != null) {
                    GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(glpe);
                    GeneralLedgerPendingEntrySequenceHelper sequenceHelper = new GeneralLedgerPendingEntrySequenceHelper(
                            glpe.getTransactionLedgerEntrySequenceNumber() + 1); // assume that the offset can use the sequence number + 1 of the original entry (which is what the original offset should have been)
                    final Integer fiscalYear = universityDateService.getFiscalYear(getDateForEntry(heldEntry));

                    generalLedgerPendingEntryService.populateOffsetGeneralLedgerPendingEntry(fiscalYear, glpe,
                            sequenceHelper, offsetEntry);

                    glpe.setFinancialDocumentApprovedCode(KFSConstants.DocumentStatusCodes.APPROVED);
                    offsetEntry.setFinancialDocumentApprovedCode(KFSConstants.DocumentStatusCodes.APPROVED);
                    entriesToSave.add(glpe);
                    entriesToSave.add(offsetEntry);
                    entriesToDelete.add(heldEntry);
                }
            }
        }
        businessObjectService.save(entriesToSave);
        businessObjectService.delete(entriesToDelete);
    }

    /**
     * Determines if an entry should be released
     * @param heldEntry the entry to determine the release eligibility of
     * @param releaseCache a cache of whether we should release entries, to avoid hitting the database so often
     * @return true if the entry should be released, false otherwise
     */
    protected boolean shouldReleaseEntry(HeldEncumbranceEntry heldEntry, Map<String, Boolean> releaseCache) {
        final String cacheKey = buildReleaseCacheKey(heldEntry);
        final Boolean cacheResults = releaseCache.get(cacheKey);
        if (cacheResults != null) {
            return cacheResults.booleanValue();
        }

        // still here? we'd best figure out if we can release or not
        final java.sql.Date releaseDate = getDateForEntry(heldEntry);
        if (releaseDate == null) {
            LOG.warn("Could not determine release date for held entry " + heldEntry.getDocumentNumber());
            releaseCache.put(cacheKey, Boolean.FALSE); // can't determine a date?  Then let's not release
            return false;
        }

        final boolean shouldReleaseEntry = !shouldHoldEntries(releaseDate);
        releaseCache.put(cacheKey, Boolean.valueOf(shouldReleaseEntry));
        return shouldReleaseEntry;
    }

    /**
     * Finds the date which should be used to determine if the entry can be released for the given entry
     * @param heldEntry the entry to find a release date for
     * @return the release date, or null if a value could not be determined
     */
    protected java.sql.Date getDateForEntry(HeldEncumbranceEntry heldEntry) {
        if (doesHeldEncumbranceRepresentAuthorization(heldEntry)) {
            return getDateForEntryFromDocument(heldEntry.getDocumentNumber());
        } else if (doesHeldEncumbranceRepresentAdvance(heldEntry)) {
            return getDateForEntryFromAdvance(heldEntry.getDocumentNumber());
        }
        return null;
    }

    /**
     * Determines if the given entry represents a travel authorization
     * @param heldEntry the held entry to find representation for
     * @return true if the held entry represents a travel authorization, false otherwise
     */
    protected boolean doesHeldEncumbranceRepresentAuthorization(HeldEncumbranceEntry heldEntry) {
        return StringUtils.equals(heldEntry.getFinancialDocumentTypeCode(),
                TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT)
                || StringUtils.equals(heldEntry.getFinancialDocumentTypeCode(),
                        TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_AMEND_DOCUMENT);
    }

    /**
     * Determines if the given entry represents a travel advance
     * @param heldEntry the held entry to find representation for
     * @return true if the held entry represents a travel advance, false otherwise
     */
    protected boolean doesHeldEncumbranceRepresentAdvance(HeldEncumbranceEntry heldEntry) {
        return StringUtils.equals(heldEntry.getFinancialDocumentTypeCode(),
                TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_WIRE_OR_FOREIGN_DRAFT_DOCUMENT)
                || StringUtils.equals(heldEntry.getFinancialDocumentTypeCode(),
                        TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_CHECK_ACH_DOCUMENT);
    }

    /**
     * Uses the trip end date of a travel authorization or travel authorization amendment to act as date for determining if entry may be released
     * @param documentNumber the document number to find the trip end date of
     * @return the trip end date or null if a value could not be determined
     */
    protected java.sql.Date getDateForEntryFromDocument(String documentNumber) {
        final TravelAuthorizationDocument travelAuth = getBusinessObjectService()
                .findBySinglePrimaryKey(TravelAuthorizationDocument.class, documentNumber); // because of extents in ojb, this will pick up travel authorizations as well
        if (travelAuth != null && travelAuth.getTripEnd() != null) {
            return new java.sql.Date(travelAuth.getTripEnd().getTime());
        }
        return null;
    }

    /**
     * Uses the due date of a travel advance to act as the date for determining if entry may be released
     * @param documentNumber the document number of the advance to find the due date of
     * @return the due date of the advance or null if a value could not be determined
     */
    protected java.sql.Date getDateForEntryFromAdvance(String documentNumber) {
        final TravelAdvance advance = getTravelAdvanceForDocumentNumber(documentNumber);
        if (advance != null) {
            return advance.getDueDate();
        }
        return null;
    }

    /**
     * Given a document number, looks up the travel advance from that document
     * @param documentNumber the document number to look up
     * @return the travel advance, or null if an advance could not be found
     */
    protected TravelAdvance getTravelAdvanceForDocumentNumber(String documentNumber) {
        Map<String, String> fieldValues = new HashMap<String, String>();
        fieldValues.put(KFSPropertyConstants.DOCUMENT_NUMBER, documentNumber); // we assume if a held entry was created, the doc was approved and the advance should be processed - so we're only going to look at the advance
        Collection<TravelAdvance> advances = getBusinessObjectService().findMatching(TravelAdvance.class,
                fieldValues);
        if (advances != null && !advances.isEmpty()) {
            final TravelAdvance advance = advances.iterator().next(); // there should only be one
            return advance;
        }
        return null;
    }

    /**
     * Creates a key for the release cache from the held encumbrance entry
     * @param heldEntry the entry to create a key for
     * @return the key for the entry
     */
    protected String buildReleaseCacheKey(HeldEncumbranceEntry heldEntry) {
        StringBuilder key = new StringBuilder();
        key.append(heldEntry.getDocumentNumber());
        key.append("-");
        key.append(heldEntry.getFinancialDocumentTypeCode());
        return key.toString();
    }

    /**
     * Uses the business object service to delete matching held encumbrances
     * @see org.kuali.kfs.module.tem.document.service.TravelEncumbranceService#deleteHeldEncumbranceEntriesForTrip(java.lang.String)
     */
    @Override
    public void deleteHeldEncumbranceEntriesForTrip(String tripId) {
        Map<String, Object> fieldValues = new HashMap<String, Object>();
        fieldValues.put(TemPropertyConstants.TRAVEL_DOCUMENT_IDENTIFIER, tripId);
        businessObjectService.deleteMatching(HeldEncumbranceEntry.class, fieldValues);
    }

    /**
     * Checks that a number of objects needed to support the entries exist - the fiscal year, the university date, the accounting period, and the offset definition
     * @see org.kuali.kfs.module.tem.service.TravelEncumbranceService#shouldHoldEntries(java.util.Date)
     */
    @Override
    public boolean shouldHoldEntries(Date tripDate) {
        final Integer currentFiscalYear = getUniversityDateService().getCurrentFiscalYear();
        final Integer tripEndFiscalYear = getUniversityDateService().getFiscalYear(tripDate);
        if (tripEndFiscalYear == null) {
            return true; // no fiscal year yet - we definitely need to hold entries
        }

        AccountingPeriod accountingPeriod = null;
        HashMap<String, Object> fieldValues = new HashMap<String, Object>();
        fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, tripEndFiscalYear);
        fieldValues.put(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE,
                TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT);

        final Integer heldEncumbranceFiscalYear = getUniversityDateService().getFiscalYear(tripDate);
        SystemOptions options = getOptionsService().getOptions(tripEndFiscalYear);

        final int matchingCount = getBusinessObjectService().countMatching(OffsetDefinition.class, fieldValues);

        try {
            if (currentFiscalYear.equals(tripEndFiscalYear)) {
                accountingPeriod = getAccountingPeriodService().getByDate(tripDate);
            } else {
                final String firstDateOfEncumbranceFiscalYear = getDateTimeService()
                        .toDateString(getUniversityDateService().getFirstDateOfFiscalYear(tripEndFiscalYear));
                accountingPeriod = getAccountingPeriodService()
                        .getByDate(getDateTimeService().convertToSqlDate(firstDateOfEncumbranceFiscalYear));
            }
        } catch (ParseException pe) {
            LOG.error("error while parsing date " + pe);
        }

        final boolean holdEncumbrance = tripEndFiscalYear == null || options == null || accountingPeriod == null
                || matchingCount == 0;
        return holdEncumbrance;
    }

    public void setDocumentService(DocumentService documentService) {
        this.documentService = documentService;
    }

    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

    public OptionsService getOptionsService() {
        return optionsService;
    }

    public void setOptionsService(OptionsService optionsService) {
        this.optionsService = optionsService;
    }

    public BusinessObjectService getBusinessObjectService() {
        return businessObjectService;
    }

    public void setTravelDocumentService(TravelDocumentService travelDocumentService) {
        this.travelDocumentService = travelDocumentService;
    }

    public void setGeneralLedgerPendingEntryService(
            GeneralLedgerPendingEntryService generalLedgerPendingEntryService) {
        this.generalLedgerPendingEntryService = generalLedgerPendingEntryService;
    }

    public void setEncumbranceService(EncumbranceService encumbranceService) {
        this.encumbranceService = encumbranceService;
    }

    /**
     * @return the injected encumbrance calculator
     */
    public EncumbranceCalculator getEncumbranceCalculator() {
        return encumbranceCalculator;
    }

    /**
     * Injects an encumbrance calculator into this service
     * @param encumbranceCalculator the implementation of EncumbranceCalculator to use
     */
    public void setEncumbranceCalculator(EncumbranceCalculator encumbranceCalculator) {
        this.encumbranceCalculator = encumbranceCalculator;
    }

    /**
     * @return the system-ste implementation of the AccountsReceivableModuleService
     */
    public AccountsReceivableModuleService getAccountsReceivableModuleService() {
        if (accountsReceivableModuleService == null) {
            accountsReceivableModuleService = SpringContext.getBean(AccountsReceivableModuleService.class);
        }
        return accountsReceivableModuleService;
    }

    /**
     * @return the injected implementation of the PaymentMaintenanceService
     */
    public PaymentMaintenanceService getPaymentMaintenanceService() {
        return paymentMaintenanceService;
    }

    /**
     * Injects an implementation of the PaymentMaintenanceService
     * @param paymentMaintenanceService the implementation of the PaymentMaintenanceService to inject
     */
    public void setPaymentMaintenanceService(PaymentMaintenanceService paymentMaintenanceService) {
        this.paymentMaintenanceService = paymentMaintenanceService;
    }

    /**
     * @return the injected implementation of the PersonService
     */
    public PersonService getPersonService() {
        return personService;
    }

    /**
     * Injects an implementation of the PersonService
     * @param personService the implementation of the PersonService to inject
     */
    public void setPersonService(PersonService personService) {
        this.personService = personService;
    }

    /**
     * @return the injected implementation of the ConfigurationService
     */
    public ConfigurationService getConfigurationService() {
        return configurationService;
    }

    /**
     * Injects an implementation of the ConfigurationService
     * @param configurationService the implementation of the ConfigurationService to inject
     */
    public void setConfigurationService(ConfigurationService configurationService) {
        this.configurationService = configurationService;
    }

    /**
     * @return the injected implementation of the BalanceTypeService
     */
    public BalanceTypeService getBalanceTypeService() {
        return balanceTypeService;
    }

    /**
     * Injects an implementation of the BalanceTypeService
     * @param balanceTypeService the implementation of the BalanceTypeService to inject
     */
    public void setBalanceTypeService(BalanceTypeService balanceTypeService) {
        this.balanceTypeService = balanceTypeService;
    }

    public DateTimeService getDateTimeService() {
        return dateTimeService;
    }

    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    public UniversityDateService getUniversityDateService() {
        return universityDateService;
    }

    public void setUniversityDateService(UniversityDateService universityDateService) {
        this.universityDateService = universityDateService;
    }

    public AccountingPeriodService getAccountingPeriodService() {
        return accountingPeriodService;
    }

    public void setAccountingPeriodService(AccountingPeriodService accountingPeriodService) {
        this.accountingPeriodService = accountingPeriodService;
    }
}