001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.coa.document.validation.impl;
017    
018    import java.sql.Date;
019    import java.sql.Timestamp;
020    import java.util.Calendar;
021    import java.util.Iterator;
022    import java.util.List;
023    
024    import org.apache.commons.lang.StringUtils;
025    import org.apache.commons.lang.time.DateUtils;
026    import org.kuali.kfs.coa.businessobject.Account;
027    import org.kuali.kfs.coa.businessobject.Organization;
028    import org.kuali.kfs.coa.service.OrganizationService;
029    import org.kuali.kfs.sys.KFSConstants;
030    import org.kuali.kfs.sys.KFSKeyConstants;
031    import org.kuali.kfs.sys.KFSPropertyConstants;
032    import org.kuali.kfs.sys.context.SpringContext;
033    import org.kuali.kfs.sys.identity.KfsKimAttributes;
034    import org.kuali.rice.kim.bo.Person;
035    import org.kuali.rice.kim.bo.types.dto.AttributeSet;
036    import org.kuali.rice.kim.service.IdentityManagementService;
037    import org.kuali.rice.kim.util.KimConstants;
038    import org.kuali.rice.kns.document.MaintenanceDocument;
039    import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
040    import org.kuali.rice.kns.service.ParameterService;
041    import org.kuali.rice.kns.util.GlobalVariables;
042    import org.kuali.rice.kns.util.ObjectUtils;
043    
044    /**
045     * This class implements the business rules specific to the {@link Org} Maintenance Document.
046     */
047    public class OrgRule extends MaintenanceDocumentRuleBase {
048    
049        protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OrgRule.class);
050    
051    
052        protected static OrganizationService orgService;
053    
054        protected Organization oldOrg;
055        protected Organization newOrg;
056        protected boolean isHrmsOrgActivated;
057    
058        /**
059         * Constructs a OrgRule and pseudo-injects services
060         */
061        public OrgRule() {
062            super();
063    
064            // Pseudo-inject some services.
065            //
066            // This approach is being used to make it simpler to convert the Rule classes
067            // to spring-managed with these services injected by Spring at some later date.
068            // When this happens, just remove these calls to the setters with
069            // SpringContext, and configure the bean defs for spring.
070            if (orgService == null) {
071                orgService = SpringContext.getBean(OrganizationService.class);
072            }
073        }
074    
075        /**
076         * This performs the following checks on document approve:
077         * <ul>
078         * <li>{@link OrgRule#checkExistenceAndActive()}</li>
079         * <li>{@link OrgRule#checkOrgClosureRules(MaintenanceDocument)}</li>
080         * <li>{@link OrgRule#checkSimpleRules(MaintenanceDocument)}</li>
081         * <li>{@link OrgRule#checkDefaultAccountNumber(MaintenanceDocument)}</li>
082         * </ul>
083         * This rule fails on rule failure
084         * 
085         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
086         */
087        protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
088    
089            boolean success = true;
090    
091            LOG.info("Entering processCustomApproveDocumentBusinessRules()");
092    
093            // determine whether HRMS ORG is activated in this app instance
094            isHrmsOrgActivated = isHrmsOrgActivated();
095    
096            // check that all sub-objects whose keys are specified have matching objects in the db
097            success &= checkExistenceAndActive();
098    
099            success &= checkOrgClosureRules(document);
100    
101            // check that end date is greater than begin date and Reports To Chart/Org should not be same as this Chart/Org
102            success &= checkSimpleRules(document);
103    
104            // check that defaultAccount is present unless
105            // ( (orgType = U or C) and ( document is a "create new" ))
106            success &= checkDefaultAccountNumber(document);
107            return success;
108        }
109    
110        /**
111         * This performs the following checks on document route:
112         * <ul>
113         * <li>{@link OrgRule#checkExistenceAndActive()}</li>
114         * <li>{@link OrgRule#checkOrgClosureRules(MaintenanceDocument)}</li>
115         * <li>{@link OrgRule#checkSimpleRules(MaintenanceDocument)}</li>
116         * <li>{@link OrgRule#checkDefaultAccountNumber(MaintenanceDocument)}</li>
117         * </ul>
118         * This rule fails on rule failure
119         * 
120         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
121         */
122        protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
123    
124            boolean success = true;
125    
126            LOG.info("Entering processCustomRouteDocumentBusinessRules()");
127    
128            // determine whether HRMS ORG is activated in this app instance
129            isHrmsOrgActivated = isHrmsOrgActivated();
130    
131            // check that all sub-objects whose keys are specified have matching objects in the db
132            success &= checkExistenceAndActive();
133    
134            // check that end date is greater than begin date and Reports To Chart/Org should not be same as this Chart/Org
135            success &= checkSimpleRules(document);
136    
137            // check that defaultAccount is present unless
138            // ( (orgType = U or C) and ( document is a "create new" ))
139            success &= checkDefaultAccountNumber(document);
140    
141            success &= checkOrgClosureRules(document);
142    
143            return success;
144        }
145    
146        /**
147         * This performs the following checks on document save:
148         * <ul>
149         * <li>{@link OrgRule#checkExistenceAndActive()}</li>
150         * <li>{@link OrgRule#checkOrgClosureRules(MaintenanceDocument)}</li>
151         * <li>{@link OrgRule#checkSimpleRules(MaintenanceDocument)}</li>
152         * <li>{@link OrgRule#checkDefaultAccountNumber(MaintenanceDocument)}</li>
153         * </ul>
154         * This rule does not fail on rule failure
155         * 
156         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
157         */
158        protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
159    
160            LOG.info("Entering processCustomSaveDocumentBusinessRules()");
161    
162            // determine whether HRMS ORG is activated in this app instance
163            isHrmsOrgActivated = isHrmsOrgActivated();
164    
165            // check that all sub-objects whose keys are specified have matching objects in the db
166            checkExistenceAndActive();
167    
168            checkOrgClosureRules(document);
169    
170            // check that end date is greater than begin date and Reports To Chart/Org should not be same as this Chart/Org
171            checkSimpleRules(document);
172    
173            // check that defaultAccount is present unless
174            // ( (orgType = U or C) and ( document is a "create new" ))
175            checkDefaultAccountNumber(document);
176    
177            return true;
178        }
179    
180        /**
181         * This checks to see if the org is active
182         * 
183         * @return true if the org is inactive or false otherwise
184         */
185        protected boolean checkExistenceAndActive() {
186    
187            LOG.info("Entering checkExistenceAndActive()");
188            boolean success = true;
189    
190            // shortcut out with no enforcement if this org is closed
191            if (!newOrg.isActive()) {
192                return success;
193            }
194    
195            success &= checkPlantAttributes();
196    
197            return success;
198        }
199    
200        /**
201         * This checks to see if a user is authorized for plant fields modification. If not then it returns true (without activating
202         * fields). If the org does not have to report to itself then it checks to see if the plant fields have been filled out
203         * correctly and fails if they haven't
204         * 
205         * @return false if user can edit plant fields but they have not been filled out correctly
206         */
207        protected boolean checkPlantAttributes() {
208    
209            boolean success = true;
210    
211            /*
212             * KULCOA-1132 - exit if the user is not a member of the plant maintainer work group.
213             */
214    
215            // get user
216            Person user = GlobalVariables.getUserSession().getPerson();
217    
218            // if not authroized to edit plant fields, exit with true
219            if (isPlantAuthorized(user) == false) {
220                return true;
221            }
222    
223            // relax this edit for
224            if (!getOrgMustReportToSelf(newOrg)) {
225                // require Org Plant ChartCode
226                success &= checkEmptyBOField("organizationPlantChartCode", newOrg.getOrganizationPlantChartCode(), "Organization Plant Chart of Accounts Code");
227    
228                // require Org Plant AccountNumber
229                success &= checkEmptyBOField("organizationPlantAccountNumber", newOrg.getOrganizationPlantAccountNumber(), "Organization Plant Account Number");
230    
231                // require Campus Plant ChartCode
232                success &= checkEmptyBOField("campusPlantChartCode", newOrg.getCampusPlantChartCode(), "Campus Plant Chart of Accounts Code");
233    
234                // require Org Plant ChartCode
235                success &= checkEmptyBOField("campusPlantAccountNumber", newOrg.getCampusPlantAccountNumber(), "Campus Plant Account Number");
236    
237                // validate Org Plant Account
238                success &= getDictionaryValidationService().validateReferenceExistsAndIsActive(newOrg, "organizationPlantAccount", MAINTAINABLE_ERROR_PREFIX + "organizationPlantAccountNumber", "Organization Plant Account");
239    
240                // validate Campus Plant Account
241                success &= getDictionaryValidationService().validateReferenceExistsAndIsActive(newOrg, "campusPlantAccount", MAINTAINABLE_ERROR_PREFIX + "campusPlantAccountNumber", "Campus Plant Account");
242            }
243    
244            return success;
245        }
246    
247        /**
248         * This method enforces the business rules surrounding when an Org becomes closed/inactive. If we are editing and switching the
249         * org to inactive or if it is a new doc and it is marked as inactive then we assume we are closing the org. If we are not then
250         * we return true. If we are then we return false if there are still active accounts tied to the org
251         * 
252         * @param document
253         * @return false if trying to close org but it still has accounts that are active linked to it
254         */
255        protected boolean checkOrgClosureRules(MaintenanceDocument document) {
256    
257            boolean success = true;
258            boolean orgBeingClosed = false;
259    
260            // if its an edit, and its being closed
261            if (document.isEdit()) {
262                if (oldOrg.isActive() && !newOrg.isActive()) {
263                    orgBeingClosed = true;
264                }
265            }
266    
267            // if its new, and is being created as closed
268            if (document.isNew()) {
269                if (!newOrg.isActive()) {
270                    orgBeingClosed = true;
271                }
272            }
273    
274            // if the org isnt being closed, stop processing here
275            if (!orgBeingClosed) {
276                return success;
277            }
278    
279            // FROM HERE ON WE'RE ASSUMING THE ORG IS BEING CLOSED
280    
281            // do not allow the org to be closed while there are active accounts tied
282            // to this org
283            List childAccounts = orgService.getActiveAccountsByOrg(newOrg.getChartOfAccountsCode(), newOrg.getOrganizationCode());
284            if (childAccounts.size() > 0) {
285    
286                // get the first three accounts on the list for display
287                StringBuffer childAccountList = new StringBuffer();
288                int count = 0;
289                String delim = "";
290                for (Iterator iter = childAccounts.iterator(); iter.hasNext();) {
291                    Account account = (Account) iter.next();
292                    childAccountList.append(delim + account.getChartOfAccountsCode() + "-" + account.getAccountNumber());
293                    count++;
294                    if (count >= 1) {
295                        delim = ", ";
296                    }
297                    if (count >= 3) {
298                        break;
299                    }
300                }
301                if (childAccounts.size() > count) {
302                    childAccountList.append(", ... (" + (childAccounts.size() - count) + " more)");
303                }
304    
305                putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_OPEN_CHILD_ACCOUNTS_ON_ORG_CLOSURE, childAccountList.toString());
306                success &= false;
307            }
308    
309            // do not allow this org to be closed while there are still active orgs
310            // that have this org as their reportsToOrg
311            List childOrgs = orgService.getActiveChildOrgs(newOrg.getChartOfAccountsCode(), newOrg.getOrganizationCode());
312            if (childOrgs.size() > 0) {
313    
314                // get the first three orgs on the list for display
315                StringBuffer childOrgsList = new StringBuffer();
316                int count = 0;
317                String delim = "";
318                for (Iterator iter = childOrgs.iterator(); iter.hasNext();) {
319                    Organization org = (Organization) iter.next();
320                    childOrgsList.append(delim + org.getChartOfAccountsCode() + "-" + org.getOrganizationCode());
321                    count++;
322                    if (count >= 1) {
323                        delim = ", ";
324                    }
325                    if (count >= 3) {
326                        break;
327                    }
328                }
329                if (childOrgs.size() > count) {
330                    childOrgsList.append(", ... (" + (childOrgs.size() - count) + " more)");
331                }
332    
333                putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_OPEN_CHILD_ORGS_ON_ORG_CLOSURE, childOrgsList.toString());
334                success &= false;
335            }
336    
337    
338            // if org is being closed, end-date must be valid and present
339            if (ObjectUtils.isNull(newOrg.getOrganizationEndDate())) {
340                success &= false;
341                putFieldError("organizationEndDate", KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_END_DATE_REQUIRED_ON_ORG_CLOSURE);
342            }
343            return success;
344    
345        }
346    
347        /**
348         * This checks to see if the org is active and if it the HRMS org is active
349         * 
350         * @param document
351         * @return true if either the org is inactive or isHrmsOrgActivated is false
352         */
353        protected boolean checkHrmsOrgRules(MaintenanceDocument document) {
354    
355            boolean success = true;
356    
357            // shortcut out with no enforcement if this org is closed
358            if (!newOrg.isActive()) {
359                return success;
360            }
361    
362            // short circuit and fail if HRMSOrg is turned off
363            if (!isHrmsOrgActivated) {
364                return success;
365            }
366    
367            // if the system has a HRMS Org record attached to this org record, then prompt the
368            // user to fill out the HRMS Org info
369    
370            // HRMS Org Campus == Org Campus
371            // HRMS Org campus code must be the same as Org campus code
372    
373    
374            // if the
375            return success;
376        }
377    
378        /**
379         * This checks our {@link Parameter} rules to see if this org needs to report to itself
380         * 
381         * @param organization
382         * @return true if it does
383         */
384        protected boolean getOrgMustReportToSelf(Organization organization) {
385            return SpringContext.getBean(ParameterService.class).getParameterEvaluator(Organization.class, KFSConstants.ChartApcParms.ORG_MUST_REPORT_TO_SELF_ORG_TYPES, organization.getOrganizationTypeCode()).evaluationSucceeds();
386        }
387    
388        /**
389         * This checks the following conditions:
390         * <ul>
391         * <li>begin date must be greater than or equal to end date</li>
392         * <li>start date must be greater than or equal to today if new Document</li>
393         * <li>Reports To Chart/Org should not be same as this Chart/Org</li>
394         * </ul>
395         * 
396         * @param document
397         * @return true if it passes all the rules, false otherwise
398         */
399        protected boolean checkSimpleRules(MaintenanceDocument document) {
400    
401            boolean success = true;
402            String lastReportsToChartOfAccountsCode;
403            String lastReportsToOrganizationCode;
404            boolean continueSearch;
405            Organization tempOrg;
406            Integer loopCount;
407            Integer maxLoopCount = 40;
408    
409            // begin date must be greater than or equal to end date
410            if ((ObjectUtils.isNotNull(newOrg.getOrganizationBeginDate()) && (ObjectUtils.isNotNull(newOrg.getOrganizationEndDate())))) {
411    
412                Date beginDate = newOrg.getOrganizationBeginDate();
413                Date endDate = newOrg.getOrganizationEndDate();
414    
415                if (endDate.before(beginDate)) {
416                    putFieldError("organizationEndDate", KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_END_DATE_GREATER_THAN_BEGIN_DATE);
417                    success &= false;
418                }
419            }
420    
421            // start date must be greater than or equal to today if new Document
422            if ((ObjectUtils.isNotNull(newOrg.getOrganizationBeginDate()) && (document.isNew()))) {
423                Timestamp today = getDateTimeService().getCurrentTimestamp();
424                today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime());
425                if (newOrg.getOrganizationBeginDate().before(today)) {
426                    putFieldError("organizationBeginDate", KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_STARTDATE_IN_PAST);
427                    success &= false;
428                }
429            }
430    
431            // Reports To Chart/Org should not be same as this Chart/Org
432            // However, allow special case where organization type is listed in the business rules
433            if (ObjectUtils.isNotNull(newOrg.getReportsToChartOfAccountsCode()) && ObjectUtils.isNotNull(newOrg.getReportsToOrganizationCode()) && ObjectUtils.isNotNull(newOrg.getChartOfAccountsCode()) && ObjectUtils.isNotNull(newOrg.getOrganizationCode())) {
434                if (!getOrgMustReportToSelf(newOrg)) {
435    
436                    if ((newOrg.getReportsToChartOfAccountsCode().equals(newOrg.getChartOfAccountsCode())) && (newOrg.getReportsToOrganizationCode().equals(newOrg.getOrganizationCode()))) {
437                        putFieldError("reportsToOrganizationCode", KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_REPORTING_ORG_CANNOT_BE_SAME_ORG);
438                        success = false;
439                    }
440                    else {
441                        // Don't allow a circular reference on Reports to Chart/Org
442                        // terminate the search when a top-level org is found
443                        lastReportsToChartOfAccountsCode = newOrg.getReportsToChartOfAccountsCode();
444                        lastReportsToOrganizationCode = newOrg.getReportsToOrganizationCode();
445                        continueSearch = true;
446                        loopCount = 0;
447                        do {
448                            tempOrg = orgService.getByPrimaryId(lastReportsToChartOfAccountsCode, lastReportsToOrganizationCode);
449                            loopCount++;
450                            ;
451                            if (ObjectUtils.isNull(tempOrg)) {
452                                continueSearch = false;
453                                // if a null is returned on the first iteration, then the reports-to org does not exist
454                                // fail the validation
455                                if (loopCount == 1) {
456                                    putFieldError("reportsToOrganizationCode", KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_REPORTING_ORG_MUST_EXIST);
457                                    success = false;
458                                }
459                            }
460                            else {
461                                // on the first iteration, check whether the reports-to organization is active
462                                if (loopCount == 1 && !tempOrg.isActive()) {
463                                    putFieldError("reportsToOrganizationCode", KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_REPORTING_ORG_MUST_EXIST);
464                                    success = false;
465                                    continueSearch = false;
466                                }
467                                else {
468                                    // LOG.info("Found Org = " + lastReportsToChartOfAccountsCode + "/" +
469                                    // lastReportsToOrganizationCode);
470                                    lastReportsToChartOfAccountsCode = tempOrg.getReportsToChartOfAccountsCode();
471                                    lastReportsToOrganizationCode = tempOrg.getReportsToOrganizationCode();
472    
473                                    if ((tempOrg.getReportsToChartOfAccountsCode().equals(newOrg.getChartOfAccountsCode())) && (tempOrg.getReportsToOrganizationCode().equals(newOrg.getOrganizationCode()))) {
474                                        putFieldError("reportsToOrganizationCode", KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_REPORTING_ORG_CANNOT_BE_CIRCULAR_REF_TO_SAME_ORG);
475                                        success = false;
476                                        continueSearch = false;
477                                    }
478                                }
479                            }
480                            if (loopCount > maxLoopCount) {
481                                continueSearch = false;
482                            }
483                            // stop the search if we reach an org that must report to itself
484                            if (continueSearch && SpringContext.getBean(ParameterService.class).getParameterEvaluator(Organization.class, KFSConstants.ChartApcParms.ORG_MUST_REPORT_TO_SELF_ORG_TYPES, tempOrg.getOrganizationTypeCode()).evaluationSucceeds()) {
485                                continueSearch = false;
486                            }
487    
488                        } while (continueSearch == true);
489                    } // end else (checking for circular ref)
490                }
491                else { // org must report to self (university level organization)
492                    if (!(newOrg.getReportsToChartOfAccountsCode().equals(newOrg.getChartOfAccountsCode()) && newOrg.getReportsToOrganizationCode().equals(newOrg.getOrganizationCode()))) {
493                        putFieldError("reportsToOrganizationCode", KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_REPORTING_ORG_MUST_BE_SAME_ORG);
494                        success = false;
495                    }
496                    // org must be the only one of that type
497                    String topLevelOrgTypeCode = SpringContext.getBean(ParameterService.class).getParameterValue(Organization.class, KFSConstants.ChartApcParms.ORG_MUST_REPORT_TO_SELF_ORG_TYPES);
498                    List<Organization> topLevelOrgs = orgService.getActiveOrgsByType(topLevelOrgTypeCode);
499                    if (!topLevelOrgs.isEmpty()) {
500                        // is the new org in the topLevelOrgs list? If not, then there's an error; if so, we're editing the top level
501                        // org
502                        if (!topLevelOrgs.contains(newOrg)) {
503                            putFieldError("organizationTypeCode", KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_ONLY_ONE_TOP_LEVEL_ORG, topLevelOrgs.get(0).getChartOfAccountsCode() + "-" + topLevelOrgs.get(0).getOrganizationCode());
504                            success = false;
505                        }
506                    }
507                }
508            }
509    
510    
511            return success;
512        }
513    
514    
515        /**
516         * This checks that defaultAccount is present unless ( (orgType = U or C) and ( document is a "create new" or "edit" ))
517         * 
518         * @param document
519         * @return false if missing default account number and it is not an exempt type code
520         */
521        protected boolean checkDefaultAccountNumber(MaintenanceDocument document) {
522    
523            boolean success = true;
524            boolean exemptOrganizationTypeCode = false;
525            boolean missingDefaultAccountNumber = StringUtils.isBlank(newOrg.getOrganizationDefaultAccountNumber());
526    
527            if (ObjectUtils.isNotNull(newOrg.getOrganizationTypeCode())) {
528                String organizationTypeCode = newOrg.getOrganizationTypeCode();
529                if (SpringContext.getBean(ParameterService.class).getParameterEvaluator(Organization.class, KFSConstants.ChartApcParms.DEFAULT_ACCOUNT_NOT_REQUIRED_ORG_TYPES, newOrg.getOrganizationTypeCode()).evaluationSucceeds()) {
530                    exemptOrganizationTypeCode = true;
531                }
532            }
533    
534            if (missingDefaultAccountNumber && (!exemptOrganizationTypeCode || (!document.isNew() && !document.isEdit()))) {
535                putFieldError("organizationDefaultAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ORGMAINT_DEFAULT_ACCOUNT_NUMBER_REQUIRED);
536                success &= false;
537            }
538    
539            return success;
540        }
541    
542        /**
543         * This method compares an old and new value, and determines if they've changed. If the old was null/blank, and the new is not,
544         * return true. If the old had a value, and the new is null/blank, return true. If both old and new had a value, and the values
545         * are different (excluding trailing or leading whitespaces, and excluding case changes), return true. If none of the above,
546         * return false.
547         * 
548         * @param oldValue - Old value to test.
549         * @param newValue - New value to test.
550         * @return true or false, based on the algorithm described above.
551         */
552        protected boolean fieldsHaveChanged(String oldValue, String newValue) {
553    
554            // if old was null/blank and new is not
555            if (StringUtils.isBlank(oldValue) && StringUtils.isNotBlank(newValue)) {
556                return true;
557            }
558    
559            // if old had a value, but new is null/blank
560            if (StringUtils.isNotBlank(oldValue) && StringUtils.isBlank(newValue)) {
561                return true;
562            }
563    
564            // at this point, we know that we had a value before, and we have a
565            // value now, so we need to test whether this value has changed
566            if (oldValue != null && newValue != null) {
567                if (!oldValue.trim().equalsIgnoreCase(newValue.trim())) {
568                    return true;
569                }
570            }
571    
572            // if we've made it to here, then no changes have happened to the values
573            return false;
574        }
575    
576        /**
577         * This method looks up in the ParameterService whether ther HRMS Org system is turned on.
578         * 
579         * @return true or false depending on the app configuration
580         */
581        protected boolean isHrmsOrgActivated() {
582            return SpringContext.getBean(ParameterService.class).getIndicatorParameter(Organization.class, KFSConstants.ChartApcParms.APC_HRMS_ACTIVE_KEY);
583        }
584    
585        /**
586         * This method sets the convenience objects like newOrg and oldOrg, so you have short and easy handles to the new and old
587         * objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load all
588         * sub-objects from the DB by their primary keys, if available.
589         * 
590         * @param document - the maintenanceDocument being evaluated
591         */
592        public void setupConvenienceObjects() {
593    
594            // setup oldAccount convenience objects, make sure all possible sub-objects are populated
595            oldOrg = (Organization) super.getOldBo();
596    
597            // setup newAccount convenience objects, make sure all possible sub-objects are populated
598            newOrg = (Organization) super.getNewBo();
599        }
600    
601        /**
602         * This method tests whether the specified user is part of the group that grants authorization to the Plant fields.
603         * 
604         * @param user - the user to test
605         * @return true if user is part of the group, false otherwise
606         */
607        protected boolean isPlantAuthorized(Person user) {
608            String principalId = user.getPrincipalId();
609            String namespaceCode = KFSConstants.ParameterNamespaces.KNS;
610            String permissionTemplateName = KimConstants.PermissionTemplateNames.MODIFY_FIELD;
611    
612            AttributeSet roleQualifiers = new AttributeSet();
613            roleQualifiers.put(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE, newOrg.getChartOfAccountsCode());
614    
615            AttributeSet permissionDetails = new AttributeSet();
616            permissionDetails.put(KfsKimAttributes.COMPONENT_NAME, Organization.class.getSimpleName());
617            permissionDetails.put(KfsKimAttributes.PROPERTY_NAME, KFSPropertyConstants.ORGANIZATION_PLANT_CHART_CODE);
618    
619            IdentityManagementService identityManagementService = SpringContext.getBean(IdentityManagementService.class);
620            Boolean isAuthorized = identityManagementService.isAuthorizedByTemplateName(principalId, namespaceCode, permissionTemplateName, permissionDetails, roleQualifiers);
621            if (!isAuthorized) {
622                LOG.info("User '" + user.getPrincipalName() + "' has no access to the Plant Chart.");
623                return false;
624            }
625    
626            permissionDetails.put(KfsKimAttributes.PROPERTY_NAME, KFSPropertyConstants.ORGANIZATION_PLANT_ACCOUNT_NUMBER);
627            isAuthorized = identityManagementService.isAuthorizedByTemplateName(principalId, namespaceCode, permissionTemplateName, permissionDetails, roleQualifiers);
628            if (!isAuthorized) {
629                LOG.info("User '" + user.getPrincipalName() + "' has no access to the Plant account.");
630                return false;
631            }
632    
633            LOG.info("User '" + user.getPrincipalName() + "' has access to the Plant fields.");
634            return true;
635        }
636    }