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