/* * 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.ld.businessobject.lookup; import static org.kuali.kfs.module.ld.LaborConstants.BalanceInquiries.EMPLOYEE_FUNDING_EXPENSE_OBJECT_TYPE_CODE; import static org.kuali.kfs.module.ld.LaborConstants.BalanceInquiries.EMPLOYEE_FUNDING_NORMAL_OP_EXPENSE_OBJECT_TYPE_CODE; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.math.BigDecimal; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.ObjectUtils; import org.kuali.kfs.gl.Constant; import org.kuali.kfs.integration.ld.businessobject.inquiry.AbstractPositionDataDetailsInquirableImpl; import org.kuali.kfs.module.ld.businessobject.EmployeeFunding; import org.kuali.kfs.module.ld.businessobject.LaborLedgerPendingEntry; import org.kuali.kfs.module.ld.businessobject.inquiry.EmployeeFundingInquirableImpl; import org.kuali.kfs.module.ld.businessobject.inquiry.PositionDataDetailsInquirableImpl; import org.kuali.kfs.module.ld.service.LaborInquiryOptionsService; import org.kuali.kfs.module.ld.service.LaborLedgerBalanceService; import org.kuali.kfs.module.ld.service.LaborLedgerPendingEntryService; import org.kuali.kfs.module.ld.util.DebitCreditUtil; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.ObjectUtil; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl; import org.kuali.rice.kns.lookup.HtmlData; import org.kuali.rice.kns.lookup.HtmlData.AnchorHtmlData; import org.kuali.rice.krad.bo.BusinessObject; import org.kuali.rice.krad.lookup.CollectionIncomplete; import org.kuali.rice.krad.util.BeanPropertyComparator; /** * The EmployeeFundingLookupableHelperServiceImpl class is the front-end for all Employee Funding balance inquiry processing. */ public class EmployeeFundingLookupableHelperServiceImpl extends AbstractLookupableHelperServiceImpl { private static final org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory.getLog(EmployeeFundingLookupableHelperServiceImpl.class); private LaborLedgerBalanceService laborLedgerBalanceService; private LaborInquiryOptionsService laborInquiryOptionsService; private LaborLedgerPendingEntryService laborLedgerPendingEntryService; /** * @see org.kuali.rice.kns.lookup.Lookupable#getInquiryUrl(org.kuali.rice.krad.bo.BusinessObject, java.lang.String) */ @Override public HtmlData getInquiryUrl(BusinessObject bo, String propertyName) { if (KFSPropertyConstants.POSITION_NUMBER.equals(propertyName)) { EmployeeFunding employeeFunding = (EmployeeFunding) bo; AbstractPositionDataDetailsInquirableImpl positionDataDetailsInquirable = new PositionDataDetailsInquirableImpl(); Map<String, String> fieldValues = new HashMap<String, String>(); fieldValues.put(propertyName, employeeFunding.getPositionNumber()); BusinessObject positionData = positionDataDetailsInquirable.getBusinessObject(fieldValues); return positionData == null ? new AnchorHtmlData(KFSConstants.EMPTY_STRING, KFSConstants.EMPTY_STRING) : positionDataDetailsInquirable.getInquiryUrl(positionData, propertyName); } return (new EmployeeFundingInquirableImpl()).getInquiryUrl(bo, propertyName); } /** * @see org.kuali.rice.kns.lookup.Lookupable#gfetSearchResults(java.util.Map) */ @Override public List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues) { setBackLocation(fieldValues.get(KFSConstants.BACK_LOCATION)); setDocFormKey(fieldValues.get(KFSConstants.DOC_FORM_KEY)); boolean showBlankLine = this.showBlankLines(fieldValues); fieldValues.remove(Constant.BLANK_LINE_OPTION); // get the pending entry option. This method must be prior to the get search results String pendingEntryOption = laborInquiryOptionsService.getSelectedPendingEntryOption(fieldValues); // test if the consolidation option is selected or not boolean isConsolidated = false; Collection<EmployeeFunding> searchResultsCollection = laborLedgerBalanceService.findEmployeeFundingWithCSFTracker(fieldValues, isConsolidated); if (!showBlankLine) { Collection<EmployeeFunding> tempSearchResultsCollection = new ArrayList<EmployeeFunding>(); for (EmployeeFunding employeeFunding : searchResultsCollection) { // KFSCNTRB-1534- Properly group CSF Items and show all CSF items that should be shown if( employeeFunding.getCurrentAmount().isNonZero() || employeeFunding.getOutstandingEncumbrance().isNonZero() || (employeeFunding.getCsfAmount() != null && employeeFunding.getCsfAmount().isNonZero()) ){ tempSearchResultsCollection.add(employeeFunding); } } searchResultsCollection = tempSearchResultsCollection; } // update search results according to the selected pending entry option updateByPendingLedgerEntry(searchResultsCollection, fieldValues, pendingEntryOption, isConsolidated); searchResultsCollection = consolidateObjectTypeCode(searchResultsCollection); // get the actual size of all qualified search results Long actualSize = new Long(searchResultsCollection.size()); return this.buildSearchResultList(searchResultsCollection, actualSize); } /** * Filter search results to consolidate them if the only difference is object type code. * * @param searchResultsCollection * @return */ private Collection<EmployeeFunding> consolidateObjectTypeCode(Collection<EmployeeFunding> searchResultsCollection) { // KFSCNTRB-1534- Properly group CSF Items and show all CSF items that should be shown Collection<EmployeeFunding> ret = new ArrayList<EmployeeFunding>(searchResultsCollection.size()); for(EmployeeFunding empFunding : searchResultsCollection) { EmployeeFunding temp = findEmployeeFunding(ret, empFunding); if (temp == null){ ret.add(empFunding); } else { /*we need to act on `temp' because that's the one from the collection.*/ temp.setCsfFullTimeEmploymentQuantity(add(temp.getCsfFullTimeEmploymentQuantity(), empFunding.getCsfFullTimeEmploymentQuantity())); temp.setCsfAmount(add(temp.getCsfAmount(), empFunding.getCsfAmount())); temp.setCurrentAmount(add(temp.getCurrentAmount(), empFunding.getCurrentAmount())); temp.setOutstandingEncumbrance(add(temp.getOutstandingEncumbrance(), empFunding.getOutstandingEncumbrance())); temp.setTotalAmount(add(temp.getTotalAmount(), empFunding.getTotalAmount())); } } return ret; } /** * Searches the given collection for an element that is equal to the given exhibit for purposes of consolidation. * * @param coll The collection to search for a like element. * @param exhibit The search criteria. * * @return The element from the collection that matches the exhibit or null if 1) no items match or * the 2) exhibit is null. * */ private static EmployeeFunding findEmployeeFunding(Collection<EmployeeFunding> coll, EmployeeFunding exhibit){ if (exhibit == null){ return null; } for (EmployeeFunding temp : coll){ if (temp == null){ continue; } if (ObjectUtils.notEqual(temp.getEmplid(), exhibit.getEmplid())){ continue; } if (ObjectUtils.notEqual(temp.getUniversityFiscalYear(), exhibit.getUniversityFiscalYear())){ continue; } if (ObjectUtils.notEqual(temp.getChartOfAccountsCode(), exhibit.getChartOfAccountsCode())){ continue; } if (ObjectUtils.notEqual(temp.getAccountNumber(), exhibit.getAccountNumber())){ continue; } if (ObjectUtils.notEqual(temp.getSubAccountNumber(), exhibit.getSubAccountNumber())){ continue; } if (ObjectUtils.notEqual(temp.getFinancialObjectCode(), exhibit.getFinancialObjectCode())){ continue; } if (ObjectUtils.notEqual(temp.getFinancialSubObjectCode(), exhibit.getFinancialSubObjectCode())){ continue; } if (ObjectUtils.notEqual(temp.getPositionNumber(), exhibit.getPositionNumber())){ continue; } return temp; } /*no items in the collection match the exhibit.*/ return null; } /** * Adds two KualiDecimal objects in a null-safe way. If one of them is null, the other is returned, otherwise, * a new KualiDecimal containing their sum is returned. * * @param one The first KualiDecimal to add. * @param two The second KualiDecimal to add. * @return The sum of the two KualiDecimals. * */ private static KualiDecimal add(KualiDecimal one, KualiDecimal two) { if (one == null){ return two; } if (two == null){ return one; } return one.add(two); } /** * Adds two BigDecimal objects. If one of them is null, the other is returned, otherwise, * a new BigDecimal containing their sum is returned. * * @param one The first BigDecimal to add. * @param two The second BigDecimal to add. * @return The sum of the two BigDecimals. * */ private static BigDecimal add(BigDecimal one, BigDecimal two) { if (one == null){ return two; } if (two == null){ return one; } return one.add(two); } private boolean showBlankLines(Map fieldValues) { String pendingEntryOption = (String) fieldValues.get(Constant.BLANK_LINE_OPTION); return Constant.SHOW_BLANK_LINE.equals(pendingEntryOption) ? true : false; } /** * build the serach result list from the given collection and the number of all qualified search results * * @param searchResultsCollection the given search results, which may be a subset of the qualified search results * @param actualSize the number of all qualified search results * @return the serach result list with the given results and actual size */ protected List buildSearchResultList(Collection searchResultsCollection, Long actualSize) { CollectionIncomplete results = new CollectionIncomplete(searchResultsCollection, actualSize); // sort list if default sort column given List searchResults = results; List defaultSortColumns = getDefaultSortColumns(); if (defaultSortColumns.size() > 0) { Collections.sort(results, new BeanPropertyComparator(defaultSortColumns, true)); } return searchResults; } /** * @see org.kuali.kfs.module.ld.service.LaborInquiryOptionsService#updateByPendingLedgerEntry(java.util.Collection, * java.util.Map, java.lang.String, boolean) */ public void updateByPendingLedgerEntry(Collection entryCollection, Map fieldValues, String pendingEntryOption, boolean isConsolidated) { // determine if search results need to be updated by pending ledger entries if (Constant.ALL_PENDING_ENTRY.equals(pendingEntryOption)) { updateEntryCollection(entryCollection, fieldValues, false, isConsolidated); } else if (Constant.APPROVED_PENDING_ENTRY.equals(pendingEntryOption)) { updateEntryCollection(entryCollection, fieldValues, true, isConsolidated); } } /** * @see org.kuali.kfs.module.ld.service.LaborInquiryOptionsService#updateEntryCollection(java.util.Collection, java.util.Map, * boolean, boolean) */ public void updateEntryCollection(Collection entryCollection, Map fieldValues, boolean isApproved, boolean isConsolidated) { // go through the pending entries to update the balance collection Iterator<LaborLedgerPendingEntry> pendingEntryIterator = laborLedgerPendingEntryService.findPendingLedgerEntriesForLedgerBalance(fieldValues, isApproved); while (pendingEntryIterator.hasNext()) { LaborLedgerPendingEntry pendingEntry = pendingEntryIterator.next(); if (!isEmployeeFunding(pendingEntry)) { continue; } // if consolidated, change the following fields into the default values for consolidation if (isConsolidated) { pendingEntry.setSubAccountNumber(Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER); pendingEntry.setFinancialSubObjectCode(Constant.CONSOLIDATED_SUB_OBJECT_CODE); pendingEntry.setFinancialObjectTypeCode(Constant.CONSOLIDATED_OBJECT_TYPE_CODE); } EmployeeFunding ledgerBalance = (EmployeeFunding) laborLedgerBalanceService.findLedgerBalance(entryCollection, pendingEntry, getKeyList()); if (ledgerBalance == null) { ledgerBalance = new EmployeeFunding(); ObjectUtil.buildObject(ledgerBalance, pendingEntry); entryCollection.add(ledgerBalance); } else { laborLedgerBalanceService.updateLedgerBalance(ledgerBalance, pendingEntry); } updateAmount(ledgerBalance, pendingEntry); } } /** * update the amount of the given employee funding with the given pending entry * * @param employeeFunding the given employee funding * @param pendingEntry the given pending entry */ private void updateAmount(EmployeeFunding employeeFunding, LaborLedgerPendingEntry pendingEntry) { String balanceTypeCode = pendingEntry.getFinancialBalanceTypeCode(); String debitCreditCode = pendingEntry.getTransactionDebitCreditCode(); KualiDecimal amount = DebitCreditUtil.getNumericAmount(pendingEntry.getTransactionLedgerEntryAmount(), pendingEntry.getTransactionDebitCreditCode()); if (StringUtils.equals(balanceTypeCode, KFSConstants.BALANCE_TYPE_ACTUAL)) { employeeFunding.setCurrentAmount(amount.add(employeeFunding.getCurrentAmount())); } else if (StringUtils.equals(balanceTypeCode, KFSConstants.BALANCE_TYPE_INTERNAL_ENCUMBRANCE)) { employeeFunding.setOutstandingEncumbrance(amount.add(employeeFunding.getOutstandingEncumbrance())); } } /** * determine whether the given pending entry is qualified to be processed as an employee funding * * @param pendingEntry the given pending entry * @return true if the given pending entry is qualified to be processed as an employee funding; otherwise, false */ private boolean isEmployeeFunding(LaborLedgerPendingEntry pendingEntry) { String balanceTypeCode = pendingEntry.getFinancialBalanceTypeCode(); if (StringUtils.equals(balanceTypeCode, KFSConstants.BALANCE_TYPE_ACTUAL)) { String objectTypeCode = pendingEntry.getFinancialObjectTypeCode(); String[] objectTypeCodes = { EMPLOYEE_FUNDING_EXPENSE_OBJECT_TYPE_CODE, EMPLOYEE_FUNDING_NORMAL_OP_EXPENSE_OBJECT_TYPE_CODE }; return ArrayUtils.contains(objectTypeCodes, objectTypeCode) ? true : false; } if (StringUtils.equals(balanceTypeCode, KFSConstants.BALANCE_TYPE_INTERNAL_ENCUMBRANCE)) { return true; } return false; } /** * construct the primary key list of the business object * * @return the primary key list of the business object */ private List<String> getKeyList() { List<String> keyList = new ArrayList<String>(); keyList.add(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR); keyList.add(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE); keyList.add(KFSPropertyConstants.ACCOUNT_NUMBER); keyList.add(KFSPropertyConstants.SUB_ACCOUNT_NUMBER); keyList.add(KFSPropertyConstants.FINANCIAL_OBJECT_CODE); keyList.add(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE); keyList.add(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE); keyList.add(KFSPropertyConstants.POSITION_NUMBER); keyList.add(KFSPropertyConstants.EMPLID); return keyList; } /** * Sets the laborLedgerBalanceService attribute value. * * @param laborLedgerBalanceService The laborLedgerBalanceService to set. */ public void setLaborLedgerBalanceService(LaborLedgerBalanceService laborLedgerBalanceService) { this.laborLedgerBalanceService = laborLedgerBalanceService; } /** * Sets the laborInquiryOptionsService attribute value. * * @param laborInquiryOptionsService The laborInquiryOptionsService to set. */ public void setLaborInquiryOptionsService(LaborInquiryOptionsService laborInquiryOptionsService) { this.laborInquiryOptionsService = laborInquiryOptionsService; } /** * Sets the laborLedgerPendingEntryService attribute value. * * @param laborLedgerPendingEntryService The laborLedgerPendingEntryService to set. */ public void setLaborLedgerPendingEntryService(LaborLedgerPendingEntryService laborLedgerPendingEntryService) { this.laborLedgerPendingEntryService = laborLedgerPendingEntryService; } }