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 }