View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.coa.document.validation.impl;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Properties;
24  import java.util.Set;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.kuali.ole.coa.businessobject.IndirectCostRecoveryExclusionAccount;
28  import org.kuali.ole.coa.businessobject.ObjectCode;
29  import org.kuali.ole.coa.businessobject.ObjectCodeGlobal;
30  import org.kuali.ole.coa.businessobject.ObjectCodeGlobalDetail;
31  import org.kuali.ole.coa.businessobject.ObjectLevel;
32  import org.kuali.ole.coa.businessobject.OffsetDefinition;
33  import org.kuali.ole.coa.service.ObjectCodeService;
34  import org.kuali.ole.coa.service.ObjectLevelService;
35  import org.kuali.ole.sys.OLEConstants;
36  import org.kuali.ole.sys.OLEKeyConstants;
37  import org.kuali.ole.sys.OLEPropertyConstants;
38  import org.kuali.ole.sys.context.SpringContext;
39  import org.kuali.ole.sys.service.UniversityDateService;
40  import org.kuali.rice.kns.document.MaintenanceDocument;
41  import org.kuali.rice.kns.maintenance.Maintainable;
42  import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
43  import org.kuali.rice.krad.bo.BusinessObject;
44  import org.kuali.rice.krad.bo.GlobalBusinessObject;
45  import org.kuali.rice.krad.bo.PersistableBusinessObject;
46  import org.kuali.rice.krad.datadictionary.InactivationBlockingMetadata;
47  import org.kuali.rice.krad.service.BusinessObjectService;
48  import org.kuali.rice.krad.service.InactivationBlockingDetectionService;
49  import org.kuali.rice.krad.util.GlobalVariables;
50  import org.kuali.rice.krad.util.KRADConstants;
51  import org.kuali.rice.krad.util.ObjectUtils;
52  import org.kuali.rice.krad.util.UrlFactory;
53  
54  /**
55   * This class represents the business rules for the maintenance of {@link ObjectCodeGlobal} business objects
56   */
57  public class ObjectCodeGlobalRule extends MaintenanceDocumentRuleBase {
58      protected ObjectCodeGlobal objectCodeGlobal;
59      protected ObjectCodeService objectCodeService;
60      protected ObjectLevelService objectLevelService;
61  
62      public ObjectCodeGlobalRule() {
63          super();
64          setObjectCodeService(SpringContext.getBean(ObjectCodeService.class));
65          setObjectLevelService(SpringContext.getBean(ObjectLevelService.class));
66      }
67  
68  
69      /**
70       * This method sets the convenience objects like objectCodeGlobal, so you have short and easy handles to the new and
71       * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
72       * all sub-objects from the DB by their primary keys, if available.
73       *
74       * @param document - the maintenanceDocument being evaluated
75       */
76      @Override
77      public void setupConvenienceObjects() {
78  
79          // setup ObjectCodeGlobal convenience objects,
80          // make sure all possible sub-objects are populated
81          objectCodeGlobal = (ObjectCodeGlobal) super.getNewBo();
82  
83          // forces refreshes on all the sub-objects in the lists
84          for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
85              objectCodeGlobalDetail.refreshNonUpdateableReferences();
86          }
87      }
88  
89      /**
90       * This performs rules checks on document approve
91       * <ul>
92       * <li>{@link ObjectCodeGlobalRule#checkSimpleRulesAllLines()}</li>
93       * </ul>
94       * This rule fails on business rule failures
95       * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
96       */
97      @Override
98      protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
99          boolean success = true;
100         setupConvenienceObjects();
101         // check simple rules
102         success &= checkSimpleRulesAllLines();
103         return success;
104     }
105 
106     /**
107      * This performs rules checks on document route
108      * <ul>
109      * <li>{@link ObjectCodeGlobalRule#checkSimpleRulesAllLines()}</li>
110      * </ul>
111      * This rule fails on business rule failures
112      * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
113      */
114     @Override
115     protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
116         boolean success = true;
117         setupConvenienceObjects();
118         // check simple rules
119         success &= checkSimpleRulesAllLines();
120         return success;
121     }
122 
123 
124     @Override
125     protected boolean processInactivationBlockChecking(MaintenanceDocument maintenanceDocument) {
126         boolean success = true;
127         if (!objectCodeGlobal.isFinancialObjectActiveIndicator()) {
128             // 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
129             Collection<? extends ObjectCodeGlobalDetail> objectCodeGlobalDetails = objectCodeGlobal.getObjectCodeGlobalDetails();
130             int i = 0;
131             for ( ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobalDetails ) {
132                 // get current object code from the DB
133                 ObjectCode objectCode = objectCodeService.getByPrimaryId(objectCodeGlobalDetail.getUniversityFiscalYear(), objectCodeGlobalDetail.getChartOfAccountsCode(), objectCodeGlobal.getFinancialObjectCode());
134                 if (objectCode != null) {
135                     if (objectCode.isActive()) {
136                         // now we know that the document intends to inactivate this object code... check to see whether a record blocks it
137                         success &= processInactivationBlockChecking(maintenanceDocument.getNewMaintainableObject(), objectCode, i);
138                     }
139                 }
140                 i++;
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             InactivationBlockingDetectionService inactivationBlockingDetectionService;
151             if (StringUtils.isBlank(inactivationBlockingDetectionServiceBeanName)) {
152                 inactivationBlockingDetectionService = SpringContext.getBean(InactivationBlockingDetectionService.class);
153             } else {
154                 inactivationBlockingDetectionService = SpringContext.getBean(InactivationBlockingDetectionService.class, inactivationBlockingDetectionServiceBeanName);
155             }
156 
157             Collection<BusinessObject> blockingBusinessObjects = inactivationBlockingDetectionService.listAllBlockerRecords(objectCode, inactivationBlockingMetadata);
158             blockingBusinessObjects = addAdditionalBlockingBusinessObjects(blockingBusinessObjects, objectCode);
159 
160             if (blockingBusinessObjects != null && !blockingBusinessObjects.isEmpty()) {
161                 final List<PersistableBusinessObject> persistingChanges = ((GlobalBusinessObject)maintainable.getBusinessObject()).generateGlobalChangesToPersist();
162                 if (!isOnlyPersistingChangesInBlockingBusinessObjects(blockingBusinessObjects, persistingChanges)) {
163                     putInactivationBlockingErrorOnPage(objectCode, inactivationBlockingMetadata, index);
164                     return false;
165                 }
166             }
167         }
168         return true;
169     }
170 
171     protected void putInactivationBlockingErrorOnPage(ObjectCode objectCode, InactivationBlockingMetadata inactivationBlockingMetadata, int index) {
172         String objectCodeSummaryString = objectCode.getUniversityFiscalYear() + " - " + objectCode.getChartOfAccountsCode() + " - " + objectCode.getFinancialObjectCode();
173 
174         Properties parameters = new Properties();
175         parameters.put(KRADConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, inactivationBlockingMetadata.getBlockedBusinessObjectClass().getName());
176         parameters.put(KRADConstants.DISPATCH_REQUEST_PARAMETER, KRADConstants.METHOD_DISPLAY_ALL_INACTIVATION_BLOCKERS);
177         parameters.put(OLEPropertyConstants.UNIVERSITY_FISCAL_YEAR, objectCode.getUniversityFiscalYear().toString());
178         parameters.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, objectCode.getChartOfAccountsCode());
179         parameters.put(OLEPropertyConstants.FINANCIAL_OBJECT_CODE, objectCode.getFinancialObjectCode());
180         String blockingUrl = UrlFactory.parameterizeUrl(KRADConstants.DISPLAY_ALL_INACTIVATION_BLOCKERS_ACTION, parameters);
181 
182         String errorPropertyPath = OLEConstants.MAINTENANCE_NEW_MAINTAINABLE + OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + index + "]." + OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE;
183 
184         // post an error about the locked document
185         GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPropertyPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INACTIVATION_BLOCKING, objectCodeSummaryString, blockingUrl);
186     }
187 
188     /**
189      * Determines if all of the given blocking business objects are among the persisting changes
190      * @param blockingBusinessObjects the Collection of blocking business objects
191      * @param persistingChanges the List of Object Codes which will be persisted by this document
192      * @return true if all the blocking business objects are persisting changes
193      */
194     protected boolean isOnlyPersistingChangesInBlockingBusinessObjects(Collection<BusinessObject> blockingBusinessObjects, List<PersistableBusinessObject> persistingChanges) {
195         for (BusinessObject bo : blockingBusinessObjects) {
196             if (bo instanceof ObjectCode) {
197                 if (!isObjectCodeInPersistingChanges(persistingChanges, (ObjectCode)bo)) {
198                     return false;
199                 }
200             } else {
201                 return false;
202             }
203         }
204         return true;
205     }
206 
207     /**
208      * Determines if the given object code is within the list of persisting changes
209      * @param persistingChanges the changes to persist
210      * @param objectCode the blocking object code to look for in the persisting changes
211      * @return true if the object code was found in the list of persisting changes, false otherwise
212      */
213     protected boolean isObjectCodeInPersistingChanges(List<PersistableBusinessObject> persistingChanges, ObjectCode objectCode) {
214         for (PersistableBusinessObject persistingObjectCodeAsObject : persistingChanges) {
215             if (isEqualObjectCode(objectCode, (ObjectCode)persistingObjectCodeAsObject)) {
216                 return true;
217             }
218         }
219         return false;
220     }
221 
222     /**
223      * Determines if the two given object codes are roughly equal
224      * @param castor an object code
225      * @param pollux another, though perhaps very similar, object code
226      * @return true if the two object codes share primary key values, false otherwise
227      */
228     protected boolean isEqualObjectCode(ObjectCode castor, ObjectCode pollux) {
229         return org.springframework.util.ObjectUtils.nullSafeEquals(castor.getUniversityFiscalYear(), pollux.getUniversityFiscalYear())
230                 && org.springframework.util.ObjectUtils.nullSafeEquals(castor.getChartOfAccountsCode(), pollux.getChartOfAccountsCode())
231                 && org.springframework.util.ObjectUtils.nullSafeEquals(castor.getFinancialObjectCode(), pollux.getFinancialObjectCode());
232     }
233 
234     /**
235      * Retrieves any additional blocking objects not handled by the inactivation framework
236      * @param blockingBusinessObjects the current list of blocking business objects
237      * @param objectCode the object code to find additional blocking objects for
238      * @return the perhaps fuller Collection of blocking business objects
239      */
240     protected Collection<BusinessObject> addAdditionalBlockingBusinessObjects(Collection<BusinessObject> blockingBusinessObjects, ObjectCode objectCode) {
241         List<BusinessObject> additionalBlockingObjects = new ArrayList<BusinessObject>();
242         retrieveBlockingOffsetDefinitions(objectCode, additionalBlockingObjects);
243         retrieveBlockingIndirectCostRecoveryExclusionAccounts(objectCode, additionalBlockingObjects);
244         if (!additionalBlockingObjects.isEmpty()) {
245             additionalBlockingObjects.addAll(blockingBusinessObjects);
246             return additionalBlockingObjects;
247         }
248         return blockingBusinessObjects;
249     }
250 
251     /**
252      * Retrieves all Offset Definitions blocking the given object code and puts them in the List of additional blocking objects
253      * @param objectCode the object code to find additional blocking objects for
254      * @param additionalBlockingObjects the List of additional blocking objects to populate
255      */
256     protected void retrieveBlockingOffsetDefinitions(ObjectCode objectCode, List<BusinessObject> additionalBlockingObjects) {
257         final BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
258 
259         Map<String, Object> keys = new HashMap<String, Object>();
260         keys.put("universityFiscalYear", objectCode.getUniversityFiscalYear());
261         keys.put("chartOfAccountsCode", objectCode.getChartOfAccountsCode());
262         keys.put("financialObjectCode", objectCode.getFinancialObjectCode());
263 
264         Collection<OffsetDefinition> offsetDefinitions = businessObjectService.findMatching(OffsetDefinition.class, keys);
265         if (offsetDefinitions != null && !offsetDefinitions.isEmpty()) {
266             additionalBlockingObjects.addAll(offsetDefinitions);
267         }
268     }
269 
270     /**
271      * Retrieves all Indirect Cost Recovery Exclusion by Account records blocking the given object code and puts them in the List of additional blocking objects
272      * @param objectCode the object code to find additional blocking objects for
273      * @param additionalBlockingObjects the List of additional blocking objects to populate
274      */
275     protected void retrieveBlockingIndirectCostRecoveryExclusionAccounts(ObjectCode objectCode, List<BusinessObject> additionalBlockingObjects) {
276         final UniversityDateService universityDateService = SpringContext.getBean(UniversityDateService.class);
277         if (objectCode.getUniversityFiscalYear() != null && objectCode.getUniversityFiscalYear().equals(universityDateService.getCurrentFiscalYear())) {
278             final BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
279 
280             Map<String, Object> keys = new HashMap<String, Object>();
281             keys.put("financialObjectChartOfAccountCode", objectCode.getChartOfAccountsCode());
282             keys.put("financialObjectCode", objectCode.getFinancialObjectCode());
283 
284             Collection<IndirectCostRecoveryExclusionAccount> icrExclusionAccounts = businessObjectService.findMatching(IndirectCostRecoveryExclusionAccount.class, keys);
285             if (icrExclusionAccounts != null && !icrExclusionAccounts.isEmpty()) {
286                 additionalBlockingObjects.addAll(icrExclusionAccounts);
287             }
288         }
289     }
290 
291     /**
292      * This performs rules checks on document save
293      * <ul>
294      * <li>{@link ObjectCodeGlobalRule#checkSimpleRulesAllLines()}</li>
295      * </ul>
296      * This rule does not fail on business rule failures
297      * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
298      */
299     @Override
300     protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
301         setupConvenienceObjects();
302         // check simple rules
303         checkSimpleRulesAllLines();
304 
305         return true;
306     }
307 
308     /**
309      * This method checks to make sure that each new {@link ObjectCodeGlobalDetail} has:
310      * <ul>
311      * <li>valid chart of accounts code</li>
312      * <li>valid fiscal year</li>
313      * <li>unique identifiers (not currently implemented)</li>
314      * </ul>
315      * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument, java.lang.String, org.kuali.rice.krad.bo.PersistableBusinessObject)
316      */
317     @Override
318     public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) {
319         boolean success = true;
320         if (bo instanceof ObjectCodeGlobalDetail) {
321             ObjectCodeGlobalDetail detail = (ObjectCodeGlobalDetail) bo;
322             if (!checkEmptyValue(detail.getChartOfAccountsCode())) {
323                 // put an error about chart code
324                 GlobalVariables.getMessageMap().putError("chartOfAccountsCode", OLEKeyConstants.ERROR_REQUIRED, "Chart of Accounts Code");
325                 success &= false;
326             }
327             if (!checkEmptyValue(detail.getUniversityFiscalYear())) {
328                 // put an error about fiscal year
329                 GlobalVariables.getMessageMap().putError("universityFiscalYear", OLEKeyConstants.ERROR_REQUIRED, "University Fiscal Year");
330                 success &= false;
331             }
332             if (!checkUniqueIdentifiers(detail)) {
333                 // TODO: put an error about unique identifier fields must not exist more than once.
334                 success &= false;
335             }
336             // both keys are present and satisfy the unique identifiers requirement, go ahead and proces the rest of the rules
337             if (success) {
338                 success &= checkObjectCodeDetails(detail);
339             }
340 
341         }
342         return success;
343     }
344 
345     /**
346      *
347      * This method (will)put an error about unique identifier fields must not exist more than once.
348      * @param dtl
349      * @return true (not currently implemented fully)
350      */
351     protected boolean checkUniqueIdentifiers(ObjectCodeGlobalDetail dtl) {
352         boolean success = true;
353         return success;
354 
355     }
356 
357     /**
358      *
359      * This checks the following conditions:
360      * <ul>
361      * <li>{@link ObjectCodeGlobalRule#checkObjectLevelCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} </li>
362      * <li>{@link ObjectCodeGlobalRule#checkNextYearObjectCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} </li>
363      * <li>{@link ObjectCodeGlobalRule#checkReportsToObjectCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)}</li>
364      * </ul>
365      * @param dtl
366      * @return true if sub-rules succeed
367      */
368     public boolean checkObjectCodeDetails(ObjectCodeGlobalDetail dtl) {
369         boolean success = true;
370         int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount();
371         getDictionaryValidationService().validateBusinessObject(dtl);
372         dtl.refreshNonUpdateableReferences();
373         // here is where we need our checks for level code nd next year object code
374         success &= checkObjectLevelCode(objectCodeGlobal, dtl, 0, true);
375         success &= checkNextYearObjectCode(objectCodeGlobal, dtl, 0, true);
376         success &= checkReportsToObjectCode(objectCodeGlobal, dtl, 0, true);
377         success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount;
378 
379         return success;
380     }
381 
382     /**
383      * 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
384      * reportToChart in the detail section
385      *
386      * @param dtl
387      * @return true if the reports to object is valid for the given reports to chart
388      */
389     protected boolean checkReportsToObjectCode(ObjectCodeGlobal objectCodeGlobal, ObjectCodeGlobalDetail dtl, int lineNum, boolean add) {
390         boolean success = true;
391         String errorPath = OLEConstants.EMPTY_STRING;
392         if (checkEmptyValue(objectCodeGlobal.getReportsToFinancialObjectCode())) {
393             // objectCodeGlobal.refreshReferenceObject("reportsToFinancialObject");
394             String reportsToObjectCode = objectCodeGlobal.getReportsToFinancialObjectCode();
395             String reportsToChartCode = dtl.getChartOfAccounts().getReportsToChartOfAccountsCode();
396             Integer fiscalYear = dtl.getUniversityFiscalYear();
397 
398             // verify that this combination exists in the db
399             ObjectCode objCode = objectCodeService.getByPrimaryId(fiscalYear, reportsToChartCode, reportsToObjectCode);
400             if (ObjectUtils.isNull(objCode)) {
401                 success &= false;
402                 String[] errorParameters = { reportsToObjectCode, reportsToChartCode, fiscalYear.toString() };
403                 if (add) {
404                     errorPath = OLEConstants.MAINTENANCE_ADD_PREFIX + OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + "chartOfAccountsCode";
405                     putFieldError(errorPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_RPTS_TO_OBJ_CODE, errorParameters);
406                 }
407                 else {
408                     errorPath = OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode";
409                     putFieldError(errorPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_RPTS_TO_OBJ_CODE, errorParameters);
410                 }
411             }
412             return success;
413 
414         }
415         else {
416             GlobalVariables.getMessageMap().putError("reportsToFinancialObjectCode", OLEKeyConstants.ERROR_REQUIRED, "Reports to Object Code");
417             success &= false;
418         }
419 
420         return success;
421     }
422 
423 
424     /**
425      * This method checks that the next year object code specified in the change document is a valid object code for a given chart
426      * and year
427      *
428      * @param dtl
429      * @return false if this object code doesn't exist in the next fiscal year
430      */
431     protected boolean checkNextYearObjectCode(ObjectCodeGlobal objectCodeGlobal, ObjectCodeGlobalDetail dtl, int lineNum, boolean add) {
432         boolean success = true;
433         String errorPath = OLEConstants.EMPTY_STRING;
434         // first check to see if the Next Year object code was filled in
435         if (checkEmptyValue(objectCodeGlobal.getNextYearFinancialObjectCode())) {
436             // then this value must also exist as a regular financial object code currently
437             ObjectCode objCode = objectCodeService.getByPrimaryId(dtl.getUniversityFiscalYear(), dtl.getChartOfAccountsCode(), objectCodeGlobal.getNextYearFinancialObjectCode());
438             if (ObjectUtils.isNull(objCode)) {
439                 success &= false;
440                 String[] errorParameters = { objectCodeGlobal.getNextYearFinancialObjectCode(), dtl.getChartOfAccountsCode(), dtl.getUniversityFiscalYear().toString() };
441                 if (add) {
442                     errorPath = OLEConstants.MAINTENANCE_ADD_PREFIX + OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + "chartOfAccountsCode";
443                     putFieldError(errorPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_NEXT_YEAR_OBJ_CODE, errorParameters);
444                 }
445                 else {
446                     errorPath = OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode";
447                     putFieldError(errorPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_NEXT_YEAR_OBJ_CODE, errorParameters);
448                 }
449             }
450             return success;
451         }
452 
453         return success;
454     }
455 
456     /**
457      * This method checks that the object level code from the object code change document actually exists for the chart object
458      * specified in the detail
459      *
460      * @param dtl
461      * @return false if object level doesn't exist for the chart, and level code filled in
462      */
463     protected boolean checkObjectLevelCode(ObjectCodeGlobal objectCodeGlobal, ObjectCodeGlobalDetail dtl, int lineNum, boolean add) {
464         boolean success = true;
465         String errorPath = OLEConstants.EMPTY_STRING;
466         // first check to see if the level code is filled in
467         if (checkEmptyValue(objectCodeGlobal.getFinancialObjectLevelCode())) {
468             ObjectLevel objLevel = objectLevelService.getByPrimaryId(dtl.getChartOfAccountsCode(), objectCodeGlobal.getFinancialObjectLevelCode());
469             if (ObjectUtils.isNull(objLevel)) {
470                 success &= false;
471                 String[] errorParameters = { objectCodeGlobal.getFinancialObjectLevelCode(), dtl.getChartOfAccountsCode() };
472                 if (add) {
473                     errorPath = OLEConstants.MAINTENANCE_ADD_PREFIX + OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + "chartOfAccountsCode";
474                     putFieldError(errorPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_OBJ_LEVEL, errorParameters);
475                 }
476                 else {
477                     errorPath = OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode";
478                     putFieldError(errorPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_INVALID_OBJ_LEVEL, errorParameters);
479                 }
480             }
481             return success;
482 
483         }
484         else {
485             GlobalVariables.getMessageMap().putError("financialObjectLevelCode", OLEKeyConstants.ERROR_REQUIRED, "Object Level Code");
486             success &= false;
487         }
488         return success;
489     }
490 
491     /**
492      * This method checks the simple rules for all lines at once and gets called on save, submit, etc. but not on add
493      *
494      * <ul>
495      * <li>{@link ObjectCodeGlobalRule#checkFiscalYearAllLines(ObjectCodeGlobal)} </li>
496      * <li>{@link ObjectCodeGlobalRule#checkChartAllLines(ObjectCodeGlobal)} </li>
497      * <li>{@link ObjectCodeGlobalRule#checkObjectLevelCodeAllLines(ObjectCodeGlobal)} </li>
498      * <li>{@link ObjectCodeGlobalRule#checkNextYearObjectCodeAllLines(ObjectCodeGlobal)} </li>
499      * <li>{@link ObjectCodeGlobalRule#checkReportsToObjectCodeAllLines(ObjectCodeGlobal)} </li>
500      * </ul>
501      * @return
502      */
503     protected boolean checkSimpleRulesAllLines() {
504         boolean success = true;
505         // check if there are any object codes and accounts, if either fails this should fail
506         if (!checkForObjectCodeGlobalDetails(objectCodeGlobal.getObjectCodeGlobalDetails())) {
507             success = false;
508         }
509         else {
510             // check object codes
511             success &= checkFiscalYearAllLines(objectCodeGlobal);
512 
513             // check chart code
514             success &= checkChartAllLines(objectCodeGlobal);
515 
516             // check object level code
517             success &= checkObjectLevelCodeAllLines(objectCodeGlobal);
518 
519             // check next year object code
520             success &= checkNextYearObjectCodeAllLines(objectCodeGlobal);
521 
522             // check reports to object code
523             success &= checkReportsToObjectCodeAllLines(objectCodeGlobal);
524 
525         }
526         return success;
527     }
528 
529     /**
530      *
531      * This checks to make sure that there is at least one {@link ObjectCodeGlobalDetail} in the collection
532      * @param objectCodeGlobalDetails
533      * @return false if the collection is empty or null
534      */
535     protected boolean checkForObjectCodeGlobalDetails(Collection<? extends PersistableBusinessObject> objectCodeGlobalDetails) {
536         if (objectCodeGlobalDetails == null || objectCodeGlobalDetails.size() == 0) {
537             putFieldError(OLEConstants.MAINTENANCE_ADD_PREFIX + OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + OLEPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_NO_CHART_FISCAL_YEAR);
538             return false;
539         }
540         return true;
541     }
542 
543     /**
544      *
545      * This method calls {@link ObjectCodeGlobalRule#checkFiscalYear(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} on each detail object
546      * @param objectCodeGlobal
547      * @return true if all lines pass
548      */
549     protected boolean checkFiscalYearAllLines(ObjectCodeGlobal objectCodeGlobal) {
550         boolean success = true;
551         int i = 0;
552         for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
553 
554             // check fiscal year first
555             success &= checkFiscalYear(objectCodeGlobal, objectCodeGlobalDetail, i, false);
556 
557             // increment counter for sub object changes list
558             i++;
559         }
560 
561         return success;
562     }
563 
564     /**
565      *
566      * This method calls {@link ObjectCodeGlobalRule#checkChartOnObjCodeDetails(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} on each detail object
567      *
568      * @param ocChangeDocument
569      * @return true if all lines pass
570      */
571     protected boolean checkChartAllLines(ObjectCodeGlobal ocChangeDocument) {
572         boolean success = true;
573         int i = 0;
574         for (ObjectCodeGlobalDetail objectCodeGlobalDetail : ocChangeDocument.getObjectCodeGlobalDetails()) {
575 
576             // check chart
577             success &= checkChartOnObjCodeDetails(ocChangeDocument, objectCodeGlobalDetail, i, false);
578             // increment counter for sub object changes list
579             i++;
580         }
581 
582         return success;
583     }
584 
585 
586     /**
587      *
588      * This method calls {@link ObjectCodeGlobalRule#checkReportsToObjectCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} on each detail object
589      *
590      * @param objectCodeGlobalDocument2
591      * @return true if all lines pass
592      */
593     protected boolean checkReportsToObjectCodeAllLines(ObjectCodeGlobal objectCodeGlobalDocument2) {
594         boolean success = true;
595         int i = 0;
596         for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
597 
598             // check fiscal year first
599             success &= checkReportsToObjectCode(objectCodeGlobal, objectCodeGlobalDetail, i, false);
600 
601             // increment counter for sub object changes list
602             i++;
603         }
604 
605         return success;
606     }
607 
608     /**
609      *
610      * This method calls {@link ObjectCodeGlobalRule#checkNextYearObjectCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} on each detail object
611      *
612      * @param objectCodeGlobalDocument2
613      * @return true if all lines pass
614      */
615     protected boolean checkNextYearObjectCodeAllLines(ObjectCodeGlobal objectCodeGlobalDocument2) {
616         boolean success = true;
617         int i = 0;
618         for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
619 
620             // check fiscal year first
621             success &= checkNextYearObjectCode(objectCodeGlobal, objectCodeGlobalDetail, i, false);
622 
623             // increment counter for sub object changes list
624             i++;
625         }
626 
627         return success;
628     }
629 
630     /**
631      *
632      * This method calls {@link ObjectCodeGlobalRule#checkObjectLevelCode(ObjectCodeGlobal, ObjectCodeGlobalDetail, int, boolean)} on each detail object
633      *
634      * @param objectCodeGlobalDocument2
635      * @return true if all lines pass
636      */
637     protected boolean checkObjectLevelCodeAllLines(ObjectCodeGlobal objectCodeGlobalDocument2) {
638         boolean success = true;
639         int i = 0;
640         for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
641 
642             // check fiscal year first
643             success &= checkObjectLevelCode(objectCodeGlobal, objectCodeGlobalDetail, i, false);
644 
645             // increment counter for sub object changes list
646             i++;
647         }
648 
649         return success;
650     }
651 
652     /**
653      *
654      * This checks to make sure that the fiscal year has been entered
655      * @param objectCodeGlobal
656      * @param objectCodeGlobalDetail
657      * @param lineNum
658      * @param add
659      * @return false if no fiscal year value
660      */
661     protected boolean checkFiscalYear(ObjectCodeGlobal objectCodeGlobal, ObjectCodeGlobalDetail objectCodeGlobalDetail, int lineNum, boolean add) {
662         boolean success = true;
663         String errorPath = OLEConstants.EMPTY_STRING;
664         // first must have an actual fiscal year
665         if (objectCodeGlobalDetail.getUniversityFiscalYear() == null) {
666             if (add) {
667                 errorPath = OLEConstants.MAINTENANCE_ADD_PREFIX + OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + "universityFiscalYear";
668                 putFieldError(errorPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_FISCAL_YEAR_MUST_EXIST);
669             }
670             else {
671                 errorPath = OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + lineNum + "]." + "universityFiscalYear";
672                 putFieldError(errorPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_FISCAL_YEAR_MUST_EXIST);
673             }
674             success &= false;
675             return success;
676         }
677 
678         return success;
679     }
680 
681     /**
682      *
683      * This checks to make sure that the chart of accounts for the detail object has been filled in
684      * @param objectCodeGlobal
685      * @param objectCodeGlobalDetail
686      * @param lineNum
687      * @param add
688      * @return false if chart of accounts code null
689      */
690     protected boolean checkChartOnObjCodeDetails(ObjectCodeGlobal objectCodeGlobal, ObjectCodeGlobalDetail objectCodeGlobalDetail, int lineNum, boolean add) {
691         boolean success = true;
692         String errorPath = OLEConstants.EMPTY_STRING;
693         // first must have an actual fiscal year
694         if (objectCodeGlobalDetail.getChartOfAccounts() == null) {
695             if (add) {
696                 errorPath = OLEConstants.MAINTENANCE_ADD_PREFIX + OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "." + "chartOfAccountsCode";
697                 putFieldError(errorPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_CHART_MUST_EXIST);
698             }
699             else {
700                 errorPath = OLEPropertyConstants.OBJECT_CODE_GLOBAL_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode";
701                 putFieldError(errorPath, OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_OBJECTMAINT_CHART_MUST_EXIST);
702             }
703             success &= false;
704             return success;
705         }
706 
707         return success;
708     }
709 
710     /**
711      *
712      * This method verifies the budget category value
713      * @param objectCodeGlobal
714      * @return true if valid
715      */
716     protected boolean checkResearchAdminAttributes(ObjectCodeGlobal objectCodeGlobal) {
717 
718 //        String budgetCategoryCode = objectCodeGlobal.getRschBudgetCategoryCode();
719 
720 //        if (StringUtils.isNotEmpty(budgetCategoryCode)) {
721 //            List<BudgetCategoryDTO> budgetCategoryList = new ArrayList<BudgetCategoryDTO>();
722 //            HashMap<String, String> criteria = new HashMap<String, String>();
723 //            criteria.put("budgetCategoryCode", budgetCategoryCode);
724 //            BudgetCategoryService budgetCategoryService = (BudgetCategoryService) GlobalResourceLoader.getService(new QName(OLEConstants.Reserch.KC_NAMESPACE_URI, OLEConstants.Reserch.KC_BUDGET_CATEGORY_SERVICE));
725 //            budgetCategoryList = budgetCategoryService.lookupBudgetCategories(criteria);
726 //            if (budgetCategoryList == null || budgetCategoryList.isEmpty()) {
727 //                GlobalVariables.getMessageMap().putErrorForSectionId(KcConstants.BudgetAdjustmentService.SECTION_ID_RESEARCH_ADMIN_ATTRIBUTES, OLEKeyConstants.ERROR_DOCUMENT_OBJECTMAINT_BUDGET_CATEGORY_CODE, "Budget Category Code 2");
728 //                return false;
729 //            }
730 //        }
731 
732         return true;
733     }
734 
735     protected void setObjectCodeService(ObjectCodeService objectCodeService) {
736         this.objectCodeService = objectCodeService;
737 
738     }
739 
740 
741     protected void setObjectLevelService(ObjectLevelService objectLevelService) {
742         this.objectLevelService = objectLevelService;
743 
744     }
745 }