001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.gl.businessobject.lookup;
017    
018    import java.util.ArrayList;
019    import java.util.Collection;
020    import java.util.Iterator;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.apache.commons.lang.ArrayUtils;
025    import org.apache.commons.lang.StringUtils;
026    import org.kuali.kfs.coa.businessobject.ObjectCode;
027    import org.kuali.kfs.gl.Constant;
028    import org.kuali.kfs.gl.GeneralLedgerConstants;
029    import org.kuali.kfs.gl.OJBUtility;
030    import org.kuali.kfs.gl.batch.service.AccountBalanceCalculator;
031    import org.kuali.kfs.gl.businessobject.AccountBalance;
032    import org.kuali.kfs.gl.businessobject.TransientBalanceInquiryAttributes;
033    import org.kuali.kfs.gl.businessobject.inquiry.AccountBalanceInquirableImpl;
034    import org.kuali.kfs.gl.service.AccountBalanceService;
035    import org.kuali.kfs.sys.KFSConstants;
036    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
037    import org.kuali.kfs.sys.businessobject.SystemOptions;
038    import org.kuali.kfs.sys.service.OptionsService;
039    import org.kuali.rice.kns.bo.BusinessObject;
040    import org.kuali.rice.kns.lookup.HtmlData;
041    import org.kuali.rice.kns.util.KualiDecimal;
042    import org.kuali.rice.kns.util.ObjectUtils;
043    
044    /**
045     * A class to support Account Balance lookups
046     */
047    public class AccountBalanceLookupableHelperServiceImpl extends AbstractGeneralLedgerLookupableHelperServiceImpl {
048    
049        private AccountBalanceCalculator postAccountBalance;
050        private AccountBalanceService accountBalanceService;
051        private OptionsService optionsService;
052    
053        /**
054         * Returns the url for the account balance inquiry
055         * @param bo the business object with a property that an inquiry drill down url is being asked for
056         * @param propertyName the property of that bo that the inquiry drill down url is being asked for
057         * @return the URL for the inquiry
058         * @see org.kuali.rice.kns.lookup.Lookupable#getInquiryUrl(org.kuali.rice.kns.bo.BusinessObject, java.lang.String)
059         */
060        @Override
061        public HtmlData getInquiryUrl(BusinessObject bo, String propertyName) {
062            return (new AccountBalanceInquirableImpl()).getInquiryUrl(bo, propertyName);
063        }
064    
065        /**
066         * Given a map of fieldValues, actually searches for the appropriate account balance records to return
067         * @param fieldValues a map of keys for the search
068         * @return a List of AccountBalance records that match the search criteria
069         * @see org.kuali.rice.kns.lookup.Lookupable#getSearchResults(java.util.Map)
070         */
071        public List getSearchResults(Map fieldValues) {
072            setBackLocation((String) fieldValues.get(KFSConstants.BACK_LOCATION));
073            setDocFormKey((String) fieldValues.get(KFSConstants.DOC_FORM_KEY));
074    
075            Collection searchResultsCollection = null;
076    
077            // get the pending entry option. This method must be prior to the get search results
078            String pendingEntryOption = this.getSelectedPendingEntryOption(fieldValues);
079    
080            // test if the consolidation option is selected or not
081            String consolidationOption = (String) fieldValues.get(GeneralLedgerConstants.DummyBusinessObject.CONSOLIDATION_OPTION);
082            boolean isConsolidated = isConsolidationSelected(fieldValues);
083    
084            // get the search result collection
085            if (isConsolidated) {
086                Iterator availableBalanceIterator = accountBalanceService.findConsolidatedAvailableAccountBalance(fieldValues);
087                searchResultsCollection = buildConsolidedAvailableBalanceCollection(availableBalanceIterator);
088            }
089            else {
090                Iterator availableBalanceIterator = accountBalanceService.findAvailableAccountBalance(fieldValues);
091                searchResultsCollection = buildDetailedAvailableBalanceCollection(availableBalanceIterator);
092            }
093    
094            // update search results according to the selected pending entry option
095            updateByPendingLedgerEntry(searchResultsCollection, fieldValues, pendingEntryOption, isConsolidated, false);
096    
097            // Put the search related stuff in the objects
098            for (Iterator iter = searchResultsCollection.iterator(); iter.hasNext();) {
099                AccountBalance ab = (AccountBalance) iter.next();
100                TransientBalanceInquiryAttributes dbo = ab.getDummyBusinessObject();
101                dbo.setConsolidationOption(consolidationOption);
102                dbo.setPendingEntryOption(pendingEntryOption);
103            }
104    
105            // get the actual size of all qualified search results
106            Integer recordCount = accountBalanceService.getAvailableAccountBalanceCount(fieldValues, isConsolidated);
107            Long actualSize = OJBUtility.getResultActualSize(searchResultsCollection, recordCount, fieldValues, new AccountBalance());
108    
109            return this.buildSearchResultList(searchResultsCollection, actualSize);
110        }
111    
112    
113        /**
114         * This method builds the available account balance collection based on the input iterator
115         * 
116         * @param iterator the iterator of search results of account balance
117         * @return the account balance collection
118         */
119        private Collection buildConsolidedAvailableBalanceCollection(Iterator iterator) {
120            Collection balanceCollection = new ArrayList();
121    
122            // build available balance collection throught analyzing the input iterator
123            while (iterator.hasNext()) {
124                Object avaiableAccountBalance = iterator.next();
125    
126                if (avaiableAccountBalance.getClass().isArray()) {
127                    int i = 0;
128                    Object[] array = (Object[]) avaiableAccountBalance;
129                    AccountBalance accountBalance = new AccountBalance();
130    
131                    accountBalance.setUniversityFiscalYear(new Integer(array[i++].toString()));
132                    accountBalance.setChartOfAccountsCode(array[i++].toString());
133    
134                    accountBalance.setAccountNumber(array[i++].toString());
135                    accountBalance.setSubAccountNumber(Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER);
136    
137                    accountBalance.setObjectCode(array[i++].toString());
138                    accountBalance.setSubObjectCode(Constant.CONSOLIDATED_SUB_OBJECT_CODE);
139    
140                    String objectTypeCode = array[i++].toString();
141                    accountBalance.getFinancialObject().setFinancialObjectTypeCode(objectTypeCode);
142    
143                    KualiDecimal budgetAmount = new KualiDecimal(array[i++].toString());
144                    accountBalance.setCurrentBudgetLineBalanceAmount(budgetAmount);
145    
146                    KualiDecimal actualsAmount = new KualiDecimal(array[i++].toString());
147                    accountBalance.setAccountLineActualsBalanceAmount(actualsAmount);
148    
149                    KualiDecimal encumbranceAmount = new KualiDecimal(array[i].toString());
150                    accountBalance.setAccountLineEncumbranceBalanceAmount(encumbranceAmount);
151    
152                    KualiDecimal variance = calculateVariance(accountBalance);
153                    accountBalance.getDummyBusinessObject().setGenericAmount(variance);
154    
155                    balanceCollection.add(accountBalance);
156                }
157            }
158            return balanceCollection;
159        }
160    
161        /**
162         * This method builds the available account balance collection based on the input collection
163         * 
164         * @param collection a collection of account balance entries
165         * @return the account balance collection
166         */
167        private Collection buildDetailedAvailableBalanceCollection(Iterator iterator) {
168            Collection balanceCollection = new ArrayList();
169    
170            // build available balance collection throught analyzing the iterator above
171            while (iterator.hasNext()) {
172                AccountBalance accountBalance = (AccountBalance) iterator.next();
173    
174                if (accountBalance.getDummyBusinessObject() == null) {
175                    accountBalance.setDummyBusinessObject(new TransientBalanceInquiryAttributes());
176                }
177    
178                KualiDecimal variance = calculateVariance(accountBalance);
179                accountBalance.getDummyBusinessObject().setGenericAmount(variance);
180    
181                balanceCollection.add(accountBalance);
182            }
183            return balanceCollection;
184        }
185    
186        /**
187         * This method calculates the variance of current budget balance, actuals balance and encumbrance balance
188         * 
189         * @param balance an account balance entry
190         */
191        private KualiDecimal calculateVariance(AccountBalance balance) {
192    
193            KualiDecimal variance = new KualiDecimal(0.0);
194            KualiDecimal budgetAmount = balance.getCurrentBudgetLineBalanceAmount();
195            KualiDecimal actualsAmount = balance.getAccountLineActualsBalanceAmount();
196            KualiDecimal encumbranceAmount = balance.getAccountLineEncumbranceBalanceAmount();
197    
198            // determine if the object type code is one of the given codes
199            if (ObjectUtils.isNull(balance.getFinancialObject()) || StringUtils.isBlank(balance.getFinancialObject().getFinancialObjectTypeCode())) {
200                balance.refreshReferenceObject("financialObject"); // refresh if we need to...
201            }
202            ObjectCode financialObject = balance.getFinancialObject();
203            String objectTypeCode = (financialObject == null) ? Constant.EMPTY_STRING : financialObject.getFinancialObjectTypeCode();
204    
205            SystemOptions options = getOptionsService().getOptions(balance.getUniversityFiscalYear());
206            if (ObjectUtils.isNull(options)) {
207                options = getOptionsService().getCurrentYearOptions();
208            }
209            String[] objectTypeCodeList = new String[3];
210            objectTypeCodeList[0] = options.getFinObjTypeExpendNotExpCode();
211            objectTypeCodeList[1] = options.getFinObjTypeExpNotExpendCode();
212            objectTypeCodeList[2] = options.getFinObjTypeExpenditureexpCd();
213    
214            boolean isObjectTypeCodeInList = ArrayUtils.contains(objectTypeCodeList, objectTypeCode);
215    
216            // calculate the variance based on the object type code of the balance
217            if (isObjectTypeCodeInList) {
218                variance = budgetAmount.subtract(actualsAmount);
219                variance = variance.subtract(encumbranceAmount);
220            }
221            else {
222                variance = actualsAmount.subtract(budgetAmount);
223            }
224            return variance;
225        }
226    
227        /**
228         * Updates the collection of entries that will be applied to the results of the inquiry
229         * 
230         * @param entryCollection a collection of balance entries
231         * @param fieldValues the map containing the search fields and values
232         * @param isApproved flag whether the approved entries or all entries will be processed
233         * @param isConsolidated flag whether the results are consolidated or not
234         * @param isCostShareExcluded flag whether the user selects to see the results with cost share subaccount
235         * @see org.kuali.module.gl.web.lookupable.AbstractGLLookupableImpl#updateEntryCollection(java.util.Collection, java.util.Map,
236         *      boolean, boolean, boolean)
237         */
238        @Override
239        protected void updateEntryCollection(Collection entryCollection, Map fieldValues, boolean isApproved, boolean isConsolidated, boolean isCostShareExcluded) {
240    
241            // convert the field names of balance object into corresponding ones of pending entry object
242            Map pendingEntryFieldValues = BusinessObjectFieldConverter.convertToTransactionFieldValues(fieldValues);
243    
244            // go through the pending entries to update the balance collection
245            Iterator pendingEntryIterator = getGeneralLedgerPendingEntryService().findPendingLedgerEntriesForAccountBalance(pendingEntryFieldValues, isApproved);
246            while (pendingEntryIterator.hasNext()) {
247                GeneralLedgerPendingEntry pendingEntry = (GeneralLedgerPendingEntry) pendingEntryIterator.next();
248    
249                if (isCostShareExcluded) {
250                    if (ObjectUtils.isNotNull(pendingEntry.getSubAccount()) && ObjectUtils.isNotNull(pendingEntry.getSubAccount().getA21SubAccount())) {
251                        if (KFSConstants.SubAccountType.COST_SHARE.equals(pendingEntry.getSubAccount().getA21SubAccount().getSubAccountTypeCode())) {
252                            // Don't process this one
253                            continue;
254                        }
255                    }
256                }
257    
258                // if consolidated, change the following fields into the default values for consolidation
259                if (isConsolidated) {
260                    pendingEntry.setSubAccountNumber(Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER);
261                    pendingEntry.setFinancialSubObjectCode(Constant.CONSOLIDATED_SUB_OBJECT_CODE);
262                    pendingEntry.setFinancialObjectTypeCode(Constant.CONSOLIDATED_OBJECT_TYPE_CODE);
263                }
264    
265                AccountBalance accountBalance = postAccountBalance.findAccountBalance(entryCollection, pendingEntry);
266                postAccountBalance.updateAccountBalance(pendingEntry, accountBalance);
267    
268                // recalculate the variance after pending entries are combined into account balances
269                if (accountBalance.getDummyBusinessObject() == null) {
270                    accountBalance.setDummyBusinessObject(new TransientBalanceInquiryAttributes());
271                }
272                KualiDecimal variance = calculateVariance(accountBalance);
273                accountBalance.getDummyBusinessObject().setGenericAmount(variance);
274            }
275        }
276    
277        /**
278         * Sets the postAccountBalance attribute value.
279         * 
280         * @param postAccountBalance The postAccountBalance to set.
281         */
282        public void setPostAccountBalance(AccountBalanceCalculator postAccountBalance) {
283            this.postAccountBalance = postAccountBalance;
284        }
285    
286        /**
287         * Sets the accountBalanceService attribute value.
288         * 
289         * @param accountBalanceService The accountBalanceService to set.
290         */
291        public void setAccountBalanceService(AccountBalanceService accountBalanceService) {
292            this.accountBalanceService = accountBalanceService;
293        }
294    
295        /**
296         * Sets the optionsService attribute value
297         * 
298         * @param optionsService The optionsService to set.
299         */
300        public void setOptionsService(OptionsService optionsService) {
301            this.optionsService = optionsService;
302        }
303    
304        /**
305         * Gets the optionsService attribute. 
306         * @return Returns the optionsService.
307         */
308        public OptionsService getOptionsService() {
309            return optionsService;
310        }
311    }