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.Collection; 022 import java.util.HashMap; 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.AccountDescription; 030 import org.kuali.kfs.coa.businessobject.AccountGuideline; 031 import org.kuali.kfs.coa.businessobject.FundGroup; 032 import org.kuali.kfs.coa.businessobject.IndirectCostRecoveryRateDetail; 033 import org.kuali.kfs.coa.businessobject.SubFundGroup; 034 import org.kuali.kfs.coa.service.AccountService; 035 import org.kuali.kfs.coa.service.SubFundGroupService; 036 import org.kuali.kfs.gl.service.BalanceService; 037 import org.kuali.kfs.integration.cg.ContractsAndGrantsModuleService; 038 import org.kuali.kfs.integration.ld.LaborModuleService; 039 import org.kuali.kfs.sys.KFSConstants; 040 import org.kuali.kfs.sys.KFSKeyConstants; 041 import org.kuali.kfs.sys.KFSPropertyConstants; 042 import org.kuali.kfs.sys.businessobject.Building; 043 import org.kuali.kfs.sys.context.SpringContext; 044 import org.kuali.kfs.sys.document.validation.impl.KfsMaintenanceDocumentRuleBase; 045 import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService; 046 import org.kuali.kfs.sys.service.UniversityDateService; 047 import org.kuali.rice.kim.bo.Person; 048 import org.kuali.rice.kns.document.MaintenanceDocument; 049 import org.kuali.rice.kns.service.DataDictionaryService; 050 import org.kuali.rice.kns.service.DictionaryValidationService; 051 import org.kuali.rice.kns.service.ParameterEvaluator; 052 import org.kuali.rice.kns.service.ParameterService; 053 import org.kuali.rice.kns.util.GlobalVariables; 054 import org.kuali.rice.kns.util.MessageMap; 055 import org.kuali.rice.kns.util.ObjectUtils; 056 057 /** 058 * Business rule(s) applicable to AccountMaintenance documents. 059 */ 060 public class AccountRule extends KfsMaintenanceDocumentRuleBase { 061 062 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountRule.class); 063 064 protected static final String ACCT_PREFIX_RESTRICTION = "PREFIXES"; 065 protected static final String ACCT_CAPITAL_SUBFUNDGROUP = "CAPITAL_SUB_FUND_GROUPS"; 066 067 protected static final String GENERAL_FUND_CD = "GF"; 068 protected static final String RESTRICTED_FUND_CD = "RF"; 069 protected static final String ENDOWMENT_FUND_CD = "EN"; 070 protected static final String PLANT_FUND_CD = "PF"; 071 072 protected static final String RESTRICTED_CD_RESTRICTED = "R"; 073 protected static final String RESTRICTED_CD_UNRESTRICTED = "U"; 074 protected static final String RESTRICTED_CD_TEMPORARILY_RESTRICTED = "T"; 075 protected static final String BUDGET_RECORDING_LEVEL_MIXED = "M"; 076 077 protected static SubFundGroupService subFundGroupService; 078 protected static ParameterService parameterService; 079 080 protected GeneralLedgerPendingEntryService generalLedgerPendingEntryService; 081 protected BalanceService balanceService; 082 protected AccountService accountService; 083 protected ContractsAndGrantsModuleService contractsAndGrantsModuleService; 084 085 protected Account oldAccount; 086 protected Account newAccount; 087 088 public AccountRule() { 089 090 // Pseudo-inject some services. 091 // 092 // This approach is being used to make it simpler to convert the Rule classes 093 // to spring-managed with these services injected by Spring at some later date. 094 // When this happens, just remove these calls to the setters with 095 // SpringContext, and configure the bean defs for spring. 096 this.setGeneralLedgerPendingEntryService(SpringContext.getBean(GeneralLedgerPendingEntryService.class)); 097 this.setBalanceService(SpringContext.getBean(BalanceService.class)); 098 this.setAccountService(SpringContext.getBean(AccountService.class)); 099 this.setContractsAndGrantsModuleService(SpringContext.getBean(ContractsAndGrantsModuleService.class)); 100 } 101 102 /** 103 * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and 104 * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load 105 * all sub-objects from the DB by their primary keys, if available. 106 */ 107 public void setupConvenienceObjects() { 108 109 // setup oldAccount convenience objects, make sure all possible sub-objects are populated 110 oldAccount = (Account) super.getOldBo(); 111 112 // setup newAccount convenience objects, make sure all possible sub-objects are populated 113 newAccount = (Account) super.getNewBo(); 114 } 115 116 /** 117 * This method calls the route rules but does not fail if any of them fail (this only happens on routing) 118 * 119 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 120 */ 121 protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) { 122 123 LOG.info("processCustomSaveDocumentBusinessRules called"); 124 // call the route rules to report all of the messages, but ignore the result 125 processCustomRouteDocumentBusinessRules(document); 126 127 // Save always succeeds, even if there are business rule failures 128 return true; 129 } 130 131 /** 132 * This method calls the following rules: checkAccountGuidelinesValidation checkEmptyValues checkGeneralRules checkCloseAccount 133 * checkContractsAndGrants checkExpirationDate checkFundGroup checkSubFundGroup checkFiscalOfficerIsValidKualiUser this rule 134 * will fail on routing 135 * 136 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 137 */ 138 protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { 139 140 LOG.info("processCustomRouteDocumentBusinessRules called"); 141 setupConvenienceObjects(); 142 143 // default to success 144 boolean success = true; 145 146 // validate the embedded AccountGuideline object 147 success &= checkAccountGuidelinesValidation(newAccount.getAccountGuideline()); 148 149 success &= checkEmptyValues(document); 150 success &= checkGeneralRules(document); 151 success &= checkCloseAccount(document); 152 success &= checkContractsAndGrants(document); 153 success &= checkExpirationDate(document); 154 success &= checkFundGroup(document); 155 success &= checkSubFundGroup(document); 156 success &= checkIncomeStreamAccountRule(); 157 success &= checkUniqueAccountNumber(document); 158 159 return success; 160 } 161 162 /** 163 * This method checks the basic rules for empty values in an account and associated objects with this account If guidelines are 164 * required for this Business Object it checks to make sure that it is filled out It also checks for partially filled out 165 * reference keys on the following: continuationAccount incomeStreamAccount endowmentIncomeAccount reportsToAccount 166 * contractControlAccount indirectCostRecoveryAcct 167 * 168 * @param maintenanceDocument 169 * @return false if any of these are empty 170 */ 171 protected boolean checkEmptyValues(MaintenanceDocument maintenanceDocument) { 172 173 LOG.info("checkEmptyValues called"); 174 175 boolean success = true; 176 177 // guidelines are always required, except when the expirationDate is set, and its 178 // earlier than today 179 boolean guidelinesRequired = areGuidelinesRequired((Account) maintenanceDocument.getNewMaintainableObject().getBusinessObject()); 180 181 // confirm that required guidelines are entered, if required 182 if (guidelinesRequired) { 183 success &= checkEmptyBOField("accountGuideline.accountExpenseGuidelineText", newAccount.getAccountGuideline().getAccountExpenseGuidelineText(), "Expense Guideline"); 184 success &= checkEmptyBOField("accountGuideline.accountIncomeGuidelineText", newAccount.getAccountGuideline().getAccountIncomeGuidelineText(), "Income Guideline"); 185 success &= checkEmptyBOField("accountGuideline.accountPurposeText", newAccount.getAccountGuideline().getAccountPurposeText(), "Account Purpose"); 186 } 187 188 // this set confirms that all fields which are grouped (ie, foreign keys of a reference 189 // object), must either be none filled out, or all filled out. 190 success &= checkForPartiallyFilledOutReferenceForeignKeys("continuationAccount"); 191 success &= checkForPartiallyFilledOutReferenceForeignKeys("incomeStreamAccount"); 192 success &= checkForPartiallyFilledOutReferenceForeignKeys("endowmentIncomeAccount"); 193 success &= checkForPartiallyFilledOutReferenceForeignKeys("reportsToAccount"); 194 success &= checkForPartiallyFilledOutReferenceForeignKeys("contractControlAccount"); 195 success &= checkForPartiallyFilledOutReferenceForeignKeys("indirectCostRecoveryAcct"); 196 197 return success; 198 } 199 200 /** 201 * This method validates that the account guidelines object is valid 202 * 203 * @param accountGuideline 204 * @return true if account guideline is valid 205 */ 206 protected boolean checkAccountGuidelinesValidation(AccountGuideline accountGuideline) { 207 MessageMap map = GlobalVariables.getMessageMap(); 208 int errorCount = map.getErrorCount(); 209 GlobalVariables.getMessageMap().addToErrorPath("document.newMaintainableObject.accountGuideline"); 210 dictionaryValidationService.validateBusinessObject(accountGuideline, false); 211 GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject.accountGuideline"); 212 return map.getErrorCount() == errorCount; 213 } 214 215 /** 216 * This method determines whether the guidelines are required, based on business rules. 217 * 218 * @param account - the populated Account bo to be evaluated 219 * @return true if guidelines are required, false otherwise 220 */ 221 protected boolean areGuidelinesRequired(Account account) { 222 223 boolean result = true; 224 225 if (account.getAccountExpirationDate() != null) { 226 Timestamp today = getDateTimeService().getCurrentTimestamp(); 227 today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); 228 if (account.getAccountExpirationDate().before(today)) { 229 result = false; 230 } 231 } 232 return result; 233 } 234 235 /** 236 * This method tests whether the accountNumber passed in is prefixed with an allowed prefix, or an illegal one. The illegal 237 * prefixes are passed in as an array of strings. 238 * 239 * @param accountNumber - The Account Number to be tested. 240 * @param illegalValues - An Array of Strings of the unallowable prefixes. 241 * @return false if the accountNumber starts with any of the illegalPrefixes, true otherwise 242 */ 243 protected boolean accountNumberStartsWithAllowedPrefix(String accountNumber, List<String> illegalValues) { 244 boolean result = true; 245 for (String illegalValue : illegalValues) { 246 if (accountNumber.startsWith(illegalValue)) { 247 result = false; 248 putFieldError("accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_NMBR_NOT_ALLOWED, new String[] { accountNumber, illegalValue }); 249 } 250 } 251 return result; 252 } 253 254 /** 255 * This method tests whether an account is being ReOpened by anyone except a system supervisor. Only system supervisors may 256 * reopen closed accounts. 257 * 258 * @param document - populated document containing the old and new accounts 259 * @param user - the user who is trying to possibly reopen the account 260 * @return true if: document is an edit document, old was closed and new is open, and the user is not one of the System 261 * Supervisors 262 */ 263 protected boolean isNonSystemSupervisorEditingAClosedAccount(MaintenanceDocument document, Person user) { 264 if (document.isEdit()) { 265 // do the test 266 if (oldAccount.isClosed() ) { 267 return !getDocumentHelperService().getDocumentAuthorizer(document).isAuthorized(document, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.EDIT_INACTIVE_ACCOUNT, user.getPrincipalId()); 268 } 269 return false; 270 } 271 return false; 272 } 273 274 /** 275 * This method tests whether a given account has the T - Temporary value for Restricted Status Code, but does not have a 276 * Restricted Status Date, which is required when the code is T. 277 * 278 * @param account 279 * @return true if the account is temporarily restricted but the status date is empty 280 */ 281 protected boolean hasTemporaryRestrictedStatusCodeButNoRestrictedStatusDate(Account account) { 282 283 boolean result = false; 284 285 if (StringUtils.isNotBlank(account.getAccountRestrictedStatusCode())) { 286 if (RESTRICTED_CD_TEMPORARILY_RESTRICTED.equalsIgnoreCase(account.getAccountRestrictedStatusCode().trim())) { 287 if (account.getAccountRestrictedStatusDate() == null) { 288 result = true; 289 } 290 } 291 } 292 return result; 293 } 294 295 /** 296 * Checks whether the account restricted status code is the default from the sub fund group. 297 * 298 * @param account 299 * @return true if the restricted status code is the same as the sub fund group's 300 */ 301 protected boolean hasDefaultRestrictedStatusCode(Account account) { 302 boolean result = false; 303 304 if (StringUtils.isNotBlank(account.getAccountRestrictedStatusCode())) { 305 result = account.getAccountRestrictedStatusCode().equals(account.getSubFundGroup().getAccountRestrictedStatusCode()); 306 } 307 308 return result; 309 } 310 311 /** 312 * This method checks some of the general business rules associated with this document Calls the following rules: 313 * accountNumberStartsWithAllowedPrefix isNonSystemSupervisorEditingAClosedAccount 314 * hasTemporaryRestrictedStatusCodeButNoRestrictedStatusDate checkFringeBenefitAccountRule checkUserStatusAndType (on fiscal 315 * officer, supervisor and manager) ensures that the fiscal officer, supervisor and manager are not the same 316 * isContinuationAccountExpired 317 * 318 * @param maintenanceDocument 319 * @return false on rules violation 320 */ 321 protected boolean checkGeneralRules(MaintenanceDocument maintenanceDocument) { 322 323 LOG.info("checkGeneralRules called"); 324 Person fiscalOfficer = newAccount.getAccountFiscalOfficerUser(); 325 Person accountManager = newAccount.getAccountManagerUser(); 326 Person accountSupervisor = newAccount.getAccountSupervisoryUser(); 327 328 boolean success = true; 329 330 // Enforce institutionally specified restrictions on account number prefixes 331 // (e.g. the account number cannot begin with a 3 or with 00.) 332 // Only bother trying if there is an account string to test 333 if (!StringUtils.isBlank(newAccount.getAccountNumber())) { 334 // test the number 335 success &= accountNumberStartsWithAllowedPrefix(newAccount.getAccountNumber(), getParameterService().getParameterValues(Account.class, ACCT_PREFIX_RESTRICTION)); 336 } 337 338 // only a FIS supervisor can reopen a closed account. (This is the central super user, not an account supervisor). 339 // we need to get the old maintanable doc here 340 if (isNonSystemSupervisorEditingAClosedAccount(maintenanceDocument, GlobalVariables.getUserSession().getPerson())) { 341 success &= false; 342 putFieldError("closed", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ONLY_SUPERVISORS_CAN_EDIT); 343 } 344 345 // check FringeBenefit account rules 346 success &= checkFringeBenefitAccountRule(newAccount); 347 348 if (ObjectUtils.isNotNull(fiscalOfficer) && !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER, fiscalOfficer.getPrincipalId())) { 349 super.putFieldError("accountFiscalOfficerUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {fiscalOfficer.getName(), KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER}); 350 success = false; 351 } 352 if (ObjectUtils.isNotNull(accountSupervisor) && !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR, accountSupervisor.getPrincipalId())) { 353 super.putFieldError("accountSupervisoryUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountSupervisor.getName(), KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR}); 354 success = false; 355 } 356 if (ObjectUtils.isNotNull(accountManager) && !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER, accountManager.getPrincipalId())) { 357 super.putFieldError("accountManagerUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountManager.getName(), KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER}); 358 success = false; 359 } 360 361 // the supervisor cannot be the same as the fiscal officer or account manager. 362 if (isSupervisorSameAsFiscalOfficer(newAccount)) { 363 success &= false; 364 putFieldError("accountsSupervisorySystemsIdentifier", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_FISCAL_OFFICER); 365 } 366 if (isSupervisorSameAsManager(newAccount)) { 367 success &= false; 368 putFieldError("accountManagerSystemIdentifier", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_ACCT_MGR); 369 } 370 371 // disallow continuation account being expired 372 if (isContinuationAccountExpired(newAccount)) { 373 success &= false; 374 putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_EXPIRED_CONTINUATION); 375 } 376 377 return success; 378 } 379 380 /** 381 * This method tests whether the continuation account entered (if any) has expired or not. 382 * 383 * @param newAccount 384 * @return true if continuation account has expired 385 */ 386 protected boolean isContinuationAccountExpired(Account newAccount) { 387 388 boolean result = false; 389 390 String chartCode = newAccount.getContinuationFinChrtOfAcctCd(); 391 String accountNumber = newAccount.getContinuationAccountNumber(); 392 393 // if either chartCode or accountNumber is not entered, then we 394 // can't continue, so exit 395 if (StringUtils.isBlank(chartCode) || StringUtils.isBlank(accountNumber)) { 396 return result; 397 } 398 399 // attempt to retrieve the continuation account from the DB 400 Account continuation = accountService.getByPrimaryId(chartCode, accountNumber); 401 402 // if the object doesn't exist, then we can't continue, so exit 403 if (ObjectUtils.isNull(continuation)) { 404 return result; 405 } 406 407 // at this point, we have a valid continuation account, so we just need to 408 // know whether its expired or not 409 result = continuation.isExpired(); 410 411 return result; 412 } 413 414 /** 415 * the fringe benefit account (otherwise known as the reportsToAccount) is required if the fringe benefit code is set to N. The 416 * fringe benefit code of the account designated to accept the fringes must be Y. 417 * 418 * @param newAccount 419 * @return 420 */ 421 protected boolean checkFringeBenefitAccountRule(Account newAccount) { 422 423 boolean result = true; 424 425 // if this account is selected as a Fringe Benefit Account, then we have nothing 426 // to test, so exit 427 if (newAccount.isAccountsFringesBnftIndicator()) { 428 return true; 429 } 430 431 // if fringe benefit is not selected ... continue processing 432 433 // fringe benefit account number is required 434 if (StringUtils.isBlank(newAccount.getReportsToAccountNumber())) { 435 putFieldError("reportsToAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_REQUIRED_IF_FRINGEBENEFIT_FALSE); 436 result &= false; 437 } 438 439 // fringe benefit chart of accounts code is required 440 if (StringUtils.isBlank(newAccount.getReportsToChartOfAccountsCode())) { 441 putFieldError("reportsToChartOfAccountsCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_REQUIRED_IF_FRINGEBENEFIT_FALSE); 442 result &= false; 443 } 444 445 // if either of the fringe benefit account fields are not present, then we're done 446 if (result == false) { 447 return result; 448 } 449 450 // attempt to load the fringe benefit account 451 Account fringeBenefitAccount = accountService.getByPrimaryId(newAccount.getReportsToChartOfAccountsCode(), newAccount.getReportsToAccountNumber()); 452 453 // fringe benefit account must exist 454 if (fringeBenefitAccount == null) { 455 putFieldError("reportsToAccountNumber", KFSKeyConstants.ERROR_EXISTENCE, getFieldLabel(Account.class, "reportsToAccountNumber")); 456 return false; 457 } 458 459 // fringe benefit account must be active 460 if (!fringeBenefitAccount.isActive()) { 461 putFieldError("reportsToAccountNumber", KFSKeyConstants.ERROR_INACTIVE, getFieldLabel(Account.class, "reportsToAccountNumber")); 462 result &= false; 463 } 464 465 // make sure the fringe benefit account specified is set to fringe benefits = Y 466 if (!fringeBenefitAccount.isAccountsFringesBnftIndicator()) { 467 putFieldError("reportsToAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_MUST_BE_FLAGGED_FRINGEBENEFIT, fringeBenefitAccount.getChartOfAccountsCode() + "-" + fringeBenefitAccount.getAccountNumber()); 468 result &= false; 469 } 470 471 return result; 472 } 473 474 /** 475 * This method is a helper method for checking if the supervisor user is the same as the fiscal officer Calls 476 * {@link AccountRule#areTwoUsersTheSame(Person, Person)} 477 * 478 * @param accountGlobals 479 * @return true if the two users are the same 480 */ 481 protected boolean isSupervisorSameAsFiscalOfficer(Account account) { 482 return areTwoUsersTheSame(account.getAccountSupervisoryUser(), account.getAccountFiscalOfficerUser()); 483 } 484 485 /** 486 * This method is a helper method for checking if the supervisor user is the same as the manager Calls 487 * {@link AccountRule#areTwoUsersTheSame(Person, Person)} 488 * 489 * @param accountGlobals 490 * @return true if the two users are the same 491 */ 492 protected boolean isSupervisorSameAsManager(Account account) { 493 return areTwoUsersTheSame(account.getAccountSupervisoryUser(), account.getAccountManagerUser()); 494 } 495 496 /** 497 * This method checks to see if two users are the same Person using their identifiers 498 * 499 * @param user1 500 * @param user2 501 * @return true if these two users are the same 502 */ 503 protected boolean areTwoUsersTheSame(Person user1, Person user2) { 504 if (ObjectUtils.isNull(user1) || user1.getPrincipalId() == null ) { 505 return false; 506 } 507 if (ObjectUtils.isNull(user2) || user2.getPrincipalId() == null ) { 508 return false; 509 } 510 return user1.getPrincipalId().equals(user2.getPrincipalId()); 511 } 512 513 /** 514 * 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 515 * additional rule checkAccountExpirationDateValidTodayOrEarlier 516 * 517 * @param maintenanceDocument 518 * @return false on rules violation 519 */ 520 protected boolean checkCloseAccount(MaintenanceDocument maintenanceDocument) { 521 522 LOG.info("checkCloseAccount called"); 523 524 boolean success = true; 525 boolean isBeingClosed = false; 526 527 // if the account isnt being closed, then dont bother processing the rest of 528 // the method 529 if (oldAccount.isActive() && !newAccount.isActive()) { 530 isBeingClosed = true; 531 } 532 533 if (!isBeingClosed) { 534 return true; 535 } 536 537 // on an account being closed, the expiration date must be 538 success &= checkAccountExpirationDateValidTodayOrEarlier(newAccount); 539 540 // when closing an account, a continuation account is required 541 if (StringUtils.isBlank(newAccount.getContinuationAccountNumber())) { 542 putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CLOSE_CONTINUATION_ACCT_REQD); 543 success &= false; 544 } 545 if (StringUtils.isBlank(newAccount.getContinuationFinChrtOfAcctCd())) { 546 putFieldError("continuationFinChrtOfAcctCd", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CLOSE_CONTINUATION_ACCT_REQD); 547 success &= false; 548 } 549 550 // must have no pending ledger entries 551 if (generalLedgerPendingEntryService.hasPendingGeneralLedgerEntry(newAccount)) { 552 putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_PENDING_LEDGER_ENTRIES); 553 success &= false; 554 } 555 556 // beginning balance must be loaded in order to close account 557 if (!balanceService.beginningBalanceLoaded(newAccount)) { 558 putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_NO_LOADED_BEGINNING_BALANCE); 559 success &= false; 560 } 561 562 // must have no base budget, must have no open encumbrances, must have no asset, liability or fund balance balances other 563 // than object code 9899 564 // (9899 is fund balance for us), and the process of closing income and expense into 9899 must take the 9899 balance to 565 // zero. 566 if (balanceService.hasAssetLiabilityFundBalanceBalances(newAccount)) { 567 putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_NO_FUND_BALANCES); 568 success &= false; 569 } 570 571 // We must not have any pending labor ledger entries 572 if (SpringContext.getBean(LaborModuleService.class).hasPendingLaborLedgerEntry(newAccount.getChartOfAccountsCode(), newAccount.getAccountNumber())) { 573 putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_PENDING_LABOR_LEDGER_ENTRIES); 574 success &= false; 575 } 576 577 return success; 578 } 579 580 /** 581 * This method checks to see if the account expiration date is today's date or earlier 582 * 583 * @param newAccount 584 * @return fails if the expiration date is null or after today's date 585 */ 586 protected boolean checkAccountExpirationDateValidTodayOrEarlier(Account newAccount) { 587 588 // get today's date, with no time component 589 Date todaysDate = new Date(getDateTimeService().getCurrentDate().getTime()); 590 todaysDate.setTime(DateUtils.truncate(todaysDate, Calendar.DAY_OF_MONTH).getTime()); 591 // TODO: convert this to using Wes' Kuali DateUtils once we're using Date's instead of Timestamp 592 593 // get the expiration date, if any 594 Date expirationDate = newAccount.getAccountExpirationDate(); 595 if (ObjectUtils.isNull(expirationDate)) { 596 putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); 597 return false; 598 } 599 600 // when closing an account, the account expiration date must be the current date or earlier 601 expirationDate.setTime(DateUtils.truncate(expirationDate, Calendar.DAY_OF_MONTH).getTime()); 602 if (expirationDate.after(todaysDate)) { 603 putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); 604 return false; 605 } 606 607 return true; 608 } 609 610 /** 611 * This method checks to see if any Contracts and Grants business rules were violated Calls the following sub-rules: 612 * checkCgRequiredFields checkCgIncomeStreamRequired 613 * 614 * @param maintenanceDocument 615 * @return false on rules violation 616 */ 617 protected boolean checkContractsAndGrants(MaintenanceDocument maintenanceDocument) { 618 619 LOG.info("checkContractsAndGrants called"); 620 621 boolean success = true; 622 623 // Certain C&G fields are required if the Account belongs to the CG Fund Group 624 success &= checkCgRequiredFields(newAccount); 625 626 // Income Stream account is required if this account is CG fund group, 627 // or GF (general fund) fund group (with some exceptions) 628 success &= checkIncomeStreamValid(newAccount); 629 630 // check if the new account has a valid responsibility id 631 if (!ObjectUtils.isNull(newAccount)) { 632 final boolean hasValidAccountResponsibility = contractsAndGrantsModuleService.hasValidAccountReponsiblityIdIfNotNull(newAccount); 633 if (!hasValidAccountResponsibility) { 634 success &= hasValidAccountResponsibility; 635 putFieldError("contractsAndGrantsAccountResponsibilityId", KFSKeyConstants.ERROR_DOCUMENT_ACCTMAINT_INVALID_CG_RESPONSIBILITY , new String[] { newAccount.getContractsAndGrantsAccountResponsibilityId().toString(), newAccount.getChartOfAccountsCode(), newAccount.getAccountNumber() }); 636 } 637 } 638 639 return success; 640 } 641 642 /** 643 * This method checks to see if the income stream account is required 644 * 645 * @param newAccount 646 * @return fails if it is required and not entered, or not valid 647 */ 648 protected boolean checkIncomeStreamValid(Account newAccount) { 649 // if the subFundGroup object is null, we can't test, so exit 650 if (ObjectUtils.isNull(newAccount.getSubFundGroup())) { 651 return true; 652 } 653 String subFundGroupCode = newAccount.getSubFundGroupCode().trim(); 654 String fundGroupCode = newAccount.getSubFundGroup().getFundGroupCode().trim(); 655 boolean valid = true; 656 if (getParameterService().getParameterEvaluator(Account.class, KFSConstants.ChartApcParms.INCOME_STREAM_ACCOUNT_REQUIRING_FUND_GROUPS, fundGroupCode).evaluationSucceeds()) { 657 if (getParameterService().getParameterEvaluator(Account.class, KFSConstants.ChartApcParms.INCOME_STREAM_ACCOUNT_REQUIRING_SUB_FUND_GROUPS, subFundGroupCode).evaluationSucceeds()) { 658 if (StringUtils.isBlank(newAccount.getIncomeStreamFinancialCoaCode())) { 659 putFieldError(KFSPropertyConstants.INCOME_STREAM_CHART_OF_ACCOUNTS_CODE, KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_INCOME_STREAM_ACCT_COA_CANNOT_BE_EMPTY, new String[] { getDdService().getAttributeLabel(FundGroup.class, KFSConstants.FUND_GROUP_CODE_PROPERTY_NAME), fundGroupCode, getDdService().getAttributeLabel(SubFundGroup.class, KFSConstants.SUB_FUND_GROUP_CODE_PROPERTY_NAME), subFundGroupCode }); 660 valid = false; 661 } 662 if (StringUtils.isBlank(newAccount.getIncomeStreamAccountNumber())) { 663 putFieldError(KFSPropertyConstants.INCOME_STREAM_ACCOUNT_NUMBER, KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_INCOME_STREAM_ACCT_NBR_CANNOT_BE_EMPTY, new String[] { getDdService().getAttributeLabel(FundGroup.class, KFSConstants.FUND_GROUP_CODE_PROPERTY_NAME), fundGroupCode, getDdService().getAttributeLabel(SubFundGroup.class, KFSConstants.SUB_FUND_GROUP_CODE_PROPERTY_NAME), subFundGroupCode}); 664 valid = false; 665 } 666 } 667 } 668 if (valid && (StringUtils.isNotBlank(newAccount.getIncomeStreamFinancialCoaCode()) || StringUtils.isNotBlank(newAccount.getIncomeStreamAccountNumber()))) { 669 if(!(newAccount.getIncomeStreamAccountNumber().equals(newAccount.getAccountNumber()) && newAccount.getIncomeStreamFinancialCoaCode().equals(newAccount.getChartOfAccountsCode()))) { 670 if (!super.getDictionaryValidationService().validateReferenceExists(newAccount, KFSPropertyConstants.INCOME_STREAM_ACCOUNT)) { 671 putFieldError(KFSPropertyConstants.INCOME_STREAM_ACCOUNT_NUMBER, KFSKeyConstants.ERROR_EXISTENCE, new StringBuffer(getDdService().getAttributeLabel(SubFundGroup.class, KFSPropertyConstants.INCOME_STREAM_ACCOUNT_NUMBER)).append(": ").append(newAccount.getIncomeStreamFinancialCoaCode()).append("-").append(newAccount.getIncomeStreamAccountNumber()).toString()); 672 valid = false; 673 } 674 } 675 } 676 return valid; 677 } 678 679 /** 680 * This method checks to make sure that if the contracts and grants fields are required they are entered correctly 681 * 682 * @param newAccount 683 * @return 684 */ 685 protected boolean checkCgRequiredFields(Account newAccount) { 686 687 boolean result = true; 688 689 // Certain C&G fields are required if the Account belongs to the CG Fund Group 690 if (ObjectUtils.isNotNull(newAccount.getSubFundGroup())) { 691 if (getSubFundGroupService().isForContractsAndGrants(newAccount.getSubFundGroup())) { 692 result &= checkEmptyBOField("acctIndirectCostRcvyTypeCd", newAccount.getAcctIndirectCostRcvyTypeCd(), replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_TYPE_CODE_CANNOT_BE_EMPTY)); 693 result &= checkEmptyBOField("financialIcrSeriesIdentifier", newAccount.getFinancialIcrSeriesIdentifier(), replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_SERIES_IDENTIFIER_CANNOT_BE_EMPTY)); 694 695 // Validation for financialIcrSeriesIdentifier 696 if (checkEmptyBOField(KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, newAccount.getFinancialIcrSeriesIdentifier(), replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_SERIES_IDENTIFIER_CANNOT_BE_EMPTY))) { 697 String fiscalYear = StringUtils.EMPTY + SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear(); 698 String icrSeriesId = newAccount.getFinancialIcrSeriesIdentifier(); 699 700 Map<String, String> pkMap = new HashMap<String, String>(); 701 pkMap.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear); 702 pkMap.put(KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, icrSeriesId); 703 Collection<IndirectCostRecoveryRateDetail> icrRateDetails = getBoService().findMatching(IndirectCostRecoveryRateDetail.class, pkMap); 704 705 if (ObjectUtils.isNull(icrRateDetails) || icrRateDetails.isEmpty()) { 706 String label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(Account.class, KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER); 707 putFieldError(KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, KFSKeyConstants.ERROR_EXISTENCE, label + " (" + icrSeriesId + ")"); 708 result &= false; 709 } 710 else { 711 for(IndirectCostRecoveryRateDetail icrRateDetail : icrRateDetails) { 712 if(ObjectUtils.isNull(icrRateDetail.getIndirectCostRecoveryRate())){ 713 putFieldError(KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, KFSKeyConstants.IndirectCostRecovery.ERROR_DOCUMENT_ICR_RATE_NOT_FOUND, new String[]{fiscalYear, icrSeriesId}); 714 result &= false; 715 break; 716 } 717 } 718 } 719 } 720 721 result &= checkEmptyBOField("indirectCostRcvyFinCoaCode", newAccount.getIndirectCostRcvyFinCoaCode(), replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_CHART_CODE_CANNOT_BE_EMPTY)); 722 result &= checkEmptyBOField("indirectCostRecoveryAcctNbr", newAccount.getIndirectCostRecoveryAcctNbr(), replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_ACCOUNT_CANNOT_BE_EMPTY)); 723 result &= checkContractControlAccountNumberRequired(newAccount); 724 } 725 else { 726 // this is not a C&G fund group. So users should not fill in any fields in the C&G tab. 727 result &= checkCGFieldNotFilledIn(newAccount, "acctIndirectCostRcvyTypeCd"); 728 result &= checkCGFieldNotFilledIn(newAccount, "financialIcrSeriesIdentifier"); 729 result &= checkCGFieldNotFilledIn(newAccount, "indirectCostRcvyFinCoaCode"); 730 result &= checkCGFieldNotFilledIn(newAccount, "indirectCostRecoveryAcctNbr"); 731 } 732 } 733 return result; 734 } 735 736 /** 737 * This method is a helper method that replaces error tokens with values for contracts and grants labels 738 * 739 * @param errorConstant 740 * @return error string that has had tokens "{0}" and "{1}" replaced 741 */ 742 protected String replaceTokens(String errorConstant) { 743 String cngLabel = getSubFundGroupService().getContractsAndGrantsDenotingAttributeLabel(); 744 String cngValue = getSubFundGroupService().getContractsAndGrantsDenotingValueForMessage(); 745 String result = getKualiConfigurationService().getPropertyString(errorConstant); 746 result = StringUtils.replace(result, "{0}", cngLabel); 747 result = StringUtils.replace(result, "{1}", cngValue); 748 return result; 749 } 750 751 /** 752 * This method checks to make sure that if the contract control account exists it is the same as the Account that we are working 753 * on 754 * 755 * @param newAccount 756 * @return false if the contract control account is entered and is not the same as the account we are maintaining 757 */ 758 protected boolean checkContractControlAccountNumberRequired(Account newAccount) { 759 760 boolean result = true; 761 762 // Contract Control account must either exist or be the same as account being maintained 763 764 if (ObjectUtils.isNull(newAccount.getContractControlFinCoaCode())) { 765 return result; 766 } 767 if (ObjectUtils.isNull(newAccount.getContractControlAccountNumber())) { 768 return result; 769 } 770 if ((newAccount.getContractControlFinCoaCode().equals(newAccount.getChartOfAccountsCode())) && (newAccount.getContractControlAccountNumber().equals(newAccount.getAccountNumber()))) { 771 return result; 772 } 773 774 // do an existence/active test 775 DictionaryValidationService dvService = super.getDictionaryValidationService(); 776 boolean referenceExists = dvService.validateReferenceExists(newAccount, "contractControlAccount"); 777 if (!referenceExists) { 778 putFieldError("contractControlAccountNumber", KFSKeyConstants.ERROR_EXISTENCE, "Contract Control Account: " + newAccount.getContractControlFinCoaCode() + "-" + newAccount.getContractControlAccountNumber()); 779 result &= false; 780 } 781 782 return result; 783 } 784 785 /** 786 * This method checks to see if any expiration date field rules were violated 787 * 788 * @param maintenanceDocument 789 * @return false on rules violation 790 */ 791 protected boolean checkExpirationDate(MaintenanceDocument maintenanceDocument) { 792 793 LOG.info("checkExpirationDate called"); 794 795 boolean success = true; 796 797 Date oldExpDate = oldAccount.getAccountExpirationDate(); 798 Date newExpDate = newAccount.getAccountExpirationDate(); 799 Date today = new Date(getDateTimeService().getCurrentTimestamp().getTime()); 800 today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); // remove any time components 801 802 // When updating an account expiration date, the date must be today or later 803 // Only run this test if this maintenance doc 804 // is an edit doc 805 if (isUpdatedExpirationDateInvalid(maintenanceDocument)) { 806 putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER); 807 success &= false; 808 } 809 810 // a continuation account is required if the expiration date is completed. 811 if (ObjectUtils.isNotNull(newExpDate)) { 812 if (StringUtils.isBlank(newAccount.getContinuationAccountNumber())) { 813 putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED); 814 } 815 if (StringUtils.isBlank(newAccount.getContinuationFinChrtOfAcctCd())) { 816 putFieldError("continuationFinChrtOfAcctCd", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_FINCODE_REQD_IF_EXP_DATE_COMPLETED); 817 // putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED); 818 success &= false; 819 } 820 } 821 822 // If creating a new account if acct_expiration_dt is set then 823 // the acct_expiration_dt must be changed to a date that is today or later 824 if (maintenanceDocument.isNew() && ObjectUtils.isNotNull(newExpDate)) { 825 if (!newExpDate.after(today) && !newExpDate.equals(today)) { 826 putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER); 827 // putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER); 828 success &= false; 829 } 830 } 831 832 // acct_expiration_dt can not be before acct_effect_dt 833 Date effectiveDate = newAccount.getAccountEffectiveDate(); 834 if (ObjectUtils.isNotNull(effectiveDate) && ObjectUtils.isNotNull(newExpDate)) { 835 if (newExpDate.before(effectiveDate)) { 836 putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE); 837 // putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE); 838 success &= false; 839 } 840 } 841 842 return success; 843 } 844 845 /** 846 * This method checks to see if the new expiration date is different from the old expiration and if it has if it is invalid 847 * 848 * @param maintDoc 849 * @return true if expiration date has changed and is invalid 850 */ 851 protected boolean isUpdatedExpirationDateInvalid(MaintenanceDocument maintDoc) { 852 853 // if this isn't an Edit document, we're not interested 854 if (!maintDoc.isEdit()) { 855 return false; 856 } 857 858 Date oldExpDate = oldAccount.getAccountExpirationDate(); 859 Date newExpDate = newAccount.getAccountExpirationDate(); 860 Date today = new Date(getDateTimeService().getCurrentDate().getTime()); 861 today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); // remove any time components 862 863 // When updating an account expiration date, the date must be today or later 864 // Only run this test if this maintenance doc 865 // is an edit doc 866 boolean expDateHasChanged = false; 867 868 // if the old version of the account had no expiration date, and the new 869 // one has a date 870 if (ObjectUtils.isNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) { 871 expDateHasChanged = true; 872 } 873 874 // if there was an old and a new expDate, but they're different 875 else if (ObjectUtils.isNotNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) { 876 if (!oldExpDate.equals(newExpDate)) { 877 expDateHasChanged = true; 878 } 879 } 880 881 // if the expiration date hasn't changed, we're not interested 882 if (!expDateHasChanged) { 883 return false; 884 } 885 886 // make a shortcut to the newAccount 887 Account newAccount = (Account) maintDoc.getNewMaintainableObject().getBusinessObject(); 888 889 // expirationDate must be today or later than today (cannot be before today) 890 if (newExpDate.equals(today) || newExpDate.after(today)) { 891 return false; 892 } 893 else 894 return true; 895 } 896 897 /** 898 * This method checks to see if any Fund Group rules were violated Specifically: if we are dealing with a "GF" (General Fund) we 899 * cannot have an account with a budget recording level of "M" (Mixed) 900 * 901 * @param maintenanceDocument 902 * @return false on rules violation 903 */ 904 protected boolean checkFundGroup(MaintenanceDocument maintenanceDocument) { 905 906 LOG.info("checkFundGroup called"); 907 908 boolean success = true; 909 SubFundGroup subFundGroup = newAccount.getSubFundGroup(); 910 911 if (ObjectUtils.isNotNull(subFundGroup)) { 912 913 // get values for fundGroupCode and restrictedStatusCode 914 String fundGroupCode = ""; 915 String restrictedStatusCode = ""; 916 if (StringUtils.isNotBlank(subFundGroup.getFundGroupCode())) { 917 fundGroupCode = subFundGroup.getFundGroupCode().trim(); 918 } 919 if (StringUtils.isNotBlank(newAccount.getAccountRestrictedStatusCode())) { 920 restrictedStatusCode = newAccount.getAccountRestrictedStatusCode().trim(); 921 } 922 } 923 924 return success; 925 } 926 927 /** 928 * This method checks to see if any SubFund Group rules were violated Specifically: if SubFundGroup is empty or not "PFCMR" we 929 * cannot have a campus code or building code if SubFundGroup is "PFCMR" then campus code and building code "must" be entered 930 * and be valid codes 931 * 932 * @param maintenanceDocument 933 * @return false on rules violation 934 */ 935 protected boolean checkSubFundGroup(MaintenanceDocument maintenanceDocument) { 936 937 LOG.info("checkSubFundGroup called"); 938 939 boolean success = true; 940 941 String subFundGroupCode = newAccount.getSubFundGroupCode(); 942 943 if (newAccount.getAccountDescription() != null) { 944 945 String campusCode = newAccount.getAccountDescription().getCampusCode(); 946 String buildingCode = newAccount.getAccountDescription().getBuildingCode(); 947 948 // check if sub fund group code is blank 949 if (StringUtils.isBlank(subFundGroupCode)) { 950 951 // check if campus code and building code are NOT blank 952 if (!StringUtils.isBlank(campusCode) || !StringUtils.isBlank(buildingCode)) { 953 954 // if sub_fund_grp_cd is blank, campus code should NOT be entered 955 if (!StringUtils.isBlank(campusCode)) { 956 putFieldError("accountDescription.campusCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_BLANK_SUBFUNDGROUP_WITH_CAMPUS_CD_FOR_BLDG, subFundGroupCode); 957 success &= false; 958 } 959 960 // if sub_fund_grp_cd is blank, then bldg_cd should NOT be entered 961 if (!StringUtils.isBlank(buildingCode)) { 962 putFieldError("accountDescription.buildingCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_BLANK_SUBFUNDGROUP_WITH_BUILDING_CD, subFundGroupCode); 963 success &= false; 964 } 965 966 } 967 else { 968 969 // if all sub fund group, campus code, building code are all blank return true 970 return success; 971 } 972 973 } 974 else if (!StringUtils.isBlank(subFundGroupCode) && !ObjectUtils.isNull(newAccount.getSubFundGroup())) { 975 976 // Attempt to get the right SubFundGroup code to check the following logic with. If the value isn't available, go 977 // ahead 978 // and die, as this indicates a mis-configured application, and important business rules wont be implemented without it. 979 ParameterEvaluator evaluator = getParameterService().getParameterEvaluator(Account.class, ACCT_CAPITAL_SUBFUNDGROUP, subFundGroupCode.trim()); 980 981 if (evaluator.evaluationSucceeds()) { 982 983 // if sub_fund_grp_cd is 'PFCMR' then campus_cd must be entered 984 if (StringUtils.isBlank(campusCode)) { 985 putFieldError("accountDescription.campusCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CAMS_SUBFUNDGROUP_WITH_MISSING_CAMPUS_CD_FOR_BLDG, subFundGroupCode); 986 success &= false; 987 } 988 989 // if sub_fund_grp_cd is 'PFCMR' then bldg_cd must be entered 990 if (StringUtils.isBlank(buildingCode)) { 991 putFieldError("accountDescription.buildingCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CAMS_SUBFUNDGROUP_WITH_MISSING_BUILDING_CD, subFundGroupCode); 992 success &= false; 993 } 994 995 // the building object (campusCode & buildingCode) must exist in the DB 996 if (!StringUtils.isBlank(campusCode) && !StringUtils.isBlank(buildingCode)) { 997 998 // make sure that primary key fields are upper case 999 DataDictionaryService dds = getDdService(); 1000 Boolean buildingCodeForceUppercase = dds.getAttributeForceUppercase(AccountDescription.class, KFSPropertyConstants.BUILDING_CODE); 1001 if (StringUtils.isNotBlank(buildingCode) && buildingCodeForceUppercase != null && buildingCodeForceUppercase.booleanValue() == true) { 1002 buildingCode = buildingCode.toUpperCase(); 1003 } 1004 1005 Boolean campusCodeForceUppercase = dds.getAttributeForceUppercase(AccountDescription.class, KFSPropertyConstants.CAMPUS_CODE); 1006 if (StringUtils.isNotBlank(campusCode) && campusCodeForceUppercase != null && campusCodeForceUppercase.booleanValue() == true) { 1007 campusCode = campusCode.toUpperCase(); 1008 } 1009 1010 Map<String, String> pkMap = new HashMap<String, String>(); 1011 pkMap.put("campusCode", campusCode); 1012 pkMap.put("buildingCode", buildingCode); 1013 1014 Building building = (Building) getBoService().findByPrimaryKey(Building.class, pkMap); 1015 if (building == null) { 1016 putFieldError("accountDescription.campusCode", KFSKeyConstants.ERROR_EXISTENCE, campusCode); 1017 putFieldError("accountDescription.buildingCode", KFSKeyConstants.ERROR_EXISTENCE, buildingCode); 1018 success &= false; 1019 } 1020 } 1021 } 1022 else { 1023 1024 // if sub_fund_grp_cd is NOT 'PFCMR', campus code should NOT be entered 1025 if (!StringUtils.isBlank(campusCode)) { 1026 putFieldError("accountDescription.campusCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_NONCAMS_SUBFUNDGROUP_WITH_CAMPUS_CD_FOR_BLDG, subFundGroupCode); 1027 success &= false; 1028 } 1029 1030 // if sub_fund_grp_cd is NOT 'PFCMR' then bldg_cd should NOT be entered 1031 if (!StringUtils.isBlank(buildingCode)) { 1032 putFieldError("accountDescription.buildingCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_NONCAMS_SUBFUNDGROUP_WITH_BUILDING_CD, subFundGroupCode); 1033 success &= false; 1034 } 1035 } 1036 } 1037 1038 } 1039 1040 return success; 1041 } 1042 1043 /** 1044 * the income stream account is required if account's sub fund group code's fund group code is either GF or CG. 1045 * 1046 * @param newAccount 1047 * @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) 1048 * else return false. 1049 */ 1050 protected boolean checkIncomeStreamAccountRule() { 1051 // KFSMI-4877: if fund group is in system parameter values then income stream account number must exist. 1052 if ( ObjectUtils.isNotNull(newAccount.getSubFundGroup()) && StringUtils.isNotBlank(newAccount.getSubFundGroup().getFundGroupCode())) { 1053 if (ObjectUtils.isNull(newAccount.getIncomeStreamAccount())) { 1054 String incomeStreamRequiringFundGroupCode = SpringContext.getBean(ParameterService.class).getParameterValue(Account.class, KFSConstants.ChartApcParms.INCOME_STREAM_ACCOUNT_REQUIRING_FUND_GROUPS); 1055 if (StringUtils.containsIgnoreCase(newAccount.getSubFundGroup().getFundGroupCode(), incomeStreamRequiringFundGroupCode)) { 1056 GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_NUMBER, KFSKeyConstants.ERROR_DOCUMENT_BA_NO_INCOME_STREAM_ACCOUNT, newAccount.getAccountNumber()); 1057 return false; 1058 } 1059 } 1060 } 1061 return true; 1062 } 1063 1064 /** 1065 * This method checks to see if the contracts and grants fields are filled in or not 1066 * 1067 * @param account 1068 * @param propertyName - property to attach error to 1069 * @return false if the contracts and grants fields are blank 1070 */ 1071 protected boolean checkCGFieldNotFilledIn(Account account, String propertyName) { 1072 boolean success = true; 1073 Object value = ObjectUtils.getPropertyValue(account, propertyName); 1074 if ((value instanceof String && !StringUtils.isBlank(value.toString())) || (value != null)) { 1075 success = false; 1076 putFieldError(propertyName, KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CG_FIELDS_FILLED_FOR_NON_CG_ACCOUNT, new String[] { account.getSubFundGroupCode() }); 1077 } 1078 1079 return success; 1080 } 1081 1082 /** 1083 * This method checks to see if account is allowed to cross chart; 1084 * and if not makes sure that the account number is unique in the whole system. 1085 * This checking is only needed when adding a new account, 1086 * since users are not allowed to change account numbers on editing. 1087 * 1088 * @param maintenanceDocument 1089 * @return false on account-cross-chart rule violation 1090 */ 1091 protected boolean checkUniqueAccountNumber(MaintenanceDocument maintenanceDocument) { 1092 boolean success = true; 1093 String accountNumber = newAccount.getAccountNumber(); 1094 1095 if (maintenanceDocument.isNew() && // if adding a new account 1096 // while account is not allowed to cross chart 1097 !accountService.accountsCanCrossCharts() && 1098 // and with an account number that already exists 1099 !accountService.getAccountsForAccountNumber(accountNumber).isEmpty()) { 1100 // report error 1101 success = false; 1102 putFieldError("accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_NMBR_NOT_UNIQUE, accountNumber); 1103 } 1104 1105 return success; 1106 } 1107 1108 /** 1109 * This method sets the generalLedgerPendingEntryService 1110 * 1111 * @param generalLedgerPendingEntryService 1112 */ 1113 public void setGeneralLedgerPendingEntryService(GeneralLedgerPendingEntryService generalLedgerPendingEntryService) { 1114 this.generalLedgerPendingEntryService = generalLedgerPendingEntryService; 1115 } 1116 1117 /** 1118 * This method sets the balanceService 1119 * 1120 * @param balanceService 1121 */ 1122 public void setBalanceService(BalanceService balanceService) { 1123 this.balanceService = balanceService; 1124 } 1125 1126 /** 1127 * Sets the accountService attribute value. 1128 * 1129 * @param accountService The accountService to set. 1130 */ 1131 public final void setAccountService(AccountService accountService) { 1132 this.accountService = accountService; 1133 } 1134 1135 /** 1136 * Sets the contractsAndGrantsModuleService attribute value. 1137 * @param contractsAndGrantsModuleService The contractsAndGrantsModuleService to set. 1138 */ 1139 public void setContractsAndGrantsModuleService(ContractsAndGrantsModuleService contractsAndGrantsModuleService) { 1140 this.contractsAndGrantsModuleService = contractsAndGrantsModuleService; 1141 } 1142 1143 public SubFundGroupService getSubFundGroupService() { 1144 if ( subFundGroupService == null ) { 1145 subFundGroupService = SpringContext.getBean(SubFundGroupService.class); 1146 } 1147 return subFundGroupService; 1148 } 1149 1150 public ParameterService getParameterService() { 1151 if ( parameterService == null ) { 1152 parameterService = SpringContext.getBean(ParameterService.class); 1153 } 1154 return parameterService; 1155 } 1156 1157 } 1158