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