View Javadoc
1   /*
2    * Copyright 2005 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.coa.document.validation.impl;
17  
18  import java.sql.Date;
19  import java.sql.Timestamp;
20  import java.util.Calendar;
21  import java.util.Collection;
22  import java.util.HashMap;
23  import java.util.Map;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.apache.commons.lang.time.DateUtils;
27  import org.kuali.ole.coa.businessobject.Account;
28  import org.kuali.ole.coa.businessobject.AccountDescription;
29  import org.kuali.ole.coa.businessobject.AccountGuideline;
30  import org.kuali.ole.coa.businessobject.FundGroup;
31  import org.kuali.ole.coa.businessobject.IndirectCostRecoveryAccount;
32  import org.kuali.ole.coa.businessobject.IndirectCostRecoveryRateDetail;
33  import org.kuali.ole.coa.businessobject.SubFundGroup;
34  import org.kuali.ole.coa.service.AccountService;
35  import org.kuali.ole.coa.service.SubFundGroupService;
36  import org.kuali.ole.gl.service.BalanceService;
37  import org.kuali.ole.gl.service.EncumbranceService;
38  import org.kuali.ole.integration.cg.ContractsAndGrantsModuleService;
39  import org.kuali.ole.select.OleSelectConstant;
40  import org.kuali.ole.sys.OLEConstants;
41  import org.kuali.ole.sys.OLEKeyConstants;
42  import org.kuali.ole.sys.OLEPropertyConstants;
43  import org.kuali.ole.sys.businessobject.Building;
44  import org.kuali.ole.sys.context.SpringContext;
45  import org.kuali.ole.sys.service.GeneralLedgerPendingEntryService;
46  import org.kuali.ole.sys.service.UniversityDateService;
47  import org.kuali.rice.core.api.datetime.DateTimeService;
48  import org.kuali.rice.core.api.parameter.ParameterEvaluator;
49  import org.kuali.rice.core.api.parameter.ParameterEvaluatorService;
50  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
51  import org.kuali.rice.kim.api.identity.Person;
52  import org.kuali.rice.kns.document.MaintenanceDocument;
53  import org.kuali.rice.kns.service.DataDictionaryService;
54  import org.kuali.rice.kns.service.DictionaryValidationService;
55  import org.kuali.rice.krad.util.GlobalVariables;
56  import org.kuali.rice.krad.util.MessageMap;
57  import org.kuali.rice.krad.util.ObjectUtils;
58  
59  /**
60   * Business rule(s) applicable to AccountMaintenance documents.
61   */
62  public class AccountRule extends IndirectCostRecoveryAccountsRule {
63  
64      protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountRule.class);
65  
66      protected static final String ACCT_PREFIX_RESTRICTION = "PREFIXES";
67      protected static final String ACCT_CAPITAL_SUBFUNDGROUP = "CAPITAL_SUB_FUND_GROUPS";
68  
69      @Deprecated
70      protected static final String RESTRICTED_CD_TEMPORARILY_RESTRICTED = "T";
71  
72      protected static SubFundGroupService subFundGroupService;
73      protected static ParameterService parameterService;
74      protected EncumbranceService encumbranceService;
75  
76      protected GeneralLedgerPendingEntryService generalLedgerPendingEntryService;
77      protected BalanceService balanceService;
78      protected AccountService accountService;
79  
80      protected ContractsAndGrantsModuleService contractsAndGrantsModuleService;
81  
82      protected Account oldAccount;
83      protected Account newAccount;
84  
85      public AccountRule() {
86  
87          // Pseudo-inject some services.
88          //
89          // This approach is being used to make it simpler to convert the Rule classes
90          // to spring-managed with these services injected by Spring at some later date.
91          // When this happens, just remove these calls to the setters with
92          // SpringContext, and configure the bean defs for spring.
93          this.setGeneralLedgerPendingEntryService(SpringContext.getBean(GeneralLedgerPendingEntryService.class));
94          this.setBalanceService(SpringContext.getBean(BalanceService.class));
95          this.setAccountService(SpringContext.getBean(AccountService.class));
96          this.setContractsAndGrantsModuleService(SpringContext.getBean(ContractsAndGrantsModuleService.class));
97      }
98  
99      /**
100      * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and
101      * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
102      * all sub-objects from the DB by their primary keys, if available.
103      */
104     @Override
105     public void setupConvenienceObjects() {
106 
107         // setup oldAccount convenience objects, make sure all possible sub-objects are populated
108         oldAccount = (Account) super.getOldBo();
109         refreshSubObjects(oldAccount);
110         // setup newAccount convenience objects, make sure all possible sub-objects are populated
111         newAccount = (Account) super.getNewBo();
112         refreshSubObjects(newAccount);
113 
114         setActiveIndirectCostRecoveryAccountList(newAccount.getActiveIndirectCostRecoveryAccounts());
115         setBoFieldPath(OLEPropertyConstants.INDIRECT_COST_RECOVERY_ACCOUNTS);
116     }
117 
118 
119     /**
120      * Refreshes the references of account
121      *
122      * @param account Account
123      */
124     protected void refreshSubObjects(Account account) {
125         if(account.getBudgetRecordingLevelCode()==null){
126             account.setBudgetRecordingLevelCode(OleSelectConstant.BUDGET_RECORDING_LEVEL_CODE);
127         }
128         if (account != null) {
129             // refresh contacts
130             if (account.getIndirectCostRecoveryAccounts() != null) {
131                 for (IndirectCostRecoveryAccount icra : account.getIndirectCostRecoveryAccounts()) {
132                     icra.refreshNonUpdateableReferences();
133                 }
134             }
135         }
136     }
137 
138     /**
139      * This method calls the route rules but does not fail if any of them fail (this only happens on routing)
140      *
141      * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
142      */
143     @Override
144     protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
145 
146         LOG.debug("processCustomSaveDocumentBusinessRules called");
147         // call the route rules to report all of the messages, but ignore the result
148         processCustomRouteDocumentBusinessRules(document);
149 
150         // Save always succeeds, even if there are business rule failures
151         return true;
152     }
153 
154     /**
155      * This method calls the following rules: checkAccountGuidelinesValidation checkEmptyValues checkGeneralRules checkCloseAccount
156      * checkContractsAndGrants checkExpirationDate checkFundGroup checkSubFundGroup checkFiscalOfficerIsValidKualiUser this rule
157      * will fail on routing
158      *
159      * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
160      */
161     @Override
162     protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
163 
164         LOG.debug("processCustomRouteDocumentBusinessRules called");
165         setupConvenienceObjects();
166 
167         // default to success
168         boolean success = true;
169 
170         // validate the embedded AccountGuideline object
171         success &= checkAccountGuidelinesValidation(newAccount.getAccountGuideline());
172 
173         success &= checkEmptyValues(document);
174         success &= checkGeneralRules(document);
175         success &= checkCloseAccount(document);
176         success &= checkContractsAndGrants(document);
177         success &= checkExpirationDate(document);
178         success &= checkFundGroup(document);
179         success &= checkSubFundGroup(document);
180         success &= checkVendorNameForSubFundGroup(document);
181         success &= checkIncomeStreamAccountRule();
182         success &= checkUniqueAccountNumber(document);
183         success &= checkOpenEncumbrances();
184         success &= checkAccountRestrictedStatusDateIsGreaterThanCurrentDate();
185         success &= validateSufficientFundCheckTab();
186 
187         // process for IndirectCostRecovery Account
188         success &= super.processCustomRouteDocumentBusinessRules(document);
189 
190         return success;
191     }
192 
193     /**
194      * This method checks the basic rules for empty values in an account and associated objects with this account If guidelines are
195      * required for this Business Object it checks to make sure that it is filled out It also checks for partially filled out
196      * reference keys on the following: continuationAccount incomeStreamAccount endowmentIncomeAccount reportsToAccount
197      * contractControlAccount
198      *
199      * @param maintenanceDocument
200      * @return false if any of these are empty
201      */
202     protected boolean checkEmptyValues(MaintenanceDocument maintenanceDocument) {
203 
204         LOG.debug("checkEmptyValues called");
205 
206         boolean success = true;
207 
208         // guidelines are always required, except when the expirationDate is set, and its
209         // earlier than today
210         boolean guidelinesRequired = areGuidelinesRequired((Account) maintenanceDocument.getNewMaintainableObject().getBusinessObject());
211 
212         // confirm that required guidelines are entered, if required
213         if (guidelinesRequired) {
214             success &= checkEmptyBOField("accountGuideline.accountExpenseGuidelineText", newAccount.getAccountGuideline().getAccountExpenseGuidelineText(), "Expense Guideline");
215          /*   success &= checkEmptyBOField("accountGuideline.accountIncomeGuidelineText", newAccount.getAccountGuideline().getAccountIncomeGuidelineText(), "Income Guideline");
216             success &= checkEmptyBOField("accountGuideline.accountPurposeText", newAccount.getAccountGuideline().getAccountPurposeText(), "Account Purpose");
217         */
218             }
219 
220         // this set confirms that all fields which are grouped (ie, foreign keys of a reference
221         // object), must either be none filled out, or all filled out.
222         success &= checkForPartiallyFilledOutReferenceForeignKeys(OLEPropertyConstants.CONTINUATION_ACCOUNT);
223         success &= checkForPartiallyFilledOutReferenceForeignKeys(OLEPropertyConstants.INCOME_STREAM_ACCOUNT);
224         success &= checkForPartiallyFilledOutReferenceForeignKeys(OLEPropertyConstants.ENDOWMENT_INCOME_ACCOUNT);
225         success &= checkForPartiallyFilledOutReferenceForeignKeys(OLEPropertyConstants.REPORTS_TO_ACCOUNT);
226         success &= checkForPartiallyFilledOutReferenceForeignKeys(OLEPropertyConstants.CONTRACT_CONTROL_ACCOUNT);
227 
228         return success;
229     }
230 
231     /**
232      * This method validates that the account guidelines object is valid
233      *
234      * @param accountGuideline
235      * @return true if account guideline is valid
236      */
237     protected boolean checkAccountGuidelinesValidation(AccountGuideline accountGuideline) {
238         MessageMap map = GlobalVariables.getMessageMap();
239         int errorCount = map.getErrorCount();
240         GlobalVariables.getMessageMap().addToErrorPath("document.newMaintainableObject.accountGuideline");
241         dictionaryValidationService.validateBusinessObject(accountGuideline, false);
242         GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject.accountGuideline");
243         return map.getErrorCount() == errorCount;
244     }
245 
246     /**
247      * This method determines whether the guidelines are required, based on business rules.
248      *
249      * @param account - the populated Account bo to be evaluated
250      * @return true if guidelines are required, false otherwise
251      */
252     protected boolean areGuidelinesRequired(Account account) {
253 
254         boolean result = true;
255 
256         if (account.getAccountExpirationDate() != null) {
257             Timestamp today = getDateTimeService().getCurrentTimestamp();
258             today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime());
259             if (account.getAccountExpirationDate().before(today)) {
260                 result = false;
261             }
262         }
263         return result;
264     }
265 
266     /**
267      * This method tests whether the accountNumber passed in is prefixed with an allowed prefix, or an illegal one. The illegal
268      * prefixes are passed in as an array of strings.
269      *
270      * @param accountNumber - The Account Number to be tested.
271      * @param illegalValues - An Array of Strings of the unallowable prefixes.
272      * @return false if the accountNumber starts with any of the illegalPrefixes, true otherwise
273      */
274     protected boolean accountNumberStartsWithAllowedPrefix(String accountNumber, Collection<String> illegalValues) {
275         boolean result = true;
276         for (String illegalValue : illegalValues) {
277             if (accountNumber.startsWith(illegalValue)) {
278                 result = false;
279                 putFieldError("accountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_NMBR_NOT_ALLOWED, new String[] { accountNumber, illegalValue });
280             }
281         }
282         return result;
283     }
284 
285     /**
286      * This method tests whether an account is being ReOpened by anyone except a system supervisor. Only system supervisors may
287      * reopen closed accounts.
288      *
289      * @param document - populated document containing the old and new accounts
290      * @param user - the user who is trying to possibly reopen the account
291      * @return true if: document is an edit document, old was closed and new is open, and the user is not one of the System
292      *         Supervisors
293      */
294     protected boolean isNonSystemSupervisorEditingAClosedAccount(MaintenanceDocument document, Person user) {
295         if (document.isEdit()) {
296             // do the test
297             if (oldAccount.isClosed() ) {
298                 return !getDocumentHelperService().getDocumentAuthorizer(document).isAuthorized(document, OLEConstants.PermissionNames.EDIT_INACTIVE_ACCOUNT.namespace, OLEConstants.PermissionNames.EDIT_INACTIVE_ACCOUNT.name, user.getPrincipalId());
299             }
300             return false;
301         }
302         return false;
303     }
304 
305     /**
306      * This method tests whether a given account has the T - Temporary value for Restricted Status Code, but does not have a
307      * Restricted Status Date, which is required when the code is T.
308      *
309      * @param account
310      * @return true if the account is temporarily restricted but the status date is empty
311      */
312     protected boolean hasTemporaryRestrictedStatusCodeButNoRestrictedStatusDate(Account account) {
313 
314         boolean result = false;
315 
316         if (StringUtils.isNotBlank(account.getAccountRestrictedStatusCode())) {
317             if (RESTRICTED_CD_TEMPORARILY_RESTRICTED.equalsIgnoreCase(account.getAccountRestrictedStatusCode().trim())) {
318                 if (account.getAccountRestrictedStatusDate() == null) {
319                     result = true;
320                 }
321             }
322         }
323         return result;
324     }
325 
326     /**
327      *
328      * This method is called by AccountRuleTest.testHasTemporaryRestrictedStatusCodeButRestrictedStatusDateisGreaterThanCurrentDate for checking
329      * AccountStatusDate is greaterThan or Lessthan for T-Restricted Accounts.
330      * @param account
331      * @returns boolean
332      */
333     protected boolean hasTemporaryRestrictedStatusCodeButRestrictedStatusDateisGreaterThanCurrentDate(Account account){
334 
335         boolean result = false;
336 
337         if(StringUtils.isNotBlank(account.getAccountRestrictedStatusCode())){
338             if(RESTRICTED_CD_TEMPORARILY_RESTRICTED.equalsIgnoreCase(account.getAccountRestrictedStatusCode().trim())){
339                 if(account.getAccountRestrictedStatusDate().compareTo(SpringContext.getBean(DateTimeService.class).getCurrentSqlDate())>0){
340                     result = true;
341                 }
342             }
343         }
344         return result;
345     }
346 
347 
348     /**
349      * Checks whether the account restricted status code is the default from the sub fund group.
350      *
351      * @param account
352      * @return true if the restricted status code is the same as the sub fund group's
353      */
354     protected boolean hasDefaultRestrictedStatusCode(Account account) {
355         boolean result = false;
356 
357         if (StringUtils.isNotBlank(account.getAccountRestrictedStatusCode())) {
358             result = account.getAccountRestrictedStatusCode().equals(account.getSubFundGroup().getAccountRestrictedStatusCode());
359         }
360 
361         return result;
362     }
363 
364     /**
365      * This method checks some of the general business rules associated with this document Calls the following rules:
366      * accountNumberStartsWithAllowedPrefix isNonSystemSupervisorEditingAClosedAccount
367      * hasTemporaryRestrictedStatusCodeButNoRestrictedStatusDate checkFringeBenefitAccountRule checkUserStatusAndType (on fiscal
368      * officer, supervisor and manager) ensures that the fiscal officer, supervisor and manager are not the same
369      * isContinuationAccountExpired
370      *
371      * @param maintenanceDocument
372      * @return false on rules violation
373      */
374     protected boolean checkGeneralRules(MaintenanceDocument maintenanceDocument) {
375 
376         LOG.debug("checkGeneralRules called");
377         Person fiscalOfficer = newAccount.getAccountFiscalOfficerUser();
378         Person accountManager = newAccount.getAccountManagerUser();
379         Person accountSupervisor = newAccount.getAccountSupervisoryUser();
380 
381         boolean success = true;
382 
383         // Enforce institutionally specified restrictions on account number prefixes
384         // (e.g. the account number cannot begin with a 3 or with 00.)
385         // Only bother trying if there is an account string to test
386         if (!StringUtils.isBlank(newAccount.getAccountNumber())) {
387             // test the number
388             success &= accountNumberStartsWithAllowedPrefix(newAccount.getAccountNumber(), getParameterService().getParameterValuesAsString(Account.class, ACCT_PREFIX_RESTRICTION));
389         }
390 
391         Boolean isFridgeBenefitCalculationEnable = accountService.isFridgeBenefitCalculationEnable();
392         //if parameter evaluated to true, then Labor Benefit Rate Category Code must be filled in
393         if (isFridgeBenefitCalculationEnable){
394             //check to see if the labor benefit category code is empty
395             if (ObjectUtils.isNull(newAccount.getLaborBenefitRateCategoryCode())) {
396                 putFieldError(OLEPropertyConstants.LABOR_BENEFIT_RATE_CATEGORY_CODE, OLEKeyConstants.ERROR_EMPTY_LABOR_BENEFIT_CATEGORY_CODE);
397                 success &= false;
398             }
399         }
400 
401         // only a FIS supervisor can reopen a closed account. (This is the central super user, not an account supervisor).
402         // we need to get the old maintanable doc here
403         if (isNonSystemSupervisorEditingAClosedAccount(maintenanceDocument, GlobalVariables.getUserSession().getPerson())) {
404             success &= false;
405             putFieldError("closed", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ONLY_SUPERVISORS_CAN_EDIT);
406         }
407 
408         // check FringeBenefit account rules
409         success &= checkFringeBenefitAccountRule(newAccount);
410 
411         if (ObjectUtils.isNotNull(fiscalOfficer) && fiscalOfficer.getPrincipalId() != null && !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.namespace, OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.name, fiscalOfficer.getPrincipalId())) {
412             super.putFieldError("accountFiscalOfficerUser.principalName", OLEKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {fiscalOfficer.getName(), OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.namespace, OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.name});
413 			success = false;
414         }
415         /*
416         if (ObjectUtils.isNotNull(accountSupervisor) && !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, OLEConstants.CoreModuleNamespaces.CHART, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR, accountSupervisor.getPrincipalId())) {
417             super.putFieldError("accountSupervisoryUser.principalName", OLEKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountSupervisor.getName(), OLEConstants.CoreModuleNamespaces.CHART, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR});
418 			success = false;
419         }
420         if (ObjectUtils.isNotNull(accountManager) && !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, OLEConstants.CoreModuleNamespaces.CHART, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER, accountManager.getPrincipalId())) {
421             super.putFieldError("accountManagerUser.principalName", OLEKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountManager.getName(), OLEConstants.CoreModuleNamespaces.CHART, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER});
422 			success = false;
423         }
424 */
425         // the supervisor cannot be the same as the fiscal officer or account manager.
426         if (isSupervisorSameAsFiscalOfficer(newAccount)) {
427             success &= false;
428             putFieldError("accountsSupervisorySystemsIdentifier", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_FISCAL_OFFICER);
429         }
430         if (isSupervisorSameAsManager(newAccount)) {
431             success &= false;
432             putFieldError("accountManagerSystemIdentifier", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_ACCT_MGR);
433         }
434 
435         //KFSMI-5961
436         if (ObjectUtils.isNotNull(newAccount.getContinuationFinChrtOfAcctCd()) &&
437                 ObjectUtils.isNotNull(newAccount.getAccountNumber())){
438             if (isAccountAndContinuationAccountAreSame(newAccount)){
439                 success &= false;
440                 putFieldError("continuationAccountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CONT_ACCOUNT_CANNOT_BE_SAME);
441             } else {
442                 // disallow continuation account being expired
443                 if (isContinuationAccountExpired(newAccount)) {
444                     success &= false;
445                     putFieldError("continuationAccountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_EXPIRED_CONTINUATION);
446                 }
447             }
448         }
449         return success;
450     }
451 
452     /**
453      * This method tests whether the account and continuation account are same.
454      *
455      * @param newAccount
456      * @return true if the account and continuation account are same
457      */
458     protected boolean isAccountAndContinuationAccountAreSame(Account newAccount) {
459 
460         return (newAccount.getChartOfAccountsCode().equals(newAccount.getContinuationFinChrtOfAcctCd()))
461                 && (newAccount.getAccountNumber().equals(newAccount.getContinuationAccountNumber()));
462     }
463 
464     /**
465      * This method tests whether the continuation account entered (if any) has expired or not.
466      *
467      * @param newAccount
468      * @return true if continuation account has expired
469      */
470     protected boolean isContinuationAccountExpired(Account newAccount) {
471 
472         boolean result = false;
473 
474         String chartCode = newAccount.getContinuationFinChrtOfAcctCd();
475         String accountNumber = newAccount.getContinuationAccountNumber();
476 
477         // if either chartCode or accountNumber is not entered, then we
478         // can't continue, so exit
479         if (StringUtils.isBlank(chartCode) || StringUtils.isBlank(accountNumber)) {
480             return result;
481         }
482 
483         // attempt to retrieve the continuation account from the DB
484         Account continuation = accountService.getByPrimaryId(chartCode, accountNumber);
485 
486         // if the object doesn't exist, then we can't continue, so exit
487         if (ObjectUtils.isNull(continuation)) {
488             return result;
489         }
490 
491         // at this point, we have a valid continuation account, so we just need to
492         // know whether its expired or not
493         result = continuation.isExpired();
494 
495         return result;
496     }
497 
498     /**
499      * the fringe benefit account (otherwise known as the reportsToAccount) is required if the fringe benefit code is set to N. The
500      * fringe benefit code of the account designated to accept the fringes must be Y.
501      *
502      * @param newAccount
503      * @return
504      */
505 
506     protected boolean checkFringeBenefitAccountRule(Account newAccount) {
507 
508         boolean result = true;
509 
510 
511         // if this account is selected as a Fringe Benefit Account, then we have nothing
512         // to test, so exit
513         if (newAccount.isAccountsFringesBnftIndicator()) {
514             return true;
515         }
516 /*mah
517         // if fringe benefit is not selected ... continue processing
518 
519         // fringe benefit account number is required
520         if (StringUtils.isBlank(newAccount.getReportsToAccountNumber())) {
521             putFieldError("reportsToAccountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_REQUIRED_IF_FRINGEBENEFIT_FALSE);
522             result &= false;
523         }
524 
525         // fringe benefit chart of accounts code is required
526         if (StringUtils.isBlank(newAccount.getReportsToChartOfAccountsCode())) {
527             putFieldError("reportsToChartOfAccountsCode", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_REQUIRED_IF_FRINGEBENEFIT_FALSE);
528             result &= false;
529         }
530 
531         // if either of the fringe benefit account fields are not present, then we're done
532         if (result == false) {
533             return result;
534         }
535 
536         // attempt to load the fringe benefit account
537         Account fringeBenefitAccount = accountService.getByPrimaryId(newAccount.getReportsToChartOfAccountsCode(), newAccount.getReportsToAccountNumber());
538 
539         // fringe benefit account must exist
540         if (fringeBenefitAccount == null) {
541             putFieldError("reportsToAccountNumber", OLEKeyConstants.ERROR_EXISTENCE, getFieldLabel(Account.class, "reportsToAccountNumber"));
542             return false;
543         }
544 
545         // fringe benefit account must be active
546         if (!fringeBenefitAccount.isActive()) {
547             putFieldError("reportsToAccountNumber", OLEKeyConstants.ERROR_INACTIVE, getFieldLabel(Account.class, "reportsToAccountNumber"));
548             result &= false;
549         }
550 
551         // make sure the fringe benefit account specified is set to fringe benefits = Y
552         if (!fringeBenefitAccount.isAccountsFringesBnftIndicator()) {
553             putFieldError("reportsToAccountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_MUST_BE_FLAGGED_FRINGEBENEFIT, fringeBenefitAccount.getChartOfAccountsCode() + "-" + fringeBenefitAccount.getAccountNumber());
554             result &= false;
555         }
556 */
557         return true;
558     }
559 
560     /**
561      * This method is a helper method for checking if the supervisor user is the same as the fiscal officer Calls
562      * {@link AccountRule#areTwoUsersTheSame(Person, Person)}
563      *
564      * @param accountGlobals
565      * @return true if the two users are the same
566      */
567     protected boolean isSupervisorSameAsFiscalOfficer(Account account) {
568         return areTwoUsersTheSame(account.getAccountSupervisoryUser(), account.getAccountFiscalOfficerUser());
569     }
570 
571     /**
572      * This method is a helper method for checking if the supervisor user is the same as the manager Calls
573      * {@link AccountRule#areTwoUsersTheSame(Person, Person)}
574      *
575      * @param accountGlobals
576      * @return true if the two users are the same
577      */
578     protected boolean isSupervisorSameAsManager(Account account) {
579         return areTwoUsersTheSame(account.getAccountSupervisoryUser(), account.getAccountManagerUser());
580     }
581 
582     /**
583      * This method checks to see if two users are the same Person using their identifiers
584      *
585      * @param user1
586      * @param user2
587      * @return true if these two users are the same
588      */
589     protected boolean areTwoUsersTheSame(Person user1, Person user2) {
590         if (ObjectUtils.isNull(user1) || user1.getPrincipalId() == null ) {
591             return false;
592         }
593         if (ObjectUtils.isNull(user2) || user2.getPrincipalId() == null ) {
594             return false;
595         }
596         return user1.getPrincipalId().equals(user2.getPrincipalId());
597     }
598 
599     /**
600      * This method checks to see if the user is trying to close the account and if so if any rules are being violated Calls the
601      * additional rule checkAccountExpirationDateValidTodayOrEarlier
602      *
603      * @param maintenanceDocument
604      * @return false on rules violation
605      */
606     protected boolean checkCloseAccount(MaintenanceDocument maintenanceDocument) {
607 
608         LOG.debug("checkCloseAccount called");
609 
610         boolean success = true;
611         boolean isBeingClosed = false;
612 
613         // if the account isnt being closed, then dont bother processing the rest of
614         // the method
615         if (oldAccount.isActive() && !newAccount.isActive()) {
616             isBeingClosed = true;
617         }
618 
619         if (!isBeingClosed) {
620             return true;
621         }
622 
623         // on an account being closed, the expiration date must be
624         success &= checkAccountExpirationDateValidTodayOrEarlier(newAccount);
625 
626         // when closing an account, a continuation account is required
627         if (StringUtils.isBlank(newAccount.getContinuationAccountNumber())) {
628             putFieldError("continuationAccountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CLOSE_CONTINUATION_ACCT_REQD);
629             success &= false;
630         }
631         if (StringUtils.isBlank(newAccount.getContinuationFinChrtOfAcctCd())) {
632             putFieldError("continuationFinChrtOfAcctCd", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CLOSE_CONTINUATION_ACCT_REQD);
633             success &= false;
634         }
635 
636         // must have no pending ledger entries
637         if (generalLedgerPendingEntryService.hasPendingGeneralLedgerEntry(newAccount)) {
638             putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_PENDING_LEDGER_ENTRIES);
639             success &= false;
640         }
641 
642         // beginning balance must be loaded in order to close account
643         if (!balanceService.beginningBalanceLoaded(newAccount)) {
644             putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_NO_LOADED_BEGINNING_BALANCE);
645             success &= false;
646         }
647 
648         // must have no base budget, must have no open encumbrances, must have no asset, liability or fund balance balances other
649         // than object code 9899
650         // (9899 is fund balance for us), and the process of closing income and expense into 9899 must take the 9899 balance to
651         // zero.
652         if (balanceService.hasAssetLiabilityFundBalanceBalances(newAccount)) {
653             putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_NO_FUND_BALANCES);
654             success &= false;
655         }
656 
657         return success;
658     }
659 
660     /**
661      * This method checks to see if the account expiration date is today's date or earlier
662      *
663      * @param newAccount
664      * @return fails if the expiration date is null or after today's date
665      */
666     protected boolean checkAccountExpirationDateValidTodayOrEarlier(Account newAccount) {
667 
668         // get today's date, with no time component
669         Date todaysDate = new Date(getDateTimeService().getCurrentDate().getTime());
670         todaysDate.setTime(DateUtils.truncate(todaysDate, Calendar.DAY_OF_MONTH).getTime());
671         // TODO: convert this to using Wes' Kuali KfsDateUtils once we're using Date's instead of Timestamp
672 
673         // get the expiration date, if any
674         Date expirationDate = newAccount.getAccountExpirationDate();
675         if (ObjectUtils.isNull(expirationDate)) {
676             putFieldError("accountExpirationDate", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID);
677             return false;
678         }
679 
680         // when closing an account, the account expiration date must be the current date or earlier
681         expirationDate.setTime(DateUtils.truncate(expirationDate, Calendar.DAY_OF_MONTH).getTime());
682         if (expirationDate.after(todaysDate)) {
683             putFieldError("accountExpirationDate", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID);
684             return false;
685         }
686 
687         return true;
688     }
689 
690     /**
691      * This method checks to see if any Contracts and Grants business rules were violated Calls the following sub-rules:
692      * checkCgRequiredFields checkCgIncomeStreamRequired
693      *
694      * @param maintenanceDocument
695      * @return false on rules violation
696      */
697     protected boolean checkContractsAndGrants(MaintenanceDocument maintenanceDocument) {
698 
699         LOG.debug("checkContractsAndGrants called");
700 
701         boolean success = true;
702 
703         // Certain C&G fields are required if the Account belongs to the CG Fund Group
704         success &= checkCgRequiredFields(newAccount);
705 
706         // Income Stream account is required if this account is CG fund group,
707         // or GF (general fund) fund group (with some exceptions)
708         success &= checkIncomeStreamValid(newAccount);
709 
710         // check if the new account has a valid responsibility id
711         if (!ObjectUtils.isNull(newAccount)) {
712             final boolean hasValidAccountResponsibility = contractsAndGrantsModuleService.hasValidAccountReponsiblityIdIfNotNull(newAccount);
713             if (!hasValidAccountResponsibility) {
714                 success &= hasValidAccountResponsibility;
715                 putFieldError("contractsAndGrantsAccountResponsibilityId", OLEKeyConstants.ERROR_DOCUMENT_ACCTMAINT_INVALID_CG_RESPONSIBILITY , new String[] { newAccount.getContractsAndGrantsAccountResponsibilityId().toString(), newAccount.getChartOfAccountsCode(), newAccount.getAccountNumber() });
716             }
717         }
718 
719         return success;
720     }
721 
722     /**
723      * This method checks to see if the income stream account is required
724      *
725      * @param newAccount
726      * @return fails if it is required and not entered, or not valid
727      */
728     protected boolean checkIncomeStreamValid(Account newAccount) {
729         // if the subFundGroup object is null, we can't test, so exit
730         if (ObjectUtils.isNull(newAccount.getSubFundGroup())) {
731             return true;
732         }
733 
734         boolean valid = true;
735 
736         //if subfundgroupcode and fundgroup code are blanks
737         if (StringUtils.isNotBlank(newAccount.getSubFundGroupCode()) && StringUtils.isNotBlank(newAccount.getSubFundGroup().getFundGroupCode())) {
738         String subFundGroupCode = newAccount.getSubFundGroupCode().trim();
739         String fundGroupCode = newAccount.getSubFundGroup().getFundGroupCode().trim();
740 
741             if (/*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(Account.class, OLEConstants.ChartApcParms.INCOME_STREAM_ACCOUNT_REQUIRING_FUND_GROUPS, fundGroupCode).evaluationSucceeds()) {
742                 if (/*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(Account.class, OLEConstants.ChartApcParms.INCOME_STREAM_ACCOUNT_REQUIRING_SUB_FUND_GROUPS, subFundGroupCode).evaluationSucceeds()) {
743                 if (StringUtils.isBlank(newAccount.getIncomeStreamFinancialCoaCode())) {
744                     putFieldError(OLEPropertyConstants.INCOME_STREAM_CHART_OF_ACCOUNTS_CODE, OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_INCOME_STREAM_ACCT_COA_CANNOT_BE_EMPTY, new String[] { getDdService().getAttributeLabel(FundGroup.class, OLEConstants.FUND_GROUP_CODE_PROPERTY_NAME), fundGroupCode, getDdService().getAttributeLabel(SubFundGroup.class, OLEConstants.SUB_FUND_GROUP_CODE_PROPERTY_NAME), subFundGroupCode });
745                     valid = false;
746                 }
747                 if (StringUtils.isBlank(newAccount.getIncomeStreamAccountNumber())) {
748                     putFieldError(OLEPropertyConstants.INCOME_STREAM_ACCOUNT_NUMBER, OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_INCOME_STREAM_ACCT_NBR_CANNOT_BE_EMPTY, new String[] { getDdService().getAttributeLabel(FundGroup.class, OLEConstants.FUND_GROUP_CODE_PROPERTY_NAME), fundGroupCode, getDdService().getAttributeLabel(SubFundGroup.class, OLEConstants.SUB_FUND_GROUP_CODE_PROPERTY_NAME), subFundGroupCode});
749                     valid = false;
750                 }
751             }
752         }
753 
754         if (valid && (StringUtils.isNotBlank(newAccount.getIncomeStreamFinancialCoaCode()) || StringUtils.isNotBlank(newAccount.getIncomeStreamAccountNumber()))) {
755                 if(!(StringUtils.equals( newAccount.getIncomeStreamAccountNumber(), newAccount.getAccountNumber())
756                         && StringUtils.equals( newAccount.getIncomeStreamFinancialCoaCode(), newAccount.getChartOfAccountsCode()))) {
757                 if (!super.getDictionaryValidationService().validateReferenceExists(newAccount, OLEPropertyConstants.INCOME_STREAM_ACCOUNT)) {
758                     putFieldError(OLEPropertyConstants.INCOME_STREAM_ACCOUNT_NUMBER, OLEKeyConstants.ERROR_EXISTENCE, new StringBuffer(getDdService().getAttributeLabel(SubFundGroup.class, OLEPropertyConstants.INCOME_STREAM_ACCOUNT_NUMBER)).append(": ").append(newAccount.getIncomeStreamFinancialCoaCode()).append("-").append(newAccount.getIncomeStreamAccountNumber()).toString());
759                     valid = false;
760                 }
761             }
762         }
763         }
764 
765         return valid;
766     }
767 
768     /**
769      * This method checks to make sure that if the contracts and grants fields are required they are entered correctly
770      *
771      * @param newAccount
772      * @return
773      */
774     protected boolean checkCgRequiredFields(Account newAccount) {
775 
776         boolean result = true;
777 
778         // Certain C&G fields are required if the Account belongs to the CG Fund Group
779         if (ObjectUtils.isNotNull(newAccount.getSubFundGroup())) {
780             if (getSubFundGroupService().isForContractsAndGrants(newAccount.getSubFundGroup())) {
781                 result &= checkEmptyBOField("acctIndirectCostRcvyTypeCd", newAccount.getAcctIndirectCostRcvyTypeCd(), replaceTokens(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_TYPE_CODE_CANNOT_BE_EMPTY));
782                 result &= checkEmptyBOField("financialIcrSeriesIdentifier", newAccount.getFinancialIcrSeriesIdentifier(), replaceTokens(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_SERIES_IDENTIFIER_CANNOT_BE_EMPTY));
783 
784                 // Validation for financialIcrSeriesIdentifier
785                 if (checkEmptyBOField(OLEPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, newAccount.getFinancialIcrSeriesIdentifier(), replaceTokens(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_SERIES_IDENTIFIER_CANNOT_BE_EMPTY))) {
786                     String fiscalYear = StringUtils.EMPTY + SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear();
787                     String icrSeriesId = newAccount.getFinancialIcrSeriesIdentifier();
788 
789                     Map<String, String> pkMap = new HashMap<String, String>();
790                     pkMap.put(OLEPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear);
791                     pkMap.put(OLEPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, icrSeriesId);
792                     Collection<IndirectCostRecoveryRateDetail> icrRateDetails = getBoService().findMatching(IndirectCostRecoveryRateDetail.class, pkMap);
793 
794                     if (ObjectUtils.isNull(icrRateDetails) || icrRateDetails.isEmpty()) {
795                         String label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(Account.class, OLEPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER);
796                         putFieldError(OLEPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, OLEKeyConstants.ERROR_EXISTENCE, label + " (" + icrSeriesId + ")");
797                         result &= false;
798                     }
799                     else {
800                         for(IndirectCostRecoveryRateDetail icrRateDetail : icrRateDetails) {
801                             if(ObjectUtils.isNull(icrRateDetail.getIndirectCostRecoveryRate())){
802                                 putFieldError(OLEPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, OLEKeyConstants.IndirectCostRecovery.ERROR_DOCUMENT_ICR_RATE_NOT_FOUND, new String[]{fiscalYear, icrSeriesId});
803                                 result &= false;
804                                 break;
805                             }
806                         }
807                     }
808                 }
809 
810               //check the ICR collection exists
811                 result &= checkICRCollectionExistWithErrorMessage(true, OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_CHART_CODE_CANNOT_BE_EMPTY,
812                         replaceTokens(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_CHART_CODE_CANNOT_BE_EMPTY));
813                 result &= checkContractControlAccountNumberRequired(newAccount);
814 
815             }
816             else {
817                 // this is not a C&G fund group. So users should not fill in any fields in the C&G tab.
818                 result &= checkCGFieldNotFilledIn(newAccount, "acctIndirectCostRcvyTypeCd");
819                 result &= checkCGFieldNotFilledIn(newAccount, "financialIcrSeriesIdentifier");
820 
821               //check the ICR collection NOT exists
822                 result &= checkICRCollectionExistWithErrorMessage(false, OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_CG_ICR_FIELDS_FILLED_FOR_NON_CG_ACCOUNT, newAccount.getSubFundGroupCode());
823 
824             }
825         }
826         return result;
827     }
828 
829     /**
830      * This method is a helper method that replaces error tokens with values for contracts and grants labels
831      *
832      * @param errorConstant
833      * @return error string that has had tokens "{0}" and "{1}" replaced
834      */
835     protected String replaceTokens(String errorConstant) {
836         String cngLabel = getSubFundGroupService().getContractsAndGrantsDenotingAttributeLabel();
837         String cngValue = getSubFundGroupService().getContractsAndGrantsDenotingValueForMessage();
838         String result = getConfigService().getPropertyValueAsString(errorConstant);
839         result = StringUtils.replace(result, "{0}", cngLabel);
840         result = StringUtils.replace(result, "{1}", cngValue);
841         return result;
842     }
843 
844     /**
845      * This method checks to make sure that if the contract control account exists it is the same as the Account that we are working
846      * on
847      *
848      * @param newAccount
849      * @return false if the contract control account is entered and is not the same as the account we are maintaining
850      */
851     protected boolean checkContractControlAccountNumberRequired(Account newAccount) {
852 
853         boolean result = true;
854 
855         // Contract Control account must either exist or be the same as account being maintained
856 
857         if (ObjectUtils.isNull(newAccount.getContractControlFinCoaCode())) {
858             return result;
859         }
860         if (ObjectUtils.isNull(newAccount.getContractControlAccountNumber())) {
861             return result;
862         }
863         if ((newAccount.getContractControlFinCoaCode().equals(newAccount.getChartOfAccountsCode())) && (newAccount.getContractControlAccountNumber().equals(newAccount.getAccountNumber()))) {
864             return result;
865         }
866 
867         // do an existence/active test
868         DictionaryValidationService dvService = super.getDictionaryValidationService();
869         boolean referenceExists = dvService.validateReferenceExists(newAccount, "contractControlAccount");
870         if (!referenceExists) {
871             putFieldError("contractControlAccountNumber", OLEKeyConstants.ERROR_EXISTENCE, "Contract Control Account: " + newAccount.getContractControlFinCoaCode() + "-" + newAccount.getContractControlAccountNumber());
872             result &= false;
873         }
874 
875         return result;
876     }
877 
878     /**
879      * This method checks to see if any expiration date field rules were violated
880      *
881      * @param maintenanceDocument
882      * @return false on rules violation
883      */
884     protected boolean checkExpirationDate(MaintenanceDocument maintenanceDocument) {
885 
886         LOG.debug("checkExpirationDate called");
887 
888         boolean success = true;
889 
890         Date oldExpDate = oldAccount.getAccountExpirationDate();
891         Date newExpDate = newAccount.getAccountExpirationDate();
892         Date today = new Date(getDateTimeService().getCurrentTimestamp().getTime());
893         today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); // remove any time components
894 
895         // When updating an account expiration date, the date must be today or later
896         // Only run this test if this maintenance doc
897         // is an edit doc
898 
899        //MSU Contribution OLEMI-8567 DTT-565 OLECNTRB-972
900         if (isUpdatedExpirationDateInvalid(maintenanceDocument)) {
901             Account newAccount = (Account) maintenanceDocument.getNewMaintainableObject().getBusinessObject();
902             if(newAccount.isClosed()){
903                 /*If the Account is being closed and the date is before today's date, the EXP date can only be today*/
904                 putFieldError("accountExpirationDate", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID);
905             }
906             else{
907                 /*If the Account is not being closed and the date is before today's date, the EXP date can only be today or at a later date*/
908                 putFieldError("accountExpirationDate", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
909             }
910             success &= false;
911         }
912 
913         // a continuation account is required if the expiration date is completed.
914         if (ObjectUtils.isNotNull(newExpDate)) {
915             if (StringUtils.isBlank(newAccount.getContinuationAccountNumber())) {
916                 putFieldError("continuationAccountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED);
917             }
918             if (StringUtils.isBlank(newAccount.getContinuationFinChrtOfAcctCd())) {
919                 putFieldError("continuationFinChrtOfAcctCd", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_FINCODE_REQD_IF_EXP_DATE_COMPLETED);
920                 // putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED);
921                 success &= false;
922             }
923         }
924 
925         // If creating a new account if acct_expiration_dt is set then
926         // the acct_expiration_dt must be changed to a date that is today or later
927         if (maintenanceDocument.isNew() && ObjectUtils.isNotNull(newExpDate)) {
928             if (!newExpDate.after(today) && !newExpDate.equals(today)) {
929                 putFieldError("accountExpirationDate", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
930                 // putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
931                 success &= false;
932             }
933         }
934 
935         // acct_expiration_dt can not be before acct_effect_dt
936         Date effectiveDate = newAccount.getAccountEffectiveDate();
937         if (ObjectUtils.isNotNull(effectiveDate) && ObjectUtils.isNotNull(newExpDate)) {
938             if (newExpDate.before(effectiveDate)) {
939                 putFieldError("accountExpirationDate", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE);
940                 // putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE);
941                 success &= false;
942             }
943         }
944 
945         return success;
946     }
947 
948     /**
949      * This method checks to see if the new expiration date is different from the old expiration and if it has if it is invalid
950      *
951      * @param maintDoc
952      * @return true if expiration date has changed and is invalid
953      */
954     protected boolean isUpdatedExpirationDateInvalid(MaintenanceDocument maintDoc) {
955 
956         // if this isn't an Edit document, we're not interested
957         if (!maintDoc.isEdit()) {
958             return false;
959         }
960 
961         Date oldExpDate = oldAccount.getAccountExpirationDate();
962         Date newExpDate = newAccount.getAccountExpirationDate();
963         Date today = new Date(getDateTimeService().getCurrentDate().getTime());
964         today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); // remove any time components
965 
966         // When updating an account expiration date, the date must be today or later
967         // Only run this test if this maintenance doc
968         // is an edit doc
969         boolean expDateHasChanged = false;
970 
971         // if the old version of the account had no expiration date, and the new
972         // one has a date
973         if (ObjectUtils.isNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) {
974             expDateHasChanged = true;
975         }
976 
977         // if there was an old and a new expDate, but they're different
978         else if (ObjectUtils.isNotNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) {
979             if (!oldExpDate.equals(newExpDate)) {
980                 expDateHasChanged = true;
981             }
982         }
983 
984         // if the expiration date hasn't changed, we're not interested
985         if (!expDateHasChanged) {
986             return false;
987         }
988 
989         // make a shortcut to the newAccount
990         Account newAccount = (Account) maintDoc.getNewMaintainableObject().getBusinessObject();
991 
992         // expirationDate must be today or later than today (cannot be before today)
993         if (newExpDate.equals(today) || newExpDate.after(today)) {
994             return false;
995         }
996         else {
997             return true;
998     }
999     }
1000 
1001     /**
1002      * This method checks to see if any Fund Group rules were violated Specifically: if we are dealing with a "GF" (General Fund) we
1003      * cannot have an account with a budget recording level of "M" (Mixed)
1004      *
1005      * @param maintenanceDocument
1006      * @return false on rules violation
1007      */
1008     protected boolean checkFundGroup(MaintenanceDocument maintenanceDocument) {
1009 
1010         LOG.debug("checkFundGroup called");
1011 
1012         boolean success = true;
1013         SubFundGroup subFundGroup = newAccount.getSubFundGroup();
1014 
1015         if (ObjectUtils.isNotNull(subFundGroup)) {
1016 
1017             // get values for fundGroupCode and restrictedStatusCode
1018             String fundGroupCode = "";
1019             String restrictedStatusCode = "";
1020             if (StringUtils.isNotBlank(subFundGroup.getFundGroupCode())) {
1021                 fundGroupCode = subFundGroup.getFundGroupCode().trim();
1022             }
1023             if (StringUtils.isNotBlank(newAccount.getAccountRestrictedStatusCode())) {
1024                 restrictedStatusCode = newAccount.getAccountRestrictedStatusCode().trim();
1025             }
1026         }
1027 
1028         return success;
1029     }
1030 
1031     /**
1032      * This method checks to see if any SubFund Group rules were violated Specifically: if SubFundGroup is empty or not "PFCMR" we
1033      * cannot have a campus code or building code if SubFundGroup is "PFCMR" then campus code and building code "must" be entered
1034      * and be valid codes
1035      *
1036      * @param maintenanceDocument
1037      * @return false on rules violation
1038      */
1039     protected boolean checkSubFundGroup(MaintenanceDocument maintenanceDocument) {
1040 
1041         LOG.debug("checkSubFundGroup called");
1042 
1043         boolean success = true;
1044 
1045         String subFundGroupCode = newAccount.getSubFundGroupCode();
1046 
1047         if (newAccount.getAccountDescription() != null) {
1048 
1049             String campusCode = newAccount.getAccountDescription().getCampusCode();
1050             String buildingCode = newAccount.getAccountDescription().getBuildingCode();
1051 
1052             // check if sub fund group code is blank
1053             if (StringUtils.isBlank(subFundGroupCode)) {
1054 
1055                 // check if campus code and building code are NOT blank
1056                 if (!StringUtils.isBlank(campusCode) || !StringUtils.isBlank(buildingCode)) {
1057 
1058                     // if sub_fund_grp_cd is blank, campus code should NOT be entered
1059                     if (!StringUtils.isBlank(campusCode)) {
1060                         putFieldError("accountDescription.campusCode", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_BLANK_SUBFUNDGROUP_WITH_CAMPUS_CD_FOR_BLDG, subFundGroupCode);
1061                         success &= false;
1062                     }
1063 
1064                     // if sub_fund_grp_cd is blank, then bldg_cd should NOT be entered
1065                     if (!StringUtils.isBlank(buildingCode)) {
1066                         putFieldError("accountDescription.buildingCode", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_BLANK_SUBFUNDGROUP_WITH_BUILDING_CD, subFundGroupCode);
1067                         success &= false;
1068                     }
1069 
1070                 }
1071                 else {
1072 
1073                     // if all sub fund group, campus code, building code are all blank return true
1074                     return success;
1075                 }
1076 
1077             }
1078             else if (!StringUtils.isBlank(subFundGroupCode) && !ObjectUtils.isNull(newAccount.getSubFundGroup())) {
1079 
1080                 // Attempt to get the right SubFundGroup code to check the following logic with. If the value isn't available, go
1081                 // ahead
1082                 // and die, as this indicates a mis-configured application, and important business rules wont be implemented without it.
1083                 ParameterEvaluator evaluator = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(Account.class, ACCT_CAPITAL_SUBFUNDGROUP, subFundGroupCode.trim());
1084 
1085                 if (evaluator.evaluationSucceeds()) {
1086 
1087                     // if sub_fund_grp_cd is 'PFCMR' then campus_cd must be entered
1088                     if (StringUtils.isBlank(campusCode)) {
1089                         putFieldError("accountDescription.campusCode", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_CAMS_SUBFUNDGROUP_WITH_MISSING_CAMPUS_CD_FOR_BLDG, subFundGroupCode);
1090                         success &= false;
1091                     }
1092 
1093                     // if sub_fund_grp_cd is 'PFCMR' then bldg_cd must be entered
1094                     if (StringUtils.isBlank(buildingCode)) {
1095                         putFieldError("accountDescription.buildingCode", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_CAMS_SUBFUNDGROUP_WITH_MISSING_BUILDING_CD, subFundGroupCode);
1096                         success &= false;
1097                     }
1098 
1099                     // the building object (campusCode & buildingCode) must exist in the DB
1100                     if (!StringUtils.isBlank(campusCode) && !StringUtils.isBlank(buildingCode)) {
1101 
1102                         // make sure that primary key fields are upper case
1103                         org.kuali.rice.krad.service.DataDictionaryService dds = getDdService();
1104                         Boolean buildingCodeForceUppercase = dds.getAttributeForceUppercase(AccountDescription.class, OLEPropertyConstants.BUILDING_CODE);
1105                         if (StringUtils.isNotBlank(buildingCode) && buildingCodeForceUppercase != null && buildingCodeForceUppercase.booleanValue() == true) {
1106                             buildingCode = buildingCode.toUpperCase();
1107                         }
1108 
1109                         Boolean campusCodeForceUppercase = dds.getAttributeForceUppercase(AccountDescription.class, OLEPropertyConstants.CAMPUS_CODE);
1110                         if (StringUtils.isNotBlank(campusCode) && campusCodeForceUppercase != null && campusCodeForceUppercase.booleanValue() == true) {
1111                             campusCode = campusCode.toUpperCase();
1112                         }
1113 
1114                         Map<String, String> pkMap = new HashMap<String, String>();
1115                         pkMap.put("campusCode", campusCode);
1116                         pkMap.put("buildingCode", buildingCode);
1117 
1118                         Building building = getBoService().findByPrimaryKey(Building.class, pkMap);
1119                         if (building == null) {
1120                             putFieldError("accountDescription.campusCode", OLEKeyConstants.ERROR_EXISTENCE, campusCode);
1121                             putFieldError("accountDescription.buildingCode", OLEKeyConstants.ERROR_EXISTENCE, buildingCode);
1122                             success &= false;
1123                         }
1124                     }
1125                 }
1126                 else {
1127 
1128                     // if sub_fund_grp_cd is NOT 'PFCMR', campus code should NOT be entered
1129                     if (!StringUtils.isBlank(campusCode)) {
1130                         putFieldError("accountDescription.campusCode", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_NONCAMS_SUBFUNDGROUP_WITH_CAMPUS_CD_FOR_BLDG, subFundGroupCode);
1131                         success &= false;
1132                     }
1133 
1134                     // if sub_fund_grp_cd is NOT 'PFCMR' then bldg_cd should NOT be entered
1135                     if (!StringUtils.isBlank(buildingCode)) {
1136                         putFieldError("accountDescription.buildingCode", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_NONCAMS_SUBFUNDGROUP_WITH_BUILDING_CD, subFundGroupCode);
1137                         success &= false;
1138                     }
1139                 }
1140             }
1141 
1142         }
1143 
1144         return success;
1145     }
1146 
1147    /**
1148     * This method checks whether the sun fund group is associated with the vendor name or not
1149     * @param maintenanceDocument
1150     * @return false if the sub fund group is not associated with the vendor name selected
1151     */
1152     protected boolean checkVendorNameForSubFundGroup(MaintenanceDocument maintenanceDocument) {
1153         LOG.debug("checkVendorName called");
1154         boolean success = true;
1155         if (StringUtils.isNotBlank(newAccount.getSubFundGroupCode())) {
1156             String fundGroupCode=null;
1157             String subFundGroupCode = newAccount.getSubFundGroupCode();
1158             SubFundGroup subFundGroup = getBoService().findBySinglePrimaryKey(SubFundGroup.class, subFundGroupCode);
1159             if(subFundGroup!=null){
1160                 fundGroupCode = newAccount.getSubFundGroup().getFundGroupCode();
1161             }
1162             String fundGroupParameter = getParameterService().getParameterValueAsString(Account.class, OleSelectConstant.FUND_GRP_CD);
1163             String subFundGroupParameter = getParameterService().getParameterValueAsString(Account.class,
1164             		OleSelectConstant.SUB_FUND_GRP_CD);
1165             if ((subFundGroupCode.equalsIgnoreCase(subFundGroupParameter) && fundGroupCode
1166                     .equalsIgnoreCase(fundGroupParameter))) {
1167                 if (newAccount.getVendorHeaderGeneratedIdentifier() != null) {
1168                     return success;
1169                 }
1170                 putFieldError(OleSelectConstant.VENDOR_NAME,
1171                         OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_VENDOR_NAME_NEEDED_FOR_SELECTED_SUBFUNDGROUP);
1172                 success = false;
1173                 return success;
1174             }
1175             else if (newAccount.getVendorHeaderGeneratedIdentifier() != null) {
1176                 putFieldError(OleSelectConstant.VENDOR_NAME,
1177                         OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_INVALID_SUBFUNDGROUP_WITH_VENDOR_NAME, subFundGroupCode);
1178                 success = false;
1179                 return success;
1180             }
1181         }
1182         return success;
1183     }
1184 
1185 
1186     /**
1187      * the income stream account is required if account's sub fund group code's fund group code is either GF or CG.
1188      *
1189      * @param newAccount
1190      * @return true if fund group code (obtained through sub fund group) is in the system parameter INCOME_STREAM_ACCOUNT_REQUIRING_FUND_GROUPS (values GF;CG)
1191      * else return false.
1192      */
1193     protected boolean checkIncomeStreamAccountRule() {
1194         // KFSMI-4877: if fund group is in system parameter values then income stream account number must exist.
1195         if ( ObjectUtils.isNotNull(newAccount.getSubFundGroup()) && StringUtils.isNotBlank(newAccount.getSubFundGroup().getFundGroupCode())) {
1196             if (ObjectUtils.isNull(newAccount.getIncomeStreamAccount())) {
1197                 String incomeStreamRequiringFundGroupCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(Account.class, OLEConstants.ChartApcParms.INCOME_STREAM_ACCOUNT_REQUIRING_FUND_GROUPS);
1198                 if (StringUtils.containsIgnoreCase(newAccount.getSubFundGroup().getFundGroupCode(), incomeStreamRequiringFundGroupCode)) {
1199                     GlobalVariables.getMessageMap().putError(OLEPropertyConstants.ACCOUNT_NUMBER, OLEKeyConstants.ERROR_DOCUMENT_BA_NO_INCOME_STREAM_ACCOUNT, newAccount.getAccountNumber());
1200                     return false;
1201                 }
1202             }
1203         }
1204         return true;
1205     }
1206 
1207     /**
1208      * This method checks to see if the contracts and grants fields are filled in or not
1209      *
1210      * @param account
1211      * @param propertyName - property to attach error to
1212      * @return false if the contracts and grants fields are blank
1213      */
1214     protected boolean checkCGFieldNotFilledIn(Account account, String propertyName) {
1215         boolean success = true;
1216         Object value = ObjectUtils.getPropertyValue(account, propertyName);
1217         if ((value instanceof String && !StringUtils.isBlank(value.toString())) || (value != null)) {
1218             success = false;
1219             putFieldError(propertyName, OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_CG_FIELDS_FILLED_FOR_NON_CG_ACCOUNT, new String[] { account.getSubFundGroupCode() });
1220         }
1221 
1222         return success;
1223     }
1224 
1225     /**
1226      * This method checks to see if account is allowed to cross chart;
1227      * and if not makes sure that the account number is unique in the whole system.
1228      * This checking is only needed when adding a new account,
1229      * since users are not allowed to change account numbers on editing.
1230      *
1231      * @param maintenanceDocument
1232      * @return false on account-cross-chart rule violation
1233      */
1234     protected boolean checkUniqueAccountNumber(MaintenanceDocument maintenanceDocument) {
1235         boolean success = true;
1236         String accountNumber = newAccount.getAccountNumber();
1237 
1238         if (maintenanceDocument.isNew() && // if adding a new account
1239                 // while account is not allowed to cross chart
1240                 !accountService.accountsCanCrossCharts() &&
1241                 // and with an account number that already exists
1242                 !accountService.getAccountsForAccountNumber(accountNumber).isEmpty()) {
1243             // report error
1244             success = false;
1245             putFieldError("accountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_NMBR_NOT_UNIQUE, accountNumber);
1246         }
1247 
1248         return success;
1249     }
1250 
1251     protected boolean checkOpenEncumbrances() {
1252         boolean success = true;
1253         if(!oldAccount.isClosed() && newAccount.isClosed()){
1254             Map<String, String> pkMap = new HashMap<String, String>();
1255             pkMap.put(OLEPropertyConstants.ACCOUNT_NUMBER, oldAccount.getAccountNumber());
1256             int encumbranceCount = getEncumbranceService().getOpenEncumbranceRecordCount(pkMap, false);
1257             if ( encumbranceCount > 0){
1258                 success = false;
1259                 putFieldError("closed", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CANNOT_CLOSE_OPEN_ENCUMBRANCE);
1260                 //putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CANNOT_CLOSE_OPEN_ENCUMBRANCE);
1261             }
1262         }
1263         return success;
1264     }
1265 
1266     /**
1267      *
1268      * This method is modified for jira OLE-2356
1269      * This method checks whether AccountRestrictedStatusDate is GreaterThan Currentdate or not displays error message if date is null or lessthan currentdate.
1270      * @return boolean
1271      */
1272     protected boolean checkAccountRestrictedStatusDateIsGreaterThanCurrentDate(){
1273         boolean success = true;
1274         if(StringUtils.isNotBlank(newAccount.getAccountRestrictedStatusCode())){
1275             if(RESTRICTED_CD_TEMPORARILY_RESTRICTED.equalsIgnoreCase(newAccount.getAccountRestrictedStatusCode().trim())){
1276                 if(newAccount.getAccountRestrictedStatusDate()==null){
1277                     success = false;
1278                     putFieldError("accountRestrictedStatusDate", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_TEMPORARY_RESTRICTED_STATUS_DATE_NOTNULL);
1279                 }
1280                 else if(newAccount.getAccountRestrictedStatusDate().compareTo(newAccount.getAccountCreateDate())<0){
1281                     success = false;
1282                     putFieldError("accountRestrictedStatusDate", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_TEMPORARY_RESTRICTED_STATUS_DATE_GREATERTHAN_CURRENTDATE);
1283                 }
1284             }
1285         }
1286         return success;
1287     }
1288 
1289     /**
1290      * This method sets the generalLedgerPendingEntryService
1291      *
1292      * @param generalLedgerPendingEntryService
1293      */
1294     public void setGeneralLedgerPendingEntryService(GeneralLedgerPendingEntryService generalLedgerPendingEntryService) {
1295         this.generalLedgerPendingEntryService = generalLedgerPendingEntryService;
1296     }
1297 
1298     /**
1299      * This method sets the balanceService
1300      *
1301      * @param balanceService
1302      */
1303     public void setBalanceService(BalanceService balanceService) {
1304         this.balanceService = balanceService;
1305     }
1306 
1307     /**
1308      * Sets the accountService attribute value.
1309      *
1310      * @param accountService The accountService to set.
1311      */
1312     public final void setAccountService(AccountService accountService) {
1313         this.accountService = accountService;
1314     }
1315 
1316     /**
1317      * Sets the contractsAndGrantsModuleService attribute value.
1318      * @param contractsAndGrantsModuleService The contractsAndGrantsModuleService to set.
1319      */
1320     public void setContractsAndGrantsModuleService(ContractsAndGrantsModuleService contractsAndGrantsModuleService) {
1321         this.contractsAndGrantsModuleService = contractsAndGrantsModuleService;
1322     }
1323 
1324     public SubFundGroupService getSubFundGroupService() {
1325         if ( subFundGroupService == null ) {
1326             subFundGroupService = SpringContext.getBean(SubFundGroupService.class);
1327         }
1328         return subFundGroupService;
1329     }
1330 
1331     public ParameterService getParameterService() {
1332         if ( parameterService == null ) {
1333             parameterService = SpringContext.getBean(ParameterService.class);
1334         }
1335         return parameterService;
1336     }
1337 
1338     public EncumbranceService getEncumbranceService() {
1339         if ( encumbranceService == null ) {
1340             encumbranceService = SpringContext.getBean(EncumbranceService.class);
1341         }
1342         return encumbranceService;
1343     }
1344 
1345     public boolean validateSufficientFundCheckTab() {
1346 
1347 
1348         boolean returnFlag = true;
1349         if (newAccount.isPendingAcctSufficientFundsIndicator() == true) {
1350 
1351             if (newAccount.getOleSufficientFundCheck().getEncumbExpenseMethod() == null) {
1352                 GlobalVariables.getMessageMap().putError(OLEConstants.SUFF_FUND_CHECK, OLEConstants.ERROR_SELECT_EMCUM_METHOD);
1353                 returnFlag = (returnFlag)&false;
1354             }
1355 
1356             if (newAccount.getOleSufficientFundCheck().getEncumbExpenseConstraintType() == null) {
1357                 GlobalVariables.getMessageMap().putError(OLEConstants.SUFF_FUND_CHECK, OLEConstants.ERROR_SELECT_EMCUM_CON_TYP);
1358                 returnFlag = (returnFlag)&false;
1359             }
1360 
1361             if (newAccount.getOleSufficientFundCheck().getEncumbranceAmount() == null) {
1362                 GlobalVariables.getMessageMap().putError(OLEConstants.SUFF_FUND_CHECK, OLEConstants.ERROR_SELECT_EMCUM_AMT);
1363                 returnFlag = (returnFlag)&false;
1364 
1365             }
1366             if (newAccount.getOleSufficientFundCheck().getExpenseAmount() == null) {
1367                 GlobalVariables.getMessageMap().putError(OLEConstants.SUFF_FUND_CHECK, OLEConstants.ERROR_SELECT_EXP_AMT);
1368                 returnFlag = (returnFlag)&false;
1369 
1370             }
1371             if (newAccount.getOleSufficientFundCheck().getNotificationOption() == null) {
1372                GlobalVariables.getMessageMap().putError(OLEConstants.SUFF_FUND_CHECK, OLEConstants.ERROR_SELECT_NOT_TYP);
1373                 returnFlag = (returnFlag)&false;
1374             }
1375         }
1376         return returnFlag;
1377     }
1378 }
1379