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.module.bc.document.service.impl;
017    
018    import java.util.ArrayList;
019    import java.util.Arrays;
020    import java.util.Calendar;
021    import java.util.Collection;
022    import java.util.Collections;
023    import java.util.HashMap;
024    import java.util.List;
025    import java.util.ListIterator;
026    import java.util.Map;
027    
028    import org.apache.commons.lang.StringUtils;
029    import org.kuali.kfs.coa.businessobject.A21SubAccount;
030    import org.kuali.kfs.coa.businessobject.Account;
031    import org.kuali.kfs.coa.businessobject.SubAccount;
032    import org.kuali.kfs.coa.businessobject.SubFundGroup;
033    import org.kuali.kfs.coa.service.OrganizationService;
034    import org.kuali.kfs.fp.service.FiscalYearFunctionControlService;
035    import org.kuali.kfs.integration.ld.LaborLedgerBenefitsCalculation;
036    import org.kuali.kfs.module.bc.BCConstants;
037    import org.kuali.kfs.module.bc.BCKeyConstants;
038    import org.kuali.kfs.module.bc.BCPropertyConstants;
039    import org.kuali.kfs.module.bc.BCConstants.MonthSpreadDeleteType;
040    import org.kuali.kfs.module.bc.businessobject.BudgetConstructionAccountOrganizationHierarchy;
041    import org.kuali.kfs.module.bc.businessobject.BudgetConstructionAccountReports;
042    import org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader;
043    import org.kuali.kfs.module.bc.businessobject.BudgetConstructionMonthly;
044    import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding;
045    import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionGeneralLedger;
046    import org.kuali.kfs.module.bc.businessobject.SalarySettingExpansion;
047    import org.kuali.kfs.module.bc.document.BudgetConstructionDocument;
048    import org.kuali.kfs.module.bc.document.dataaccess.BudgetConstructionDao;
049    import org.kuali.kfs.module.bc.document.service.BenefitsCalculationService;
050    import org.kuali.kfs.module.bc.document.service.BudgetDocumentService;
051    import org.kuali.kfs.module.bc.document.service.BudgetParameterService;
052    import org.kuali.kfs.module.bc.document.validation.event.DeleteMonthlySpreadEvent;
053    import org.kuali.kfs.module.bc.document.validation.impl.BudgetConstructionRuleUtil;
054    import org.kuali.kfs.module.bc.document.web.struts.BudgetConstructionForm;
055    import org.kuali.kfs.module.bc.document.web.struts.MonthlyBudgetForm;
056    import org.kuali.kfs.module.bc.util.BudgetParameterFinder;
057    import org.kuali.kfs.sys.KFSConstants;
058    import org.kuali.kfs.sys.KFSPropertyConstants;
059    import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
060    import org.kuali.kfs.sys.service.NonTransactional;
061    import org.kuali.kfs.sys.service.OptionsService;
062    import org.kuali.rice.kew.exception.WorkflowException;
063    import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
064    import org.kuali.rice.kim.bo.Person;
065    import org.kuali.rice.kns.dao.DocumentDao;
066    import org.kuali.rice.kns.document.Document;
067    import org.kuali.rice.kns.exception.ValidationException;
068    import org.kuali.rice.kns.rule.event.KualiDocumentEvent;
069    import org.kuali.rice.kns.rule.event.SaveDocumentEvent;
070    import org.kuali.rice.kns.service.BusinessObjectService;
071    import org.kuali.rice.kns.service.DocumentService;
072    import org.kuali.rice.kns.service.KualiModuleService;
073    import org.kuali.rice.kns.service.ParameterService;
074    import org.kuali.rice.kns.service.PersistenceService;
075    import org.kuali.rice.kns.util.GlobalVariables;
076    import org.kuali.rice.kns.util.KualiDecimal;
077    import org.kuali.rice.kns.util.KualiInteger;
078    import org.kuali.rice.kns.util.MessageList;
079    import org.kuali.rice.kns.util.ObjectUtils;
080    import org.kuali.rice.kns.workflow.service.WorkflowDocumentService;
081    import org.springframework.dao.OptimisticLockingFailureException;
082    import org.springframework.transaction.annotation.Transactional;
083    
084    /**
085     * Implements the BudgetDocumentService interface. Methods here operate on objects associated with the Budget Construction document
086     * such as BudgetConstructionHeader
087     */
088    public class BudgetDocumentServiceImpl implements BudgetDocumentService {
089        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BudgetDocumentServiceImpl.class);
090    
091        private BudgetConstructionDao budgetConstructionDao;
092        private DocumentDao documentDao;
093        private DocumentService documentService;
094        private WorkflowDocumentService workflowDocumentService;
095        private BenefitsCalculationService benefitsCalculationService;
096        private BusinessObjectService businessObjectService;
097        private KualiModuleService kualiModuleService;
098        private ParameterService parameterService;
099        private BudgetParameterService budgetParameterService;
100        private FiscalYearFunctionControlService fiscalYearFunctionControlService;
101        private OptionsService optionsService;
102        private PersistenceService persistenceService;
103        private OrganizationService organizationService;
104    
105        /**
106         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#getByCandidateKey(java.lang.String, java.lang.String,
107         *      java.lang.String, java.lang.Integer)
108         */
109        @Transactional
110        public BudgetConstructionHeader getByCandidateKey(String chartOfAccountsCode, String accountNumber, String subAccountNumber, Integer fiscalYear) {
111            return budgetConstructionDao.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear);
112        }
113    
114        /**
115         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#saveDocument(org.kuali.rice.kns.document.Document)
116         *      similar to DocumentService.saveDocument()
117         */
118        @Transactional
119        public Document saveDocument(BudgetConstructionDocument budgetConstructionDocument) throws WorkflowException, ValidationException {
120    
121            // user did explicit save here so mark as touched
122            budgetConstructionDocument.getDocumentHeader().setFinancialDocumentStatusCode(KFSConstants.DocumentStatusCodes.ENROUTE);
123    
124            this.saveDocumentNoWorkflow(budgetConstructionDocument);
125    
126            GlobalVariables.getUserSession().setWorkflowDocument(budgetConstructionDocument.getDocumentHeader().getWorkflowDocument());
127    
128            // save any messages up to this point and put them back in after logDocumentAction()
129            // this is a hack to get around the problem where messageLists gets cleared
130            // that is PostProcessorServiceImpl.doActionTaken(ActionTakenEventDTO), establishGlobalVariables(), which does
131            // GlobalVariables.clear()
132            // not sure why this doesn't trash the GlobalVariables.getMessageMap()
133            MessageList messagesSoFar = GlobalVariables.getMessageList();
134    
135            budgetConstructionDocument.getDocumentHeader().getWorkflowDocument().logDocumentAction("Document Updated");
136    
137            // putting messages back in
138            GlobalVariables.getMessageList().addAll(messagesSoFar);
139    
140            return budgetConstructionDocument;
141        }
142    
143        /**
144         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#saveDocumentNoWorkflow(org.kuali.rice.kns.document.Document)
145         */
146        @Transactional
147        public Document saveDocumentNoWorkflow(BudgetConstructionDocument bcDoc) throws ValidationException {
148            return this.saveDocumentNoWorkFlow(bcDoc, MonthSpreadDeleteType.NONE, true);
149        }
150    
151        /**
152         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#saveDocumentNoWorkFlow(org.kuali.kfs.module.bc.document.BudgetConstructionDocument,
153         *      org.kuali.kfs.module.bc.BCConstants.MonthSpreadDeleteType, boolean)
154         */
155        @Transactional
156        public Document saveDocumentNoWorkFlow(BudgetConstructionDocument bcDoc, MonthSpreadDeleteType monthSpreadDeleteType, boolean doMonthRICheck) throws ValidationException {
157    
158            checkForNulls(bcDoc);
159    
160            bcDoc.prepareForSave();
161    
162            // validate and save the local objects not workflow objects
163            // this eventually calls BudgetConstructionRules.processSaveDocument() which overrides the method in DocumentRuleBase
164            if (doMonthRICheck) {
165                validateAndPersistDocument(bcDoc, new SaveDocumentEvent(bcDoc));
166            }
167            else {
168                validateAndPersistDocument(bcDoc, new DeleteMonthlySpreadEvent(bcDoc, monthSpreadDeleteType));
169            }
170            return bcDoc;
171        }
172    
173        @Transactional
174        public void saveMonthlyBudget(MonthlyBudgetForm monthlyBudgetForm, BudgetConstructionMonthly budgetConstructionMonthly) {
175    
176            BudgetConstructionForm budgetConstructionForm = (BudgetConstructionForm) GlobalVariables.getUserSession().retrieveObject(monthlyBudgetForm.getReturnFormKey());
177            BudgetConstructionDocument bcDoc = budgetConstructionForm.getBudgetConstructionDocument();
178    
179            // handle any override situation
180            // getting here assumes that the line is not a salary detail line and that overrides are allowed
181            KualiInteger changeAmount = KualiInteger.ZERO;
182            KualiInteger monthTotalAmount = budgetConstructionMonthly.getFinancialDocumentMonthTotalLineAmount();
183            KualiInteger pbglRequestAmount = budgetConstructionMonthly.getPendingBudgetConstructionGeneralLedger().getAccountLineAnnualBalanceAmount();
184            if (!monthTotalAmount.equals(pbglRequestAmount)) {
185    
186                changeAmount = monthTotalAmount.subtract(pbglRequestAmount);
187    
188                // change the pbgl request amount store it and sync the object in session
189                budgetConstructionMonthly.refreshReferenceObject("pendingBudgetConstructionGeneralLedger");
190    
191                PendingBudgetConstructionGeneralLedger sourceRow = (PendingBudgetConstructionGeneralLedger) businessObjectService.retrieve(budgetConstructionMonthly.getPendingBudgetConstructionGeneralLedger());
192                sourceRow.setAccountLineAnnualBalanceAmount(monthTotalAmount);
193                businessObjectService.save(sourceRow);
194    
195                this.addOrUpdatePBGLRow(bcDoc, sourceRow);
196                bcDoc.setExpenditureAccountLineAnnualBalanceAmountTotal(bcDoc.getExpenditureAccountLineAnnualBalanceAmountTotal().add(changeAmount));
197    
198            }
199    
200            businessObjectService.save(budgetConstructionMonthly);
201            this.callForBenefitsCalcIfNeeded(bcDoc, budgetConstructionMonthly, changeAmount);
202        }
203    
204        /**
205         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#callForBenefitsCalcIfNeeded(org.kuali.kfs.module.bc.document.BudgetConstructionDocument,
206         *      org.kuali.kfs.module.bc.businessobject.BudgetConstructionMonthly, org.kuali.rice.kns.util.KualiInteger)
207         */
208        @Transactional
209        public void callForBenefitsCalcIfNeeded(BudgetConstructionDocument bcDoc, BudgetConstructionMonthly budgetConstructionMonthly, KualiInteger pbglChangeAmount) {
210    
211            if (!benefitsCalculationService.isBenefitsCalculationDisabled()) {
212                if (budgetConstructionMonthly.getPendingBudgetConstructionGeneralLedger().getPositionObjectBenefit() != null && !budgetConstructionMonthly.getPendingBudgetConstructionGeneralLedger().getPositionObjectBenefit().isEmpty()) {
213    
214                    bcDoc.setMonthlyBenefitsCalcNeeded(true);
215                    if (pbglChangeAmount.isNonZero()) {
216                        bcDoc.setBenefitsCalcNeeded(true);
217                    }
218                }
219            }
220        }
221    
222        /**
223         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#calculateBenefitsIfNeeded(org.kuali.kfs.module.bc.document.BudgetConstructionDocument)
224         */
225        @Transactional
226        public void calculateBenefitsIfNeeded(BudgetConstructionDocument bcDoc) {
227    
228            if (bcDoc.isBenefitsCalcNeeded() || bcDoc.isMonthlyBenefitsCalcNeeded()) {
229    
230                if (bcDoc.isBenefitsCalcNeeded()) {
231                    this.calculateAnnualBenefits(bcDoc);
232                }
233    
234                if (bcDoc.isMonthlyBenefitsCalcNeeded()) {
235                    this.calculateMonthlyBenefits(bcDoc);
236                }
237    
238                // reload from the DB and refresh refs
239                this.reloadBenefitsLines(bcDoc);
240            }
241        }
242    
243        /**
244         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#calculateBenefits(org.kuali.kfs.module.bc.document.BudgetConstructionDocument)
245         */
246        @Transactional
247        public void calculateBenefits(BudgetConstructionDocument bcDoc) {
248    
249            this.calculateAnnualBenefits(bcDoc);
250            this.calculateMonthlyBenefits(bcDoc);
251    
252            // reload from the DB and refresh refs
253            this.reloadBenefitsLines(bcDoc);
254        }
255    
256        /**
257         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#calculateAnnualBenefits(org.kuali.kfs.module.bc.document.BudgetConstructionDocument)
258         */
259        @Transactional
260        protected void calculateAnnualBenefits(BudgetConstructionDocument bcDoc) {
261    
262            // allow benefits calculation if document's account is not salary setting only lines
263            bcDoc.setBenefitsCalcNeeded(false);
264            if (!bcDoc.isSalarySettingOnly()) {
265    
266                // pbgl lines are saved at this point, calc benefits
267                benefitsCalculationService.calculateAnnualBudgetConstructionGeneralLedgerBenefits(bcDoc.getDocumentNumber(), bcDoc.getUniversityFiscalYear(), bcDoc.getChartOfAccountsCode(), bcDoc.getAccountNumber(), bcDoc.getSubAccountNumber());
268    
269                // write global message on calc success
270                GlobalVariables.getMessageList().add(BCKeyConstants.MESSAGE_BENEFITS_CALCULATED);
271            }
272        }
273    
274        /**
275         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#calculateMonthlyBenefits(org.kuali.kfs.module.bc.document.BudgetConstructionDocument)
276         */
277        @Transactional
278        protected void calculateMonthlyBenefits(BudgetConstructionDocument bcDoc) {
279    
280            // allow benefits calculation if document's account is not salary setting only lines
281            bcDoc.setMonthlyBenefitsCalcNeeded(false);
282            if (!bcDoc.isSalarySettingOnly()) {
283    
284                // pbgl lines are saved at this point, calc benefits
285                benefitsCalculationService.calculateMonthlyBudgetConstructionGeneralLedgerBenefits(bcDoc.getDocumentNumber(), bcDoc.getUniversityFiscalYear(), bcDoc.getChartOfAccountsCode(), bcDoc.getAccountNumber(), bcDoc.getSubAccountNumber());
286    
287                // write global message on calc success
288                GlobalVariables.getMessageList().add(BCKeyConstants.MESSAGE_BENEFITS_MONTHLY_CALCULATED);
289            }
290        }
291    
292        /**
293         * Does sanity checks for null document object and null documentNumber
294         * 
295         * @param document
296         */
297        @NonTransactional
298        protected void checkForNulls(Document document) {
299            if (document == null) {
300                throw new IllegalArgumentException("invalid (null) document");
301            }
302            else if (document.getDocumentNumber() == null) {
303                throw new IllegalStateException("invalid (null) documentHeaderId");
304            }
305        }
306    
307        /**
308         * Runs validation and persists a document to the database.
309         * 
310         * @param document
311         * @param event
312         * @throws WorkflowException
313         * @throws ValidationException
314         */
315        @Transactional
316        public void validateAndPersistDocument(Document document, KualiDocumentEvent event) throws ValidationException {
317            if (document == null) {
318                LOG.error("document passed to validateAndPersist was null");
319                throw new IllegalArgumentException("invalid (null) document");
320            }
321            LOG.info("validating and preparing to persist document " + document.getDocumentNumber());
322    
323            // runs business rules event.validate() and creates rule instance and runs rule method recursively
324            document.validateBusinessRules(event);
325    
326            // calls overriden method for specific document for anything that needs to happen before the save
327            // currently nothing for BC document
328            document.prepareForSave(event);
329    
330            // save the document to the database
331            try {
332                LOG.info("storing document " + document.getDocumentNumber());
333                documentDao.save(document);
334            }
335            catch (OptimisticLockingFailureException e) {
336                LOG.error("exception encountered on store of document " + e.getMessage());
337                throw e;
338            }
339    
340            document.postProcessSave(event);
341    
342    
343        }
344    
345        /**
346         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#validateDocument(org.kuali.rice.kns.document.Document)
347         */
348        @Transactional
349        public void validateDocument(Document document) throws ValidationException {
350            if (document == null) {
351                LOG.error("document passed to validateDocument was null");
352                throw new IllegalArgumentException("invalid (null) document");
353            }
354            LOG.info("validating document " + document.getDocumentNumber());
355            document.validateBusinessRules(new SaveDocumentEvent(document));
356    
357        }
358    
359        /**
360         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#getPBGLSalarySettingRows(org.kuali.kfs.module.bc.document.BudgetConstructionDocument)
361         */
362        @Transactional
363        public List<PendingBudgetConstructionGeneralLedger> getPBGLSalarySettingRows(BudgetConstructionDocument bcDocument) {
364    
365            List<String> ssObjects = budgetConstructionDao.getDetailSalarySettingLaborObjects(bcDocument.getUniversityFiscalYear(), bcDocument.getChartOfAccountsCode());
366            ssObjects.add(KFSConstants.BudgetConstructionConstants.OBJECT_CODE_2PLG);
367            List<PendingBudgetConstructionGeneralLedger> pbglSalarySettingRows = budgetConstructionDao.getPBGLSalarySettingRows(bcDocument.getDocumentNumber(), ssObjects);
368    
369            return pbglSalarySettingRows;
370        }
371    
372        /**
373         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#addOrUpdatePBGLRow(org.kuali.kfs.module.bc.document.BudgetConstructionDocument,
374         *      org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionGeneralLedger)
375         */
376        @NonTransactional
377        public BudgetConstructionDocument addOrUpdatePBGLRow(BudgetConstructionDocument bcDoc, PendingBudgetConstructionGeneralLedger sourceRow) {
378    
379            List<PendingBudgetConstructionGeneralLedger> expenditureRows = bcDoc.getPendingBudgetConstructionGeneralLedgerExpenditureLines();
380    
381            // add or update salary setting row to set in memory - this assumes at least one row in the set
382            // we can't even do salary setting without at least one salary detail row
383            int index = 0;
384            boolean insertNeeded = true;
385            for (PendingBudgetConstructionGeneralLedger expRow : expenditureRows) {
386                String expRowKey = expRow.getFinancialObjectCode() + expRow.getFinancialSubObjectCode();
387                String sourceRowKey = sourceRow.getFinancialObjectCode() + sourceRow.getFinancialSubObjectCode();
388                if (expRowKey.compareToIgnoreCase(sourceRowKey) == 0) {
389                    // update
390                    insertNeeded = false;
391                    expRow.setAccountLineAnnualBalanceAmount(sourceRow.getAccountLineAnnualBalanceAmount());
392                    expRow.setPersistedAccountLineAnnualBalanceAmount(sourceRow.getAccountLineAnnualBalanceAmount());
393                    expRow.setVersionNumber(sourceRow.getVersionNumber());
394                    break;
395                }
396                else {
397                    if (expRowKey.compareToIgnoreCase(sourceRowKey) > 0) {
398                        // insert here - drop out
399                        break;
400                    }
401                }
402                index++;
403            }
404            if (insertNeeded) {
405                // insert the row
406                sourceRow.setPersistedAccountLineAnnualBalanceAmount(sourceRow.getAccountLineAnnualBalanceAmount());
407                expenditureRows.add(index, sourceRow);
408            }
409    
410            return bcDoc;
411        }
412    
413        /**
414         * Reloads benefits target accounting lines. Usually called right after an annual benefits calculation and the display needs
415         * updated with a fresh copy from the database. All old row versions are removed and database row versions are inserted in the
416         * list in the correct order.
417         * 
418         * @param bcDoc
419         */
420        @Transactional
421        protected void reloadBenefitsLines(BudgetConstructionDocument bcDoc) {
422    
423            // get list of potential fringe objects to use as an in query param
424            Map<String, Object> fieldValues = new HashMap<String, Object>();
425            fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, bcDoc.getUniversityFiscalYear());
426            fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, bcDoc.getChartOfAccountsCode());
427    
428            List<LaborLedgerBenefitsCalculation> benefitsCalculation = kualiModuleService.getResponsibleModuleService(LaborLedgerBenefitsCalculation.class).getExternalizableBusinessObjectsList(LaborLedgerBenefitsCalculation.class, fieldValues);
429    
430            List<String> fringeObjects = new ArrayList<String>();
431            for (LaborLedgerBenefitsCalculation element : benefitsCalculation) {
432                fringeObjects.add(element.getPositionFringeBenefitObjectCode());
433            }
434    
435            List<PendingBudgetConstructionGeneralLedger> dbPBGLFringeLines = budgetConstructionDao.getDocumentPBGLFringeLines(bcDoc.getDocumentNumber(), fringeObjects);
436            List<PendingBudgetConstructionGeneralLedger> docPBGLExpLines = bcDoc.getPendingBudgetConstructionGeneralLedgerExpenditureLines();
437    
438            // holds the request sums of removed, added records and used to adjust the document expenditure request total
439            KualiInteger docRequestTotals = KualiInteger.ZERO;
440            KualiInteger dbRequestTotals = KualiInteger.ZERO;
441    
442            // remove the current set of fringe lines
443            ListIterator docLines = docPBGLExpLines.listIterator();
444            while (docLines.hasNext()) {
445                PendingBudgetConstructionGeneralLedger docLine = (PendingBudgetConstructionGeneralLedger) docLines.next();
446                if (fringeObjects.contains(docLine.getFinancialObjectCode())) {
447                    docRequestTotals = docRequestTotals.add(docLine.getAccountLineAnnualBalanceAmount());
448                    docLines.remove();
449                }
450            }
451    
452            // add the dbset of fringe lines, if any
453            if (dbPBGLFringeLines != null && !dbPBGLFringeLines.isEmpty()) {
454    
455                if (docPBGLExpLines == null || docPBGLExpLines.isEmpty()) {
456                    docPBGLExpLines.addAll(dbPBGLFringeLines);
457                }
458                else {
459                    ListIterator dbLines = dbPBGLFringeLines.listIterator();
460                    docLines = docPBGLExpLines.listIterator();
461                    PendingBudgetConstructionGeneralLedger dbLine = (PendingBudgetConstructionGeneralLedger) dbLines.next();
462                    PendingBudgetConstructionGeneralLedger docLine = (PendingBudgetConstructionGeneralLedger) docLines.next();
463                    boolean dbDone = false;
464                    boolean docDone = false;
465                    while (!dbDone) {
466                        if (docDone || docLine.getFinancialObjectCode().compareToIgnoreCase(dbLine.getFinancialObjectCode()) > 0) {
467                            if (!docDone) {
468                                docLine = (PendingBudgetConstructionGeneralLedger) docLines.previous();
469                            }
470                            dbRequestTotals = dbRequestTotals.add(dbLine.getAccountLineAnnualBalanceAmount());
471                            dbLine.setPersistedAccountLineAnnualBalanceAmount(dbLine.getAccountLineAnnualBalanceAmount());
472                            this.populatePBGLLine(dbLine);
473                            docLines.add(dbLine);
474                            if (!docDone) {
475                                docLine = (PendingBudgetConstructionGeneralLedger) docLines.next();
476                            }
477                            if (dbLines.hasNext()) {
478                                dbLine = (PendingBudgetConstructionGeneralLedger) dbLines.next();
479                            }
480                            else {
481                                dbDone = true;
482                            }
483                        }
484                        else {
485                            if (docLines.hasNext()) {
486                                docLine = (PendingBudgetConstructionGeneralLedger) docLines.next();
487                            }
488                            else {
489                                docDone = true;
490                            }
491                        }
492                    }
493                }
494            }
495    
496            // adjust the request total for the removed and added recs
497            bcDoc.setExpenditureAccountLineAnnualBalanceAmountTotal(bcDoc.getExpenditureAccountLineAnnualBalanceAmountTotal().add(dbRequestTotals.subtract(docRequestTotals)));
498    
499        }
500    
501        /**
502         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#populatePBGLLine(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionGeneralLedger)
503         */
504        @Transactional
505        public void populatePBGLLine(PendingBudgetConstructionGeneralLedger line) {
506    
507            final List REFRESH_FIELDS;
508            if (StringUtils.isNotBlank(line.getFinancialSubObjectCode())) {
509                REFRESH_FIELDS = Collections.unmodifiableList(Arrays.asList(new String[] { KFSPropertyConstants.FINANCIAL_OBJECT, KFSPropertyConstants.FINANCIAL_SUB_OBJECT, BCPropertyConstants.BUDGET_CONSTRUCTION_MONTHLY }));
510            }
511            else {
512                REFRESH_FIELDS = Collections.unmodifiableList(Arrays.asList(new String[] { KFSPropertyConstants.FINANCIAL_OBJECT, BCPropertyConstants.BUDGET_CONSTRUCTION_MONTHLY }));
513            }
514            persistenceService.retrieveReferenceObjects(line, REFRESH_FIELDS);
515    
516        }
517    
518        /**
519         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#getPendingBudgetConstructionAppointmentFundingRequestSum(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionGeneralLedger)
520         */
521        @Transactional
522        public KualiInteger getPendingBudgetConstructionAppointmentFundingRequestSum(PendingBudgetConstructionGeneralLedger salaryDetailLine) {
523            return budgetConstructionDao.getPendingBudgetConstructionAppointmentFundingRequestSum(salaryDetailLine);
524        }
525    
526        /**
527         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#isBudgetableDocument(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader)
528         */
529        @NonTransactional
530        public boolean isBudgetableDocument(BudgetConstructionHeader bcHeader) {
531            if (bcHeader == null) {
532                return false;
533            }
534    
535            Integer budgetYear = bcHeader.getUniversityFiscalYear();
536            Account account = bcHeader.getAccount();
537            boolean isBudgetableAccount = this.isBudgetableAccount(budgetYear, account, true);
538    
539            if (isBudgetableAccount) {
540                SubAccount subAccount = bcHeader.getSubAccount();
541                String subAccountNumber = bcHeader.getSubAccountNumber();
542    
543                return this.isBudgetableSubAccount(subAccount, subAccountNumber);
544            }
545    
546            return false;
547        }
548    
549        @NonTransactional
550        public boolean isBudgetableDocumentNoWagesCheck(BudgetConstructionHeader bcHeader) {
551            if (bcHeader == null) {
552                return false;
553            }
554    
555            Integer budgetYear = bcHeader.getUniversityFiscalYear();
556            Account account = bcHeader.getAccount();
557            boolean isBudgetableAccount = this.isBudgetableAccount(budgetYear, account, false);
558    
559            if (isBudgetableAccount) {
560                SubAccount subAccount = bcHeader.getSubAccount();
561                String subAccountNumber = bcHeader.getSubAccountNumber();
562    
563                return this.isBudgetableSubAccount(subAccount, subAccountNumber);
564            }
565    
566            return false;
567        }
568    
569        /**
570         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#isBudgetableDocument(org.kuali.kfs.module.bc.document.BudgetConstructionDocument)
571         */
572        @NonTransactional
573        public boolean isBudgetableDocument(BudgetConstructionDocument document) {
574            if (document == null) {
575                return false;
576            }
577    
578            Integer budgetYear = document.getUniversityFiscalYear();
579            Account account = document.getAccount();
580            boolean isBudgetableAccount = this.isBudgetableAccount(budgetYear, account, true);
581    
582            if (isBudgetableAccount) {
583                SubAccount subAccount = document.getSubAccount();
584                String subAccountNumber = document.getSubAccountNumber();
585    
586                return this.isBudgetableSubAccount(subAccount, subAccountNumber);
587            }
588    
589            return false;
590        }
591    
592        /**
593         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#isBudgetableDocumentNoWagesCheck(org.kuali.kfs.module.bc.document.BudgetConstructionDocument)
594         */
595        @NonTransactional
596        public boolean isBudgetableDocumentNoWagesCheck(BudgetConstructionDocument document) {
597            if (document == null) {
598                return false;
599            }
600    
601            Integer budgetYear = document.getUniversityFiscalYear();
602            Account account = document.getAccount();
603            boolean isBudgetableAccount = this.isBudgetableAccount(budgetYear, account, false);
604    
605            if (isBudgetableAccount) {
606                SubAccount subAccount = document.getSubAccount();
607                String subAccountNumber = document.getSubAccountNumber();
608    
609                return this.isBudgetableSubAccount(subAccount, subAccountNumber);
610            }
611    
612            return false;
613        }
614    
615        /**
616         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#isAssociatedWithBudgetableDocument(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
617         */
618        @NonTransactional
619        public boolean isAssociatedWithBudgetableDocument(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
620            BudgetConstructionHeader bcHeader = this.getBudgetConstructionHeader(appointmentFunding);
621            return this.isBudgetableDocument(bcHeader);
622        }
623    
624        /**
625         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#isBudgetableAccount(java.lang.Integer,
626         *      org.kuali.kfs.coa.businessobject.Account)
627         */
628        @NonTransactional
629        public boolean isBudgetableAccount(Integer budgetYear, Account account, boolean isWagesCheck) {
630            if (budgetYear == null || account == null) {
631                return false;
632            }
633    
634            // account cannot be closed.
635            if (!account.isActive()) {
636                return false;
637            }
638    
639            // account cannot be expired before beginning of 6th accounting period, 2 years before budget construction fiscal year.
640            Calendar expDate = BudgetConstructionRuleUtil.getNoBudgetAllowedExpireDate(budgetYear);
641            if (account.isExpired(expDate)) {
642                return false;
643            }
644    
645            // account cannot be a cash control account
646            if (StringUtils.equals(account.getBudgetRecordingLevelCode(), BCConstants.BUDGET_RECORDING_LEVEL_N)) {
647                return false;
648            }
649    
650            // this check is needed for salary setting
651            if (isWagesCheck) {
652    
653                // account must be flagged as wages allowed
654                SubFundGroup subFundGroup = account.getSubFundGroup();
655                if (subFundGroup == null || !subFundGroup.isSubFundGroupWagesIndicator()) {
656                    return false;
657                }
658            }
659    
660            return true;
661        }
662    
663        /**
664         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#isBudgetableSubAccount(org.kuali.kfs.coa.businessobject.SubAccount,
665         *      java.lang.String)
666         */
667        @NonTransactional
668        public boolean isBudgetableSubAccount(SubAccount subAccount, String subAccountNumber) {
669            if (StringUtils.isNotEmpty(subAccountNumber) || StringUtils.equals(subAccountNumber, KFSConstants.getDashSubAccountNumber())) {
670                return true;
671            }
672    
673            // sub account must exist and be active.
674            if (subAccount == null || !subAccount.isActive()) {
675                return false;
676            }
677    
678            // sub account must not be flagged cost share
679            A21SubAccount a21SubAccount = subAccount.getA21SubAccount();
680            if (ObjectUtils.isNotNull(a21SubAccount) && StringUtils.equals(a21SubAccount.getSubAccountTypeCode(), KFSConstants.SubAccountType.COST_SHARE)) {
681                return false;
682            }
683    
684            return true;
685        }
686    
687        /**
688         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#isAccountReportsExist(java.lang.String, java.lang.String)
689         */
690        @Transactional
691        public boolean isAccountReportsExist(String chartOfAccountsCode, String accountNumber) {
692    
693            BudgetConstructionAccountReports accountReports = (BudgetConstructionAccountReports) budgetConstructionDao.getAccountReports(chartOfAccountsCode, accountNumber);
694            if (accountReports == null) {
695                return false;
696            }
697            else {
698                return true;
699            }
700        }
701    
702        /**
703         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#updatePendingBudgetGeneralLedger(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
704         *      org.kuali.rice.kns.util.KualiInteger)
705         */
706        @Transactional
707        public void updatePendingBudgetGeneralLedger(PendingBudgetConstructionAppointmentFunding appointmentFunding, KualiInteger updateAmount) {
708            BudgetConstructionHeader budgetConstructionHeader = this.getBudgetConstructionHeader(appointmentFunding);
709            if (budgetConstructionHeader == null) {
710                return;
711            }
712    
713            PendingBudgetConstructionGeneralLedger pendingRecord = this.getPendingBudgetConstructionGeneralLedger(budgetConstructionHeader, appointmentFunding, updateAmount, false);
714            businessObjectService.save(pendingRecord);
715        }
716    
717        /**
718         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#updatePendingBudgetGeneralLedgerPlug(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
719         *      org.kuali.rice.kns.util.KualiInteger)
720         */
721        @Transactional
722        public void updatePendingBudgetGeneralLedgerPlug(PendingBudgetConstructionAppointmentFunding appointmentFunding, KualiInteger updateAmount) {
723            if (updateAmount == null) {
724                throw new IllegalArgumentException("The update amount cannot be null");
725            }
726    
727            BudgetConstructionHeader budgetConstructionHeader = this.getBudgetConstructionHeader(appointmentFunding);
728            if (budgetConstructionHeader == null) {
729                return;
730            }
731    
732            if (this.canUpdatePlugRecord(appointmentFunding)) {
733                PendingBudgetConstructionGeneralLedger plugRecord = this.getPendingBudgetConstructionGeneralLedger(budgetConstructionHeader, appointmentFunding, updateAmount, true);
734    
735                KualiInteger annualBalanceAmount = plugRecord.getAccountLineAnnualBalanceAmount();
736                KualiInteger beginningBalanceAmount = plugRecord.getFinancialBeginningBalanceLineAmount();
737    
738                if ((annualBalanceAmount == null || annualBalanceAmount.isZero()) && (beginningBalanceAmount == null || beginningBalanceAmount.isZero())) {
739                    businessObjectService.delete(plugRecord);
740                }
741                else {
742                    businessObjectService.save(plugRecord);
743                }
744            }
745        }
746    
747        /**
748         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#updatePendingBudgetGeneralLedgerPlug(org.kuali.kfs.module.bc.document.BudgetConstructionDocument,
749         *      org.kuali.rice.kns.util.KualiInteger)
750         */
751        @Transactional
752        public PendingBudgetConstructionGeneralLedger updatePendingBudgetGeneralLedgerPlug(BudgetConstructionDocument bcDoc, KualiInteger updateAmount) {
753    
754            String twoPlugKey = KFSConstants.BudgetConstructionConstants.OBJECT_CODE_2PLG + KFSConstants.getDashFinancialSubObjectCode();
755            List<PendingBudgetConstructionGeneralLedger> expenditureRows = bcDoc.getPendingBudgetConstructionGeneralLedgerExpenditureLines();
756            PendingBudgetConstructionGeneralLedger twoPlugRow = null;
757    
758            // update or insert the 2plg row - this assumes at least one row in the set
759            // we can't even do salary setting without at least one detail line
760            int index = 0;
761            boolean insertNeeded = true;
762            for (PendingBudgetConstructionGeneralLedger expRow : expenditureRows) {
763                String expRowKey = expRow.getFinancialObjectCode() + expRow.getFinancialSubObjectCode();
764                if (expRowKey.compareToIgnoreCase(twoPlugKey) == 0) {
765    
766                    // update the existing row
767                    insertNeeded = false;
768                    expRow.setAccountLineAnnualBalanceAmount(expRow.getAccountLineAnnualBalanceAmount().add(updateAmount.negated()));
769                    expRow.setPersistedAccountLineAnnualBalanceAmount(expRow.getAccountLineAnnualBalanceAmount());
770                    businessObjectService.save(expRow);
771                    expRow.refresh();
772                    twoPlugRow = expRow;
773                    break;
774                }
775                else {
776                    if (expRowKey.compareToIgnoreCase(twoPlugKey) > 0) {
777    
778                        // case where offsetting salary setting updates under different object codes - insert a new row here
779                        break;
780                    }
781                }
782                index++;
783            }
784            if (insertNeeded) {
785    
786                // do insert in the middle or at end of list
787                String objectCode = KFSConstants.BudgetConstructionConstants.OBJECT_CODE_2PLG;
788                String subObjectCode = KFSConstants.getDashFinancialSubObjectCode();
789                String objectTypeCode = optionsService.getOptions(bcDoc.getUniversityFiscalYear()).getFinObjTypeExpenditureexpCd();
790    
791                PendingBudgetConstructionGeneralLedger pendingRecord = new PendingBudgetConstructionGeneralLedger();
792    
793                pendingRecord.setDocumentNumber(bcDoc.getDocumentNumber());
794                pendingRecord.setUniversityFiscalYear(bcDoc.getUniversityFiscalYear());
795                pendingRecord.setChartOfAccountsCode(bcDoc.getChartOfAccountsCode());
796                pendingRecord.setAccountNumber(bcDoc.getAccountNumber());
797                pendingRecord.setSubAccountNumber(bcDoc.getSubAccountNumber());
798    
799                pendingRecord.setFinancialObjectCode(objectCode);
800                pendingRecord.setFinancialSubObjectCode(subObjectCode);
801                pendingRecord.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_BASE_BUDGET);
802                pendingRecord.setFinancialObjectTypeCode(objectTypeCode);
803    
804                pendingRecord.setFinancialBeginningBalanceLineAmount(KualiInteger.ZERO);
805                pendingRecord.setAccountLineAnnualBalanceAmount(updateAmount);
806    
807                // store and add to memory set
808                pendingRecord.setPersistedAccountLineAnnualBalanceAmount(pendingRecord.getAccountLineAnnualBalanceAmount());
809                businessObjectService.save(pendingRecord);
810                expenditureRows.add(index, pendingRecord);
811                twoPlugRow = pendingRecord;
812                bcDoc.setContainsTwoPlug(true);
813            }
814    
815            bcDoc.setExpenditureAccountLineAnnualBalanceAmountTotal(bcDoc.getExpenditureAccountLineAnnualBalanceAmountTotal().add(updateAmount.negated()));
816            return twoPlugRow;
817        }
818    
819        /**
820         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#getBudgetConstructionHeader(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
821         */
822        @NonTransactional
823        public BudgetConstructionHeader getBudgetConstructionHeader(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
824            String chartOfAccountsCode = appointmentFunding.getChartOfAccountsCode();
825            String accountNumber = appointmentFunding.getAccountNumber();
826            String subAccountNumber = appointmentFunding.getSubAccountNumber();
827            Integer fiscalYear = appointmentFunding.getUniversityFiscalYear();
828    
829            return this.getByCandidateKey(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear);
830        }
831    
832        /**
833         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#getBudgetConstructionDocument(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
834         */
835        @NonTransactional
836        public BudgetConstructionDocument getBudgetConstructionDocument(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
837            Map<String, Object> fieldValues = new HashMap<String, Object>();
838            fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, appointmentFunding.getUniversityFiscalYear());
839            fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, appointmentFunding.getChartOfAccountsCode());
840            fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, appointmentFunding.getAccountNumber());
841            fieldValues.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, appointmentFunding.getSubAccountNumber());
842    
843            // fiscalyear, chart, account, subaccount is a candidate key for BC document
844            // This should not need the special handling and just return the first (only document) in the collection
845            Collection<BudgetConstructionDocument> documents = businessObjectService.findMatching(BudgetConstructionDocument.class, fieldValues);
846            for (BudgetConstructionDocument document : documents) {
847                try {
848                    return (BudgetConstructionDocument) documentService.getByDocumentHeaderId(document.getDocumentHeader().getDocumentNumber());
849                }
850                catch (WorkflowException e) {
851                    throw new RuntimeException("Fail to retrieve the document for appointment funding" + appointmentFunding, e);
852                }
853            }
854    
855            return null;
856        }
857    
858        /**
859         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#getBudgetConstructionDocument(org.kuali.kfs.module.bc.businessobject.SalarySettingExpansion)
860         */
861        @NonTransactional
862        public BudgetConstructionDocument getBudgetConstructionDocument(SalarySettingExpansion salarySettingExpansion) {
863            try {
864                return (BudgetConstructionDocument) documentService.getByDocumentHeaderId(salarySettingExpansion.getDocumentNumber());
865            }
866            catch (WorkflowException e) {
867                throw new RuntimeException("Fail to retrieve the document for salary expansion" + salarySettingExpansion, e);
868            }
869        }
870    
871        /**
872         * determine whether the plug line can be updated or created. If the given appointment funding is in the plug override mode or
873         * it associates with a contract and grant account, then no plug can be updated or created
874         * 
875         * @param appointmentFunding the given appointment funding
876         * @return true if the plug line can be updated or created; otherwise, false
877         */
878        @Transactional
879        protected boolean canUpdatePlugRecord(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
880            // no plug if the override mode is enabled
881            if (appointmentFunding.isOverride2PlugMode()) {
882                return false;
883            }
884    
885            Account account = appointmentFunding.getAccount();
886    
887            // no plug for the account with the sub groups setup as a system parameter
888            if (BudgetParameterFinder.getNotGenerate2PlgSubFundGroupCodes().contains(account.getSubFundGroupCode())) {
889                return false;
890            }
891    
892            // no plug for the contract and grant account
893            if (account.isForContractsAndGrants()) {
894                return false;
895            }
896    
897            return true;
898        }
899    
900    
901        /**
902         * get a pending budget construction GL record, and set its to the given update amount if it exists in database; otherwise,
903         * create it with the given information
904         * 
905         * @param budgetConstructionHeader the budget construction header of the pending budget construction GL record
906         * @param appointmentFunding the appointment funding associated with the pending budget construction GL record
907         * @param updateAmount the amount being used to update the retrieved pending budget construction GL record
908         * @param is2PLG the flag used to instrcut to retrieve a pending budget construction GL plug record
909         * @return a pending budget construction GL record if any; otherwise, create one with the given information
910         */
911        @Transactional
912        protected PendingBudgetConstructionGeneralLedger getPendingBudgetConstructionGeneralLedger(BudgetConstructionHeader budgetConstructionHeader, PendingBudgetConstructionAppointmentFunding appointmentFunding, KualiInteger updateAmount, boolean is2PLG) {
913            if (budgetConstructionHeader == null) {
914                throw new IllegalArgumentException("The given budget construction document header cannot be null");
915            }
916    
917            if (appointmentFunding == null) {
918                throw new IllegalArgumentException("The given pending budget appointment funding cannot be null");
919            }
920    
921            if (updateAmount == null) {
922                throw new IllegalArgumentException("The update amount cannot be null");
923            }
924    
925            PendingBudgetConstructionGeneralLedger pendingRecord = this.retrievePendingBudgetConstructionGeneralLedger(budgetConstructionHeader, appointmentFunding, is2PLG);
926    
927            if (pendingRecord != null) {
928                KualiInteger newAnnaulBalanceAmount = pendingRecord.getAccountLineAnnualBalanceAmount().add(updateAmount);
929                pendingRecord.setAccountLineAnnualBalanceAmount(newAnnaulBalanceAmount);
930            }
931            else if (!is2PLG || (is2PLG && updateAmount.isNonZero())) {
932                // initialize a new pending record if not plug line or plug line not zero
933    
934                Integer budgetYear = appointmentFunding.getUniversityFiscalYear();
935                String objectCode = is2PLG ? KFSConstants.BudgetConstructionConstants.OBJECT_CODE_2PLG : appointmentFunding.getFinancialObjectCode();
936                String subObjectCode = is2PLG ? KFSConstants.getDashFinancialSubObjectCode() : appointmentFunding.getFinancialSubObjectCode();
937                String objectTypeCode = optionsService.getOptions(budgetYear).getFinObjTypeExpenditureexpCd();
938    
939                pendingRecord = new PendingBudgetConstructionGeneralLedger();
940    
941                pendingRecord.setDocumentNumber(budgetConstructionHeader.getDocumentNumber());
942                pendingRecord.setUniversityFiscalYear(appointmentFunding.getUniversityFiscalYear());
943                pendingRecord.setChartOfAccountsCode(appointmentFunding.getChartOfAccountsCode());
944                pendingRecord.setAccountNumber(appointmentFunding.getAccountNumber());
945                pendingRecord.setSubAccountNumber(appointmentFunding.getSubAccountNumber());
946    
947                pendingRecord.setFinancialObjectCode(objectCode);
948                pendingRecord.setFinancialSubObjectCode(subObjectCode);
949                pendingRecord.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_BASE_BUDGET);
950                pendingRecord.setFinancialObjectTypeCode(objectTypeCode);
951    
952                pendingRecord.setFinancialBeginningBalanceLineAmount(KualiInteger.ZERO);
953                pendingRecord.setAccountLineAnnualBalanceAmount(updateAmount);
954            }
955    
956            return pendingRecord;
957        }
958    
959        /**
960         * retrieve a pending budget construction GL record based on the given infromation
961         * 
962         * @param budgetConstructionHeader the budget construction header of the pending budget construction GL record to be retrieved
963         * @param appointmentFunding the appointment funding associated with the pending budget construction GL record to be retrieved
964         * @param is2PLG the flag used to instrcut to retrieve a pending budget construction GL plug record
965         * @return a pending budget construction GL record if any; otherwise, null
966         */
967        @NonTransactional
968        protected PendingBudgetConstructionGeneralLedger retrievePendingBudgetConstructionGeneralLedger(BudgetConstructionHeader budgetConstructionHeader, PendingBudgetConstructionAppointmentFunding appointmentFunding, boolean is2PLG) {
969            String objectCode = is2PLG ? KFSConstants.BudgetConstructionConstants.OBJECT_CODE_2PLG : appointmentFunding.getFinancialObjectCode();
970            String subObjectCode = is2PLG ? KFSConstants.getDashFinancialSubObjectCode() : appointmentFunding.getFinancialSubObjectCode();
971    
972            Map<String, Object> searchCriteria = new HashMap<String, Object>();
973    
974            searchCriteria.put(KFSPropertyConstants.DOCUMENT_NUMBER, budgetConstructionHeader.getDocumentNumber());
975            searchCriteria.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, budgetConstructionHeader.getUniversityFiscalYear());
976            searchCriteria.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, budgetConstructionHeader.getChartOfAccountsCode());
977            searchCriteria.put(KFSPropertyConstants.ACCOUNT_NUMBER, budgetConstructionHeader.getAccountNumber());
978            searchCriteria.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, budgetConstructionHeader.getSubAccountNumber());
979            searchCriteria.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, KFSConstants.BALANCE_TYPE_BASE_BUDGET);
980            searchCriteria.put(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE, optionsService.getOptions(appointmentFunding.getUniversityFiscalYear()).getFinObjTypeExpenditureexpCd());
981    
982            searchCriteria.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, objectCode);
983            searchCriteria.put(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE, subObjectCode);
984    
985            return (PendingBudgetConstructionGeneralLedger) businessObjectService.findByPrimaryKey(PendingBudgetConstructionGeneralLedger.class, searchCriteria);
986        }
987    
988        /**
989         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#retrievePendingBudgetConstructionGeneralLedger(org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader)
990         */
991        @NonTransactional
992        public List<PendingBudgetConstructionGeneralLedger> retrievePendingBudgetConstructionGeneralLedger(BudgetConstructionHeader budgetConstructionHeader) {
993            Map<String, Object> searchCriteria = new HashMap<String, Object>();
994    
995            searchCriteria.put(KFSPropertyConstants.DOCUMENT_NUMBER, budgetConstructionHeader.getDocumentNumber());
996            searchCriteria.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, budgetConstructionHeader.getUniversityFiscalYear());
997            searchCriteria.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, budgetConstructionHeader.getChartOfAccountsCode());
998            searchCriteria.put(KFSPropertyConstants.ACCOUNT_NUMBER, budgetConstructionHeader.getAccountNumber());
999            searchCriteria.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, budgetConstructionHeader.getSubAccountNumber());
1000    
1001            return (List<PendingBudgetConstructionGeneralLedger>) businessObjectService.findMatching(PendingBudgetConstructionGeneralLedger.class, searchCriteria);
1002        }
1003    
1004        /**
1005         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#retrieveOrBuildAccountOrganizationHierarchy(java.lang.Integer,
1006         *      java.lang.String, java.lang.String)
1007         */
1008        @Transactional
1009        public List<BudgetConstructionAccountOrganizationHierarchy> retrieveOrBuildAccountOrganizationHierarchy(Integer universityFiscalYear, String chartOfAccountsCode, String accountNumber) {
1010    
1011            List<BudgetConstructionAccountOrganizationHierarchy> accountOrgHier = new ArrayList<BudgetConstructionAccountOrganizationHierarchy>();
1012            BudgetConstructionAccountReports accountReports = (BudgetConstructionAccountReports) budgetConstructionDao.getAccountReports(chartOfAccountsCode, accountNumber);
1013            if (accountReports != null) {
1014                accountOrgHier = budgetConstructionDao.getAccountOrgHierForAccount(chartOfAccountsCode, accountNumber, universityFiscalYear);
1015                if (accountOrgHier == null || accountOrgHier.isEmpty()) {
1016    
1017                    // attempt to build it
1018                    String[] rootNode = organizationService.getRootOrganizationCode();
1019                    String rootChart = rootNode[0];
1020                    String rootOrganization = rootNode[1];
1021                    Integer currentLevel = new Integer(1);
1022                    String organizationChartOfAccountsCode = accountReports.getReportsToChartOfAccountsCode();
1023                    String organizationCode = accountReports.getReportsToOrganizationCode();
1024                    boolean overFlow = budgetConstructionDao.insertAccountIntoAccountOrganizationHierarchy(rootChart, rootOrganization, universityFiscalYear, chartOfAccountsCode, accountNumber, currentLevel, organizationChartOfAccountsCode, organizationCode);
1025                    if (!overFlow) {
1026                        accountOrgHier = budgetConstructionDao.getAccountOrgHierForAccount(chartOfAccountsCode, accountNumber, universityFiscalYear);
1027                    }
1028                }
1029            }
1030            return accountOrgHier;
1031        }
1032    
1033        /**
1034         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#instantiateNewBudgetConstructionDocument(org.kuali.kfs.module.bc.document.BudgetConstructionDocument)
1035         */
1036        @Transactional
1037        public BudgetConstructionDocument instantiateNewBudgetConstructionDocument(BudgetConstructionDocument budgetConstructionDocument) throws WorkflowException {
1038    
1039            budgetConstructionDocument.setOrganizationLevelChartOfAccountsCode(BCConstants.INITIAL_ORGANIZATION_LEVEL_CHART_OF_ACCOUNTS_CODE);
1040            budgetConstructionDocument.setOrganizationLevelOrganizationCode(BCConstants.INITIAL_ORGANIZATION_LEVEL_ORGANIZATION_CODE);
1041            budgetConstructionDocument.setOrganizationLevelCode(BCConstants.INITIAL_ORGANIZATION_LEVEL_CODE);
1042            budgetConstructionDocument.setBudgetTransactionLockUserIdentifier(BCConstants.DEFAULT_BUDGET_HEADER_LOCK_IDS);
1043            budgetConstructionDocument.setBudgetLockUserIdentifier(BCConstants.DEFAULT_BUDGET_HEADER_LOCK_IDS);
1044    
1045            FinancialSystemDocumentHeader kualiDocumentHeader = budgetConstructionDocument.getDocumentHeader();
1046            budgetConstructionDocument.setDocumentNumber(budgetConstructionDocument.getDocumentHeader().getDocumentNumber());
1047            kualiDocumentHeader.setOrganizationDocumentNumber(budgetConstructionDocument.getUniversityFiscalYear().toString());
1048            kualiDocumentHeader.setFinancialDocumentStatusCode(KFSConstants.INITIAL_KUALI_DOCUMENT_STATUS_CD);
1049            kualiDocumentHeader.setFinancialDocumentTotalAmount(KualiDecimal.ZERO);
1050            kualiDocumentHeader.setDocumentDescription(String.format("%s %d %s %s", BCConstants.BUDGET_CONSTRUCTION_DOCUMENT_DESCRIPTION, budgetConstructionDocument.getUniversityFiscalYear(), budgetConstructionDocument.getChartOfAccountsCode(), budgetConstructionDocument.getAccountNumber()));
1051            kualiDocumentHeader.setExplanation(BCConstants.BUDGET_CONSTRUCTION_DOCUMENT_DESCRIPTION);
1052    
1053            budgetConstructionDao.saveBudgetConstructionDocument(budgetConstructionDocument);
1054            List<String> emptyAdHocList = new ArrayList<String>();
1055    
1056            // call route with document type configured for no route paths or post processor
1057            documentService.routeDocument(budgetConstructionDocument, "created by application UI", emptyAdHocList);
1058    
1059            return budgetConstructionDocument;
1060        }
1061    
1062        /**
1063         * @see org.kuali.kfs.module.bc.document.service.BudgetDocumentService#getPushPullLevelList(org.kuali.kfs.module.bc.document.BudgetConstructionDocument,
1064         *      org.kuali.rice.kim.bo.Person)
1065         */
1066        @Transactional
1067        public List<BudgetConstructionAccountOrganizationHierarchy> getPushPullLevelList(BudgetConstructionDocument bcDoc, Person person) {
1068            List<BudgetConstructionAccountOrganizationHierarchy> pushOrPullList = new ArrayList<BudgetConstructionAccountOrganizationHierarchy>();
1069    
1070            pushOrPullList.addAll(budgetConstructionDao.getAccountOrgHierForAccount(bcDoc.getChartOfAccountsCode(), bcDoc.getAccountNumber(), bcDoc.getUniversityFiscalYear()));
1071    
1072            if (pushOrPullList.size() >= 1) {
1073                BudgetConstructionAccountOrganizationHierarchy levelZero = new BudgetConstructionAccountOrganizationHierarchy();
1074                levelZero.setUniversityFiscalYear(bcDoc.getUniversityFiscalYear());
1075                levelZero.setChartOfAccountsCode(bcDoc.getChartOfAccountsCode());
1076                levelZero.setAccountNumber(bcDoc.getAccountNumber());
1077                levelZero.setOrganizationLevelCode(0);
1078                levelZero.setOrganizationChartOfAccountsCode(pushOrPullList.get(0).getOrganizationChartOfAccountsCode());
1079                levelZero.setOrganizationCode(pushOrPullList.get(0).getOrganizationCode());
1080                pushOrPullList.add(0, levelZero);
1081            }
1082    
1083            return pushOrPullList;
1084        }
1085    
1086        /**
1087         * Sets the budgetConstructionDao attribute value.
1088         * 
1089         * @param budgetConstructionDao The budgetConstructionDao to set.
1090         */
1091        @NonTransactional
1092        public void setBudgetConstructionDao(BudgetConstructionDao budgetConstructionDao) {
1093            this.budgetConstructionDao = budgetConstructionDao;
1094        }
1095    
1096        /**
1097         * Sets the documentService attribute value.
1098         * 
1099         * @param documentService The documentService to set.
1100         */
1101        @NonTransactional
1102        public void setDocumentService(DocumentService documentService) {
1103            this.documentService = documentService;
1104        }
1105    
1106        /**
1107         * Sets the workflowDocumentService attribute value.
1108         * 
1109         * @param workflowDocumentService The workflowDocumentService to set.
1110         */
1111        @NonTransactional
1112        public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
1113            this.workflowDocumentService = workflowDocumentService;
1114        }
1115    
1116        /**
1117         * Sets the documentDao attribute value.
1118         * 
1119         * @param documentDao The documentDao to set.
1120         */
1121        @NonTransactional
1122        public void setDocumentDao(DocumentDao documentDao) {
1123            this.documentDao = documentDao;
1124        }
1125    
1126    
1127        /**
1128         * Sets the benefitsCalculationService attribute value.
1129         * 
1130         * @param benefitsCalculationService The benefitsCalculationService to set.
1131         */
1132        @NonTransactional
1133        public void setBenefitsCalculationService(BenefitsCalculationService benefitsCalculationService) {
1134            this.benefitsCalculationService = benefitsCalculationService;
1135        }
1136    
1137    
1138        /**
1139         * Sets the businessObjectService attribute value.
1140         * 
1141         * @param businessObjectService The businessObjectService to set.
1142         */
1143        @NonTransactional
1144        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
1145            this.businessObjectService = businessObjectService;
1146        }
1147    
1148        /**
1149         * Sets the budgetParameterService attribute value.
1150         * 
1151         * @param budgetParameterService The budgetParameterService to set.
1152         */
1153        @NonTransactional
1154        public void setBudgetParameterService(BudgetParameterService budgetParameterService) {
1155            this.budgetParameterService = budgetParameterService;
1156        }
1157    
1158        /**
1159         * Sets the parameterService attribute value.
1160         * 
1161         * @param parameterService The parameterService to set.
1162         */
1163        @NonTransactional
1164        public void setParameterService(ParameterService parameterService) {
1165            this.parameterService = parameterService;
1166        }
1167    
1168        /**
1169         * Sets the fiscalYearFunctionControlService attribute value.
1170         * 
1171         * @param fiscalYearFunctionControlService The fiscalYearFunctionControlService to set.
1172         */
1173        @NonTransactional
1174        public void setFiscalYearFunctionControlService(FiscalYearFunctionControlService fiscalYearFunctionControlService) {
1175            this.fiscalYearFunctionControlService = fiscalYearFunctionControlService;
1176        }
1177    
1178    
1179        /**
1180         * Sets the optionsService attribute value.
1181         * 
1182         * @param optionsService The optionsService to set.
1183         */
1184        @NonTransactional
1185        public void setOptionsService(OptionsService optionsService) {
1186            this.optionsService = optionsService;
1187        }
1188    
1189        /**
1190         * Gets the persistenceService attribute.
1191         * 
1192         * @return Returns the persistenceService.
1193         */
1194        @NonTransactional
1195        public PersistenceService getPersistenceService() {
1196            return persistenceService;
1197        }
1198    
1199        /**
1200         * Sets the persistenceService attribute value.
1201         * 
1202         * @param persistenceService The persistenceService to set.
1203         */
1204        @NonTransactional
1205        public void setPersistenceService(PersistenceService persistenceService) {
1206            this.persistenceService = persistenceService;
1207        }
1208    
1209        /**
1210         * Sets the organizationService attribute value.
1211         * 
1212         * @param organizationService The organizationService to set.
1213         */
1214        @NonTransactional
1215        public void setOrganizationService(OrganizationService organizationService) {
1216            this.organizationService = organizationService;
1217        }
1218    
1219        /**
1220         * Sets the kualiModuleService attribute value.
1221         * 
1222         * @param kualiModuleService The kualiModuleService to set.
1223         */
1224        @NonTransactional
1225        public void setKualiModuleService(KualiModuleService kualiModuleService) {
1226            this.kualiModuleService = kualiModuleService;
1227        }
1228    
1229    }