001/*
002 * Copyright 2006 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.ole.gl.service.impl;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024
025import org.kuali.ole.coa.businessobject.ObjectCode;
026import org.kuali.ole.coa.service.AccountService;
027import org.kuali.ole.coa.service.ObjectLevelService;
028import org.kuali.ole.coa.service.ObjectTypeService;
029import org.kuali.ole.fp.document.YearEndDocument;
030import org.kuali.ole.gl.batch.dataaccess.SufficientFundsDao;
031import org.kuali.ole.gl.businessobject.SufficientFundBalances;
032import org.kuali.ole.gl.businessobject.SufficientFundRebuild;
033import org.kuali.ole.gl.businessobject.Transaction;
034import org.kuali.ole.gl.dataaccess.SufficientFundBalancesDao;
035import org.kuali.ole.gl.service.SufficientFundsService;
036import org.kuali.ole.gl.service.SufficientFundsServiceConstants;
037import org.kuali.ole.module.purap.businessobject.PaymentRequestAccount;
038import org.kuali.ole.select.businessobject.OlePaymentRequestItem;
039import org.kuali.ole.select.businessobject.OleSufficientFundCheck;
040import org.kuali.ole.select.document.OlePaymentRequestDocument;
041import org.kuali.ole.sys.OLEConstants;
042import org.kuali.ole.sys.OLEPropertyConstants;
043import org.kuali.ole.sys.businessobject.SufficientFundsItem;
044import org.kuali.ole.sys.businessobject.SystemOptions;
045import org.kuali.ole.sys.context.SpringContext;
046import org.kuali.ole.sys.document.GeneralLedgerPostingDocument;
047import org.kuali.ole.sys.service.GeneralLedgerPendingEntryService;
048import org.kuali.ole.sys.service.OptionsService;
049import org.kuali.rice.core.api.config.property.ConfigurationService;
050import org.kuali.rice.core.api.util.type.KualiDecimal;
051import org.kuali.rice.krad.service.BusinessObjectService;
052import org.kuali.rice.krad.util.ObjectUtils;
053import org.springframework.transaction.annotation.Transactional;
054
055/**
056 * The base implementation of SufficientFundsService
057 */
058@Transactional
059public class SufficientFundsServiceImpl implements SufficientFundsService, SufficientFundsServiceConstants {
060    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SufficientFundsServiceImpl.class);
061
062    private AccountService accountService;
063    private ObjectLevelService objectLevelService;
064    private ConfigurationService kualiConfigurationService;
065    private SufficientFundsDao sufficientFundsDao;
066    private SufficientFundBalancesDao sufficientFundBalancesDao;
067    private OptionsService optionsService;
068    private GeneralLedgerPendingEntryService generalLedgerPendingEntryService;
069    private BusinessObjectService businessObjectService;
070
071    /**
072     * Default constructor
073     */
074    public SufficientFundsServiceImpl() {
075        super();
076    }
077
078    /**
079     * This operation derives the acct_sf_finobj_cd which is used to populate the General Ledger Pending entry table, so that later
080     * we can do Suff Fund checking against that entry
081     *
082     * @param financialObject the object code being checked against
083     * @param accountSufficientFundsCode the kind of sufficient funds checking turned on in this system
084     * @return the object code that should be used for the sufficient funds inquiry, or a blank String
085     * @see org.kuali.ole.gl.service.SufficientFundsService#getSufficientFundsObjectCode(org.kuali.ole.coa.businessobject.ObjectCode,
086     *      java.lang.String)
087     */
088    @Override
089    public String getSufficientFundsObjectCode(ObjectCode financialObject, String accountSufficientFundsCode) {
090        if ( LOG.isDebugEnabled() ) {
091            LOG.debug("Obtaining SF Object code for: " + accountSufficientFundsCode + " - " + financialObject.getFinancialObjectCode() );
092        }
093        if (OLEConstants.SF_TYPE_NO_CHECKING.equals(accountSufficientFundsCode)) {
094            return OLEConstants.NOT_AVAILABLE_STRING;
095        }
096        else if (OLEConstants.SF_TYPE_ACCOUNT.equals(accountSufficientFundsCode)) {
097            return "    ";
098        }
099        else if (OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(accountSufficientFundsCode)) {
100            return "    ";
101        }
102        else if (OLEConstants.SF_TYPE_OBJECT.equals(accountSufficientFundsCode)) {
103            return financialObject.getFinancialObjectCode();
104        }
105        else if (OLEConstants.SF_TYPE_LEVEL.equals(accountSufficientFundsCode)) {
106            return financialObject.getFinancialObjectLevelCode();
107        }
108        else if (OLEConstants.SF_TYPE_CONSOLIDATION.equals(accountSufficientFundsCode)) {
109            financialObject.refreshReferenceObject("financialObjectLevel");
110            return financialObject.getFinancialObjectLevel().getFinancialConsolidationObjectCode();
111        }
112        else {
113            throw new IllegalArgumentException("Invalid Sufficient Funds Code: " + accountSufficientFundsCode);
114        }
115    }
116
117    /**
118     * Checks for sufficient funds on a single document
119     *
120     * @param document document to check
121     * @return Empty List if has sufficient funds for all accounts, List of SufficientFundsItem if not
122     * @see org.kuali.ole.gl.service.SufficientFundsService#checkSufficientFunds(org.kuali.rice.krad.document.FinancialDocument)
123     */
124    @Override
125    public List<SufficientFundsItem> checkSufficientFunds(GeneralLedgerPostingDocument document) {
126        LOG.debug("checkSufficientFunds() started");
127
128        return checkSufficientFunds(document.getPendingLedgerEntriesForSufficientFundsChecking());
129    }
130
131    /**
132     * checks to see if a document is a <code>YearEndDocument</code>
133     *
134     * @param documentClass the class of a Document to check
135     * @return true if the class implements <code>YearEndDocument</code>
136     */
137    @SuppressWarnings("unchecked")
138    protected boolean isYearEndDocument(Class documentClass) {
139        return YearEndDocument.class.isAssignableFrom(documentClass);
140    }
141
142    /**
143     * Checks for sufficient funds on a list of transactions
144     *
145     * @param transactions list of transactions
146     * @return Empty List if has sufficient funds for all accounts, List of SufficientFundsItem if not
147     * @see org.kuali.ole.gl.service.SufficientFundsService#checkSufficientFunds(java.util.List)
148     */
149    @Override
150    @SuppressWarnings("unchecked")
151    public List<SufficientFundsItem> checkSufficientFunds(List<? extends Transaction> transactions) {
152        LOG.debug("checkSufficientFunds() started");
153
154        for (Transaction e : transactions) {
155            e.refreshNonUpdateableReferences();
156        }
157
158        List<SufficientFundsItem> summaryItems = summarizeTransactions(transactions);
159        for (Iterator iter = summaryItems.iterator(); iter.hasNext();) {
160            SufficientFundsItem item = (SufficientFundsItem) iter.next();
161            if ( LOG.isDebugEnabled() ) {
162                LOG.debug("checkSufficientFunds() " + item.toString());
163            }
164            if (hasSufficientFundsOnItem(item)) {
165                iter.remove();
166            }
167        }
168
169        if ( LOG.isDebugEnabled() ) {
170            LOG.debug("Sufficient Funds Check Complete.  Returning: " + summaryItems );
171        }
172        return summaryItems;
173    }
174
175    @Override
176    @SuppressWarnings("unchecked")
177    public List<SufficientFundsItem> checkSufficientFundsForPREQ(List<? extends Transaction> transactions) {
178        LOG.debug("checkSufficientFunds() started");
179
180        for (Transaction e : transactions) {
181            e.refreshNonUpdateableReferences();
182        }
183
184        List<SufficientFundsItem> summaryItems = summarizeTransactions(transactions);
185        for (Iterator iter = summaryItems.iterator(); iter.hasNext();) {
186            SufficientFundsItem item = (SufficientFundsItem) iter.next();
187            if (LOG.isDebugEnabled()) {
188                LOG.debug("checkSufficientFunds() " + item.toString());
189            }
190            if (hasSufficientFundsOnPREQItem(item)) {
191                iter.remove();
192            }
193        }
194
195        if (LOG.isDebugEnabled()) {
196            LOG.debug("PREQ Sufficient Funds Check Complete.  Returning: " + summaryItems);
197        }
198        return summaryItems;
199    }
200
201    @Override
202    @SuppressWarnings("unchecked")
203    public List<SufficientFundsItem> checkSufficientFundsForInvoice(List<? extends Transaction> transactions) {
204        LOG.debug("checkSufficientFunds() for Invoice started");
205
206        for (Transaction e : transactions) {
207            e.refreshNonUpdateableReferences();
208        }
209
210        List<SufficientFundsItem> summaryItems = summarizeTransactions(transactions);
211        for (Iterator iter = summaryItems.iterator(); iter.hasNext();) {
212            SufficientFundsItem item = (SufficientFundsItem) iter.next();
213            if (LOG.isDebugEnabled()) {
214                LOG.debug("checkSufficientFunds() for Invoice " + item.toString());
215            }
216           /* if (hasSufficientFundsOnPREQItem(item)) {
217                iter.remove();
218            }*/
219        }
220
221        if (LOG.isDebugEnabled()) {
222            LOG.debug("Invoice Sufficient Funds Check Complete.  Returning: " + summaryItems);
223        }
224        return summaryItems;
225    }
226
227    /**
228     * For each transaction, fetches the appropriate sufficient funds item to check against
229     *
230     * @param transactions a list of Transactions
231     * @return a List of corresponding SufficientFundsItem
232     */
233    @SuppressWarnings("unchecked")
234    protected List<SufficientFundsItem> summarizeTransactions(List<? extends Transaction> transactions) {
235        Map<String, SufficientFundsItem> items = new HashMap<String, SufficientFundsItem>();
236
237        SystemOptions currentYear = optionsService.getCurrentYearOptions();
238
239        // loop over the given transactions, grouping into SufficientFundsItem objects
240        // which are keyed by the appropriate chart/account/SF type, and derived object value
241        // see getSufficientFundsObjectCode() for the "object" used for grouping
242        for (Object element : transactions) {
243            Transaction tran = (Transaction) element;
244
245            SystemOptions year = tran.getOption();
246            if (year == null) {
247                year = currentYear;
248            }
249            if (ObjectUtils.isNull(tran.getAccount())) {
250                throw new IllegalArgumentException("Invalid account: " + tran.getChartOfAccountsCode() + "-" + tran.getAccountNumber());
251            }
252            SufficientFundsItem sfi = new SufficientFundsItem(year, tran, getSufficientFundsObjectCode(tran.getFinancialObject(), tran.getAccount().getAccountSufficientFundsCode()));
253            sfi.setDocumentTypeCode(tran.getFinancialDocumentTypeCode());
254
255            if (items.containsKey(sfi.getKey())) {
256                SufficientFundsItem item = items.get(sfi.getKey());
257                item.add(tran);
258            }
259            else {
260                items.put(sfi.getKey(), sfi);
261            }
262        }
263
264        if ( LOG.isDebugEnabled() ) {
265            LOG.debug( "Returning Summarized transactions for sufficient funds checking: " + items );
266        }
267        return new ArrayList<SufficientFundsItem>(items.values());
268    }
269
270    /**
271     * Given a sufficient funds item record, determines if there are sufficient funds available for the transaction
272     *
273     * @param item the item to check
274     * @return true if there are sufficient funds available, false otherwise
275     */
276    protected boolean hasSufficientFundsOnItem(SufficientFundsItem item) {
277        if (item.getAmount().equals(KualiDecimal.ZERO)) {
278            LOG.debug("hasSufficientFundsOnItem() Transactions with zero amounts shold pass");
279            return true;
280        }
281
282        if (!item.getYear().isBudgetCheckingOptionsCode()) {
283            LOG.debug("hasSufficientFundsOnItem() No sufficient funds checking");
284            return true;
285        }
286
287        if (!item.getAccount().isPendingAcctSufficientFundsIndicator()) {
288            if (LOG.isDebugEnabled()) {
289                LOG.debug("hasSufficientFundsOnItem() No checking on eDocs for account " + item.getAccount().getChartOfAccountsCode() + "-" + item.getAccount().getAccountNumber());
290            }
291            return true;
292        }
293
294        // exit sufficient funds checking if not enabled for an account
295        if (OLEConstants.SF_TYPE_NO_CHECKING.equals(item.getAccountSufficientFundsCode())) {
296            if (LOG.isDebugEnabled()) {
297                LOG.debug("hasSufficientFundsOnItem() sufficient funds not enabled for account " + item.getAccount().getChartOfAccountsCode() + "-" + item.getAccount().getAccountNumber());
298            }
299            return true;
300        }
301
302        ObjectTypeService objectTypeService = SpringContext.getBean(ObjectTypeService.class);
303        List<String> expenseObjectTypes = objectTypeService.getCurrentYearExpenseObjectTypes();
304
305        if (OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(item.getAccount().getAccountSufficientFundsCode())
306                && !item.getFinancialObject().getChartOfAccounts().getFinancialCashObjectCode()
307                .equals(item.getFinancialObject().getFinancialObjectCode())) {
308            LOG.debug("hasSufficientFundsOnItem() SF checking is cash and transaction is not cash");
309            return true;
310        }
311
312        else if (!OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(item.getAccount().getAccountSufficientFundsCode())
313                && !expenseObjectTypes.contains(item.getFinancialObjectType().getCode())) {
314            LOG.debug("hasSufficientFundsOnItem() SF checking is budget and transaction is not expense");
315            return true;
316        }
317
318        Map<String, Object> keys = new HashMap<String, Object>();
319        keys.put(OLEPropertyConstants.UNIVERSITY_FISCAL_YEAR, item.getYear().getUniversityFiscalYear());
320        keys.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, item.getAccount().getChartOfAccountsCode());
321        keys.put(OLEPropertyConstants.ACCOUNT_NUMBER, item.getAccount().getAccountNumber());
322        keys.put(OLEPropertyConstants.FINANCIAL_OBJECT_CODE, item.getSufficientFundsObjectCode());
323        SufficientFundBalances sfBalance = businessObjectService.findByPrimaryKey(SufficientFundBalances.class, keys);
324
325        if (sfBalance == null) {
326            Map criteria = new HashMap();
327            criteria.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, item.getAccount().getChartOfAccountsCode());
328            criteria.put(OLEPropertyConstants.ACCOUNT_NUMBER_FINANCIAL_OBJECT_CODE, item.getAccount().getAccountNumber());
329
330            Collection sufficientFundRebuilds = businessObjectService.findMatching(SufficientFundRebuild.class, criteria);
331            if (sufficientFundRebuilds != null && sufficientFundRebuilds.size() > 0) {
332                LOG.debug("hasSufficientFundsOnItem() No balance record and waiting on rebuild, no sufficient funds");
333                return false;
334            }
335            else {
336                sfBalance = new SufficientFundBalances();
337                sfBalance.setAccountActualExpenditureAmt(KualiDecimal.ZERO);
338                sfBalance.setAccountEncumbranceAmount(KualiDecimal.ZERO);
339                sfBalance.setCurrentBudgetBalanceAmount(KualiDecimal.ZERO);
340            }
341        }
342        if (LOG.isDebugEnabled()) {
343            LOG.debug("Current SF Balances: Budget:      " + sfBalance.getCurrentBudgetBalanceAmount());
344            LOG.debug("Current SF Balances: Actuals:     " + sfBalance.getAccountEncumbranceAmount());
345            LOG.debug("Current SF Balances: Encumbrance: " + sfBalance.getAccountActualExpenditureAmt());
346        }
347
348        KualiDecimal balanceAmount = item.getAmount();
349        if (OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(item.getAccount().getAccountSufficientFundsCode())
350                || item.getYear().getBudgetCheckingBalanceTypeCd().equals(item.getBalanceTyp().getCode())) {
351            // We need to change the sign on the amount because the amount in the item is an increase in cash. We only care
352            // about decreases in cash.
353
354            // Also, negating if this is a balance type code of budget checking and the transaction is a budget transaction.
355
356            balanceAmount = balanceAmount.negated();
357        }
358
359        if (balanceAmount.isNegative()) {
360            LOG.debug("hasSufficientFundsOnItem() balanceAmount is negative, allow transaction to proceed");
361            return true;
362        }
363
364        PendingAmounts priorYearPending = new PendingAmounts();
365        // if we're checking the CASH_AT_ACCOUNT type, then we need to consider the prior year pending transactions
366        // if the balance forwards have not been run
367        if ((OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(item.getAccount().getAccountSufficientFundsCode()))
368                && (!item.getYear().isFinancialBeginBalanceLoadInd())) {
369            priorYearPending = getPriorYearSufficientFundsBalanceAmount(item);
370        }
371
372        PendingAmounts pending = getPendingBalanceAmount(item);
373
374        KualiDecimal availableBalance = null;
375        KualiDecimal freeBalance = null;
376        if (OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(item.getAccount().getAccountSufficientFundsCode())) {
377            // if the beginning balances have not loaded for the transaction FY, pull the remaining balance from last year
378            if (!item.getYear().isFinancialBeginBalanceLoadInd()) {
379                availableBalance = sfBalance.getCurrentBudgetBalanceAmount().add(priorYearPending.budget) // add the remaining
380                        // budget from last year
381                        // (assumed to carry to
382                        // this year's)
383                        .add(pending.actual) // any pending expenses (remember sense is negated)
384                        .subtract(sfBalance.getAccountEncumbranceAmount()) // subtract the encumbrances (not reflected in cash yet)
385                        .subtract(priorYearPending.encumbrance);
386            }
387            else { // balance forwards have been run, don't need to consider prior year remaining budget
388                availableBalance = sfBalance.getCurrentBudgetBalanceAmount().add(pending.actual)
389                        .subtract(sfBalance.getAccountEncumbranceAmount());
390            }
391        }
392        else {
393            availableBalance = sfBalance.getCurrentBudgetBalanceAmount() // current budget balance
394                    .add(pending.budget) // pending budget entries
395                    .subtract(sfBalance.getAccountActualExpenditureAmt()) // minus all current and pending actuals and encumbrances
396                    .subtract(pending.actual).subtract(sfBalance.getAccountEncumbranceAmount())
397                    .subtract(pending.encumbrance);
398            freeBalance = sfBalance.getCurrentBudgetBalanceAmount()
399                    .subtract(sfBalance.getAccountActualExpenditureAmt()).subtract(pending.actual);
400        }
401
402        if (LOG.isDebugEnabled()) {
403            LOG.debug("hasSufficientFundsOnItem() balanceAmount: " + balanceAmount + " availableBalance: " + availableBalance);
404        }
405        String chart = item.getAccount().getChartOfAccountsCode();
406        String account = item.getAccount().getAccountNumber();
407        String sfCode = item.getAccount().getAccountSufficientFundsCode();
408
409        Map<String, Object> key = new HashMap<String, Object>();
410        key.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, chart);
411        key.put(OLEPropertyConstants.ACCOUNT_NUMBER, account);
412        OleSufficientFundCheck oleSufficientFundCheck = businessObjectService.findByPrimaryKey(
413                OleSufficientFundCheck.class, key);
414        String option = "";
415        boolean checkBalance;
416        if (oleSufficientFundCheck != null) {
417            option = oleSufficientFundCheck.getNotificationOption() != null ? oleSufficientFundCheck
418                    .getNotificationOption() : "";
419            if (option.equals(OLEPropertyConstants.BUD_REVIEW)) {
420                checkBalance = checkEncumbrance(item, balanceAmount, availableBalance, sfBalance);
421                return checkBalance;
422            }
423            /*
424             * else if (option.equals(OLEPropertyConstants.NOTIFICATION)) { OleNotifyServiceImpl notification = new
425             * OleNotifyServiceImpl(); List user = new ArrayList(); try { user.add(OLEConstants.NOTIFICATION_APPROVER);
426             * notification.notify(user, OLEConstants.ACC_NUM + account + OLEConstants.EXC_BUD_AMT); return true; } catch
427             * (WorkflowException exception) { LOG.debug("Exception occured while notification sent to budget reviewer" +
428             * exception); } }
429             */
430            else if (option.equals(OLEPropertyConstants.WARNING_MSG)) {
431                return true;
432            }
433            else if (option.equals(OLEPropertyConstants.BLOCK_USE)) {
434                return true;
435            }
436
437        }
438        else {
439            checkBalance = checkEncumbrance(item, balanceAmount, availableBalance, sfBalance);
440            return checkBalance;
441        }
442        return false;
443    }
444
445    public boolean checkEncumbrance(SufficientFundsItem item, KualiDecimal balanceAmount,
446                                    KualiDecimal availableBalance, SufficientFundBalances sfBalance) {
447        String chart = item.getAccount().getChartOfAccountsCode();
448        String account = item.getAccount().getAccountNumber();
449        String sfCode = item.getAccount().getAccountSufficientFundsCode();
450
451        Map<String, Object> key = new HashMap<String, Object>();
452        key.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, chart);
453        key.put(OLEPropertyConstants.ACCOUNT_NUMBER, account);
454        OleSufficientFundCheck oleSufficientFundCheck = businessObjectService.findByPrimaryKey(
455                OleSufficientFundCheck.class, key);
456        if (oleSufficientFundCheck != null) {
457            KualiDecimal encumbranceAmount = KualiDecimal.ZERO;
458            KualiDecimal encumAvailableAmount = null;
459            if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_TYP_PERCENTAGE.equals(oleSufficientFundCheck
460                    .getEncumbExpenseConstraintType())) {
461                encumbranceAmount = new KualiDecimal(oleSufficientFundCheck.getEncumbranceAmount());
462                if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_OVER.equals(oleSufficientFundCheck
463                        .getEncumbExpenseMethod())) {
464                    encumAvailableAmount = availableBalance.add((sfBalance.getCurrentBudgetBalanceAmount()
465                            .multiply(encumbranceAmount)).divide(new KualiDecimal(100)));
466                }
467                else if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_UNDER.equals(oleSufficientFundCheck
468                        .getEncumbExpenseMethod())) {
469                    encumAvailableAmount = availableBalance.subtract((sfBalance.getCurrentBudgetBalanceAmount()
470                            .multiply(encumbranceAmount)).divide(new KualiDecimal(100)));
471                }
472                if (balanceAmount.compareTo(encumAvailableAmount) > 0) {
473                    return false;
474                }
475                return true;
476            }
477            else if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_TYP_CASH.equals(oleSufficientFundCheck
478                    .getEncumbExpenseConstraintType())) {
479                encumbranceAmount = new KualiDecimal(oleSufficientFundCheck.getEncumbranceAmount());
480                if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_OVER.equals(oleSufficientFundCheck
481                        .getEncumbExpenseMethod())) {
482                    encumAvailableAmount = availableBalance.add(encumbranceAmount);
483                }
484                else if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_UNDER.equals(oleSufficientFundCheck
485                        .getEncumbExpenseMethod())) {
486                    encumAvailableAmount = availableBalance.subtract(encumbranceAmount);
487                }
488                if (balanceAmount.compareTo(encumAvailableAmount) > 0) {
489                    return false;
490                }
491                return true;
492            }
493        }
494        if (availableBalance.isGreaterThan(balanceAmount)) {
495            return true;
496        }
497        return false;
498    }
499
500
501    protected boolean hasSufficientFundsOnPREQItem(SufficientFundsItem item) {
502
503
504        if (item.getAmount().equals(KualiDecimal.ZERO)) {
505            LOG.debug("hasSufficientFundsOnItem() Transactions with zero amounts shold pass");
506            return true;
507        }
508
509        if (!item.getYear().isBudgetCheckingOptionsCode()) {
510            LOG.debug("hasSufficientFundsOnItem() No sufficient funds checking");
511            return true;
512        }
513
514        if (!item.getAccount().isPendingAcctSufficientFundsIndicator()) {
515            if (LOG.isDebugEnabled()) {
516                LOG.debug("hasSufficientFundsOnItem() No checking on eDocs for account "
517                        + item.getAccount().getChartOfAccountsCode() + "-" + item.getAccount().getAccountNumber());
518            }
519            return true;
520        }
521
522        // exit sufficient funds checking if not enabled for an account
523        if (OLEConstants.SF_TYPE_NO_CHECKING.equals(item.getAccountSufficientFundsCode())) {
524            if (LOG.isDebugEnabled()) {
525                LOG.debug("hasSufficientFundsOnItem() sufficient funds not enabled for account "
526                        + item.getAccount().getChartOfAccountsCode() + "-" + item.getAccount().getAccountNumber());
527            }
528            return true;
529        }
530
531        ObjectTypeService objectTypeService = SpringContext.getBean(ObjectTypeService.class);
532        List<String> expenseObjectTypes = objectTypeService.getCurrentYearExpenseObjectTypes();
533
534        if (OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(item.getAccount().getAccountSufficientFundsCode())
535                && !item.getFinancialObject().getChartOfAccounts().getFinancialCashObjectCode()
536                .equals(item.getFinancialObject().getFinancialObjectCode())) {
537            LOG.debug("hasSufficientFundsOnItem() SF checking is cash and transaction is not cash");
538            return true;
539        }
540
541        else if (!OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(item.getAccount().getAccountSufficientFundsCode())
542                && !expenseObjectTypes.contains(item.getFinancialObjectType().getCode())) {
543            LOG.debug("hasSufficientFundsOnItem() SF checking is budget and transaction is not expense");
544            return true;
545        }
546
547        Map<String, Object> keys = new HashMap<String, Object>();
548        keys.put(OLEPropertyConstants.UNIVERSITY_FISCAL_YEAR, item.getYear().getUniversityFiscalYear());
549        keys.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, item.getAccount().getChartOfAccountsCode());
550        keys.put(OLEPropertyConstants.ACCOUNT_NUMBER, item.getAccount().getAccountNumber());
551        keys.put(OLEPropertyConstants.FINANCIAL_OBJECT_CODE, item.getSufficientFundsObjectCode());
552        SufficientFundBalances sfBalance = businessObjectService.findByPrimaryKey(SufficientFundBalances.class, keys);
553        if (sfBalance == null) {
554            Map criteria = new HashMap();
555            criteria.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, item.getAccount().getChartOfAccountsCode());
556            criteria.put(OLEPropertyConstants.ACCOUNT_NUMBER_FINANCIAL_OBJECT_CODE, item.getAccount()
557                    .getAccountNumber());
558
559            Collection sufficientFundRebuilds = businessObjectService.findMatching(SufficientFundRebuild.class,
560                    criteria);
561            if (sufficientFundRebuilds != null && sufficientFundRebuilds.size() > 0) {
562                LOG.debug("hasSufficientFundsOnItem() No balance record and waiting on rebuild, no sufficient funds");
563                return false;
564            }
565            else {
566                sfBalance = new SufficientFundBalances();
567                sfBalance.setAccountActualExpenditureAmt(KualiDecimal.ZERO);
568                sfBalance.setAccountEncumbranceAmount(KualiDecimal.ZERO);
569                sfBalance.setCurrentBudgetBalanceAmount(KualiDecimal.ZERO);
570            }
571        }
572        if (LOG.isDebugEnabled()) {
573            LOG.debug("Current SF Balances: Budget:      " + sfBalance.getCurrentBudgetBalanceAmount());
574            LOG.debug("Current SF Balances: Actuals:     " + sfBalance.getAccountEncumbranceAmount());
575            LOG.debug("Current SF Balances: Encumbrance: " + sfBalance.getAccountActualExpenditureAmt());
576        }
577
578        KualiDecimal balanceAmount = item.getAmount();
579        if (balanceAmount.isLessThan(KualiDecimal.ZERO)) {
580            balanceAmount = balanceAmount.negated();
581        }
582        // balanceAmount = balanceAmount.negated();
583        if (OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(item.getAccount().getAccountSufficientFundsCode())
584                || item.getYear().getBudgetCheckingBalanceTypeCd().equals(item.getBalanceTyp().getCode())) {
585            // We need to change the sign on the amount because the amount in the item is an increase in cash. We only care
586            // about decreases in cash.
587
588            // Also, negating if this is a balance type code of budget checking and the transaction is a budget transaction.
589
590            balanceAmount = balanceAmount.negated();
591        }
592
593        /*
594         * if (balanceAmount.isNegative()) {
595         * LOG.debug("hasSufficientFundsOnItem() balanceAmount is negative, allow transaction to proceed"); return true; }
596         */
597
598        PendingAmounts priorYearPending = new PendingAmounts();
599        // if we're checking the CASH_AT_ACCOUNT type, then we need to consider the prior year pending transactions
600        // if the balance forwards have not been run
601        if ((OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(item.getAccount().getAccountSufficientFundsCode()))
602                && (!item.getYear().isFinancialBeginBalanceLoadInd())) {
603            priorYearPending = getPriorYearSufficientFundsBalanceAmount(item);
604        }
605
606        PendingAmounts pending = getPendingBalanceAmount(item);
607
608        KualiDecimal availableBalance = KualiDecimal.ZERO;
609        KualiDecimal freeBalance = KualiDecimal.ZERO;
610        KualiDecimal expenditures = KualiDecimal.ZERO;
611
612        if (OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(item.getAccount().getAccountSufficientFundsCode())) {
613            // if the beginning balances have not loaded for the transaction FY, pull the remaining balance from last year
614            if (!item.getYear().isFinancialBeginBalanceLoadInd()) {
615                availableBalance = sfBalance.getCurrentBudgetBalanceAmount().add(priorYearPending.budget) // add the remaining
616                        // budget from last year
617                        // (assumed to carry to
618                        // this year's)
619                        .add(pending.actual) // any pending expenses (remember sense is negated)
620                        .subtract(sfBalance.getAccountEncumbranceAmount()) // subtract the encumbrances (not reflected in cash yet)
621                        .subtract(priorYearPending.encumbrance);
622            }
623            else { // balance forwards have been run, don't need to consider prior year remaining budget
624                availableBalance = sfBalance.getCurrentBudgetBalanceAmount().add(pending.actual)
625                        .subtract(sfBalance.getAccountEncumbranceAmount());
626            }
627        }
628        else {
629            availableBalance = sfBalance.getCurrentBudgetBalanceAmount()
630
631                    // pending budget entries
632                    .subtract(sfBalance.getAccountActualExpenditureAmt())
633                            // minus all current and pending actuals and encumbrances
634                    .subtract(pending.actual);
635            freeBalance = sfBalance.getCurrentBudgetBalanceAmount()
636                    .subtract(sfBalance.getAccountActualExpenditureAmt()).subtract(pending.actual);
637        }
638        /*
639         * Map searchCriteria = new HashMap(); searchCriteria.put("chartOfAccountsCode",
640         * item.getAccount().getChartOfAccountsCode()); searchCriteria.put("accountNumber", item.getAccount().getAccountNumber());
641         * searchCriteria.put("financialObjectCode", item.getSufficientFundsObjectCode());
642         * searchCriteria.put("financialDocumentTypeCode", "OLE_PREQ"); // searchCriteria.put("transactionDebitCreditCode", null);
643         * List<GeneralLedgerPendingEntry> pendingList = (List<GeneralLedgerPendingEntry>) SpringContext.getBean(
644         * BusinessObjectService.class).findMatching(GeneralLedgerPendingEntry.class, searchCriteria); for
645         * (GeneralLedgerPendingEntry pendingAmountEntry : pendingList) { if (pendingAmountEntry.getTransactionDebitCreditCode() ==
646         * null) { expenditures = expenditures.add(pendingAmountEntry.getTransactionLedgerEntryAmount()); } }
647         */
648        if (item.getAccount().getAccountSufficientFundsCode().equals(OLEConstants.SF_TYPE_OBJECT)) {
649            expenditures = getSumPaidInvoicesForObject(item.getAccount().getChartOfAccountsCode(), item.getAccount()
650                    .getAccountNumber(), item.getSufficientFundsObjectCode());
651        }
652        else if (item.getAccount().getAccountSufficientFundsCode().equals(OLEConstants.SF_TYPE_ACCOUNT)) {
653            expenditures = getSumPaidInvoicesForAccount(item.getAccount().getChartOfAccountsCode(), item.getAccount()
654                    .getAccountNumber(), item.getSufficientFundsObjectCode());
655        }
656        availableBalance = availableBalance.subtract(expenditures);
657        availableBalance = availableBalance.subtract(item.getAmount());
658        String chart = item.getAccount().getChartOfAccountsCode();
659        String account = item.getAccount().getAccountNumber();
660        String sfCode = item.getAccount().getAccountSufficientFundsCode();
661        Map<String, Object> key = new HashMap<String, Object>();
662        key.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, chart);
663        key.put(OLEPropertyConstants.ACCOUNT_NUMBER, account);
664        OleSufficientFundCheck oleSufficientFundCheck = businessObjectService.findByPrimaryKey(
665                OleSufficientFundCheck.class, key);
666        if (oleSufficientFundCheck != null) {
667            String option = oleSufficientFundCheck.getNotificationOption() != null ? oleSufficientFundCheck
668                    .getNotificationOption() : "";
669            boolean checkBalance;
670
671            if (option.equals(OLEPropertyConstants.BUD_REVIEW)) {
672                checkBalance = checkExpense(item, balanceAmount, availableBalance, sfBalance, freeBalance);
673                return checkBalance;
674            }/*
675              * else if (option.equals(OLEPropertyConstants.NOTIFICATION)) { OleNotifyServiceImpl notification = new
676              * OleNotifyServiceImpl(); List user = new ArrayList(); try { user.add(OLEConstants.NOTIFICATION_APPROVER);
677              * notification.notify(user, "Account number " + account + " exceeds budget amount"); return true; } catch
678              * (WorkflowException exception) { LOG.debug("Exception occured while notification sent to budget reviewer" +
679              * exception); } }
680              */
681
682
683        }
684        else {
685            if (availableBalance.isGreaterThan(balanceAmount)) {
686                return true;
687            }
688        }
689        return false;
690    }
691
692    public boolean checkExpense(SufficientFundsItem item, KualiDecimal balanceAmount, KualiDecimal availableBalance,
693                                SufficientFundBalances sfBalance, KualiDecimal freeBalance) {
694        String chart = item.getAccount().getChartOfAccountsCode();
695        String account = item.getAccount().getAccountNumber();
696        String sfCode = item.getAccount().getAccountSufficientFundsCode();
697
698        Map<String, Object> key = new HashMap<String, Object>();
699        key.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, chart);
700        key.put(OLEPropertyConstants.ACCOUNT_NUMBER, account);
701        OleSufficientFundCheck oleSufficientFundCheck = businessObjectService.findByPrimaryKey(
702                OleSufficientFundCheck.class, key);
703        if (oleSufficientFundCheck != null) {
704            KualiDecimal expenseAvailableAmount = KualiDecimal.ZERO;
705            KualiDecimal expenseAmount = KualiDecimal.ZERO;
706            if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_TYP_PERCENTAGE.equals(oleSufficientFundCheck
707                    .getEncumbExpenseConstraintType())) {
708                expenseAmount = new KualiDecimal(oleSufficientFundCheck.getExpenseAmount());
709                if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_OVER.equals(oleSufficientFundCheck
710                        .getEncumbExpenseMethod())) {
711                    expenseAvailableAmount = availableBalance.add(
712                            sfBalance.getCurrentBudgetBalanceAmount().multiply(expenseAmount)).divide(
713                            new KualiDecimal(100));
714                }
715                else if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_UNDER.equals(oleSufficientFundCheck
716                        .getEncumbExpenseMethod())) {
717                    expenseAvailableAmount = availableBalance.subtract((sfBalance.getCurrentBudgetBalanceAmount()
718                            .multiply(expenseAmount)).divide(new KualiDecimal(100)));
719                }
720                if (balanceAmount.compareTo(expenseAvailableAmount) > 0) {
721                    return false;
722                }
723                return true;
724            }
725            else if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_TYP_CASH.equals(oleSufficientFundCheck
726                    .getEncumbExpenseConstraintType())) {
727                expenseAmount = new KualiDecimal(oleSufficientFundCheck.getExpenseAmount());
728                if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_OVER.equals(oleSufficientFundCheck
729                        .getEncumbExpenseMethod())) {
730                    expenseAvailableAmount = availableBalance.add(expenseAmount);
731                }
732                else if (OLEPropertyConstants.SUFFICIENT_FUND_ENC_UNDER.equals(oleSufficientFundCheck
733                        .getEncumbExpenseMethod())) {
734                    expenseAvailableAmount = availableBalance.subtract(expenseAmount);
735                }
736                if (balanceAmount.compareTo(expenseAvailableAmount) > 0) {
737                    return false;
738                }
739                return true;
740            }
741        }
742        if (availableBalance.isGreaterThan(balanceAmount)) {
743            return true;
744        }
745        return false;
746    }
747
748
749    public KualiDecimal getSumPaidInvoicesForAccount(String chartCode, String accountNo, String objectCode) {
750        KualiDecimal paidInvoices = KualiDecimal.ZERO;
751        List<OlePaymentRequestDocument> payDocList = (List<OlePaymentRequestDocument>) SpringContext.getBean(
752                BusinessObjectService.class).findAll(OlePaymentRequestDocument.class);
753        if (payDocList.size() > 0) {
754            for (OlePaymentRequestDocument olePaymentRequestDocument : payDocList) {
755                Integer payReqId = olePaymentRequestDocument.getPurapDocumentIdentifier();
756                Map docMap = new HashMap();
757                docMap.put(OLEConstants.PUR_AP_IDEN, payReqId);
758                docMap.put(OLEConstants.ITM_TYP_CD_KEY, OLEConstants.ITM_TYP_CD);
759                List<OlePaymentRequestItem> itemList = (List<OlePaymentRequestItem>) SpringContext.getBean(
760                        BusinessObjectService.class).findMatching(OlePaymentRequestItem.class, docMap);
761                HashMap acctMap = new HashMap();
762                for (OlePaymentRequestItem olePaymentRequestItem : itemList) {
763                    Integer itemIdentifier = olePaymentRequestItem.getItemIdentifier();
764                    acctMap.put("itemIdentifier", olePaymentRequestItem.getItemIdentifier());
765                    acctMap.put("chartOfAccountsCode", chartCode);
766                    acctMap.put("accountNumber", accountNo);
767                    List<PaymentRequestAccount> olePaymentRequestAccount = (List<PaymentRequestAccount>) SpringContext
768                            .getBean(BusinessObjectService.class).findMatching(PaymentRequestAccount.class, acctMap);
769                    for (PaymentRequestAccount payReqAcct : olePaymentRequestAccount) {
770                        paidInvoices = paidInvoices.add(payReqAcct.getAmount());
771                    }
772                }
773            }
774        }
775        return paidInvoices;
776    }
777
778
779    public KualiDecimal getSumPaidInvoicesForObject(String chartCode, String accountNo, String objectCode) {
780        KualiDecimal paidInvoices = KualiDecimal.ZERO;
781        List<OlePaymentRequestDocument> payDocList = (List<OlePaymentRequestDocument>) SpringContext.getBean(
782                BusinessObjectService.class).findAll(OlePaymentRequestDocument.class);
783        if (payDocList.size() > 0) {
784            for (OlePaymentRequestDocument olePaymentRequestDocument : payDocList) {
785                Integer payReqId = olePaymentRequestDocument.getPurapDocumentIdentifier();
786                Map docMap = new HashMap();
787                docMap.put(OLEConstants.PUR_AP_IDEN, payReqId);
788                docMap.put(OLEConstants.ITM_TYP_CD_KEY, OLEConstants.ITM_TYP_CD);
789                List<OlePaymentRequestItem> itemList = (List<OlePaymentRequestItem>) SpringContext.getBean(
790                        BusinessObjectService.class).findMatching(OlePaymentRequestItem.class, docMap);
791                HashMap acctMap = new HashMap();
792                for (OlePaymentRequestItem olePaymentRequestItem : itemList) {
793                    acctMap.put("itemIdentifier", olePaymentRequestItem.getItemIdentifier());
794                    acctMap.put("chartOfAccountsCode", chartCode);
795                    acctMap.put("accountNumber", accountNo);
796                    acctMap.put("financialObjectCode", objectCode);
797                    List<PaymentRequestAccount> olePaymentRequestAccount = (List<PaymentRequestAccount>) SpringContext
798                            .getBean(BusinessObjectService.class).findMatching(PaymentRequestAccount.class, acctMap);
799                    for (PaymentRequestAccount payReqAcct : olePaymentRequestAccount) {
800                        paidInvoices = paidInvoices.add(payReqAcct.getAmount());
801                    }
802                }
803            }
804        }
805        return paidInvoices;
806    }
807
808    /**
809     * An inner class to hold summary totals of pending ledger entry amounts
810     */
811    protected class PendingAmounts {
812        public KualiDecimal budget = KualiDecimal.ZERO;
813        public KualiDecimal actual = KualiDecimal.ZERO;
814        public KualiDecimal encumbrance = KualiDecimal.ZERO;
815    }
816
817    /**
818     * Given a sufficient funds item to check, gets the prior year sufficient funds balance to check against
819     *
820     * @param item the sufficient funds item to check against
821     * @return a PendingAmounts record with the pending budget and encumbrance
822     */
823    protected PendingAmounts getPriorYearSufficientFundsBalanceAmount(SufficientFundsItem item) {
824        PendingAmounts amounts = new PendingAmounts();
825
826        // This only gets called for sufficient funds type of Cash at Account (H). The object code in the table for this type is
827        // always
828        // 4 spaces.
829        Map<String, Object> keys = new HashMap<String, Object>();
830        keys.put(OLEPropertyConstants.UNIVERSITY_FISCAL_YEAR, Integer.valueOf(item.getYear().getUniversityFiscalYear().intValue() - 1));
831        keys.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, item.getAccount().getChartOfAccountsCode());
832        keys.put(OLEPropertyConstants.ACCOUNT_NUMBER, item.getAccount().getAccountNumber());
833        keys.put(OLEPropertyConstants.FINANCIAL_OBJECT_CODE, "    ");
834        SufficientFundBalances bal = businessObjectService.findByPrimaryKey(SufficientFundBalances.class, keys);
835
836        if (bal != null) {
837            amounts.budget = bal.getCurrentBudgetBalanceAmount();
838            amounts.encumbrance = bal.getAccountEncumbranceAmount();
839        }
840
841        if ( LOG.isDebugEnabled() ) {
842            LOG.debug("getPriorYearSufficientFundsBalanceAmount() budget      " + amounts.budget);
843            LOG.debug("getPriorYearSufficientFundsBalanceAmount() encumbrance " + amounts.encumbrance);
844        }
845        return amounts;
846    }
847
848    /**
849     * Totals the amounts of actual, encumbrance, and budget amounts from related pending entries
850     *
851     * @param item a sufficient funds item to find pending amounts for
852     * @return the totals encapsulated in a PendingAmounts object
853     */
854    @SuppressWarnings("unchecked")
855    protected PendingAmounts getPendingBalanceAmount(SufficientFundsItem item) {
856        LOG.debug("getPendingBalanceAmount() started");
857
858        Integer fiscalYear = item.getYear().getUniversityFiscalYear();
859        String chart = item.getAccount().getChartOfAccountsCode();
860        String account = item.getAccount().getAccountNumber();
861        String sfCode = item.getAccount().getAccountSufficientFundsCode();
862
863        PendingAmounts amounts = new PendingAmounts();
864
865        if (OLEConstants.SF_TYPE_CASH_AT_ACCOUNT.equals(sfCode)) {
866            // Cash checking
867            List years = new ArrayList();
868            years.add(item.getYear().getUniversityFiscalYear());
869
870            // If the beginning balance isn't loaded, we need to include cash from
871            // the previous fiscal year
872            if (!item.getYear().isFinancialBeginBalanceLoadInd()) {
873                years.add(item.getYear().getUniversityFiscalYear() - 1);
874            }
875
876            // Calculate the pending actual amount
877            // Get Cash (debit amount - credit amount)
878            amounts.actual = generalLedgerPendingEntryService.getCashSummary(years, chart, account, true);
879            amounts.actual = amounts.actual.subtract(generalLedgerPendingEntryService.getCashSummary(years, chart, account, false));
880
881            // Get Payables (credit amount - debit amount)
882            amounts.actual = amounts.actual.add(generalLedgerPendingEntryService.getActualSummary(years, chart, account, true));
883            amounts.actual = amounts.actual.subtract(generalLedgerPendingEntryService.getActualSummary(years, chart, account, false));
884        }
885        else {
886            // Non-Cash checking
887
888            // Get expenditure (debit - credit)
889            amounts.actual = generalLedgerPendingEntryService.getExpenseSummary(fiscalYear, chart, account, item.getSufficientFundsObjectCode(), true, item.getDocumentTypeCode().startsWith("YE"));
890            amounts.actual = amounts.actual.subtract(generalLedgerPendingEntryService.getExpenseSummary(fiscalYear, chart, account, item.getSufficientFundsObjectCode(), false, item.getDocumentTypeCode().startsWith("YE")));
891
892            // Get budget
893            amounts.budget = generalLedgerPendingEntryService.getBudgetSummary(fiscalYear, chart, account, item.getSufficientFundsObjectCode(), item.getDocumentTypeCode().startsWith("YE"));
894
895            // Get encumbrance (debit - credit)
896            amounts.encumbrance = generalLedgerPendingEntryService.getEncumbranceSummary(fiscalYear, chart, account, item.getSufficientFundsObjectCode(), true, item.getDocumentTypeCode().startsWith("YE"));
897            amounts.encumbrance = amounts.encumbrance.subtract(generalLedgerPendingEntryService.getEncumbranceSummary(fiscalYear, chart, account, item.getSufficientFundsObjectCode(), false, item.getDocumentTypeCode().startsWith("YE")));
898        }
899
900        if ( LOG.isDebugEnabled() ) {
901            LOG.debug("getPendingBalanceAmount() actual      " + amounts.actual);
902            LOG.debug("getPendingBalanceAmount() budget      " + amounts.budget);
903            LOG.debug("getPendingBalanceAmount() encumbrance " + amounts.encumbrance);
904        }
905        return amounts;
906    }
907
908    /**
909     * Purge the sufficient funds balance table by year/chart
910     *
911     * @param chart the chart of sufficient fund balances to purge
912     * @param year the fiscal year of sufficient fund balances to purge
913     */
914    @Override
915    public void purgeYearByChart(String chart, int year) {
916        sufficientFundsDao.purgeYearByChart(chart, year);
917    }
918
919    public void setAccountService(AccountService accountService) {
920        this.accountService = accountService;
921    }
922
923    public void setGeneralLedgerPendingEntryService(GeneralLedgerPendingEntryService generalLedgerPendingEntryService) {
924        this.generalLedgerPendingEntryService = generalLedgerPendingEntryService;
925    }
926
927    public void setConfigurationService(ConfigurationService kualiConfigurationService) {
928        this.kualiConfigurationService = kualiConfigurationService;
929    }
930
931    public void setObjectLevelService(ObjectLevelService objectLevelService) {
932        this.objectLevelService = objectLevelService;
933    }
934
935    public void setOptionsService(OptionsService optionsService) {
936        this.optionsService = optionsService;
937    }
938
939    public void setSufficientFundBalancesDao(SufficientFundBalancesDao sufficientFundBalancesDao) {
940        this.sufficientFundBalancesDao = sufficientFundBalancesDao;
941    }
942
943    public void setSufficientFundsDao(SufficientFundsDao sufficientFundsDao) {
944        this.sufficientFundsDao = sufficientFundsDao;
945    }
946
947    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
948        this.businessObjectService = businessObjectService;
949    }
950}