View Javadoc

1   /*
2    * Copyright 2007 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.coa.document.validation.impl;
17  
18  import java.sql.Date;
19  import java.sql.Timestamp;
20  import java.util.Calendar;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.commons.lang.time.DateUtils;
28  import org.kuali.ole.coa.businessobject.Account;
29  import org.kuali.ole.coa.businessobject.AccountGlobal;
30  import org.kuali.ole.coa.businessobject.AccountGlobalDetail;
31  import org.kuali.ole.coa.businessobject.SubFundGroup;
32  import org.kuali.ole.coa.service.OrganizationService;
33  import org.kuali.ole.coa.service.SubFundGroupService;
34  import org.kuali.ole.sys.OLEConstants;
35  import org.kuali.ole.sys.OLEKeyConstants;
36  import org.kuali.ole.sys.context.SpringContext;
37  import org.kuali.rice.kim.api.identity.Person;
38  import org.kuali.rice.kns.document.MaintenanceDocument;
39  import org.kuali.rice.kns.service.DictionaryValidationService;
40  import org.kuali.rice.krad.bo.PersistableBusinessObject;
41  import org.kuali.rice.krad.service.BusinessObjectService;
42  import org.kuali.rice.krad.util.GlobalVariables;
43  import org.kuali.rice.krad.util.ObjectUtils;
44  
45  /**
46   * This class represents the business rules for the maintenance of {@link AccountGlobal} business objects
47   */
48  public class AccountGlobalRule extends GlobalDocumentRuleBase {
49      protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountGlobalRule.class);
50  
51      // These constants should not be here - need to be in system parameters
52      @Deprecated
53      protected static final String GENERAL_FUND_CD = "GF";
54      @Deprecated
55      protected static final String SUB_FUND_GROUP_MEDICAL_PRACTICE_FUNDS = "MPRACT";
56  
57      protected AccountGlobal newAccountGlobal;
58      protected Timestamp today;
59  
60      /**
61       * This method sets the convenience objects like newAccountGlobal and oldAccount, so you have short and easy handles to the new
62       * and old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to
63       * load all sub-objects from the DB by their primary keys, if available.
64       */
65      @Override
66      public void setupConvenienceObjects() {
67  
68          // setup newDelegateGlobal convenience objects, make sure all possible sub-objects are populated
69          newAccountGlobal = (AccountGlobal) super.getNewBo();
70          today = getDateTimeService().getCurrentTimestamp();
71          today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); // remove any time components
72      }
73  
74      /**
75       * This method checks the following rules: checkEmptyValues checkGeneralRules checkContractsAndGrants checkExpirationDate
76       * checkOnlyOneChartErrorWrapper checkFiscalOfficerIsValidKualiUser but does not fail if any of them fail (this only happens on
77       * routing)
78       * 
79       * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
80       */
81      @Override
82      protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
83  
84          LOG.info("processCustomSaveDocumentBusinessRules called");
85          setupConvenienceObjects();
86  
87          checkEmptyValues();
88          checkGeneralRules(document);
89          checkOrganizationValidity(newAccountGlobal);
90          checkContractsAndGrants();
91          checkExpirationDate(document);
92          checkOnlyOneChartErrorWrapper(newAccountGlobal.getAccountGlobalDetails());
93          // checkFundGroup(document);
94          // checkSubFundGroup(document);
95  
96          // Save always succeeds, even if there are business rule failures
97          return true;
98      }
99  
100     /**
101      * This method checks the following rules: checkEmptyValues checkGeneralRules checkContractsAndGrants checkExpirationDate
102      * checkOnlyOneChartErrorWrapper checkFiscalOfficerIsValidKualiUser but does fail if any of these rule checks fail
103      * 
104      * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
105      */
106     @Override
107     protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
108 
109         LOG.info("processCustomRouteDocumentBusinessRules called");
110         setupConvenienceObjects();
111 
112         // default to success
113         boolean success = true;
114 
115         success &= checkEmptyValues();
116         success &= checkGeneralRules(document);
117         success &= checkContractsAndGrants();
118         success &= checkExpirationDate(document);
119         success &= checkAccountDetails(document, newAccountGlobal.getAccountGlobalDetails());
120         // success &= checkFundGroup(document);
121         // success &= checkSubFundGroup(document);
122 
123         return success;
124     }
125 
126     /**
127      * This method loops through the list of {@link AccountGlobalDetail}s and passes them off to checkAccountDetails for further
128      * rule analysis One rule it does check is checkOnlyOneChartErrorWrapper
129      * 
130      * @param document
131      * @param details
132      * @return true if the collection of {@link AccountGlobalDetail}s passes the sub-rules
133      */
134     public boolean checkAccountDetails(MaintenanceDocument document, List<AccountGlobalDetail> details) {
135         boolean success = true;
136 
137         // check if there are any accounts
138         if (details.size() == 0) {
139 
140             putFieldError(OLEConstants.MAINTENANCE_ADD_PREFIX + "accountGlobalDetails.accountNumber", OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_NO_ACCOUNTS);
141 
142             success = false;
143         }
144         else {
145             // check each account
146             int index = 0;
147             for (AccountGlobalDetail dtl : details) {
148                 String errorPath = MAINTAINABLE_ERROR_PREFIX + "accountGlobalDetails[" + index + "]";
149                 GlobalVariables.getMessageMap().addToErrorPath(errorPath);
150                 success &= checkAccountDetails(dtl);
151                 GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
152                 index++;
153             }
154             success &= checkOnlyOneChartErrorWrapper(details);
155         }
156 
157         return success;
158     }
159 
160     /**
161      * This method ensures that each {@link AccountGlobalDetail} is valid and has a valid account number
162      * 
163      * @param dtl
164      * @return true if the detail object contains a valid account
165      */
166     public boolean checkAccountDetails(AccountGlobalDetail dtl) {
167         boolean success = true;
168         int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount();
169         getDictionaryValidationService().validateBusinessObject(dtl);
170         if (StringUtils.isNotBlank(dtl.getAccountNumber()) && StringUtils.isNotBlank(dtl.getChartOfAccountsCode())) {
171             dtl.refreshReferenceObject("account");
172             if (ObjectUtils.isNull(dtl.getAccount())) {
173                 GlobalVariables.getMessageMap().putError("accountNumber", OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ACCOUNT, new String[] { dtl.getChartOfAccountsCode(), dtl.getAccountNumber() });
174             }
175         }
176         success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount;
177 
178         return success;
179     }
180 
181     /**
182      * This method checks the basic rules for empty reference key values on a continuation account and an income stream account
183      * 
184      * @return true if no empty values or partially filled out reference keys
185      */
186     protected boolean checkEmptyValues() {
187 
188         LOG.info("checkEmptyValues called");
189 
190         boolean success = true;
191 
192         // this set confirms that all fields which are grouped (ie, foreign keys of a referenc
193         // object), must either be none filled out, or all filled out.
194         success &= checkForPartiallyFilledOutReferenceForeignKeys("continuationAccount");
195         success &= checkForPartiallyFilledOutReferenceForeignKeys("incomeStreamAccount");
196 
197         return success;
198     }
199 
200     /**
201      * This method checks some of the general business rules associated with this document Such as: valid user for fiscal officer,
202      * supervisor or account manager (and not the same individual) are they trying to use an expired continuation account
203      * 
204      * @param maintenanceDocument
205      * @return false on rules violation
206      */
207     protected boolean checkGeneralRules(MaintenanceDocument maintenanceDocument) {
208 
209         LOG.info("checkGeneralRules called");
210         Person fiscalOfficer = newAccountGlobal.getAccountFiscalOfficerUser();
211         Person accountManager = newAccountGlobal.getAccountManagerUser();
212         Person accountSupervisor = newAccountGlobal.getAccountSupervisoryUser();
213 
214         boolean success = true;
215 
216         if (!StringUtils.isBlank(newAccountGlobal.getAccountFiscalOfficerSystemIdentifier()) && (ObjectUtils.isNull(fiscalOfficer) || StringUtils.isBlank(fiscalOfficer.getPrincipalId()) || !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.namespace, OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.name, fiscalOfficer.getPrincipalId()))) {
217             final String fiscalOfficerName = fiscalOfficer != null ? fiscalOfficer.getName() : newAccountGlobal.getAccountFiscalOfficerSystemIdentifier();
218             super.putFieldError("accountFiscalOfficerUser.principalName", OLEKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {fiscalOfficerName, OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.namespace, OLEConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.name});
219 			success = false;
220         }
221         if (!StringUtils.isBlank(newAccountGlobal.getAccountsSupervisorySystemsIdentifier()) && (ObjectUtils.isNull(accountSupervisor) || StringUtils.isBlank(accountSupervisor.getPrincipalId()) || !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.namespace, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.name, accountSupervisor.getPrincipalId()))) {
222             final String accountSupervisorName = accountSupervisor != null ? accountSupervisor.getName() : newAccountGlobal.getAccountsSupervisorySystemsIdentifier();
223             super.putFieldError("accountSupervisoryUser.principalName", OLEKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountSupervisorName, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.namespace, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.name});
224 			success = false;
225         }
226         if (!StringUtils.isBlank(newAccountGlobal.getAccountManagerSystemIdentifier()) && (ObjectUtils.isNull(accountManager) || StringUtils.isBlank(accountManager.getPrincipalId()) || !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.namespace, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.name, accountManager.getPrincipalId()))) {
227             final String accountManagerName = accountManager != null ? accountManager.getName() : newAccountGlobal.getAccountManagerSystemIdentifier();
228             super.putFieldError("accountManagerUser.principalName", OLEKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountManagerName, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.namespace, OLEConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.name});
229 			success = false;
230         }
231 
232         // the supervisor cannot be the same as the fiscal officer or account manager.
233         if (isSupervisorSameAsFiscalOfficer(newAccountGlobal)) {
234             success &= false;
235             putFieldError("accountsSupervisorySystemsIdentifier", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_FISCAL_OFFICER);
236         }
237         if (isSupervisorSameAsManager(newAccountGlobal)) {
238             success &= false;
239             putFieldError("accountManagerSystemIdentifier", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_ACCT_MGR);
240         }
241 
242         // disallow continuation account being expired
243         if (isContinuationAccountExpired(newAccountGlobal)) {
244             success &= false;
245             putFieldError("continuationAccountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_EXPIRED_CONTINUATION);
246         }
247 
248         // loop over change detail objects to test if the supervisor/FO/mgr restrictions are in place
249         // only need to do this check if the entered information does not already violate the rules
250         if (!isSupervisorSameAsFiscalOfficer(newAccountGlobal) && !isSupervisorSameAsManager(newAccountGlobal)) {
251             success &= checkAllAccountUsers(newAccountGlobal, fiscalOfficer, accountManager, accountSupervisor);
252         }
253 
254         return success;
255     }
256 
257     /**
258      * This method checks to make sure that if the users are filled out (fiscal officer, supervisor, manager) that they are not the
259      * same individual Only need to check this if these are new users that override existing users on the {@link Account} object
260      * 
261      * @param doc
262      * @param newFiscalOfficer
263      * @param newManager
264      * @param newSupervisor
265      * @return true if the users are either not changed or pass the sub-rules
266      */
267     protected boolean checkAllAccountUsers(AccountGlobal doc, Person newFiscalOfficer, Person newManager, Person newSupervisor) {
268         boolean success = true;
269 
270         if (LOG.isDebugEnabled()) {
271             LOG.debug("newSupervisor: " + newSupervisor);
272             LOG.debug("newFiscalOfficer: " + newFiscalOfficer);
273             LOG.debug("newManager: " + newManager);
274         }
275         // only need to do this check if at least one of the user fields is
276         // non null
277         if (newSupervisor != null || newFiscalOfficer != null || newManager != null) {
278             // loop over all AccountGlobalDetail records
279             int index = 0;
280             for (AccountGlobalDetail detail : doc.getAccountGlobalDetails()) {
281                 success &= checkAccountUsers(detail, newFiscalOfficer, newManager, newSupervisor, index);
282                 index++;
283             }
284         }
285 
286         return success;
287     }
288 
289     /**
290      * This method checks that the new users (fiscal officer, supervisor, manager) are not the same individual for the
291      * {@link Account} being changed (contained in the {@link AccountGlobalDetail})
292      * 
293      * @param detail - where the Account information is stored
294      * @param newFiscalOfficer
295      * @param newManager
296      * @param newSupervisor
297      * @param index - for storing the error line
298      * @return true if the new users pass this sub-rule
299      */
300     protected boolean checkAccountUsers(AccountGlobalDetail detail, Person newFiscalOfficer, Person newManager, Person newSupervisor, int index) {
301         boolean success = true;
302 
303         // only need to do this check if at least one of the user fields is non null
304         if (newSupervisor != null || newFiscalOfficer != null || newManager != null) {
305             // loop over all AccountGlobalDetail records
306             detail.refreshReferenceObject("account");
307             Account account = detail.getAccount();
308             if (ObjectUtils.isNotNull(account)){
309                 if (LOG.isDebugEnabled()) {
310                     LOG.debug("old-Supervisor: " + account.getAccountSupervisoryUser());
311                     LOG.debug("old-FiscalOfficer: " + account.getAccountFiscalOfficerUser());
312                     LOG.debug("old-Manager: " + account.getAccountManagerUser());
313                 }
314                 // only need to check if they are not being overridden by the change document
315                 if (newSupervisor != null && newSupervisor.getPrincipalId() != null) {
316                     if (areTwoUsersTheSame(newSupervisor, account.getAccountFiscalOfficerUser())) {
317                         success = false;
318                         putFieldError("accountGlobalDetails[" + index + "].accountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_EQUAL_EXISTING_FISCAL_OFFICER, new String[] { account.getAccountFiscalOfficerUser().getPrincipalName(), "Fiscal Officer", detail.getAccountNumber() });
319                     }
320                     if (areTwoUsersTheSame(newSupervisor, account.getAccountManagerUser())) {
321                         success = false;
322                         putFieldError("accountGlobalDetails[" + index + "].accountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_EQUAL_EXISTING_ACCT_MGR, new String[] { account.getAccountManagerUser().getPrincipalName(), "Account Manager", detail.getAccountNumber() });
323                     }
324                 }
325                 if (newManager != null && newManager.getPrincipalId() != null) {
326                     if (areTwoUsersTheSame(newManager, account.getAccountSupervisoryUser())) {
327                         success = false;
328                         putFieldError("accountGlobalDetails[" + index + "].accountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_MGR_CANNOT_EQUAL_EXISTING_ACCT_SUPERVISOR, new String[] { account.getAccountSupervisoryUser().getPrincipalName(), "Account Supervisor", detail.getAccountNumber() });
329                     }
330                 }
331                 if (newFiscalOfficer != null && newFiscalOfficer.getPrincipalId() != null) {
332                     if (areTwoUsersTheSame(newFiscalOfficer, account.getAccountSupervisoryUser())) {
333                         success = false;
334                         putFieldError("accountGlobalDetails[" + index + "].accountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_FISCAL_OFFICER_CANNOT_EQUAL_EXISTING_ACCT_SUPERVISOR, new String[] { account.getAccountSupervisoryUser().getPrincipalName(), "Account Supervisor", detail.getAccountNumber() });
335                     }
336                 }
337             }
338             else {
339                 LOG.warn("AccountGlobalDetail object has null account object:" + detail.getChartOfAccountsCode() + "-" + detail.getAccountNumber());
340             }
341         }
342 
343         return success;
344     }
345 
346     /**
347      * This method is a helper method for checking if the supervisor user is the same as the fiscal officer Calls
348      * {@link AccountGlobalRule#areTwoUsersTheSame(Person, Person)}
349      * 
350      * @param accountGlobals
351      * @return true if the two users are the same
352      */
353     protected boolean isSupervisorSameAsFiscalOfficer(AccountGlobal accountGlobals) {
354         return areTwoUsersTheSame(accountGlobals.getAccountSupervisoryUser(), accountGlobals.getAccountFiscalOfficerUser());
355     }
356 
357     /**
358      * This method is a helper method for checking if the supervisor user is the same as the manager Calls
359      * {@link AccountGlobalRule#areTwoUsersTheSame(Person, Person)}
360      * 
361      * @param accountGlobals
362      * @return true if the two users are the same
363      */
364     protected boolean isSupervisorSameAsManager(AccountGlobal accountGlobals) {
365         return areTwoUsersTheSame(accountGlobals.getAccountSupervisoryUser(), accountGlobals.getAccountManagerUser());
366     }
367 
368     /**
369      * This method checks to see if two users are the same Person using their identifiers
370      * 
371      * @param user1
372      * @param user2
373      * @return true if these two users are the same
374      */
375     protected boolean areTwoUsersTheSame(Person user1, Person user2) {
376         if (ObjectUtils.isNull(user1) || user1.getPrincipalId() == null ) {
377             return false;
378         }
379         if (ObjectUtils.isNull(user2) || user2.getPrincipalId() == null ) {
380             return false;
381         }
382         return user1.getPrincipalId().equals(user2.getPrincipalId());
383     }
384 
385     /**
386      * This method checks to see if any expiration date field rules were violated Loops through each detail object and calls
387      * {@link AccountGlobalRule#checkExpirationDate(MaintenanceDocument, AccountGlobalDetail)}
388      * 
389      * @param maintenanceDocument
390      * @return false on rules violation
391      */
392     protected boolean checkExpirationDate(MaintenanceDocument maintenanceDocument) {
393         LOG.info("checkExpirationDate called");
394 
395         boolean success = true;
396         Date newExpDate = newAccountGlobal.getAccountExpirationDate();
397 
398         // If creating a new account if acct_expiration_dt is set and the fund_group is not "CG" then
399         // the acct_expiration_dt must be changed to a date that is today or later
400         if (ObjectUtils.isNotNull(newExpDate)) {
401             if (ObjectUtils.isNotNull(newAccountGlobal.getSubFundGroup())) {
402                 if (!SpringContext.getBean(SubFundGroupService.class).isForContractsAndGrants(newAccountGlobal.getSubFundGroup())) {
403                     if (!newExpDate.after(today) && !newExpDate.equals(today)) {
404                         putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
405                         success &= false;
406                     }
407                 }
408             }
409         }
410 
411         // a continuation account is required if the expiration date is completed.
412         success &= checkContinuationAccount(maintenanceDocument, newExpDate);
413 
414         for (AccountGlobalDetail detail : newAccountGlobal.getAccountGlobalDetails()) {
415             success &= checkExpirationDate(maintenanceDocument, detail);
416         }
417         return success;
418     }
419 
420     /**
421      * This method checks to see if any expiration date field rules were violated in relation to the given detail record
422      * 
423      * @param maintenanceDocument
424      * @param detail - the account detail we are investigating
425      * @return false on rules violation
426      */
427     protected boolean checkExpirationDate(MaintenanceDocument maintenanceDocument, AccountGlobalDetail detail) {
428         boolean success = true;
429         Date newExpDate = newAccountGlobal.getAccountExpirationDate();
430 
431         // load the object by keys
432         Account account = (Account) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Account.class, detail.getPrimaryKeys());
433         if (ObjectUtils.isNotNull(account)) {
434             Date oldExpDate = account.getAccountExpirationDate();
435 
436             // When updating an account expiration date, the date must be today or later
437             // (except for C&G accounts). Only run this test if this maint doc
438             // is an edit doc
439             if (isUpdatedExpirationDateInvalid(account, newAccountGlobal)) {
440                 putFieldError("accountExpirationDate", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
441                 success &= false;
442             }
443 
444             // If creating a new account if acct_expiration_dt is set and the fund_group is not "CG" then
445             // the acct_expiration_dt must be changed to a date that is today or later
446             if (ObjectUtils.isNotNull(newExpDate) && ObjectUtils.isNull(newAccountGlobal.getSubFundGroup())) {
447                 if (ObjectUtils.isNotNull(account.getSubFundGroup())) {
448                     if (!account.isForContractsAndGrants()) {
449                         if (!newExpDate.after(today) && !newExpDate.equals(today)) {
450                             putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
451                             success &= false;
452                         }
453                     }
454                 }
455             }
456             // acct_expiration_dt can not be before acct_effect_dt
457             Date effectiveDate = account.getAccountEffectiveDate();
458             if (ObjectUtils.isNotNull(effectiveDate) && ObjectUtils.isNotNull(newExpDate)) {
459                 if (newExpDate.before(effectiveDate)) {
460                     putGlobalError(OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE);
461                     success &= false;
462                 }
463             }
464         }
465 
466         return success;
467     }
468 
469     /*
470      * protected boolean checkAccountExpirationDateValidTodayOrEarlier(Account newAccount) { // get today's date, with no time
471      * component Timestamp todaysDate = getDateTimeService().getCurrentTimestamp();
472      * todaysDate.setTime(KfsDateUtils.truncate(todaysDate, Calendar.DAY_OF_MONTH).getTime()); // TODO: convert this to using Wes'
473      * kuali KfsDateUtils once we're using Date's instead of Timestamp // get the expiration date, if any Timestamp expirationDate =
474      * newAccount.getAccountExpirationDate(); if (ObjectUtils.isNull(expirationDate)) { putFieldError("accountExpirationDate",
475      * OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); return false; } // when closing an account,
476      * the account expiration date must be the current date or earlier expirationDate.setTime(KfsDateUtils.truncate(expirationDate,
477      * Calendar.DAY_OF_MONTH).getTime()); if (expirationDate.after(todaysDate)) { putFieldError("accountExpirationDate",
478      * OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); return false; } return true; }
479      */
480 
481     /**
482      * This method checks to see if the updated expiration is not a valid one Only gets checked for specific {@link SubFundGroup}s
483      * 
484      * @param oldAccount
485      * @param newAccountGlobal
486      * @return true if date has changed and is invalid
487      */
488     protected boolean isUpdatedExpirationDateInvalid(Account oldAccount, AccountGlobal newAccountGlobal) {
489 
490         Date oldExpDate = oldAccount.getAccountExpirationDate();
491         Date newExpDate = newAccountGlobal.getAccountExpirationDate();
492 
493         // When updating an account expiration date, the date must be today or later
494         // (except for C&G accounts). Only run this test if this maint doc
495         // is an edit doc
496         boolean expDateHasChanged = false;
497 
498         // if the old version of the account had no expiration date, and the new
499         // one has a date
500         if (ObjectUtils.isNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) {
501             expDateHasChanged = true;
502         }
503 
504         // if there was an old and a new expDate, but they're different
505         else if (ObjectUtils.isNotNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) {
506             if (!oldExpDate.equals(newExpDate)) {
507                 expDateHasChanged = true;
508             }
509         }
510 
511         // if the expiration date hasnt changed, we're not interested
512         if (!expDateHasChanged) {
513             return false;
514         }
515 
516         // if a subFundGroup isnt present, we cannot continue the testing
517         SubFundGroup subFundGroup = newAccountGlobal.getSubFundGroup();
518         if (ObjectUtils.isNull(subFundGroup)) {
519             return false;
520         }
521 
522         // get the fundGroup code
523         String fundGroupCode = newAccountGlobal.getSubFundGroup().getFundGroupCode().trim();
524 
525         // if the account is part of the CG fund group, then this rule does not
526         // apply, so we're done
527         if (SpringContext.getBean(SubFundGroupService.class).isForContractsAndGrants(newAccountGlobal.getSubFundGroup())) {
528             return false;
529         }
530 
531         // at this point, we know its not a CG fund group, so we must apply the rule
532 
533         // expirationDate must be today or later than today (cannot be before today)
534         if (newExpDate.equals(today) || newExpDate.after(today)) {
535             return false;
536         }
537         else
538             return true;
539     }
540 
541 
542     /**
543      * This method tests whether the continuation account entered (if any) has expired or not.
544      * 
545      * @param accountGlobals
546      * @return true if the continuation account has expired
547      */
548     protected boolean isContinuationAccountExpired(AccountGlobal accountGlobals) {
549 
550         boolean result = false;
551 
552         String chartCode = accountGlobals.getContinuationFinChrtOfAcctCd();
553         String accountNumber = accountGlobals.getContinuationAccountNumber();
554 
555         // if either chartCode or accountNumber is not entered, then we
556         // cant continue, so exit
557         if (StringUtils.isBlank(chartCode) || StringUtils.isBlank(accountNumber)) {
558             return result;
559         }
560 
561         // attempt to retrieve the continuation account from the DB
562         Account continuation = null;
563         Map<String,String> pkMap = new HashMap<String,String>();
564         pkMap.put("chartOfAccountsCode", chartCode);
565         pkMap.put("accountNumber", accountNumber);
566         continuation = (Account) super.getBoService().findByPrimaryKey(Account.class, pkMap);
567 
568         // if the object doesnt exist, then we cant continue, so exit
569         if (ObjectUtils.isNull(continuation)) {
570             return result;
571         }
572 
573         // at this point, we have a valid continuation account, so we just need to
574         // know whether its expired or not
575         result = continuation.isExpired();
576 
577         return result;
578     }
579 
580     /**
581      * This method checks to see if any Contracts and Grants business rules were violated
582      * 
583      * @return false on rules violation
584      */
585     protected boolean checkContractsAndGrants() {
586 
587         LOG.info("checkContractsAndGrants called");
588 
589         boolean success = true;
590 
591         // Income Stream account is required if this account is CG fund group,
592         // or GF (general fund) fund group (with some exceptions)
593         success &= checkCgIncomeStreamRequired(newAccountGlobal);
594 
595         return success;
596     }
597 
598     /**
599      * This method checks to see if the contracts and grants income stream account is required
600      * 
601      * @param accountGlobals
602      * @return false if it is required (and not entered) or invalid/inactive
603      */
604     protected boolean checkCgIncomeStreamRequired(AccountGlobal accountGlobals) {
605 
606         boolean result = true;
607         boolean required = false;
608 
609         // if the subFundGroup object is null, we cant test, so exit
610         if (ObjectUtils.isNull(accountGlobals.getSubFundGroup())) {
611             return result;
612         }
613 
614         // retrieve the subfundcode and fundgroupcode
615         String subFundGroupCode = accountGlobals.getSubFundGroupCode().trim();
616         String fundGroupCode = accountGlobals.getSubFundGroup().getFundGroupCode().trim();
617 
618         // if this is a CG fund group, then its required
619         if (SpringContext.getBean(SubFundGroupService.class).isForContractsAndGrants(accountGlobals.getSubFundGroup())) {
620             required = true;
621         }
622 
623         // if this is a general fund group, then its required
624         else if (GENERAL_FUND_CD.equalsIgnoreCase(fundGroupCode)) {
625             // unless its part of the MPRACT subfundgroup
626             if (!SUB_FUND_GROUP_MEDICAL_PRACTICE_FUNDS.equalsIgnoreCase(subFundGroupCode)) {
627                 required = true;
628             }
629         }
630 
631         // if the income stream account is not required, then we're done
632         if (!required) {
633             return result;
634         }
635 
636         // make sure both coaCode and accountNumber are filled out
637         result &= checkEmptyBOField("incomeStreamAccountNumber", accountGlobals.getIncomeStreamAccountNumber(), "When Fund Group is CG or GF, Income Stream Account Number");
638         result &= checkEmptyBOField("incomeStreamFinancialCoaCode", accountGlobals.getIncomeStreamFinancialCoaCode(), "When Fund Group is CG or GF, Income Stream Chart Of Accounts Code");
639 
640         // if both fields arent present, then we're done
641         if (result == false) {
642             return result;
643         }
644 
645         // do an existence/active test
646         DictionaryValidationService dvService = super.getDictionaryValidationService();
647         boolean referenceExists = dvService.validateReferenceExists(accountGlobals, "incomeStreamAccount");
648         if (!referenceExists) {
649             putFieldError("incomeStreamAccount", OLEKeyConstants.ERROR_EXISTENCE, "Income Stream Account: " + accountGlobals.getIncomeStreamFinancialCoaCode() + "-" + accountGlobals.getIncomeStreamAccountNumber());
650             result &= false;
651         }
652 
653         return result;
654     }
655 
656     /**
657      * This method calls checkAccountDetails checkExpirationDate checkOnlyOneChartAddLineErrorWrapper whenever a new
658      * {@link AccountGlobalDetail} is added to this global
659      * 
660      * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument,
661      *      java.lang.String, org.kuali.rice.krad.bo.PersistableBusinessObject)
662      */
663     @Override
664     public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) {
665         AccountGlobalDetail detail = (AccountGlobalDetail) bo;
666         boolean success = true;
667 
668         success &= checkAccountDetails(detail);
669         success &= checkExpirationDate(document, detail);
670         success &= checkOnlyOneChartAddLineErrorWrapper(detail, newAccountGlobal.getAccountGlobalDetails());
671 
672         return success;
673     }
674 
675     /**
676      * This method validates that a continuation account is required and that the values provided exist
677      * 
678      * @param document An instance of the maintenance document being validated.
679      * @param newExpDate The expiration date assigned to the account being validated for submission.
680      * @return True if the continuation account values are valid for the associated account, false otherwise.
681      */
682     protected boolean checkContinuationAccount(MaintenanceDocument document, Date newExpDate) {
683         LOG.info("checkContinuationAccount called");
684 
685         boolean result = true;
686         boolean continuationAccountIsValid = true;
687 
688         // make sure both coaCode and accountNumber are filled out
689         if (ObjectUtils.isNotNull(newExpDate)) {
690             if (!checkEmptyValue(newAccountGlobal.getContinuationAccountNumber())) {
691                 putFieldError("continuationAccountNumber", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED);
692                 continuationAccountIsValid = false;
693             }
694             if (!checkEmptyValue(newAccountGlobal.getContinuationFinChrtOfAcctCd())) {
695                 putFieldError("continuationFinChrtOfAcctCd", OLEKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_FINCODE_REQD_IF_EXP_DATE_COMPLETED);
696                 continuationAccountIsValid = false;
697             }
698         }
699 
700         // if both fields aren't present, then we're done
701         if (continuationAccountIsValid && ObjectUtils.isNotNull(newAccountGlobal.getContinuationAccountNumber()) && ObjectUtils.isNotNull(newAccountGlobal.getContinuationFinChrtOfAcctCd())) {
702             // do an existence/active test
703             DictionaryValidationService dvService = super.getDictionaryValidationService();
704             boolean referenceExists = dvService.validateReferenceExists(newAccountGlobal, "continuationAccount");
705             if (!referenceExists) {
706                 putFieldError("continuationAccountNumber", OLEKeyConstants.ERROR_EXISTENCE, "Continuation Account: " + newAccountGlobal.getContinuationFinChrtOfAcctCd() + "-" + newAccountGlobal.getContinuationAccountNumber());
707                 continuationAccountIsValid = false;
708             }
709         }
710 
711         if (continuationAccountIsValid) {
712             result = true;
713         }
714         else {
715             List<AccountGlobalDetail> gAcctDetails = newAccountGlobal.getAccountGlobalDetails();
716             for (AccountGlobalDetail detail : gAcctDetails) {
717                 if (null != detail.getAccountNumber() && null != newAccountGlobal.getContinuationAccountNumber()) {
718                     result &= detail.getAccountNumber().equals(newAccountGlobal.getContinuationAccountNumber());
719                     result &= detail.getChartOfAccountsCode().equals(newAccountGlobal.getContinuationFinChrtOfAcctCd());
720                 }
721             }
722         }
723 
724         return result;
725     }
726     
727     /**
728      * Validate that the object code on the form (if entered) is valid for all charts used in the detail sections.
729      * 
730      * @param acctGlobal
731      * @return
732      */
733     protected boolean checkOrganizationValidity( AccountGlobal acctGlobal ) {
734         boolean result = true;
735         
736         // check that an org has been entered
737         if ( StringUtils.isNotBlank( acctGlobal.getOrganizationCode() ) ) {           
738             // get all distinct charts
739             HashSet<String> charts = new HashSet<String>(10); 
740             for ( AccountGlobalDetail acct : acctGlobal.getAccountGlobalDetails() ) {
741                 charts.add( acct.getChartOfAccountsCode() );
742             }
743             OrganizationService orgService = SpringContext.getBean(OrganizationService.class);
744             // test for an invalid organization
745             for ( String chartCode : charts ) {
746                 if ( StringUtils.isNotBlank(chartCode) ) {
747                     if ( null == orgService.getByPrimaryIdWithCaching( chartCode, acctGlobal.getOrganizationCode() ) ) {
748                         result = false;
749                         putFieldError("organizationCode", OLEKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ORG, new String[] { chartCode, acctGlobal.getOrganizationCode() } );
750                         break;
751                     }
752                 }
753             }
754         }
755                 
756         return result;
757     }
758 }
759