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 }