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.coa.document.validation.impl;
017    
018    import java.util.ArrayList;
019    import java.util.Collection;
020    import java.util.HashMap;
021    import java.util.List;
022    import java.util.Map;
023    import java.util.Properties;
024    import java.util.Set;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.kuali.kfs.coa.businessobject.IndirectCostRecoveryExclusionAccount;
028    import org.kuali.kfs.coa.businessobject.ObjectCode;
029    import org.kuali.kfs.coa.businessobject.ObjectCodeGlobal;
030    import org.kuali.kfs.coa.businessobject.ObjectCodeGlobalDetail;
031    import org.kuali.kfs.coa.businessobject.ObjectLevel;
032    import org.kuali.kfs.coa.businessobject.OffsetDefinition;
033    import org.kuali.kfs.coa.service.ObjectCodeService;
034    import org.kuali.kfs.coa.service.ObjectLevelService;
035    import org.kuali.kfs.sys.KFSConstants;
036    import org.kuali.kfs.sys.KFSKeyConstants;
037    import org.kuali.kfs.sys.KFSPropertyConstants;
038    import org.kuali.kfs.sys.context.SpringContext;
039    import org.kuali.kfs.sys.service.UniversityDateService;
040    import org.kuali.rice.kns.bo.BusinessObject;
041    import org.kuali.rice.kns.bo.GlobalBusinessObject;
042    import org.kuali.rice.kns.bo.PersistableBusinessObject;
043    import org.kuali.rice.kns.datadictionary.InactivationBlockingMetadata;
044    import org.kuali.rice.kns.document.MaintenanceDocument;
045    import org.kuali.rice.kns.maintenance.Maintainable;
046    import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
047    import org.kuali.rice.kns.service.BusinessObjectService;
048    import org.kuali.rice.kns.service.InactivationBlockingDetectionService;
049    import org.kuali.rice.kns.service.KNSServiceLocator;
050    import org.kuali.rice.kns.util.GlobalVariables;
051    import org.kuali.rice.kns.util.KNSConstants;
052    import org.kuali.rice.kns.util.ObjectUtils;
053    import org.kuali.rice.kns.util.UrlFactory;
054    
055    /**
056     * This class represents the business rules for the maintenance of {@link ObjectCodeGlobal} business objects
057     */
058    public class ObjectCodeGlobalRule extends MaintenanceDocumentRuleBase {
059        protected ObjectCodeGlobal objectCodeGlobal;
060        protected ObjectCodeService objectCodeService;
061        protected ObjectLevelService objectLevelService;
062    
063        public ObjectCodeGlobalRule() {
064            super();
065            setObjectCodeService(SpringContext.getBean(ObjectCodeService.class));
066            setObjectLevelService(SpringContext.getBean(ObjectLevelService.class));
067        }
068    
069    
070        /**
071         * This method sets the convenience objects like objectCodeGlobal, so you have short and easy handles to the new and
072         * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
073         * all sub-objects from the DB by their primary keys, if available.
074         * 
075         * @param document - the maintenanceDocument being evaluated
076         */
077        @Override
078        public void setupConvenienceObjects() {
079    
080            // setup ObjectCodeGlobal convenience objects,
081            // make sure all possible sub-objects are populated
082            objectCodeGlobal = (ObjectCodeGlobal) super.getNewBo();
083    
084            // forces refreshes on all the sub-objects in the lists
085            for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
086                objectCodeGlobalDetail.refreshNonUpdateableReferences();
087            }
088        }
089    
090        /**
091         * This performs rules checks on document approve
092         * <ul>
093         * <li>{@link ObjectCodeGlobalRule#checkSimpleRulesAllLines()}</li>
094         * </ul>
095         * This rule fails on business rule failures
096         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
097         */
098        @Override
099        protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
100            boolean success = true;
101            setupConvenienceObjects();
102            // check simple rules
103            success &= checkSimpleRulesAllLines();
104            return success;
105        }
106    
107        /**
108         * This performs rules checks on document route
109         * <ul>
110         * <li>{@link ObjectCodeGlobalRule#checkSimpleRulesAllLines()}</li>
111         * </ul>
112         * This rule fails on business rule failures
113         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
114         */
115        @Override
116        protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
117            boolean success = true;
118            setupConvenienceObjects();
119            // check simple rules
120            success &= checkSimpleRulesAllLines();
121            return success;
122        }
123    
124        
125        @Override
126        protected boolean processInactivationBlockChecking(MaintenanceDocument maintenanceDocument) {
127            boolean success = true;
128            if (!objectCodeGlobal.isFinancialObjectActiveIndicator()) {
129                // we can only inactivate if the new active status will be false, now check whether the object codes on the document exist and are currently true
130                List<ObjectCodeGlobalDetail> objectCodeGlobalDetails = objectCodeGlobal.getObjectCodeGlobalDetails();
131                for (int i = 0; i < objectCodeGlobalDetails.size(); i++) {
132                    ObjectCodeGlobalDetail objectCodeGlobalDetail = objectCodeGlobalDetails.get(i);
133                    // get current object code from the DB
134                    ObjectCode objectCode = objectCodeService.getByPrimaryId(objectCodeGlobalDetail.getUniversityFiscalYear(), objectCodeGlobalDetail.getChartOfAccountsCode(), objectCodeGlobal.getFinancialObjectCode());
135                    if (ObjectUtils.isNotNull(objectCode)) {
136                        if (objectCode.isActive()) {
137                            // now we know that the document intends to inactivate this object code... check to see whether a record blocks it
138                            success &= processInactivationBlockChecking(maintenanceDocument.getNewMaintainableObject(), objectCode, i);
139                        }
140                    }
141                }
142            }
143            return success;
144        }
145    
146        protected boolean processInactivationBlockChecking(Maintainable maintainable, ObjectCode objectCode, int index) {
147            Set<InactivationBlockingMetadata> inactivationBlockingMetadatas = ddService.getAllInactivationBlockingDefinitions(ObjectCode.class);
148            for (InactivationBlockingMetadata inactivationBlockingMetadata : inactivationBlockingMetadatas) {
149                String inactivationBlockingDetectionServiceBeanName = inactivationBlockingMetadata.getInactivationBlockingDetectionServiceBeanName();
150                if (StringUtils.isBlank(inactivationBlockingDetectionServiceBeanName)) {
151                    inactivationBlockingDetectionServiceBeanName = KNSServiceLocator.DEFAULT_INACTIVATION_BLOCKING_DETECTION_SERVICE;
152                }
153                InactivationBlockingDetectionService inactivationBlockingDetectionService = KNSServiceLocator.getInactivationBlockingDetectionService(inactivationBlockingDetectionServiceBeanName);
154    
155                Collection<BusinessObject> blockingBusinessObjects = inactivationBlockingDetectionService.listAllBlockerRecords(objectCode, inactivationBlockingMetadata);
156                blockingBusinessObjects = addAdditionalBlockingBusinessObjects(blockingBusinessObjects, objectCode);
157                
158                if (blockingBusinessObjects != null && !blockingBusinessObjects.isEmpty()) {
159                    final List<PersistableBusinessObject> persistingChanges = ((GlobalBusinessObject)maintainable.getBusinessObject()).generateGlobalChangesToPersist();
160                    if (!isOnlyPersistingChangesInBlockingBusinessObjects(blockingBusinessObjects, persistingChanges)) {
161                        putInactivationBlockingErrorOnPage(objectCode, inactivationBlockingMetadata, index);
162                        return false;
163                    }
164                }
165            }
166            return true;
167        }
168        
169        protected void putInactivationBlockingErrorOnPage(ObjectCode objectCode, InactivationBlockingMetadata inactivationBlockingMetadata, int index) {
170            String objectCodeSummaryString = objectCode.getUniversityFiscalYear() + " - " + objectCode.getChartOfAccountsCode() + " - " + objectCode.getFinancialObjectCode();
171            
172            Properties parameters = new Properties();
173            parameters.put(KNSConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, inactivationBlockingMetadata.getBlockedBusinessObjectClass().getName());        
174            parameters.put(KNSConstants.DISPATCH_REQUEST_PARAMETER, KNSConstants.METHOD_DISPLAY_ALL_INACTIVATION_BLOCKERS);
175            parameters.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, objectCode.getUniversityFiscalYear().toString());
176            parameters.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, objectCode.getChartOfAccountsCode());
177            parameters.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, objectCode.getFinancialObjectCode());
178            String blockingUrl = UrlFactory.parameterizeUrl(KNSConstants.DISPLAY_ALL_INACTIVATION_BLOCKERS_ACTION, parameters);
179            
180            String errorPropertyPath = KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + index + "]." + KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE;
181            
182            // post an error about the locked document
183            GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPropertyPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INACTIVATION_BLOCKING, objectCodeSummaryString, blockingUrl);
184        }
185        
186        /**
187         * Determines if all of the given blocking business objects are among the persisting changes
188         * @param blockingBusinessObjects the Collection of blocking business objects
189         * @param persistingChanges the List of Object Codes which will be persisted by this document
190         * @return true if all the blocking business objects are persisting changes
191         */
192        protected boolean isOnlyPersistingChangesInBlockingBusinessObjects(Collection<BusinessObject> blockingBusinessObjects, List<PersistableBusinessObject> persistingChanges) {
193            for (BusinessObject bo : blockingBusinessObjects) {
194                if (bo instanceof ObjectCode) {
195                    if (!isObjectCodeInPersistingChanges(persistingChanges, (ObjectCode)bo)) return false;
196                } else {
197                    return false;
198                }
199            }
200            return true;
201        }
202        
203        /**
204         * Determines if the given object code is within the list of persisting changes
205         * @param persistingChanges the changes to persist
206         * @param objectCode the blocking object code to look for in the persisting changes
207         * @return true if the object code was found in the list of persisting changes, false otherwise
208         */
209        protected boolean isObjectCodeInPersistingChanges(List<PersistableBusinessObject> persistingChanges, ObjectCode objectCode) {
210            for (PersistableBusinessObject persistingObjectCodeAsObject : persistingChanges) {
211                if (isEqualObjectCode(objectCode, (ObjectCode)persistingObjectCodeAsObject)) return true;
212            }
213            return false;
214        }
215    
216        /**
217         * Determines if the two given object codes are roughly equal
218         * @param castor an object code
219         * @param pollux another, though perhaps very similar, object code
220         * @return true if the two object codes share primary key values, false otherwise
221         */
222        protected boolean isEqualObjectCode(ObjectCode castor, ObjectCode pollux) {
223            return ObjectUtils.nullSafeEquals(castor.getUniversityFiscalYear(), pollux.getUniversityFiscalYear()) && ObjectUtils.nullSafeEquals(castor.getChartOfAccountsCode(), pollux.getChartOfAccountsCode()) && ObjectUtils.nullSafeEquals(castor.getFinancialObjectCode(), pollux.getFinancialObjectCode());
224        }
225        
226        /**
227         * Retrieves any additional blocking objects not handled by the inactivation framework
228         * @param blockingBusinessObjects the current list of blocking business objects
229         * @param objectCode the object code to find additional blocking objects for
230         * @return the perhaps fuller Collection of blocking business objects
231         */
232        protected Collection<BusinessObject> addAdditionalBlockingBusinessObjects(Collection<BusinessObject> blockingBusinessObjects, ObjectCode objectCode) {
233            List<BusinessObject> additionalBlockingObjects = new ArrayList<BusinessObject>();
234            retrieveBlockingOffsetDefinitions(objectCode, additionalBlockingObjects);
235            retrieveBlockingIndirectCostRecoveryExclusionAccounts(objectCode, additionalBlockingObjects);
236            if (!additionalBlockingObjects.isEmpty()) {
237                additionalBlockingObjects.addAll(blockingBusinessObjects);
238                return additionalBlockingObjects;
239            }
240            return blockingBusinessObjects;
241        }
242        
243        /**
244         * Retrieves all Offset Definitions blocking the given object code and puts them in the List of additional blocking objects
245         * @param objectCode the object code to find additional blocking objects for
246         * @param additionalBlockingObjects the List of additional blocking objects to populate
247         */
248        protected void retrieveBlockingOffsetDefinitions(ObjectCode objectCode, List<BusinessObject> additionalBlockingObjects) {
249            final BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
250            
251            Map<String, Object> keys = new HashMap<String, Object>();
252            keys.put("universityFiscalYear", objectCode.getUniversityFiscalYear());
253            keys.put("chartOfAccountsCode", objectCode.getChartOfAccountsCode());
254            keys.put("financialObjectCode", objectCode.getFinancialObjectCode());
255            
256            Collection<BusinessObject> offsetDefinitions = (Collection<BusinessObject>)businessObjectService.findMatching(OffsetDefinition.class, keys);
257            if (offsetDefinitions != null && !offsetDefinitions.isEmpty()) {
258                additionalBlockingObjects.addAll(offsetDefinitions);
259            }
260        }
261        
262        /**
263         * Retrieves all Indirect Cost Recovery Exclusion by Account records blocking the given object code and puts them in the List of additional blocking objects
264         * @param objectCode the object code to find additional blocking objects for
265         * @param additionalBlockingObjects the List of additional blocking objects to populate
266         */
267        protected void retrieveBlockingIndirectCostRecoveryExclusionAccounts(ObjectCode objectCode, List<BusinessObject> additionalBlockingObjects) {
268            final UniversityDateService universityDateService = SpringContext.getBean(UniversityDateService.class);
269            if (objectCode.getUniversityFiscalYear() != null && objectCode.getUniversityFiscalYear().equals(universityDateService.getCurrentFiscalYear())) {
270                final BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
271                
272                Map<String, Object> keys = new HashMap<String, Object>();
273                keys.put("financialObjectChartOfAccountCode", objectCode.getChartOfAccountsCode());
274                keys.put("financialObjectCode", objectCode.getFinancialObjectCode());
275                
276                Collection<BusinessObject> icrExclusionAccounts = (Collection<BusinessObject>)businessObjectService.findMatching(IndirectCostRecoveryExclusionAccount.class, keys);
277                if (icrExclusionAccounts != null && !icrExclusionAccounts.isEmpty()) {
278                    additionalBlockingObjects.addAll(icrExclusionAccounts);
279                }
280            }
281        }
282    
283        /**
284         * This performs rules checks on document save
285         * <ul>
286         * <li>{@link ObjectCodeGlobalRule#checkSimpleRulesAllLines()}</li>
287         * </ul>
288         * This rule does not fail on business rule failures
289         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
290         */
291        @Override
292        protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
293            setupConvenienceObjects();
294            // check simple rules
295            checkSimpleRulesAllLines();
296    
297            return true;
298        }
299    
300        /**
301         * This method checks to make sure that each new {@link ObjectCodeGlobalDetail} has: 
302         * <ul>
303         * <li>valid chart of accounts code</li>
304         * <li>valid fiscal year</li>
305         * <li>unique identifiers (not currently implemented)</li>
306         * </ul>
307         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument, java.lang.String, org.kuali.rice.kns.bo.PersistableBusinessObject)
308         */
309        @Override
310        public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) {
311            boolean success = true;
312            if (bo instanceof ObjectCodeGlobalDetail) {
313                ObjectCodeGlobalDetail detail = (ObjectCodeGlobalDetail) bo;
314                if (!checkEmptyValue(detail.getChartOfAccountsCode())) {
315                    // put an error about chart code
316                    GlobalVariables.getMessageMap().putError("chartOfAccountsCode", KFSKeyConstants.ERROR_REQUIRED, "Chart of Accounts Code");
317                    success &= false;
318                }
319                if (!checkEmptyValue(detail.getUniversityFiscalYear())) {
320                    // put an error about fiscal year
321                    GlobalVariables.getMessageMap().putError("universityFiscalYear", KFSKeyConstants.ERROR_REQUIRED, "University Fiscal Year");
322                    success &= false;
323                }
324                if (!checkUniqueIdentifiers(detail)) {
325                    // TODO: put an error about unique identifier fields must not exist more than once.
326                    success &= false;
327                }
328                // both keys are present and satisfy the unique identifiers requirement, go ahead and proces the rest of the rules
329                if (success) {
330                    success &= checkObjectCodeDetails(detail);
331                }
332    
333            }
334            return success;
335        }
336    
337        /**
338         * 
339         * This method (will)put an error about unique identifier fields must not exist more than once.
340         * @param dtl
341         * @return true (not currently implemented fully)
342         */
343        protected boolean checkUniqueIdentifiers(ObjectCodeGlobalDetail dtl) {
344            boolean success = true;
345            return success;
346    
347        }
348    
349        /**
350         * 
351         * This checks the following conditions:
352         * <ul>
353         * <li>{@link ObjectCodeGlobalRule#checkObjectLevelCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} </li>
354         * <li>{@link ObjectCodeGlobalRule#checkNextYearObjectCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} </li>
355         * <li>{@link ObjectCodeGlobalRule#checkReportsToObjectCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)}</li>
356         * </ul>
357         * @param dtl
358         * @return true if sub-rules succeed
359         */
360        public boolean checkObjectCodeDetails(ObjectCodeGlobalDetail dtl) {
361            boolean success = true;
362            int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount();
363            getDictionaryValidationService().validateBusinessObject(dtl);
364            dtl.refreshNonUpdateableReferences();
365            // here is where we need our checks for level code nd next year object code
366            success &= checkObjectLevelCode(objectCodeGlobal, dtl, 0, true);
367            success &= checkNextYearObjectCode(objectCodeGlobal, dtl, 0, true);
368            success &= checkReportsToObjectCode(objectCodeGlobal, dtl, 0, true);
369            success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount;
370    
371            return success;
372        }
373    
374        /**
375         * This method checks that the reports to object code input on the top level of the global document is valid for a given chart's
376         * reportToChart in the detail section
377         * 
378         * @param dtl
379         * @return true if the reports to object is valid for the given reports to chart
380         */
381        protected boolean checkReportsToObjectCode(ObjectCodeGlobal objectCodeGlobal, ObjectCodeGlobalDetail dtl, int lineNum, boolean add) {
382            boolean success = true;
383            String errorPath = KFSConstants.EMPTY_STRING;
384            if (checkEmptyValue(objectCodeGlobal.getReportsToFinancialObjectCode())) {
385                // objectCodeGlobal.refreshReferenceObject("reportsToFinancialObject");
386                String reportsToObjectCode = objectCodeGlobal.getReportsToFinancialObjectCode();
387                String reportsToChartCode = dtl.getChartOfAccounts().getReportsToChartOfAccountsCode();
388                Integer fiscalYear = dtl.getUniversityFiscalYear();
389    
390                // verify that this combination exists in the db
391                ObjectCode objCode = objectCodeService.getByPrimaryId(fiscalYear, reportsToChartCode, reportsToObjectCode);
392                if (ObjectUtils.isNull(objCode)) {
393                    success &= false;
394                    String[] errorParameters = { reportsToObjectCode, reportsToChartCode, fiscalYear.toString() };
395                    if (add) {
396                        errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + "chartOfAccountsCode";
397                        putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_RPTS_TO_OBJ_CODE, errorParameters);
398                    }
399                    else {
400                        errorPath = KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode";
401                        putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_RPTS_TO_OBJ_CODE, errorParameters);
402                    }
403                }
404                return success;
405    
406            }
407            else {
408                GlobalVariables.getMessageMap().putError("reportsToFinancialObjectCode", KFSKeyConstants.ERROR_REQUIRED, "Reports to Object Code");
409                success &= false;
410            }
411    
412            return success;
413        }
414    
415    
416        /**
417         * This method checks that the next year object code specified in the change document is a valid object code for a given chart
418         * and year
419         * 
420         * @param dtl
421         * @return false if this object code doesn't exist in the next fiscal year
422         */
423        protected boolean checkNextYearObjectCode(ObjectCodeGlobal objectCodeGlobal, ObjectCodeGlobalDetail dtl, int lineNum, boolean add) {
424            boolean success = true;
425            String errorPath = KFSConstants.EMPTY_STRING;
426            // first check to see if the Next Year object code was filled in
427            if (checkEmptyValue(objectCodeGlobal.getNextYearFinancialObjectCode())) {
428                // then this value must also exist as a regular financial object code currently
429                ObjectCode objCode = objectCodeService.getByPrimaryId(dtl.getUniversityFiscalYear(), dtl.getChartOfAccountsCode(), objectCodeGlobal.getNextYearFinancialObjectCode());
430                if (ObjectUtils.isNull(objCode)) {
431                    success &= false;
432                    String[] errorParameters = { objectCodeGlobal.getNextYearFinancialObjectCode(), dtl.getChartOfAccountsCode(), dtl.getUniversityFiscalYear().toString() };
433                    if (add) {
434                        errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + "chartOfAccountsCode";
435                        putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_NEXT_YEAR_OBJ_CODE, errorParameters);
436                    }
437                    else {
438                        errorPath = KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode";
439                        putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_NEXT_YEAR_OBJ_CODE, errorParameters);
440                    }
441                }
442                return success;
443            }
444    
445            return success;
446        }
447    
448        /**
449         * This method checks that the object level code from the object code change document actually exists for the chart object
450         * specified in the detail
451         * 
452         * @param dtl
453         * @return false if object level doesn't exist for the chart, and level code filled in
454         */
455        protected boolean checkObjectLevelCode(ObjectCodeGlobal objectCodeGlobal, ObjectCodeGlobalDetail dtl, int lineNum, boolean add) {
456            boolean success = true;
457            String errorPath = KFSConstants.EMPTY_STRING;
458            // first check to see if the level code is filled in
459            if (checkEmptyValue(objectCodeGlobal.getFinancialObjectLevelCode())) {
460                ObjectLevel objLevel = objectLevelService.getByPrimaryId(dtl.getChartOfAccountsCode(), objectCodeGlobal.getFinancialObjectLevelCode());
461                if (ObjectUtils.isNull(objLevel)) {
462                    success &= false;
463                    String[] errorParameters = { objectCodeGlobal.getFinancialObjectLevelCode(), dtl.getChartOfAccountsCode() };
464                    if (add) {
465                        errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + "chartOfAccountsCode";
466                        putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_OBJ_LEVEL, errorParameters);
467                    }
468                    else {
469                        errorPath = KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode";
470                        putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_OBJ_LEVEL, errorParameters);
471                    }
472                }
473                return success;
474    
475            }
476            else {
477                GlobalVariables.getMessageMap().putError("financialObjectLevelCode", KFSKeyConstants.ERROR_REQUIRED, "Object Level Code");
478                success &= false;
479            }
480            return success;
481        }
482    
483        /**
484         * This method checks the simple rules for all lines at once and gets called on save, submit, etc. but not on add
485         * 
486         * <ul>
487         * <li>{@link ObjectCodeGlobalRule#checkFiscalYearAllLines(ObjectCodeGlobal)} </li>
488         * <li>{@link ObjectCodeGlobalRule#checkChartAllLines(ObjectCodeGlobal)} </li>
489         * <li>{@link ObjectCodeGlobalRule#checkObjectLevelCodeAllLines(ObjectCodeGlobal)} </li>
490         * <li>{@link ObjectCodeGlobalRule#checkNextYearObjectCodeAllLines(ObjectCodeGlobal)} </li>
491         * <li>{@link ObjectCodeGlobalRule#checkReportsToObjectCodeAllLines(ObjectCodeGlobal)} </li>
492         * </ul>
493         * @return
494         */
495        protected boolean checkSimpleRulesAllLines() {
496            boolean success = true;
497            // check if there are any object codes and accounts, if either fails this should fail
498            if (!checkForObjectCodeGlobalDetails(objectCodeGlobal.getObjectCodeGlobalDetails())) {
499                success = false;
500            }
501            else {
502                // check object codes
503                success &= checkFiscalYearAllLines(objectCodeGlobal);
504    
505                // check chart code
506                success &= checkChartAllLines(objectCodeGlobal);
507    
508                // check object level code
509                success &= checkObjectLevelCodeAllLines(objectCodeGlobal);
510    
511                // check next year object code
512                success &= checkNextYearObjectCodeAllLines(objectCodeGlobal);
513    
514                // check reports to object code
515                success &= checkReportsToObjectCodeAllLines(objectCodeGlobal);
516                
517            }
518            return success;
519        }
520    
521        /**
522         * 
523         * This checks to make sure that there is at least one {@link ObjectCodeGlobalDetail} in the collection
524         * @param objectCodeGlobalDetails
525         * @return false if the collection is empty or null
526         */
527        protected boolean checkForObjectCodeGlobalDetails(List<ObjectCodeGlobalDetail> objectCodeGlobalDetails) {
528            if (objectCodeGlobalDetails == null || objectCodeGlobalDetails.size() == 0) {
529                putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_NO_CHART_FISCAL_YEAR);
530                return false;
531            }
532            return true;
533        }
534    
535        /**
536         * 
537         * This method calls {@link ObjectCodeGlobalRule#checkFiscalYear(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} on each detail object
538         * @param objectCodeGlobal
539         * @return true if all lines pass
540         */
541        protected boolean checkFiscalYearAllLines(ObjectCodeGlobal objectCodeGlobal) {
542            boolean success = true;
543            int i = 0;
544            for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
545    
546                // check fiscal year first
547                success &= checkFiscalYear(objectCodeGlobal, objectCodeGlobalDetail, i, false);
548    
549                // increment counter for sub object changes list
550                i++;
551            }
552    
553            return success;
554        }
555    
556        /**
557         * 
558         * This method calls {@link ObjectCodeGlobalRule#checkChartOnObjCodeDetails(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} on each detail object
559         * 
560         * @param ocChangeDocument
561         * @return true if all lines pass
562         */
563        protected boolean checkChartAllLines(ObjectCodeGlobal ocChangeDocument) {
564            boolean success = true;
565            int i = 0;
566            for (ObjectCodeGlobalDetail objectCodeGlobalDetail : ocChangeDocument.getObjectCodeGlobalDetails()) {
567    
568                // check chart
569                success &= checkChartOnObjCodeDetails(ocChangeDocument, objectCodeGlobalDetail, i, false);
570                // increment counter for sub object changes list
571                i++;
572            }
573    
574            return success;
575        }
576    
577    
578        /**
579         * 
580         * This method calls {@link ObjectCodeGlobalRule#checkReportsToObjectCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} on each detail object
581         * 
582         * @param objectCodeGlobalDocument2
583         * @return true if all lines pass
584         */
585        protected boolean checkReportsToObjectCodeAllLines(ObjectCodeGlobal objectCodeGlobalDocument2) {
586            boolean success = true;
587            int i = 0;
588            for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
589    
590                // check fiscal year first
591                success &= checkReportsToObjectCode(objectCodeGlobal, objectCodeGlobalDetail, i, false);
592    
593                // increment counter for sub object changes list
594                i++;
595            }
596    
597            return success;
598        }
599    
600        /**
601         * 
602         * This method calls {@link ObjectCodeGlobalRule#checkNextYearObjectCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} on each detail object
603         * 
604         * @param objectCodeGlobalDocument2
605         * @return true if all lines pass
606         */
607        protected boolean checkNextYearObjectCodeAllLines(ObjectCodeGlobal objectCodeGlobalDocument2) {
608            boolean success = true;
609            int i = 0;
610            for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
611    
612                // check fiscal year first
613                success &= checkNextYearObjectCode(objectCodeGlobal, objectCodeGlobalDetail, i, false);
614    
615                // increment counter for sub object changes list
616                i++;
617            }
618    
619            return success;
620        }
621    
622        /**
623         * 
624         * This method calls {@link ObjectCodeGlobalRule#checkObjectLevelCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} on each detail object
625         * 
626         * @param objectCodeGlobalDocument2
627         * @return true if all lines pass
628         */
629        protected boolean checkObjectLevelCodeAllLines(ObjectCodeGlobal objectCodeGlobalDocument2) {
630            boolean success = true;
631            int i = 0;
632            for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
633    
634                // check fiscal year first
635                success &= checkObjectLevelCode(objectCodeGlobal, objectCodeGlobalDetail, i, false);
636    
637                // increment counter for sub object changes list
638                i++;
639            }
640    
641            return success;
642        }    
643    
644        /**
645         * 
646         * This checks to make sure that the fiscal year has been entered
647         * @param objectCodeGlobal
648         * @param objectCodeGlobalDetail
649         * @param lineNum
650         * @param add
651         * @return false if no fiscal year value
652         */
653        protected boolean checkFiscalYear(ObjectCodeGlobal objectCodeGlobal, ObjectCodeGlobalDetail objectCodeGlobalDetail, int lineNum, boolean add) {
654            boolean success = true;
655            String errorPath = KFSConstants.EMPTY_STRING;
656            // first must have an actual fiscal year
657            if (objectCodeGlobalDetail.getUniversityFiscalYear() == null) {
658                if (add) {
659                    errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + "universityFiscalYear";
660                    putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_FISCAL_YEAR_MUST_EXIST);
661                }
662                else {
663                    errorPath = KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + lineNum + "]." + "universityFiscalYear";
664                    putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_FISCAL_YEAR_MUST_EXIST);
665                }
666                success &= false;
667                return success;
668            }
669    
670            return success;
671        }
672    
673        /**
674         * 
675         * This checks to make sure that the chart of accounts for the detail object has been filled in
676         * @param objectCodeGlobal
677         * @param objectCodeGlobalDetail
678         * @param lineNum
679         * @param add
680         * @return false if chart of accounts code null
681         */
682        protected boolean checkChartOnObjCodeDetails(ObjectCodeGlobal objectCodeGlobal, ObjectCodeGlobalDetail objectCodeGlobalDetail, int lineNum, boolean add) {
683            boolean success = true;
684            String errorPath = KFSConstants.EMPTY_STRING;
685            // first must have an actual fiscal year
686            if (objectCodeGlobalDetail.getChartOfAccounts() == null) {
687                if (add) {
688                    errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + "chartOfAccountsCode";
689                    putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_CHART_MUST_EXIST);
690                }
691                else {
692                    errorPath = KFSPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode";
693                    putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_CHART_MUST_EXIST);
694                }
695                success &= false;
696                return success;
697            }
698    
699            return success;
700        }
701        
702        protected void setObjectCodeService(ObjectCodeService objectCodeService) {
703            this.objectCodeService = objectCodeService;
704    
705        }
706    
707    
708        protected void setObjectLevelService(ObjectLevelService objectLevelService) {
709            this.objectLevelService = objectLevelService;
710    
711        }
712    }