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.service.impl;
017    
018    import java.util.ArrayList;
019    import java.util.Arrays;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.apache.commons.collections.IteratorUtils;
027    import org.kuali.kfs.coa.businessobject.Account;
028    import org.kuali.kfs.gl.OJBUtility;
029    import org.kuali.kfs.gl.businessobject.Balance;
030    import org.kuali.kfs.gl.businessobject.GlSummary;
031    import org.kuali.kfs.gl.dataaccess.BalanceDao;
032    import org.kuali.kfs.gl.service.BalanceService;
033    import org.kuali.kfs.sys.businessobject.SystemOptions;
034    import org.kuali.kfs.sys.context.SpringContext;
035    import org.kuali.kfs.sys.service.OptionsService;
036    import org.kuali.kfs.sys.service.UniversityDateService;
037    import org.kuali.rice.kns.util.KualiDecimal;
038    import org.springframework.transaction.annotation.Transactional;
039    
040    /**
041     * This class is the OJB implementation of the Balance Service
042     */
043    @Transactional
044    public class BalanceServiceImpl implements BalanceService {
045        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BalanceServiceImpl.class);
046    
047        protected BalanceDao balanceDao;
048        protected OptionsService optionsService;
049    
050        // must have no asset, liability or fund balance balances other than object code 9899
051    
052        String[] assetLiabilityFundBalanceObjectTypeCodes = null;
053        String[] encumbranceBaseBudgetBalanceTypeCodes = null;
054        String[] actualBalanceCodes = null;
055        String[] incomeObjectTypeCodes = null;
056        String[] expenseObjectTypeCodes = null;
057    
058        /**
059         * Turns an array of Strings into a List of Strings
060         * 
061         * @param s an array of Strings
062         * @return an implementation of Collection (a List) of Strings
063         */
064        protected Collection wrap(String[] s) {
065            return Arrays.asList(s);
066        }
067    
068        /**
069         * @param universityFiscalYear the fiscal year to find balances for
070         * @param balanceTypeCodes the balance types to summarize
071         * @return a list of summarized GL balances
072         * @see org.kuali.kfs.gl.service.BalanceService#getGlSummary(int, java.util.List)
073         */
074        public List<GlSummary> getGlSummary(int universityFiscalYear, List<String> balanceTypeCodes) {
075            LOG.debug("getGlSummary() started");
076    
077            List<GlSummary> sum = new ArrayList<GlSummary>();
078    
079            Iterator<Object[]> i = balanceDao.getGlSummary(universityFiscalYear, balanceTypeCodes);
080            while (i.hasNext()) {
081                Object[] data = i.next();
082                sum.add(new GlSummary(data));
083            }
084            return sum;
085        }
086    
087        /**
088         * Defers to the DAO to find all balances in the fiscal year.
089         * 
090         * @param fiscalYear the fiscal year to find balances for
091         * @return an Iterator full of balances from the given fiscal year
092         * @see org.kuali.kfs.gl.service.BalanceService#findBalancesForFiscalYear(java.lang.Integer)
093         */
094        public Iterator<Balance> findBalancesForFiscalYear(Integer fiscalYear) {
095            
096    
097            return (Iterator<Balance>) balanceDao.findBalancesForFiscalYear(fiscalYear);
098        }
099    
100        /**
101         * Checks the given account to see if there are any non zero asset fund liability fund balances for them
102         * 
103         * @param account an account to find balances for
104         * @return true if there are non zero asset liability fund balances, false if otherwise
105         * @see org.kuali.kfs.gl.service.BalanceService#hasAssetLiabilityFundBalanceBalances(org.kuali.kfs.coa.businessobject.Account)
106         */
107        public boolean hasAssetLiabilityFundBalanceBalances(Account account) {
108            
109            /*
110             * Here is an excerpt from the original Oracle trigger: SELECT fin_object_cd FROM gl_balance_t WHERE
111             * univ_fiscal_yr = p_univ_fiscal_yr AND fin_coa_cd = p_fin_coa_cd AND account_nbr = p_account_nbr AND fin_object_cd != '9899'
112             * AND fin_obj_typ_cd IN ('AS', 'LI', 'FB') AND fin_balance_typ_cd = 'AC' GROUP BY fin_object_cd HAVING
113             * ABS(SUM(fin_beg_bal_ln_amt + acln_annl_bal_amt)) > 0); added absolute value function to sum--prevents the case of 2 entries
114             * (1 pos and 1 neg) from canceling each other out and allowing the acct to be closed when it shouldn't be.
115             */
116    
117            Integer fiscalYear = SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear();
118            ArrayList fundBalanceObjectCodes = new ArrayList();
119            fundBalanceObjectCodes.add(null == account.getChartOfAccounts() ? null : account.getChartOfAccounts().getFundBalanceObjectCode());
120            Iterator balances = balanceDao.findBalances(account, fiscalYear, null, fundBalanceObjectCodes, wrap(getAssetLiabilityFundBalanceBalanceTypeCodes()), wrap(getActualBalanceCodes()));
121    
122            KualiDecimal begin;
123            KualiDecimal annual;
124    
125            // TODO KULCOA-335 - is absolute value necessary to prevent obscure sets of values
126            // from masking accounts that should remain open?
127    
128            Map groups = new HashMap();
129    
130            while (balances.hasNext()) {
131                Balance balance = (Balance) balances.next();
132                begin = balance.getBeginningBalanceLineAmount();
133                annual = balance.getAccountLineAnnualBalanceAmount();
134    
135                String objectCode = balance.getObjectCode();
136    
137                KualiDecimal runningTotal = (KualiDecimal) groups.get(objectCode);
138    
139                if (runningTotal == null) {
140                    runningTotal = KualiDecimal.ZERO;
141                }
142    
143                runningTotal = runningTotal.add(begin);
144                runningTotal = runningTotal.add(annual);
145    
146                groups.put(objectCode, runningTotal);
147    
148    
149            }
150    
151            boolean success = false;
152    
153            Iterator iter = groups.keySet().iterator();
154            while (iter.hasNext()) {
155                success |= ((KualiDecimal) groups.get(iter.next())).isNonZero();
156            }
157    
158            return success;
159    
160        }
161    
162        /**
163         * Given an iterator of balances, this returns the sum of each balance's beginning balance line amount + annual account linge balance amount
164         * 
165         * @param balances an Iterator of balances to sum
166         * @return the sum of all of those balances
167         */
168        protected KualiDecimal sumBalances(Iterator balances) {
169            KualiDecimal runningTotal = KualiDecimal.ZERO;
170    
171            KualiDecimal begin;
172            KualiDecimal annual;
173    
174            while (balances.hasNext()) {
175                Balance balance = (Balance) balances.next();
176                begin = balance.getBeginningBalanceLineAmount();
177                annual = balance.getAccountLineAnnualBalanceAmount();
178    
179                runningTotal = runningTotal.add(begin);
180                runningTotal = runningTotal.add(annual);
181            }
182    
183            return runningTotal;
184    
185        }
186    
187        /**
188         * Returns the sum of balances considered as income for the given account
189         * 
190         * @param account the account to find income balances for
191         * @return the sum of income balances
192         */
193        protected KualiDecimal incomeBalances(Account account) {
194            
195            /*
196             * SELECT SUM(fin_beg_bal_ln_amt + acln_annl_bal_amt) INTO v_y FROM gl_balance_t WHERE univ_fiscal_yr = p_univ_fiscal_yr AND
197             * fin_coa_cd = p_fin_coa_cd AND account_nbr = p_account_nbr AND (fin_object_cd = '9899' OR fin_obj_typ_cd IN ('CH', 'IC', 'IN',
198             * 'TI')) AND fin_balance_typ_cd = 'AC';
199             * 
200             * @return
201             */
202    
203            Integer fiscalYear = SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear();
204    
205            ArrayList fundBalanceObjectCodes = new ArrayList();
206            fundBalanceObjectCodes.add(account.getChartOfAccounts().getFundBalanceObjectCode());
207            Iterator balances = balanceDao.findBalances(account, fiscalYear, fundBalanceObjectCodes, null, wrap(getIncomeObjectTypeCodes()), wrap(getActualBalanceCodes()));
208    
209            return sumBalances(balances);
210    
211        }
212    
213        /**
214         * Sums all the balances associated with a given account that would be considered "expense" balances
215         * 
216         * @param account an account to find expense balances for
217         * @return the sum of those balances
218         */
219        protected KualiDecimal expenseBalances(Account account) {
220            /*
221             * Here is an excerpt from the original Oracle Trigger: SELECT SUM(fin_beg_bal_ln_amt || acln_annl_bal_amt) INTO v_x FROM
222             * gl_balance_t WHERE univ_fiscal_yr = p_univ_fiscal_yr AND fin_coa_cd = p_fin_coa_cd AND account_nbr = p_account_nbr AND
223             * fin_obj_typ_cd IN ('EE', 'ES', 'EX', 'TE') AND fin_balance_typ_cd = 'AC'; This method...
224             */
225            
226            Integer fiscalYear = SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear();
227            Iterator balances = balanceDao.findBalances(account, fiscalYear, null, null, wrap(getExpenseObjectTypeCodes()), wrap(getActualBalanceCodes()));
228    
229            return sumBalances(balances);
230    
231        }
232    
233        /**
234         * Checks to see if the total income balances for the given account equal the total expense balances for the given account
235         * 
236         * @param an account to find balances for
237         * @return true if income balances equal expense balances, false otherwise
238         * @see org.kuali.kfs.gl.service.BalanceService#fundBalanceWillNetToZero(org.kuali.kfs.coa.businessobject.Account)
239         */
240        public boolean fundBalanceWillNetToZero(Account account) {
241            KualiDecimal income = incomeBalances(account);
242            KualiDecimal expense = expenseBalances(account);
243    
244            return income.equals(expense);
245        }
246    
247        /**
248         * Finds all of the encumbrance balances for the given account, and figures out if those encumbrances will have a net impact on the budget
249         *
250         * @param account an account to find balances for
251         * @return true if summed encumbrances for the account are not zero (meaning encumbrances will have a net impact on the budget), false if otherwise
252         * @see org.kuali.kfs.gl.service.BalanceService#hasEncumbrancesOrBaseBudgets(org.kuali.kfs.coa.businessobject.Account)
253         */
254        public boolean hasEncumbrancesOrBaseBudgets(Account account) {
255            
256            /*
257             * check for Encumbrances and base budgets Here is an excerpt from the original Oracle Trigger: SELECT SUM(fin_beg_bal_ln_amt +
258             * acln_annl_bal_amt) INTO v_y FROM gl_balance_t WHERE univ_fiscal_yr = p_univ_fiscal_yr AND fin_coa_cd = p_fin_coa_cd AND
259             * account_nbr = p_account_nbr AND fin_balance_typ_cd IN ('EX', 'IE', 'PE', 'BB'); v_rowcnt := SQL%ROWCOUNT;
260             */
261    
262            Integer fiscalYear = SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear();
263            Iterator balances = balanceDao.findBalances(account, fiscalYear, null, null, null, wrap(getEncumbranceBaseBudgetBalanceTypeCodes()));
264    
265            return sumBalances(balances).isNonZero();
266        }
267    
268        /**
269         * Returns whether or not the beginning budget is loaded for the given account.  Of course, it doesn't
270         * really check the account...just the options for the current year to see if all the beginning balances
271         * have been loaded
272         * 
273         * @param an account to check whether the beginning balance is loaded for
274         * @return true if the beginning balance is loaded, false otherwise
275         * @see org.kuali.kfs.gl.service.BalanceService#beginningBalanceLoaded(org.kuali.kfs.coa.businessobject.Account)
276         */
277        public boolean beginningBalanceLoaded(Account account) {
278            return optionsService.getCurrentYearOptions().isFinancialBeginBalanceLoadInd();
279        }
280    
281        /**
282         * Determines if the account has asset/liability balances associated with it that will have a net impact
283         * 
284         * @param account an account to check balances for
285         * @return true if the account has an asset liability balance, false otherwise
286         * @see org.kuali.kfs.gl.service.BalanceService#hasAssetLiabilityOrFundBalance(org.kuali.kfs.coa.businessobject.Account)
287         */
288        public boolean hasAssetLiabilityOrFundBalance(Account account) {
289            return hasAssetLiabilityFundBalanceBalances(account) || !fundBalanceWillNetToZero(account) || hasEncumbrancesOrBaseBudgets(account);
290        }
291    
292        /**
293         * Saves the balance in a no-nonsense, straight away, three piece suit sort of way
294         * 
295         * @param b the balance to save
296         * @see org.kuali.kfs.gl.service.BalanceService#save(org.kuali.kfs.gl.businessobject.Balance)
297         */
298        public void save(Balance b) {
299            balanceDao.save(b);
300        }
301    
302        public void setBalanceDao(BalanceDao balanceDao) {
303            this.balanceDao = balanceDao;
304        }
305    
306        public void setOptionsService(OptionsService optionsService) {
307            this.optionsService = optionsService;
308        }
309    
310        /**
311         * This method finds the summary records of balance entries according to input fields an values, using the DAO
312         * 
313         * @param fieldValues the input fields an values
314         * @param isConsolidated consolidation option is applied or not
315         * @return the summary records of balance entries
316         * @see org.kuali.kfs.gl.service.BalanceService#findCashBalance(java.util.Map, boolean)
317         */
318        public Iterator findCashBalance(Map fieldValues, boolean isConsolidated) {
319            LOG.debug("findCashBalance() started");
320    
321            return balanceDao.findCashBalance(fieldValues, isConsolidated);
322        }
323    
324        /**
325         * This method gets the size of cash balance entries according to input fields and values
326         * 
327         * @param fieldValues the input fields and values
328         * @param isConsolidated consolidation option is applied or not
329         * @return the count of cash balance entries
330         * @see org.kuali.kfs.gl.service.BalanceService#getCashBalanceRecordCount(java.util.Map, boolean)
331         */
332        public Integer getCashBalanceRecordCount(Map fieldValues, boolean isConsolidated) {
333            LOG.debug("getCashBalanceRecordCount() started");
334    
335            Integer recordCount = new Integer(0);
336            if (!isConsolidated) {
337                recordCount = balanceDao.getDetailedCashBalanceRecordCount(fieldValues);
338            }
339            else {
340                Iterator recordCountIterator = balanceDao.getConsolidatedCashBalanceRecordCount(fieldValues);
341                // TODO: WL: why build a list and waste time/memory when we can just iterate through the iterator and do a count?
342                List recordCountList = IteratorUtils.toList(recordCountIterator);
343                recordCount = recordCountList.size();
344            }
345            return recordCount;
346        }
347    
348        /**
349         * This method gets the size of balance entries according to input fields and values
350         * 
351         * @param fieldValues the input fields and values
352         * @param isConsolidated consolidation option is applied or not
353         * @return the size of balance entries
354         * @see org.kuali.kfs.gl.service.BalanceService#findBalance(java.util.Map, boolean)
355         */
356        public Iterator findBalance(Map fieldValues, boolean isConsolidated) {
357            LOG.debug("findBalance() started");
358            return balanceDao.findBalance(fieldValues, isConsolidated);
359        }
360    
361        /**
362         * This method finds the summary records of balance entries according to input fields and values
363         * 
364         * @param fieldValues the input fields and values
365         * @param isConsolidated consolidation option is applied or not
366         * @return the summary records of balance entries
367         * @see org.kuali.kfs.gl.service.BalanceService#getBalanceRecordCount(java.util.Map, boolean)
368         */
369        public Integer getBalanceRecordCount(Map fieldValues, boolean isConsolidated) {
370            LOG.debug("getBalanceRecordCount() started");
371    
372            Integer recordCount = null;
373            if (!isConsolidated) {
374                recordCount = OJBUtility.getResultSizeFromMap(fieldValues, new Balance()).intValue();
375            }
376            else {
377                Iterator recordCountIterator = balanceDao.getConsolidatedBalanceRecordCount(fieldValues);
378                // TODO: WL: why build a list and waste time/memory when we can just iterate through the iterator and do a count?
379                List recordCountList = IteratorUtils.toList(recordCountIterator);
380                recordCount = recordCountList.size();
381            }
382            return recordCount;
383        }
384    
385        /**
386         * Purge the balance table by year/chart
387         * 
388         * @param chart the chart of balances to purge
389         * @param year the year of balances to purge
390         */
391        public void purgeYearByChart(String chart, int year) {
392            LOG.debug("purgeYearByChart() started");
393    
394            balanceDao.purgeYearByChart(chart, year);
395        }
396    
397        /**
398         * Private method to load the values from the system options service and store them locally for later use.
399         */
400        protected void loadConstantsFromOptions() {
401            LOG.debug("loadConstantsFromOptions() started");
402            SystemOptions options = optionsService.getCurrentYearOptions();
403            // String[] actualBalanceCodes = new String[] { "AC" };
404            actualBalanceCodes = new String[] { options.getActualFinancialBalanceTypeCd() }; // AC
405            // String[] incomeObjectTypeCodes = new String[] { "CH", "IC", "IN", "TI" };
406            incomeObjectTypeCodes = new String[] { options.getFinObjTypeIncomeNotCashCd(), // IC
407                    options.getFinObjectTypeIncomecashCode(), // IN
408                    options.getFinObjTypeCshNotIncomeCd(), // CH
409                    options.getFinancialObjectTypeTransferIncomeCd() // TI
410            };
411            // String[] expenseObjectTypeCodes = new String[] { "EE", "ES", "EX", "TE" };
412            expenseObjectTypeCodes = new String[] { options.getFinObjTypeExpendNotExpCode(), // EE?
413                    options.getFinObjTypeExpenditureexpCd(), // ES
414                    options.getFinObjTypeExpNotExpendCode(), // EX?
415                    options.getFinancialObjectTypeTransferExpenseCd() // TE
416            };
417            // String[] assetLiabilityFundBalanceBalanceTypeCodes = new String[] { "AS", "LI", "FB" };
418            assetLiabilityFundBalanceObjectTypeCodes = new String[] { options.getFinancialObjectTypeAssetsCd(), // AS
419                    options.getFinObjectTypeLiabilitiesCode(), // LI
420                    options.getFinObjectTypeFundBalanceCd() // FB
421            };
422            // String[] encumbranceBaseBudgetBalanceTypeCodes = new String[] { "EX", "IE", "PE", "BB" };
423            encumbranceBaseBudgetBalanceTypeCodes = new String[] { options.getExtrnlEncumFinBalanceTypCd(), // EX
424                    options.getIntrnlEncumFinBalanceTypCd(), // IE
425                    options.getPreencumbranceFinBalTypeCd(), // PE
426                    options.getBaseBudgetFinancialBalanceTypeCd() // BB
427            };
428        }
429    
430        /**
431         * Use the options table to get a list of all the balance type codes associated with actual balances 
432         *
433         * @return an array of balance type codes for actual balances
434         */
435        protected String[] getActualBalanceCodes() {
436            if (actualBalanceCodes == null) {
437                loadConstantsFromOptions();
438            }
439            return actualBalanceCodes;
440        }
441    
442        /**
443         * Uses the options table to find all the balance type codes associated with income
444         * 
445         * @return an array of income balance type codes
446         */
447        protected String[] getIncomeObjectTypeCodes() {
448            if (incomeObjectTypeCodes == null) {
449                loadConstantsFromOptions();
450            }
451            return incomeObjectTypeCodes;
452        }
453    
454        /**
455         * Uses the options table to find all the balance type codes associated with expenses
456         * 
457         * @return an array of expense option type codes
458         */
459        protected String[] getExpenseObjectTypeCodes() {
460            if (expenseObjectTypeCodes == null) {
461                loadConstantsFromOptions();
462            }
463            return expenseObjectTypeCodes;
464        }
465    
466        /**
467         * Uses the options table to find all the balance type codes associated with asset/liability
468         * 
469         * @return an array of asset/liability balance type codes
470         */
471        protected String[] getAssetLiabilityFundBalanceBalanceTypeCodes() {
472            if (assetLiabilityFundBalanceObjectTypeCodes == null) {
473                loadConstantsFromOptions();
474            }
475            return assetLiabilityFundBalanceObjectTypeCodes;
476        }
477    
478        /**
479         * Uses the options table to create a list of all the balance type codes associated with encumbrances
480         * 
481         * @return an array of encumbrance balance type codes
482         */
483        protected String[] getEncumbranceBaseBudgetBalanceTypeCodes() {
484            if (encumbranceBaseBudgetBalanceTypeCodes == null) {
485                loadConstantsFromOptions();
486            }
487            return encumbranceBaseBudgetBalanceTypeCodes;
488        }
489    
490        /**
491         * Uses the DAO to count the number of balances associated with the given fiscal year
492         * 
493         * @param fiscal year a fiscal year to count balances for
494         * @return an integer with the number of balances 
495         * @see org.kuali.kfs.gl.service.BalanceService#countBalancesForFiscalYear(java.lang.Integer)
496         */
497        public int countBalancesForFiscalYear(Integer year) {
498            return balanceDao.countBalancesForFiscalYear(year);
499        }
500    
501        /**
502         * This method returns all of the balances specifically for the nominal activity closing job 
503         * @param year year to find balances for
504         * @return an Iterator of nominal activity balances
505         * @see org.kuali.kfs.gl.service.BalanceService#findNominalActivityBalancesForFiscalYear(java.lang.Integer)
506         */
507        public Iterator<Balance> findNominalActivityBalancesForFiscalYear(Integer year) {
508            return balanceDao.findNominalActivityBalancesForFiscalYear(year);
509        }
510    
511        /**
512         * Returns all the balances to be forwarded for the "cumulative" rule
513         * @param year the fiscal year to find balances for
514         * @return an Iterator of balances to process for the cumulative/active balance forward process
515         * @see org.kuali.kfs.gl.service.BalanceService#findCumulativeBalancesToForwardForFiscalYear(java.lang.Integer)
516         */
517        public Iterator<Balance> findCumulativeBalancesToForwardForFiscalYear(Integer year) {
518            return balanceDao.findCumulativeBalancesToForwardForFiscalYear(year);
519        }
520    
521        /**
522         * Returns all the balances specifically to be processed by the balance forwards job for the "general" rule
523         * @param year the fiscal year to find balances for
524         * @return an Iterator of balances to process for the general balance forward process
525         * @see org.kuali.kfs.gl.service.BalanceService#findGeneralBalancesToForwardForFiscalYear(java.lang.Integer)
526         */
527        public Iterator<Balance> findGeneralBalancesToForwardForFiscalYear(Integer year) {
528            return balanceDao.findGeneralBalancesToForwardForFiscalYear(year);
529        }
530    
531        /**
532         * Returns all of the balances to be forwarded for the organization reversion process
533         * @param year the year of balances to find
534         * @param endOfYear whether the organization reversion process is running end of year (before the fiscal year change over) or beginning of year (after the fiscal year change over)
535         * @return an iterator of balances to put through the strenuous organization reversion process
536         * @see org.kuali.kfs.gl.service.BalanceService#findOrganizationReversionBalancesForFiscalYear(java.lang.Integer, boolean)
537         */
538        public Iterator<Balance> findOrganizationReversionBalancesForFiscalYear(Integer year, boolean endOfYear) {
539            return balanceDao.findOrganizationReversionBalancesForFiscalYear(year, endOfYear);
540        }
541    
542    }