001/*
002 * Copyright 2006 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 */
016package org.kuali.ole.gl.businessobject.lookup;
017
018import java.util.*;
019
020import org.apache.commons.lang.ArrayUtils;
021import org.apache.commons.lang.StringUtils;
022import org.kuali.ole.coa.businessobject.Account;
023import org.kuali.ole.coa.businessobject.ObjectCode;
024import org.kuali.ole.gl.Constant;
025import org.kuali.ole.gl.GeneralLedgerConstants;
026import org.kuali.ole.gl.OJBUtility;
027import org.kuali.ole.gl.batch.service.AccountBalanceCalculator;
028import org.kuali.ole.gl.businessobject.AccountBalance;
029import org.kuali.ole.gl.businessobject.TransientBalanceInquiryAttributes;
030import org.kuali.ole.gl.businessobject.inquiry.AccountBalanceInquirableImpl;
031import org.kuali.ole.gl.service.AccountBalanceService;
032import org.kuali.ole.sys.OLEConstants;
033import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntry;
034import org.kuali.ole.sys.businessobject.SystemOptions;
035import org.kuali.ole.sys.context.SpringContext;
036import org.kuali.ole.sys.service.OptionsService;
037import org.kuali.rice.core.api.util.type.KualiDecimal;
038import org.kuali.rice.kns.lookup.HtmlData;
039import org.kuali.rice.krad.bo.BusinessObject;
040import org.kuali.rice.krad.service.BusinessObjectService;
041import org.kuali.rice.krad.util.ObjectUtils;
042
043/**
044 * A class to support Account Balance lookups
045 */
046public class AccountBalanceLookupableHelperServiceImpl extends AbstractGeneralLedgerLookupableHelperServiceImpl {
047
048    private AccountBalanceCalculator postAccountBalance;
049    private AccountBalanceService accountBalanceService;
050    private OptionsService optionsService;
051
052    /**
053     * Returns the url for the account balance inquiry
054     * @param bo the business object with a property that an inquiry drill down url is being asked for
055     * @param propertyName the property of that bo that the inquiry drill down url is being asked for
056     * @return the URL for the inquiry
057     * @see org.kuali.rice.kns.lookup.Lookupable#getInquiryUrl(org.kuali.rice.krad.bo.BusinessObject, java.lang.String)
058     */
059    @Override
060    public HtmlData getInquiryUrl(BusinessObject bo, String propertyName) {
061        return (new AccountBalanceInquirableImpl()).getInquiryUrl(bo, propertyName);
062    }
063
064    /**
065     * Given a map of fieldValues, actually searches for the appropriate account balance records to return
066     * @param fieldValues a map of keys for the search
067     * @return a List of AccountBalance records that match the search criteria
068     * @see org.kuali.rice.kns.lookup.Lookupable#getSearchResults(java.util.Map)
069     * 
070     * KRAD Conversion: Lookupable modifies the search results based on the fields consolidated.
071     * But all field definitions are in data dictionary.
072     */
073    public List getSearchResults(Map fieldValues) {
074        setBackLocation((String) fieldValues.get(OLEConstants.BACK_LOCATION));
075        setDocFormKey((String) fieldValues.get(OLEConstants.DOC_FORM_KEY));
076
077        Collection searchResultsCollection = null;
078
079        // get the pending entry option. This method must be prior to the get search results
080        String pendingEntryOption = this.getSelectedPendingEntryOption(fieldValues);
081
082        // KFSMI-410: added one more node for consolidationOption
083        String consolidationOption = (String) fieldValues.get(GeneralLedgerConstants.DummyBusinessObject.CONSOLIDATION_OPTION);
084        // test if the consolidation option is selected or not
085        boolean isConsolidated = isConsolidationSelected(fieldValues); 
086        
087        // get the search result collection
088        // KFSMI-410: added one more node for consolidationOption
089        if (consolidationOption.equals(Constant.EXCLUDE_SUBACCOUNTS)){
090            fieldValues.put(Constant.SUB_ACCOUNT_OPTION, OLEConstants.getDashSubAccountNumber());
091            isConsolidated = false;
092        } 
093        
094        if (isConsolidated) {
095            Iterator availableBalanceIterator = accountBalanceService.findConsolidatedAvailableAccountBalance(fieldValues);
096            searchResultsCollection = buildConsolidedAvailableBalanceCollection(availableBalanceIterator);
097        }
098        else {
099            Iterator availableBalanceIterator = accountBalanceService.findAvailableAccountBalance(fieldValues);
100            searchResultsCollection = buildDetailedAvailableBalanceCollection(availableBalanceIterator);
101        }
102
103        // update search results according to the selected pending entry option
104        updateByPendingLedgerEntry(searchResultsCollection, fieldValues, pendingEntryOption, isConsolidated, false);
105
106        // Put the search related stuff in the objects
107        for (Iterator iter = searchResultsCollection.iterator(); iter.hasNext();) {
108            AccountBalance ab = (AccountBalance) iter.next();
109            TransientBalanceInquiryAttributes dbo = ab.getDummyBusinessObject();
110            dbo.setConsolidationOption(consolidationOption);
111            dbo.setPendingEntryOption(pendingEntryOption);
112        }
113
114        // get the actual size of all qualified search results
115            Integer recordCount = accountBalanceService.getAvailableAccountBalanceCount(fieldValues, isConsolidated);
116            Long actualSize = OJBUtility.getResultActualSize(searchResultsCollection, recordCount, fieldValues, new AccountBalance());
117            // Get the entry in Account
118            SystemOptions option = getBusinessObjectService().findBySinglePrimaryKey(SystemOptions.class, Integer.parseInt((String)fieldValues.get(OLEConstants.FISCAL_YEAR)));
119            if(option != null){
120                if(searchResultsCollection.size() < 1) {
121                    String accountNumber = fieldValues.get(OLEConstants.ACCOUNT_NUMBER).toString();
122                    String chartCode = fieldValues.get(OLEConstants.CHART_CODE).toString();
123                    if(!chartCode.equals("*") || !accountNumber.equals("*")){
124                    List<Account> accountList =  checkAccountEntry(accountNumber,chartCode);
125                    for (Iterator<Account> accountIterator = accountList.iterator(); accountIterator.hasNext(); ) {
126                        Account account = accountIterator.next();
127                        AccountBalance balance = new AccountBalance();
128                        balance.setChartOfAccountsCode(account.getChartOfAccountsCode());
129                        balance.setAccountNumber(account.getAccountNumber());
130                        String fiscalYear = fieldValues.get(OLEConstants.FISCAL_YEAR).toString();
131                        balance.setUniversityFiscalYear(Integer.parseInt(fiscalYear));
132                        balance.setObjectCode(Constant.CONSOLIDATED_OBJECT_TYPE_CODE);
133                        balance.setSubAccountNumber(Constant.CONSOLIDATED_OBJECT_TYPE_CODE);
134                        balance.setSubObjectCode(Constant.CONSOLIDATED_OBJECT_TYPE_CODE);
135                        balance.setCurrentBudgetLineBalanceAmount(KualiDecimal.ZERO);
136                        balance.setAccountLineActualsBalanceAmount(KualiDecimal.ZERO);
137                        balance.setAccountLineEncumbranceBalanceAmount(KualiDecimal.ZERO);
138                        searchResultsCollection.add(balance);
139                    }
140                    }
141                }
142            }
143        return this.buildSearchResultList(searchResultsCollection, actualSize);
144    }
145
146
147    public List<Account> checkAccountEntry(String accountNumber,String chartCode) {
148        boolean exists = false;
149        Map searchMap = new HashMap();
150        searchMap.put(OLEConstants.ACCOUNT_NUMBER, accountNumber);
151        searchMap.put(OLEConstants.CHART_CODE,chartCode);
152        List<Account> accountList = (List) SpringContext.getBean(BusinessObjectService.class).findMatching(Account.class, searchMap);
153        return accountList;
154    }
155    /**
156     * This method builds the available account balance collection based on the input iterator
157     * 
158     * @param iterator the iterator of search results of account balance
159     * @return the account balance collection
160     */
161    private Collection buildConsolidedAvailableBalanceCollection(Iterator iterator) {
162        Collection balanceCollection = new ArrayList();
163
164        // build available balance collection throught analyzing the input iterator
165        while (iterator.hasNext()) {
166            Object avaiableAccountBalance = iterator.next();
167
168            if (avaiableAccountBalance.getClass().isArray()) {
169                int i = 0;
170                Object[] array = (Object[]) avaiableAccountBalance;
171                AccountBalance accountBalance = new AccountBalance();
172
173                accountBalance.setUniversityFiscalYear(new Integer(array[i++].toString()));
174                accountBalance.setChartOfAccountsCode(array[i++].toString());
175
176                accountBalance.setAccountNumber(array[i++].toString());
177                accountBalance.setSubAccountNumber(Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER);
178
179                accountBalance.setObjectCode(array[i++].toString());
180                accountBalance.setSubObjectCode(Constant.CONSOLIDATED_SUB_OBJECT_CODE);
181
182                String objectTypeCode = array[i++].toString();
183                accountBalance.getFinancialObject().setFinancialObjectTypeCode(objectTypeCode);
184
185                KualiDecimal budgetAmount = new KualiDecimal(array[i++].toString());
186                accountBalance.setCurrentBudgetLineBalanceAmount(budgetAmount);
187
188                KualiDecimal actualsAmount = new KualiDecimal(array[i++].toString());
189                accountBalance.setAccountLineActualsBalanceAmount(actualsAmount);
190
191                KualiDecimal encumbranceAmount = new KualiDecimal(array[i].toString());
192                accountBalance.setAccountLineEncumbranceBalanceAmount(encumbranceAmount);
193
194                KualiDecimal variance = calculateVariance(accountBalance);
195                accountBalance.getDummyBusinessObject().setGenericAmount(variance);
196
197                balanceCollection.add(accountBalance);
198            }
199        }
200        return balanceCollection;
201    }
202
203    /**
204     * This method builds the available account balance collection based on the input collection
205     * 
206     * @param collection a collection of account balance entries
207     * @return the account balance collection
208     */
209    private Collection buildDetailedAvailableBalanceCollection(Iterator iterator) {
210        Collection balanceCollection = new ArrayList();
211
212        // build available balance collection throught analyzing the iterator above
213        while (iterator.hasNext()) {
214            AccountBalance accountBalance = (AccountBalance) iterator.next();
215
216            if (accountBalance.getDummyBusinessObject() == null) {
217                accountBalance.setDummyBusinessObject(new TransientBalanceInquiryAttributes());
218            }
219
220            KualiDecimal variance = calculateVariance(accountBalance);
221            accountBalance.getDummyBusinessObject().setGenericAmount(variance);
222
223            balanceCollection.add(accountBalance);
224        }
225        return balanceCollection;
226    }
227
228    /**
229     * This method calculates the variance of current budget balance, actuals balance and encumbrance balance
230     * 
231     * @param balance an account balance entry
232     */
233    private KualiDecimal calculateVariance(AccountBalance balance) {
234
235        KualiDecimal variance = new KualiDecimal(0.0);
236        KualiDecimal budgetAmount = balance.getCurrentBudgetLineBalanceAmount();
237        KualiDecimal actualsAmount = balance.getAccountLineActualsBalanceAmount();
238        KualiDecimal encumbranceAmount = balance.getAccountLineEncumbranceBalanceAmount();
239
240        // determine if the object type code is one of the given codes
241        if (ObjectUtils.isNull(balance.getFinancialObject()) || StringUtils.isBlank(balance.getFinancialObject().getFinancialObjectTypeCode())) {
242            balance.refreshReferenceObject("financialObject"); // refresh if we need to...
243        }
244        ObjectCode financialObject = balance.getFinancialObject();
245        String objectTypeCode = (financialObject == null) ? Constant.EMPTY_STRING : financialObject.getFinancialObjectTypeCode();
246
247        SystemOptions options = getOptionsService().getOptions(balance.getUniversityFiscalYear());
248        if (ObjectUtils.isNull(options)) {
249            options = getOptionsService().getCurrentYearOptions();
250        }
251        String[] objectTypeCodeList = new String[3];
252        objectTypeCodeList[0] = options.getFinObjTypeExpendNotExpCode();
253        objectTypeCodeList[1] = options.getFinObjTypeExpNotExpendCode();
254        objectTypeCodeList[2] = options.getFinObjTypeExpenditureexpCd();
255
256        boolean isObjectTypeCodeInList = ArrayUtils.contains(objectTypeCodeList, objectTypeCode);
257
258        // calculate the variance based on the object type code of the balance
259        if (isObjectTypeCodeInList) {
260            variance = budgetAmount.subtract(actualsAmount);
261            variance = variance.subtract(encumbranceAmount);
262        }
263        else {
264            variance = actualsAmount.subtract(budgetAmount);
265        }
266        return variance;
267    }
268
269    /**
270     * Updates the collection of entries that will be applied to the results of the inquiry
271     * 
272     * @param entryCollection a collection of balance entries
273     * @param fieldValues the map containing the search fields and values
274     * @param isApproved flag whether the approved entries or all entries will be processed
275     * @param isConsolidated flag whether the results are consolidated or not
276     * @param isCostShareExcluded flag whether the user selects to see the results with cost share subaccount
277     * @see org.kuali.module.gl.web.lookupable.AbstractGLLookupableImpl#updateEntryCollection(java.util.Collection, java.util.Map,
278     *      boolean, boolean, boolean)
279     */
280    @Override
281    protected void updateEntryCollection(Collection entryCollection, Map fieldValues, boolean isApproved, boolean isConsolidated, boolean isCostShareExcluded) {
282
283        // convert the field names of balance object into corresponding ones of pending entry object
284        Map pendingEntryFieldValues = BusinessObjectFieldConverter.convertToTransactionFieldValues(fieldValues);
285
286        // go through the pending entries to update the balance collection
287        Iterator pendingEntryIterator = getGeneralLedgerPendingEntryService().findPendingLedgerEntriesForAccountBalance(pendingEntryFieldValues, isApproved);
288        while (pendingEntryIterator.hasNext()) {
289            GeneralLedgerPendingEntry pendingEntry = (GeneralLedgerPendingEntry) pendingEntryIterator.next();
290
291            if (isCostShareExcluded) {
292                if (ObjectUtils.isNotNull(pendingEntry.getSubAccount()) && ObjectUtils.isNotNull(pendingEntry.getSubAccount().getA21SubAccount())) {
293                    if (OLEConstants.SubAccountType.COST_SHARE.equals(pendingEntry.getSubAccount().getA21SubAccount().getSubAccountTypeCode())) {
294                        // Don't process this one
295                        continue;
296                    }
297                }
298            }
299
300            // if consolidated, change the following fields into the default values for consolidation
301            if (isConsolidated) {
302                pendingEntry.setSubAccountNumber(Constant.CONSOLIDATED_SUB_ACCOUNT_NUMBER);
303                pendingEntry.setFinancialSubObjectCode(Constant.CONSOLIDATED_SUB_OBJECT_CODE);
304                pendingEntry.setFinancialObjectTypeCode(Constant.CONSOLIDATED_OBJECT_TYPE_CODE);
305            }
306
307            AccountBalance accountBalance = postAccountBalance.findAccountBalance(entryCollection, pendingEntry);
308            postAccountBalance.updateAccountBalance(pendingEntry, accountBalance);
309
310            // recalculate the variance after pending entries are combined into account balances
311            if (accountBalance.getDummyBusinessObject() == null) {
312                accountBalance.setDummyBusinessObject(new TransientBalanceInquiryAttributes());
313            }
314            KualiDecimal variance = calculateVariance(accountBalance);
315            accountBalance.getDummyBusinessObject().setGenericAmount(variance);
316        }
317    }
318
319    /**
320     * Sets the postAccountBalance attribute value.
321     * 
322     * @param postAccountBalance The postAccountBalance to set.
323     */
324    public void setPostAccountBalance(AccountBalanceCalculator postAccountBalance) {
325        this.postAccountBalance = postAccountBalance;
326    }
327
328    /**
329     * Sets the accountBalanceService attribute value.
330     * 
331     * @param accountBalanceService The accountBalanceService to set.
332     */
333    public void setAccountBalanceService(AccountBalanceService accountBalanceService) {
334        this.accountBalanceService = accountBalanceService;
335    }
336
337    /**
338     * Sets the optionsService attribute value
339     * 
340     * @param optionsService The optionsService to set.
341     */
342    public void setOptionsService(OptionsService optionsService) {
343        this.optionsService = optionsService;
344    }
345
346    /**
347     * Gets the optionsService attribute. 
348     * @return Returns the optionsService.
349     */
350    public OptionsService getOptionsService() {
351        return optionsService;
352    }
353}