001/*
002 * Copyright 2007 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.ole.coa.document.validation.impl;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Properties;
024import java.util.Set;
025
026import org.apache.commons.lang.StringUtils;
027import org.kuali.ole.coa.businessobject.IndirectCostRecoveryExclusionAccount;
028import org.kuali.ole.coa.businessobject.ObjectCode;
029import org.kuali.ole.coa.businessobject.ObjectCodeGlobal;
030import org.kuali.ole.coa.businessobject.ObjectCodeGlobalDetail;
031import org.kuali.ole.coa.businessobject.ObjectLevel;
032import org.kuali.ole.coa.businessobject.OffsetDefinition;
033import org.kuali.ole.coa.service.ObjectCodeService;
034import org.kuali.ole.coa.service.ObjectLevelService;
035import org.kuali.ole.sys.OLEConstants;
036import org.kuali.ole.sys.OLEKeyConstants;
037import org.kuali.ole.sys.OLEPropertyConstants;
038import org.kuali.ole.sys.context.SpringContext;
039import org.kuali.ole.sys.service.UniversityDateService;
040import org.kuali.rice.kns.document.MaintenanceDocument;
041import org.kuali.rice.kns.maintenance.Maintainable;
042import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
043import org.kuali.rice.krad.bo.BusinessObject;
044import org.kuali.rice.krad.bo.GlobalBusinessObject;
045import org.kuali.rice.krad.bo.PersistableBusinessObject;
046import org.kuali.rice.krad.datadictionary.InactivationBlockingMetadata;
047import org.kuali.rice.krad.service.BusinessObjectService;
048import org.kuali.rice.krad.service.InactivationBlockingDetectionService;
049import org.kuali.rice.krad.util.GlobalVariables;
050import org.kuali.rice.krad.util.KRADConstants;
051import org.kuali.rice.krad.util.ObjectUtils;
052import org.kuali.rice.krad.util.UrlFactory;
053
054/**
055 * This class represents the business rules for the maintenance of {@link ObjectCodeGlobal} business objects
056 */
057public class ObjectCodeGlobalRule extends MaintenanceDocumentRuleBase {
058    protected ObjectCodeGlobal objectCodeGlobal;
059    protected ObjectCodeService objectCodeService;
060    protected ObjectLevelService objectLevelService;
061
062    public ObjectCodeGlobalRule() {
063        super();
064        setObjectCodeService(SpringContext.getBean(ObjectCodeService.class));
065        setObjectLevelService(SpringContext.getBean(ObjectLevelService.class));
066    }
067
068
069    /**
070     * This method sets the convenience objects like objectCodeGlobal, so you have short and easy handles to the new and
071     * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
072     * all sub-objects from the DB by their primary keys, if available.
073     *
074     * @param document - the maintenanceDocument being evaluated
075     */
076    @Override
077    public void setupConvenienceObjects() {
078
079        // setup ObjectCodeGlobal convenience objects,
080        // make sure all possible sub-objects are populated
081        objectCodeGlobal = (ObjectCodeGlobal) super.getNewBo();
082
083        // forces refreshes on all the sub-objects in the lists
084        for (ObjectCodeGlobalDetail objectCodeGlobalDetail : objectCodeGlobal.getObjectCodeGlobalDetails()) {
085            objectCodeGlobalDetail.refreshNonUpdateableReferences();
086        }
087    }
088
089    /**
090     * This performs rules checks on document approve
091     * <ul>
092     * <li>{@link ObjectCodeGlobalRule#checkSimpleRulesAllLines()}</li>
093     * </ul>
094     * This rule fails on business rule failures
095     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
096     */
097    @Override
098    protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
099        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}