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