001: /*
002: * Copyright 2006-2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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.module.gl.service.impl;
017:
018: import java.sql.Date;
019: import java.util.ArrayList;
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.kuali.core.service.DateTimeService;
027: import org.kuali.core.service.KualiConfigurationService;
028: import org.kuali.core.util.KualiDecimal;
029: import org.kuali.kfs.KFSConstants;
030: import org.kuali.kfs.KFSKeyConstants;
031: import org.kuali.kfs.bo.Options;
032: import org.kuali.kfs.context.SpringContext;
033: import org.kuali.kfs.dao.OptionsDao;
034: import org.kuali.kfs.service.ParameterService;
035: import org.kuali.kfs.service.impl.ParameterConstants;
036: import org.kuali.module.chart.bo.Account;
037: import org.kuali.module.chart.service.AccountService;
038: import org.kuali.module.gl.GLConstants;
039: import org.kuali.module.gl.bo.Balance;
040: import org.kuali.module.gl.bo.SufficientFundBalances;
041: import org.kuali.module.gl.bo.SufficientFundRebuild;
042: import org.kuali.module.gl.dao.BalanceDao;
043: import org.kuali.module.gl.dao.SufficientFundBalancesDao;
044: import org.kuali.module.gl.service.ReportService;
045: import org.kuali.module.gl.service.SufficientFundRebuildService;
046: import org.kuali.module.gl.service.SufficientFundsRebuilderService;
047: import org.kuali.module.gl.service.SufficientFundsService;
048: import org.kuali.module.gl.util.Summary;
049: import org.springframework.transaction.annotation.Transactional;
050:
051: /**
052: * The default implementation of SufficientFundsRebuilderService
053: */
054: @Transactional
055: public class SufficientFundsRebuilderServiceImpl implements
056: SufficientFundsRebuilderService {
057: private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
058: .getLogger(SufficientFundsRebuilderServiceImpl.class);
059:
060: private DateTimeService dateTimeService;
061: private KualiConfigurationService kualiConfigurationService;
062: private BalanceDao balanceDao;
063: private SufficientFundBalancesDao sufficientFundBalancesDao;
064: private SufficientFundsService sufficientFundsService;
065: private SufficientFundRebuildService sufficientFundRebuildService;
066: private OptionsDao optionsDao;
067: private ReportService reportService;
068: private AccountService accountService;
069:
070: private Date runDate;
071: private Options options;
072:
073: Map batchError;
074: List reportSummary;
075: List transactionErrors;
076:
077: private Integer universityFiscalYear;
078: private int sfrbRecordsConvertedCount;
079: private int sfrbRecordsReadCount;
080: private int sfrbRecordsDeletedCount;
081: private int sfrbNotDeletedCount;
082: private int sfblInsertedCount;
083: private int sfblUpdatedCount;
084: private int warningCount;
085:
086: private SufficientFundBalances currentSfbl = null;
087:
088: /**
089: * Constructs a SufficientFundsRebuilderServiceImpl instance
090: */
091: public SufficientFundsRebuilderServiceImpl() {
092: super ();
093: }
094:
095: /**
096: * Returns the fiscal year, set in a parameter, of sufficient funds to rebuild
097: *
098: * @return the fiscal year
099: */
100: private Integer getFiscalYear() {
101: String val = SpringContext.getBean(ParameterService.class)
102: .getParameterValue(
103: ParameterConstants.GENERAL_LEDGER_BATCH.class,
104: GLConstants.ANNUAL_CLOSING_FISCAL_YEAR_PARM);
105: int yr = Integer.parseInt(val);
106: return new Integer(yr);
107: }
108:
109: /**
110: * Rebuilds all necessary sufficient funds balances.
111: * @see org.kuali.module.gl.service.SufficientFundsRebuilderService#rebuildSufficientFunds()
112: */
113: public void rebuildSufficientFunds() { // driver
114: LOG.debug("rebuildSufficientFunds() started");
115:
116: universityFiscalYear = getFiscalYear();
117: initService();
118:
119: // Get all the O types and convert them to A types
120: LOG
121: .debug("rebuildSufficientFunds() Converting O types to A types");
122: for (Iterator iter = sufficientFundRebuildService
123: .getAllObjectEntries().iterator(); iter.hasNext();) {
124: SufficientFundRebuild sfrb = (SufficientFundRebuild) iter
125: .next();
126: ++sfrbRecordsReadCount;
127:
128: transactionErrors = new ArrayList();
129:
130: convertOtypeToAtypes(sfrb);
131:
132: if (transactionErrors.size() > 0) {
133: batchError.put(sfrb, transactionErrors);
134: } else {
135: sufficientFundRebuildService.delete(sfrb);
136: }
137: }
138:
139: // Get all the A types and process them
140: LOG
141: .debug("rebuildSufficientFunds() Calculating SF balances for all A types");
142: for (Iterator iter = sufficientFundRebuildService
143: .getAllAccountEntries().iterator(); iter.hasNext();) {
144: SufficientFundRebuild sfrb = (SufficientFundRebuild) iter
145: .next();
146: ++sfrbRecordsReadCount;
147:
148: transactionErrors = new ArrayList();
149:
150: calculateSufficientFundsByAccount(sfrb);
151:
152: if (transactionErrors.size() > 0) {
153: batchError.put(sfrb, transactionErrors);
154: }
155:
156: sufficientFundRebuildService.delete(sfrb);
157:
158: }
159:
160: // Look at all the left over rows. There shouldn't be any left if all are O's and A's without error.
161: // Write out error messages for any that aren't A or O
162: LOG
163: .debug("rebuildSufficientFunds() Handle any non-A and non-O types");
164: for (Iterator iter = sufficientFundRebuildService.getAll()
165: .iterator(); iter.hasNext();) {
166: SufficientFundRebuild sfrb = (SufficientFundRebuild) iter
167: .next();
168:
169: if ((!KFSConstants.SF_TYPE_ACCOUNT.equals(sfrb
170: .getAccountFinancialObjectTypeCode()))
171: && (!KFSConstants.SF_TYPE_OBJECT.equals(sfrb
172: .getAccountFinancialObjectTypeCode()))) {
173: ++sfrbRecordsReadCount;
174: transactionErrors = new ArrayList();
175: addTransactionError(kualiConfigurationService
176: .getPropertyString(KFSKeyConstants.ERROR_INVALID_SF_OBJECT_TYPE_CODE));
177: ++warningCount;
178: ++sfrbNotDeletedCount;
179: batchError.put(sfrb, transactionErrors);
180: }
181: }
182:
183: // write out report and errors
184: LOG.debug("rebuildSufficientFunds() Create report");
185: reportSummary.add(new Summary(1,
186: "SFRB records converted from Object to Account",
187: new Integer(sfrbRecordsConvertedCount)));
188: reportSummary.add(new Summary(2,
189: "Post conversion SFRB records read", new Integer(
190: sfrbRecordsReadCount)));
191: reportSummary.add(new Summary(3, "SFRB records deleted",
192: new Integer(sfrbRecordsDeletedCount)));
193: reportSummary.add(new Summary(4,
194: "SFRB records kept due to errors", new Integer(
195: sfrbNotDeletedCount)));
196: reportSummary.add(new Summary(6, "SFBL records added",
197: new Integer(sfblInsertedCount)));
198: reportSummary.add(new Summary(7, "SFBL records updated",
199: new Integer(sfblUpdatedCount)));
200: reportService.generateSufficientFundsReport(batchError,
201: reportSummary, runDate, 0);
202: }
203:
204: /**
205: * Initializes the process at the beginning of a run.
206: */
207: private void initService() {
208: batchError = new HashMap();
209: reportSummary = new ArrayList();
210:
211: runDate = new Date(dateTimeService.getCurrentDate().getTime());
212:
213: options = optionsDao.getByPrimaryId(universityFiscalYear);
214:
215: if (options == null) {
216: throw new IllegalStateException(
217: kualiConfigurationService
218: .getPropertyString(KFSKeyConstants.ERROR_UNIV_DATE_NOT_FOUND));
219: }
220: }
221:
222: /**
223: * Given an O SF rebuild type, it will look up all of the matching balances in the table and add each account it finds as an A
224: * SF rebuild type.
225: *
226: * @param sfrb the sufficient fund rebuild record to convert
227: */
228: private void convertOtypeToAtypes(SufficientFundRebuild sfrb) {
229: ++sfrbRecordsConvertedCount;
230: Collection fundBalances = sufficientFundBalancesDao
231: .getByObjectCode(universityFiscalYear, sfrb
232: .getChartOfAccountsCode(), sfrb
233: .getAccountNumberFinancialObjectCode());
234:
235: for (Iterator fundBalancesIter = fundBalances.iterator(); fundBalancesIter
236: .hasNext();) {
237: SufficientFundBalances sfbl = (SufficientFundBalances) fundBalancesIter
238: .next();
239:
240: SufficientFundRebuild altSfrb = sufficientFundRebuildService
241: .get(sfbl.getChartOfAccountsCode(),
242: KFSConstants.SF_TYPE_ACCOUNT, sfbl
243: .getAccountNumber());
244: if (altSfrb == null) {
245: altSfrb = new SufficientFundRebuild();
246: altSfrb
247: .setAccountFinancialObjectTypeCode(KFSConstants.SF_TYPE_ACCOUNT);
248: altSfrb.setAccountNumberFinancialObjectCode(sfbl
249: .getAccountNumber());
250: altSfrb.setChartOfAccountsCode(sfbl
251: .getChartOfAccountsCode());
252: sufficientFundRebuildService.save(altSfrb);
253: }
254: }
255: }
256:
257: /**
258: * Updates sufficient funds balances for the given account
259: *
260: * @param sfrb the sufficient fund rebuild record, with a chart and account number
261: */
262: private void calculateSufficientFundsByAccount(
263: SufficientFundRebuild sfrb) {
264: Account sfrbAccount = accountService.getByPrimaryId(sfrb
265: .getChartOfAccountsCode(), sfrb
266: .getAccountNumberFinancialObjectCode());
267:
268: if ((sfrbAccount.getAccountSufficientFundsCode() != null)
269: && (KFSConstants.SF_TYPE_ACCOUNT.equals(sfrbAccount
270: .getAccountSufficientFundsCode())
271: || KFSConstants.SF_TYPE_CASH_AT_ACCOUNT
272: .equals(sfrbAccount
273: .getAccountSufficientFundsCode())
274: || KFSConstants.SF_TYPE_CONSOLIDATION
275: .equals(sfrbAccount
276: .getAccountSufficientFundsCode())
277: || KFSConstants.SF_TYPE_LEVEL
278: .equals(sfrbAccount
279: .getAccountSufficientFundsCode())
280: || KFSConstants.SF_TYPE_OBJECT
281: .equals(sfrbAccount
282: .getAccountSufficientFundsCode()) || KFSConstants.SF_TYPE_NO_CHECKING
283: .equals(sfrbAccount
284: .getAccountSufficientFundsCode()))) {
285: ++sfrbRecordsDeletedCount;
286: sufficientFundBalancesDao.deleteByAccountNumber(
287: universityFiscalYear,
288: sfrb.getChartOfAccountsCode(), sfrbAccount
289: .getAccountNumber());
290:
291: if ((!sfrbAccount.isPendingAcctSufficientFundsIndicator())
292: || (KFSConstants.SF_TYPE_NO_CHECKING
293: .equalsIgnoreCase(sfrbAccount
294: .getAccountSufficientFundsCode()))) {
295: // nothing to do here, no errors either, just return
296: return;
297: }
298:
299: Iterator balancesIterator = balanceDao.findAccountBalances(
300: universityFiscalYear,
301: sfrb.getChartOfAccountsCode(), sfrb
302: .getAccountNumberFinancialObjectCode(),
303: sfrbAccount.getAccountSufficientFundsCode());
304:
305: if (balancesIterator == null) {
306: addTransactionError(kualiConfigurationService
307: .getPropertyString(KFSKeyConstants.ERROR_BALANCE_NOT_FOUND_FOR)
308: + universityFiscalYear + ")");
309: ++warningCount;
310: ++sfrbNotDeletedCount;
311: return;
312: }
313:
314: String currentFinObjectCd = "";
315:
316: while (balancesIterator.hasNext()) {
317: Balance balance = (Balance) balancesIterator.next();
318:
319: String tempFinObjectCd = sufficientFundsService
320: .getSufficientFundsObjectCode(balance
321: .getFinancialObject(), sfrbAccount
322: .getAccountSufficientFundsCode());
323:
324: if (!tempFinObjectCd.equals(currentFinObjectCd)) {
325: // we have a change or are on the last record, write out the data if there is any
326: currentFinObjectCd = tempFinObjectCd;
327:
328: if (currentSfbl != null
329: && amountsAreNonZero(currentSfbl)) {
330: sufficientFundBalancesDao.save(currentSfbl);
331: ++sfblInsertedCount;
332: }
333:
334: currentSfbl = new SufficientFundBalances();
335: currentSfbl
336: .setUniversityFiscalYear(universityFiscalYear);
337: currentSfbl.setChartOfAccountsCode(sfrb
338: .getChartOfAccountsCode());
339: currentSfbl.setAccountNumber(sfrbAccount
340: .getAccountNumber());
341: currentSfbl
342: .setFinancialObjectCode(currentFinObjectCd);
343: currentSfbl
344: .setAccountSufficientFundsCode(sfrbAccount
345: .getAccountSufficientFundsCode());
346: currentSfbl
347: .setAccountActualExpenditureAmt(KualiDecimal.ZERO);
348: currentSfbl
349: .setAccountEncumbranceAmount(KualiDecimal.ZERO);
350: currentSfbl
351: .setCurrentBudgetBalanceAmount(KualiDecimal.ZERO);
352: }
353:
354: if (sfrbAccount.isForContractsAndGrants()) {
355: balance
356: .setAccountLineAnnualBalanceAmount(balance
357: .getAccountLineAnnualBalanceAmount()
358: .add(
359: balance
360: .getContractsGrantsBeginningBalanceAmount()));
361: }
362:
363: if (KFSConstants.SF_TYPE_CASH_AT_ACCOUNT
364: .equals(sfrbAccount
365: .getAccountSufficientFundsCode())) {
366: processCash(sfrbAccount, balance);
367: } else {
368: processObjectOrAccount(sfrbAccount, balance);
369: }
370: }
371:
372: // save the last one
373: if (currentSfbl != null && amountsAreNonZero(currentSfbl)) {
374: sufficientFundBalancesDao.save(currentSfbl);
375: ++sfblInsertedCount;
376: }
377: } else {
378: addTransactionError(kualiConfigurationService
379: .getPropertyString(KFSKeyConstants.ERROR_INVALID_ACCOUNT_SF_CODE_FOR));
380: ++warningCount;
381: ++sfrbNotDeletedCount;
382: return;
383: }
384: }
385:
386: /**
387: * Determines if all sums associated with a sufficient funds balance are zero
388: *
389: * @param sfbl the sufficient funds balance to check
390: * @return true if all sums in the balance are zero, false otherwise
391: */
392: private boolean amountsAreNonZero(SufficientFundBalances sfbl) {
393: boolean zero = true;
394: zero &= KualiDecimal.ZERO.equals(sfbl
395: .getAccountActualExpenditureAmt());
396: zero &= KualiDecimal.ZERO.equals(sfbl
397: .getAccountEncumbranceAmount());
398: zero &= KualiDecimal.ZERO.equals(sfbl
399: .getCurrentBudgetBalanceAmount());
400: return !zero;
401: }
402:
403: /**
404: * Determines how best to process the given balance
405: *
406: * @param sfrbAccount the account of the current sufficient funds balance rebuild record
407: * @param balance the cash encumbrance balance to update the sufficient funds balance with
408: */
409: private void processObjectOrAccount(Account sfrbAccount,
410: Balance balance) {
411: if (options.getFinObjTypeExpenditureexpCd().equals(
412: balance.getObjectTypeCode())
413: || options.getFinObjTypeExpendNotExpCode().equals(
414: balance.getObjectTypeCode())
415: || options.getFinObjTypeExpNotExpendCode().equals(
416: balance.getObjectTypeCode())
417: || options.getFinancialObjectTypeTransferExpenseCd()
418: .equals(balance.getObjectTypeCode())) {
419: if (options.getActualFinancialBalanceTypeCd().equals(
420: balance.getBalanceTypeCode())) {
421: processObjtAcctActual(balance);
422: } else if (options.getExtrnlEncumFinBalanceTypCd().equals(
423: balance.getBalanceTypeCode())
424: || options.getIntrnlEncumFinBalanceTypCd().equals(
425: balance.getBalanceTypeCode())
426: || options.getPreencumbranceFinBalTypeCd().equals(
427: balance.getBalanceTypeCode())
428: || options.getCostShareEncumbranceBalanceTypeCd()
429: .equals(balance.getBalanceTypeCode())) {
430: processObjtAcctEncmbrnc(balance);
431: } else if (options.getBudgetCheckingBalanceTypeCd().equals(
432: balance.getBalanceTypeCode())) {
433: processObjtAcctBudget(balance);
434: }
435: }
436: }
437:
438: /**
439: * Updates the current sufficient fund balance record with a non-cash actual balance
440: *
441: * @param balance the cash encumbrance balance to update the sufficient funds balance with
442: */
443: private void processObjtAcctActual(Balance balance) {
444: currentSfbl.setAccountActualExpenditureAmt(currentSfbl
445: .getAccountActualExpenditureAmt().add(
446: balance.getAccountLineAnnualBalanceAmount()));
447: }
448:
449: /**
450: * Updates the current sufficient fund balance record with a non-cash encumbrance balance
451: *
452: * @param balance the cash encumbrance balance to update the sufficient funds balance with
453: */
454: private void processObjtAcctEncmbrnc(Balance balance) {
455: currentSfbl.setAccountEncumbranceAmount(currentSfbl
456: .getAccountEncumbranceAmount().add(
457: balance.getAccountLineAnnualBalanceAmount()));
458: currentSfbl.setAccountEncumbranceAmount(currentSfbl
459: .getAccountEncumbranceAmount().add(
460: balance.getBeginningBalanceLineAmount()));
461: }
462:
463: /**
464: * Updates the current sufficient fund balance record with a non-cash budget balance
465: *
466: * @param balance the cash encumbrance balance to update the sufficient funds balance with
467: */
468: private void processObjtAcctBudget(Balance balance) {
469: currentSfbl.setCurrentBudgetBalanceAmount(currentSfbl
470: .getCurrentBudgetBalanceAmount().add(
471: balance.getAccountLineAnnualBalanceAmount()));
472: currentSfbl.setCurrentBudgetBalanceAmount(currentSfbl
473: .getCurrentBudgetBalanceAmount().add(
474: balance.getBeginningBalanceLineAmount()));
475: }
476:
477: /**
478: * Determines how best to process a cash balance
479: *
480: * @param sfrbAccount the account of the current sufficient funds balance record
481: * @param balance the cash encumbrance balance to update the sufficient funds balance with
482: */
483: private void processCash(Account sfrbAccount, Balance balance) {
484: if (balance.getBalanceTypeCode().equals(
485: options.getActualFinancialBalanceTypeCd())) {
486: if (balance.getObjectCode().equals(
487: sfrbAccount.getChartOfAccounts()
488: .getFinancialCashObjectCode())
489: || balance.getObjectCode().equals(
490: sfrbAccount.getChartOfAccounts()
491: .getFinAccountsPayableObjectCode())) {
492: processCashActual(sfrbAccount, balance);
493: }
494: } else if (balance.getBalanceTypeCode().equals(
495: options.getExtrnlEncumFinBalanceTypCd())
496: || balance.getBalanceTypeCode().equals(
497: options.getIntrnlEncumFinBalanceTypCd())
498: || balance.getBalanceTypeCode().equals(
499: options.getPreencumbranceFinBalTypeCd())
500: || options.getCostShareEncumbranceBalanceTypeCd()
501: .equals(balance.getBalanceTypeCode())) {
502: if (balance.getObjectTypeCode().equals(
503: options.getFinObjTypeExpenditureexpCd())
504: || balance.getObjectTypeCode().equals(
505: options.getFinObjTypeExpendNotExpCode())
506: || options
507: .getFinancialObjectTypeTransferExpenseCd()
508: .equals(balance.getObjectTypeCode())
509: || options.getFinObjTypeExpNotExpendCode().equals(
510: balance.getObjectTypeCode())) {
511: processCashEncumbrance(balance);
512: }
513: }
514: }
515:
516: /**
517: * Updates the current sufficient fund balance record with a cash actual balance
518: *
519: * @param sfrbAccount the account of the current sufficient funds balance record
520: * @param balance the cash encumbrance balance to update the sufficient funds balance with
521: */
522: private void processCashActual(Account sfrbAccount, Balance balance) {
523: if (balance.getObjectCode().equals(
524: sfrbAccount.getChartOfAccounts()
525: .getFinancialCashObjectCode())) {
526: currentSfbl.setCurrentBudgetBalanceAmount(currentSfbl
527: .getCurrentBudgetBalanceAmount()
528: .add(balance.getAccountLineAnnualBalanceAmount()));
529: currentSfbl.setCurrentBudgetBalanceAmount(currentSfbl
530: .getCurrentBudgetBalanceAmount().add(
531: balance.getBeginningBalanceLineAmount()));
532: }
533: if (balance.getObjectCode().equals(
534: sfrbAccount.getChartOfAccounts()
535: .getFinAccountsPayableObjectCode())) {
536: currentSfbl
537: .setCurrentBudgetBalanceAmount(currentSfbl
538: .getCurrentBudgetBalanceAmount()
539: .subtract(
540: balance
541: .getAccountLineAnnualBalanceAmount()));
542: currentSfbl.setCurrentBudgetBalanceAmount(currentSfbl
543: .getCurrentBudgetBalanceAmount().subtract(
544: balance.getBeginningBalanceLineAmount()));
545: }
546: }
547:
548: /**
549: * Updates the current sufficient funds balance with a cash encumbrance balance
550: *
551: * @param balance the cash encumbrance balance to update the sufficient funds balance with
552: */
553: private void processCashEncumbrance(Balance balance) {
554: currentSfbl.setAccountEncumbranceAmount(currentSfbl
555: .getAccountEncumbranceAmount().add(
556: balance.getAccountLineAnnualBalanceAmount()));
557: currentSfbl.setAccountEncumbranceAmount(currentSfbl
558: .getAccountEncumbranceAmount().add(
559: balance.getBeginningBalanceLineAmount()));
560: }
561:
562: /**
563: * Adds an error message to this instance's List of error messages
564: * @param errorMessage the error message to keep
565: */
566: private void addTransactionError(String errorMessage) {
567: transactionErrors.add(errorMessage);
568: }
569:
570: public void setDateTimeService(DateTimeService dateTimeService) {
571: this .dateTimeService = dateTimeService;
572: }
573:
574: public void setKualiConfigurationService(
575: KualiConfigurationService kualiConfigurationService) {
576: this .kualiConfigurationService = kualiConfigurationService;
577: }
578:
579: public void setBalanceDao(BalanceDao balanceDao) {
580: this .balanceDao = balanceDao;
581: }
582:
583: public void setSufficientFundBalancesDao(
584: SufficientFundBalancesDao sufficientFundBalancesDao) {
585: this .sufficientFundBalancesDao = sufficientFundBalancesDao;
586: }
587:
588: public void setSufficientFundRebuildService(
589: SufficientFundRebuildService sufficientFundRebuildService) {
590: this .sufficientFundRebuildService = sufficientFundRebuildService;
591: }
592:
593: public void setOptionsDao(OptionsDao optionsDao) {
594: this .optionsDao = optionsDao;
595: }
596:
597: public void setReportService(ReportService sfrs) {
598: reportService = sfrs;
599: }
600:
601: public void setAccountService(AccountService accountService) {
602: this .accountService = accountService;
603: }
604:
605: public void setSufficientFundsService(SufficientFundsService sfs) {
606: sufficientFundsService = sfs;
607: }
608: }
|