/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.coa.document.validation.impl; import java.sql.Date; import java.sql.Timestamp; import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DateUtils; import org.kuali.kfs.coa.businessobject.Account; import org.kuali.kfs.coa.businessobject.AccountDescription; import org.kuali.kfs.coa.businessobject.AccountGuideline; import org.kuali.kfs.coa.businessobject.FundGroup; import org.kuali.kfs.coa.businessobject.IndirectCostRecoveryAccount; import org.kuali.kfs.coa.businessobject.IndirectCostRecoveryRateDetail; import org.kuali.kfs.coa.businessobject.SubFundGroup; import org.kuali.kfs.coa.service.AccountService; import org.kuali.kfs.coa.service.SubFundGroupService; import org.kuali.kfs.gl.service.BalanceService; import org.kuali.kfs.gl.service.EncumbranceService; import org.kuali.kfs.integration.cg.ContractsAndGrantsModuleService; import org.kuali.kfs.integration.ld.LaborModuleService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSKeyConstants; import org.kuali.kfs.sys.KFSParameterKeyConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.Building; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService; import org.kuali.kfs.sys.service.UniversityDateService; import org.kuali.kfs.sys.service.impl.KfsParameterConstants; import org.kuali.rice.core.api.parameter.ParameterEvaluator; import org.kuali.rice.core.api.parameter.ParameterEvaluatorService; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.kns.document.MaintenanceDocument; import org.kuali.rice.kns.service.DataDictionaryService; import org.kuali.rice.kns.service.DictionaryValidationService; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.MessageMap; import org.kuali.rice.krad.util.ObjectUtils; /** * Business rule(s) applicable to AccountMaintenance documents. */ public class AccountRule extends IndirectCostRecoveryAccountsRule { protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountRule.class); protected static final String ACCT_PREFIX_RESTRICTION = "PREFIXES"; protected static final String ACCT_CAPITAL_SUBFUNDGROUP = "CAPITAL_SUB_FUND_GROUPS"; @Deprecated protected static final String RESTRICTED_CD_TEMPORARILY_RESTRICTED = "T"; protected static SubFundGroupService subFundGroupService; protected static ParameterService parameterService; protected EncumbranceService encumbranceService; protected GeneralLedgerPendingEntryService generalLedgerPendingEntryService; protected BalanceService balanceService; protected AccountService accountService; protected ContractsAndGrantsModuleService contractsAndGrantsModuleService; protected Account oldAccount; protected Account newAccount; public AccountRule() { // Pseudo-inject some services. // // This approach is being used to make it simpler to convert the Rule classes // to spring-managed with these services injected by Spring at some later date. // When this happens, just remove these calls to the setters with // SpringContext, and configure the bean defs for spring. this.setGeneralLedgerPendingEntryService(SpringContext.getBean(GeneralLedgerPendingEntryService.class)); this.setBalanceService(SpringContext.getBean(BalanceService.class)); this.setAccountService(SpringContext.getBean(AccountService.class)); this.setContractsAndGrantsModuleService(SpringContext.getBean(ContractsAndGrantsModuleService.class)); } /** * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load * all sub-objects from the DB by their primary keys, if available. */ @Override public void setupConvenienceObjects() { // setup oldAccount convenience objects, make sure all possible sub-objects are populated oldAccount = (Account) super.getOldBo(); refreshSubObjects(oldAccount); // setup newAccount convenience objects, make sure all possible sub-objects are populated newAccount = (Account) super.getNewBo(); refreshSubObjects(newAccount); setActiveIndirectCostRecoveryAccountList(newAccount.getActiveIndirectCostRecoveryAccounts()); setBoFieldPath(KFSPropertyConstants.INDIRECT_COST_RECOVERY_ACCOUNTS); } /** * Refreshes the references of account * * @param account Account */ protected void refreshSubObjects(Account account) { if (account != null) { // refresh contacts if (account.getIndirectCostRecoveryAccounts() != null) { for (IndirectCostRecoveryAccount icra : account.getIndirectCostRecoveryAccounts()) { icra.refreshNonUpdateableReferences(); } } } } /** * This method calls the route rules but does not fail if any of them fail (this only happens on routing) * * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) { LOG.info("processCustomSaveDocumentBusinessRules called"); // call the route rules to report all of the messages, but ignore the result processCustomRouteDocumentBusinessRules(document); // Save always succeeds, even if there are business rule failures return true; } /** * This method calls the following rules: checkAccountGuidelinesValidation checkEmptyValues checkGeneralRules checkCloseAccount * checkContractsAndGrants checkExpirationDate checkFundGroup checkSubFundGroup checkFiscalOfficerIsValidKualiUser this rule * will fail on routing * * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { LOG.info("processCustomRouteDocumentBusinessRules called"); setupConvenienceObjects(); // default to success boolean success = true; // validate the embedded AccountGuideline object success &= checkAccountGuidelinesValidation(newAccount.getAccountGuideline()); success &= checkEmptyValues(document); success &= checkGeneralRules(document); success &= checkCloseAccount(document); success &= checkContractsAndGrants(document); success &= checkExpirationDate(document); success &= checkFundGroup(document); success &= checkSubFundGroup(document); success &= checkIncomeStreamAccountRule(); success &= checkUniqueAccountNumber(document); success &= checkOpenEncumbrances(); // process for IndirectCostRecovery Account success &= super.processCustomRouteDocumentBusinessRules(document); return success; } /** * This method checks the basic rules for empty values in an account and associated objects with this account If guidelines are * required for this Business Object it checks to make sure that it is filled out It also checks for partially filled out * reference keys on the following: continuationAccount incomeStreamAccount endowmentIncomeAccount reportsToAccount * contractControlAccount * * @param maintenanceDocument * @return false if any of these are empty */ protected boolean checkEmptyValues(MaintenanceDocument maintenanceDocument) { LOG.info("checkEmptyValues called"); boolean success = true; // guidelines are always required, except when the expirationDate is set, and its // earlier than today boolean guidelinesRequired = areGuidelinesRequired((Account) maintenanceDocument.getNewMaintainableObject().getBusinessObject()); // confirm that required guidelines are entered, if required if (guidelinesRequired) { success &= checkEmptyBOField("accountGuideline.accountExpenseGuidelineText", newAccount.getAccountGuideline().getAccountExpenseGuidelineText(), "Expense Guideline"); success &= checkEmptyBOField("accountGuideline.accountIncomeGuidelineText", newAccount.getAccountGuideline().getAccountIncomeGuidelineText(), "Income Guideline"); success &= checkEmptyBOField("accountGuideline.accountPurposeText", newAccount.getAccountGuideline().getAccountPurposeText(), "Account Purpose"); } // this set confirms that all fields which are grouped (ie, foreign keys of a reference // object), must either be none filled out, or all filled out. success &= checkForPartiallyFilledOutReferenceForeignKeys(KFSPropertyConstants.CONTINUATION_ACCOUNT); success &= checkForPartiallyFilledOutReferenceForeignKeys(KFSPropertyConstants.INCOME_STREAM_ACCOUNT); success &= checkForPartiallyFilledOutReferenceForeignKeys(KFSPropertyConstants.ENDOWMENT_INCOME_ACCOUNT); success &= checkForPartiallyFilledOutReferenceForeignKeys(KFSPropertyConstants.REPORTS_TO_ACCOUNT); success &= checkForPartiallyFilledOutReferenceForeignKeys(KFSPropertyConstants.CONTRACT_CONTROL_ACCOUNT); return success; } /** * This method validates that the account guidelines object is valid * * @param accountGuideline * @return true if account guideline is valid */ protected boolean checkAccountGuidelinesValidation(AccountGuideline accountGuideline) { MessageMap map = GlobalVariables.getMessageMap(); int errorCount = map.getErrorCount(); GlobalVariables.getMessageMap().addToErrorPath("document.newMaintainableObject.accountGuideline"); dictionaryValidationService.validateBusinessObject(accountGuideline, false); GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject.accountGuideline"); return map.getErrorCount() == errorCount; } /** * This method determines whether the guidelines are required, based on business rules. * * @param account - the populated Account bo to be evaluated * @return true if guidelines are required, false otherwise */ protected boolean areGuidelinesRequired(Account account) { boolean result = true; if (account.getAccountExpirationDate() != null) { Timestamp today = getDateTimeService().getCurrentTimestamp(); today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); if (account.getAccountExpirationDate().before(today)) { result = false; } } return result; } /** * This method tests whether the accountNumber passed in is prefixed with an allowed prefix, or an illegal one. The illegal * prefixes are passed in as an array of strings. * * @param accountNumber - The Account Number to be tested. * @param illegalValues - An Array of Strings of the unallowable prefixes. * @return false if the accountNumber starts with any of the illegalPrefixes, true otherwise */ protected boolean accountNumberStartsWithAllowedPrefix(String accountNumber, Collection<String> illegalValues) { boolean result = true; for (String illegalValue : illegalValues) { if (accountNumber.startsWith(illegalValue)) { result = false; putFieldError("accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_NMBR_NOT_ALLOWED, new String[] { accountNumber, illegalValue }); } } return result; } /** * This method tests whether an account is being ReOpened by anyone except a system supervisor. Only system supervisors may * reopen closed accounts. * * @param document - populated document containing the old and new accounts * @param user - the user who is trying to possibly reopen the account * @return true if: document is an edit document, old was closed and new is open, and the user is not one of the System * Supervisors */ protected boolean isNonSystemSupervisorEditingAClosedAccount(MaintenanceDocument document, Person user) { if (document.isEdit()) { // do the test if (oldAccount.isClosed() ) { return !getDocumentHelperService().getDocumentAuthorizer(document).isAuthorized(document, KFSConstants.PermissionNames.EDIT_INACTIVE_ACCOUNT.namespace, KFSConstants.PermissionNames.EDIT_INACTIVE_ACCOUNT.name, user.getPrincipalId()); } return false; } return false; } /** * This method tests whether a given account has the T - Temporary value for Restricted Status Code, but does not have a * Restricted Status Date, which is required when the code is T. * * @param account * @return true if the account is temporarily restricted but the status date is empty */ protected boolean hasTemporaryRestrictedStatusCodeButNoRestrictedStatusDate(Account account) { boolean result = false; if (StringUtils.isNotBlank(account.getAccountRestrictedStatusCode())) { if (RESTRICTED_CD_TEMPORARILY_RESTRICTED.equalsIgnoreCase(account.getAccountRestrictedStatusCode().trim())) { if (account.getAccountRestrictedStatusDate() == null) { result = true; } } } return result; } /** * Checks whether the account restricted status code is the default from the sub fund group. * * @param account * @return true if the restricted status code is the same as the sub fund group's */ protected boolean hasDefaultRestrictedStatusCode(Account account) { boolean result = false; if (StringUtils.isNotBlank(account.getAccountRestrictedStatusCode())) { result = account.getAccountRestrictedStatusCode().equals(account.getSubFundGroup().getAccountRestrictedStatusCode()); } return result; } /** * This method checks some of the general business rules associated with this document Calls the following rules: * accountNumberStartsWithAllowedPrefix isNonSystemSupervisorEditingAClosedAccount * hasTemporaryRestrictedStatusCodeButNoRestrictedStatusDate checkFringeBenefitAccountRule checkUserStatusAndType (on fiscal * officer, supervisor and manager) ensures that the fiscal officer, supervisor and manager are not the same * isContinuationAccountExpired * * @param maintenanceDocument * @return false on rules violation */ protected boolean checkGeneralRules(MaintenanceDocument maintenanceDocument) { LOG.info("checkGeneralRules called"); Person fiscalOfficer = newAccount.getAccountFiscalOfficerUser(); Person accountManager = newAccount.getAccountManagerUser(); Person accountSupervisor = newAccount.getAccountSupervisoryUser(); boolean success = true; // Enforce institutionally specified restrictions on account number prefixes // (e.g. the account number cannot begin with a 3 or with 00.) // Only bother trying if there is an account string to test if (!StringUtils.isBlank(newAccount.getAccountNumber())) { // test the number success &= accountNumberStartsWithAllowedPrefix(newAccount.getAccountNumber(), getParameterService().getParameterValuesAsString(Account.class, ACCT_PREFIX_RESTRICTION)); } Boolean isFridgeBenefitCalculationEnable = accountService.isFridgeBenefitCalculationEnable(); //if parameter evaluated to true, then Labor Benefit Rate Category Code must be filled in if (isFridgeBenefitCalculationEnable){ //check to see if the labor benefit category code is empty if (ObjectUtils.isNull(newAccount.getLaborBenefitRateCategoryCode())) { putFieldError(KFSPropertyConstants.LABOR_BENEFIT_RATE_CATEGORY_CODE, KFSKeyConstants.ERROR_EMPTY_LABOR_BENEFIT_CATEGORY_CODE); success &= false; } } // only a FIS supervisor can reopen a closed account. (This is the central super user, not an account supervisor). // we need to get the old maintanable doc here if (isNonSystemSupervisorEditingAClosedAccount(maintenanceDocument, GlobalVariables.getUserSession().getPerson())) { success &= false; putFieldError("closed", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ONLY_SUPERVISORS_CAN_EDIT); } // check FringeBenefit account rules success &= checkFringeBenefitAccountRule(newAccount); // When closing an account (or editing a closed account) skip the edits to see if fiscal officer, manager and supervisor are allowed in that role. // There are cases where the people in these roles have left the university but showing that they did serve in those roles at the time the account // was active is desired. if (!newAccount.isClosed() && !oldAccount.isClosed()) { if (ObjectUtils.isNotNull(fiscalOfficer) && fiscalOfficer.getPrincipalId() != null && !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.name, fiscalOfficer.getPrincipalId())) { super.putFieldError("accountFiscalOfficerUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {fiscalOfficer.getName(), KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.name}); success = false; } if (ObjectUtils.isNotNull(accountSupervisor) && accountSupervisor.getPrincipalId() != null && !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.namespace, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.name, accountSupervisor.getPrincipalId())) { super.putFieldError("accountSupervisoryUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountSupervisor.getName(), KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.namespace, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.name}); success = false; } if (ObjectUtils.isNotNull(accountManager) && accountManager.getPrincipalId() != null && !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.namespace, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.name, accountManager.getPrincipalId())) { super.putFieldError("accountManagerUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountManager.getName(), KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.namespace,KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.name}); success = false; } } // the supervisor cannot be the same as the fiscal officer or account manager. if (isSupervisorSameAsFiscalOfficer(newAccount)) { success &= false; putFieldError("accountsSupervisorySystemsIdentifier", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_FISCAL_OFFICER); } if (isSupervisorSameAsManager(newAccount)) { success &= false; putFieldError("accountManagerSystemIdentifier", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_ACCT_MGR); } //KFSMI-5961 if (ObjectUtils.isNotNull(newAccount.getContinuationFinChrtOfAcctCd()) && ObjectUtils.isNotNull(newAccount.getAccountNumber())){ if (isAccountAndContinuationAccountAreSame(newAccount)){ success &= false; putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CONT_ACCOUNT_CANNOT_BE_SAME); } else { // disallow continuation account being expired if (isContinuationAccountExpired(newAccount)) { success &= false; putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_EXPIRED_CONTINUATION); } } } success &= isLaborBenefitRateCategoryCodeValid(); return success; } protected boolean isLaborBenefitRateCategoryCodeValid() { //make sure the system parameter exists boolean success = true; if (SpringContext.getBean(ParameterService.class).parameterExists(KfsParameterConstants.FINANCIAL_SYSTEM_ALL.class, KFSParameterKeyConstants.LdParameterConstants.ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_IND)) { //check the system param to see if the labor benefit rate category should be filled in String sysParam = SpringContext.getBean(ParameterService.class).getParameterValueAsString(KfsParameterConstants.FINANCIAL_SYSTEM_ALL.class, KFSParameterKeyConstants.LdParameterConstants.ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_IND); LOG.debug("sysParam: " + sysParam); //if sysParam == Y then Labor Benefit Rate Category Code must be filled in if (sysParam.equalsIgnoreCase("Y")) { //check to see if the labor benefit category code is empty if (ObjectUtils.isNull(newAccount.getLaborBenefitRateCategoryCode())) { putFieldError("laborBenefitRateCategoryCode", KFSKeyConstants.ERROR_EMPTY_LABOR_BENEFIT_CATEGORY_CODE); success &= false; } else { // validate labor benefit category code is valid newAccount.refreshReferenceObject("laborBenefitRateCategory"); if (newAccount.getLaborBenefitRateCategory() == null) { putFieldError("laborBenefitRateCategoryCode", KFSKeyConstants.ERROR_LABOR_BENEFIT_CATEGORY_CODE); success &= false; } } } } return success; } /** * This method tests whether the account and continuation account are same. * * @param newAccount * @return true if the account and continuation account are same */ protected boolean isAccountAndContinuationAccountAreSame(Account newAccount) { return (newAccount.getChartOfAccountsCode().equals(newAccount.getContinuationFinChrtOfAcctCd())) && (newAccount.getAccountNumber().equals(newAccount.getContinuationAccountNumber())); } /** * This method tests whether the continuation account entered (if any) has expired or not. * * @param newAccount * @return true if continuation account has expired */ protected boolean isContinuationAccountExpired(Account newAccount) { boolean result = false; String chartCode = newAccount.getContinuationFinChrtOfAcctCd(); String accountNumber = newAccount.getContinuationAccountNumber(); // if either chartCode or accountNumber is not entered, then we // can't continue, so exit if (StringUtils.isBlank(chartCode) || StringUtils.isBlank(accountNumber)) { return result; } // attempt to retrieve the continuation account from the DB Account continuation = accountService.getByPrimaryId(chartCode, accountNumber); // if the object doesn't exist, then we can't continue, so exit if (ObjectUtils.isNull(continuation)) { return result; } // at this point, we have a valid continuation account, so we just need to // know whether its expired or not result = continuation.isExpired(); return result; } /** * the fringe benefit account (otherwise known as the reportsToAccount) is required if the fringe benefit code is set to N. The * fringe benefit code of the account designated to accept the fringes must be Y. * * @param newAccount * @return */ protected boolean checkFringeBenefitAccountRule(Account newAccount) { boolean result = true; // if this account is selected as a Fringe Benefit Account, then we have nothing // to test, so exit if (newAccount.isAccountsFringesBnftIndicator()) { return true; } // if fringe benefit is not selected ... continue processing // fringe benefit account number is required if (StringUtils.isBlank(newAccount.getReportsToAccountNumber())) { putFieldError("reportsToAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_REQUIRED_IF_FRINGEBENEFIT_FALSE); result &= false; } // fringe benefit chart of accounts code is required if (StringUtils.isBlank(newAccount.getReportsToChartOfAccountsCode())) { putFieldError("reportsToChartOfAccountsCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_REQUIRED_IF_FRINGEBENEFIT_FALSE); result &= false; } // if either of the fringe benefit account fields are not present, then we're done if (result == false) { return result; } // attempt to load the fringe benefit account Account fringeBenefitAccount = accountService.getByPrimaryId(newAccount.getReportsToChartOfAccountsCode(), newAccount.getReportsToAccountNumber()); // fringe benefit account must exist if (fringeBenefitAccount == null) { putFieldError("reportsToAccountNumber", KFSKeyConstants.ERROR_EXISTENCE, getFieldLabel(Account.class, "reportsToAccountNumber")); return false; } // fringe benefit account must be active if (!fringeBenefitAccount.isActive()) { putFieldError("reportsToAccountNumber", KFSKeyConstants.ERROR_INACTIVE, getFieldLabel(Account.class, "reportsToAccountNumber")); result &= false; } // make sure the fringe benefit account specified is set to fringe benefits = Y if (!fringeBenefitAccount.isAccountsFringesBnftIndicator()) { putFieldError("reportsToAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_MUST_BE_FLAGGED_FRINGEBENEFIT, fringeBenefitAccount.getChartOfAccountsCode() + "-" + fringeBenefitAccount.getAccountNumber()); result &= false; } return result; } /** * This method is a helper method for checking if the supervisor user is the same as the fiscal officer Calls * {@link AccountRule#areTwoUsersTheSame(Person, Person)} * * @param accountGlobals * @return true if the two users are the same */ protected boolean isSupervisorSameAsFiscalOfficer(Account account) { return areTwoUsersTheSame(account.getAccountSupervisoryUser(), account.getAccountFiscalOfficerUser()); } /** * This method is a helper method for checking if the supervisor user is the same as the manager Calls * {@link AccountRule#areTwoUsersTheSame(Person, Person)} * * @param accountGlobals * @return true if the two users are the same */ protected boolean isSupervisorSameAsManager(Account account) { return areTwoUsersTheSame(account.getAccountSupervisoryUser(), account.getAccountManagerUser()); } /** * This method checks to see if two users are the same Person using their identifiers * * @param user1 * @param user2 * @return true if these two users are the same */ protected boolean areTwoUsersTheSame(Person user1, Person user2) { if (ObjectUtils.isNull(user1) || user1.getPrincipalId() == null ) { return false; } if (ObjectUtils.isNull(user2) || user2.getPrincipalId() == null ) { return false; } return user1.getPrincipalId().equals(user2.getPrincipalId()); } /** * 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 * additional rule checkAccountExpirationDateValidTodayOrEarlier * * @param maintenanceDocument * @return false on rules violation */ protected boolean checkCloseAccount(MaintenanceDocument maintenanceDocument) { LOG.info("checkCloseAccount called"); boolean success = true; boolean isBeingClosed = false; // if the account isnt being closed, then dont bother processing the rest of // the method if (oldAccount.isActive() && !newAccount.isActive()) { isBeingClosed = true; } if (!isBeingClosed) { return true; } // on an account being closed, the expiration date must be success &= checkAccountExpirationDateValidTodayOrEarlier(newAccount); // when closing an account, a continuation account is required if (StringUtils.isBlank(newAccount.getContinuationAccountNumber())) { putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CLOSE_CONTINUATION_ACCT_REQD); success &= false; } if (StringUtils.isBlank(newAccount.getContinuationFinChrtOfAcctCd())) { putFieldError("continuationFinChrtOfAcctCd", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CLOSE_CONTINUATION_CHART_CODE_REQD); success &= false; } // must have no pending ledger entries if (generalLedgerPendingEntryService.hasPendingGeneralLedgerEntry(newAccount)) { putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_PENDING_LEDGER_ENTRIES); success &= false; } // beginning balance must be loaded in order to close account if (!balanceService.beginningBalanceLoaded(newAccount)) { putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_NO_LOADED_BEGINNING_BALANCE); success &= false; } // must have no base budget, must have no open encumbrances, must have no asset, liability or fund balance balances other // than object code 9899 // (9899 is fund balance for us), and the process of closing income and expense into 9899 must take the 9899 balance to // zero. if (balanceService.hasAssetLiabilityFundBalanceBalances(newAccount)) { putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_NO_FUND_BALANCES); success &= false; } // We must not have any pending labor ledger entries if (SpringContext.getBean(LaborModuleService.class).hasPendingLaborLedgerEntry(newAccount.getChartOfAccountsCode(), newAccount.getAccountNumber())) { putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_PENDING_LABOR_LEDGER_ENTRIES); success &= false; } return success; } /** * This method checks to see if the account expiration date is today's date or earlier * * @param newAccount * @return fails if the expiration date is null or after today's date */ protected boolean checkAccountExpirationDateValidTodayOrEarlier(Account newAccount) { // get today's date, with no time component Date todaysDate = new Date(getDateTimeService().getCurrentDate().getTime()); todaysDate.setTime(DateUtils.truncate(todaysDate, Calendar.DAY_OF_MONTH).getTime()); // TODO: convert this to using Wes' Kuali KfsDateUtils once we're using Date's instead of Timestamp // get the expiration date, if any Date expirationDate = newAccount.getAccountExpirationDate(); if (ObjectUtils.isNull(expirationDate)) { putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); return false; } // when closing an account, the account expiration date must be the current date or earlier expirationDate.setTime(DateUtils.truncate(expirationDate, Calendar.DAY_OF_MONTH).getTime()); if (expirationDate.after(todaysDate)) { putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); return false; } return true; } /** * This method checks to see if any Contracts and Grants business rules were violated Calls the following sub-rules: * checkCgRequiredFields checkCgIncomeStreamRequired * * @param maintenanceDocument * @return false on rules violation */ protected boolean checkContractsAndGrants(MaintenanceDocument maintenanceDocument) { LOG.info("checkContractsAndGrants called"); boolean success = true; // Certain C&G fields are required if the Account belongs to the CG Fund Group success &= checkCgRequiredFields(newAccount); // Income Stream account is required if this account is CG fund group, // or GF (general fund) fund group (with some exceptions) success &= checkIncomeStreamValid(newAccount); // check if the new account has a valid responsibility id if (!ObjectUtils.isNull(newAccount)) { final boolean hasValidAccountResponsibility = contractsAndGrantsModuleService.hasValidAccountReponsiblityIdIfNotNull(newAccount); if (!hasValidAccountResponsibility) { success &= hasValidAccountResponsibility; putFieldError("contractsAndGrantsAccountResponsibilityId", KFSKeyConstants.ERROR_DOCUMENT_ACCTMAINT_INVALID_CG_RESPONSIBILITY , new String[] { newAccount.getContractsAndGrantsAccountResponsibilityId().toString(), newAccount.getChartOfAccountsCode(), newAccount.getAccountNumber() }); } } return success; } /** * This method checks to see if the income stream account is required * * @param newAccount * @return fails if it is required and not entered, or not valid */ protected boolean checkIncomeStreamValid(Account newAccount) { // if the subFundGroup object is null, we can't test, so exit if (ObjectUtils.isNull(newAccount.getSubFundGroup())) { return true; } boolean valid = true; //if subfundgroupcode and fundgroup code are blanks if (StringUtils.isNotBlank(newAccount.getSubFundGroupCode()) && StringUtils.isNotBlank(newAccount.getSubFundGroup().getFundGroupCode())) { String subFundGroupCode = newAccount.getSubFundGroupCode().trim(); String fundGroupCode = newAccount.getSubFundGroup().getFundGroupCode().trim(); if (/*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(Account.class, KFSConstants.ChartApcParms.INCOME_STREAM_ACCOUNT_REQUIRING_FUND_GROUPS, fundGroupCode).evaluationSucceeds()) { if (/*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(Account.class, KFSConstants.ChartApcParms.INCOME_STREAM_ACCOUNT_REQUIRING_SUB_FUND_GROUPS, subFundGroupCode).evaluationSucceeds()) { if (StringUtils.isBlank(newAccount.getIncomeStreamFinancialCoaCode())) { 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 }); valid = false; } if (StringUtils.isBlank(newAccount.getIncomeStreamAccountNumber())) { 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}); valid = false; } } } if (valid && (StringUtils.isNotBlank(newAccount.getIncomeStreamFinancialCoaCode()) || StringUtils.isNotBlank(newAccount.getIncomeStreamAccountNumber()))) { if(!(StringUtils.equals( newAccount.getIncomeStreamAccountNumber(), newAccount.getAccountNumber()) && StringUtils.equals( newAccount.getIncomeStreamFinancialCoaCode(), newAccount.getChartOfAccountsCode()))) { if (!super.getDictionaryValidationService().validateReferenceExists(newAccount, KFSPropertyConstants.INCOME_STREAM_ACCOUNT)) { 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()); valid = false; } } } } return valid; } /** * This method checks to make sure that if the contracts and grants fields are required they are entered correctly * * @param newAccount * @return */ protected boolean checkCgRequiredFields(Account newAccount) { boolean result = true; // Certain C&G fields are required if the Account belongs to the CG Fund Group if (ObjectUtils.isNotNull(newAccount.getSubFundGroup())) { if (getSubFundGroupService().isForContractsAndGrants(newAccount.getSubFundGroup())) { result &= checkEmptyBOField("acctIndirectCostRcvyTypeCd", newAccount.getAcctIndirectCostRcvyTypeCd(), replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_TYPE_CODE_CANNOT_BE_EMPTY)); result &= checkEmptyBOField("financialIcrSeriesIdentifier", newAccount.getFinancialIcrSeriesIdentifier(), replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_SERIES_IDENTIFIER_CANNOT_BE_EMPTY)); // Validation for financialIcrSeriesIdentifier if (checkEmptyBOField(KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, newAccount.getFinancialIcrSeriesIdentifier(), replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_SERIES_IDENTIFIER_CANNOT_BE_EMPTY))) { String fiscalYear = StringUtils.EMPTY + SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear(); String icrSeriesId = newAccount.getFinancialIcrSeriesIdentifier(); Map<String, String> pkMap = new HashMap<String, String>(); pkMap.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, fiscalYear); pkMap.put(KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, icrSeriesId); Collection<IndirectCostRecoveryRateDetail> icrRateDetails = getBoService().findMatching(IndirectCostRecoveryRateDetail.class, pkMap); if (ObjectUtils.isNull(icrRateDetails) || icrRateDetails.isEmpty()) { String label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(Account.class, KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER); putFieldError(KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, KFSKeyConstants.ERROR_EXISTENCE, label + " (" + icrSeriesId + ")"); result &= false; } else { for(IndirectCostRecoveryRateDetail icrRateDetail : icrRateDetails) { if(ObjectUtils.isNull(icrRateDetail.getIndirectCostRecoveryRate())){ putFieldError(KFSPropertyConstants.FINANCIAL_ICR_SERIES_IDENTIFIER, KFSKeyConstants.IndirectCostRecovery.ERROR_DOCUMENT_ICR_RATE_NOT_FOUND, new String[]{fiscalYear, icrSeriesId}); result &= false; break; } } } } //check the ICR collection exists result &= checkICRCollectionExistWithErrorMessage(true, KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_CHART_CODE_CANNOT_BE_EMPTY, replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_CHART_CODE_CANNOT_BE_EMPTY)); result &= checkContractControlAccountNumberRequired(newAccount); } else { // this is not a C&G fund group. So users should not fill in any fields in the C&G tab. result &= checkCGFieldNotFilledIn(newAccount, "acctIndirectCostRcvyTypeCd"); result &= checkCGFieldNotFilledIn(newAccount, "financialIcrSeriesIdentifier"); //check the ICR collection NOT exists result &= checkICRCollectionExistWithErrorMessage(false, KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CG_ICR_FIELDS_FILLED_FOR_NON_CG_ACCOUNT, newAccount.getSubFundGroupCode()); } } return result; } /** * This method is a helper method that replaces error tokens with values for contracts and grants labels * * @param errorConstant * @return error string that has had tokens "{0}" and "{1}" replaced */ protected String replaceTokens(String errorConstant) { String cngLabel = getSubFundGroupService().getContractsAndGrantsDenotingAttributeLabel(); String cngValue = getSubFundGroupService().getContractsAndGrantsDenotingValueForMessage(); String result = getConfigService().getPropertyValueAsString(errorConstant); result = StringUtils.replace(result, "{0}", cngLabel); result = StringUtils.replace(result, "{1}", cngValue); return result; } /** * This method checks to make sure that if the contract control account exists it is the same as the Account that we are working * on * * @param newAccount * @return false if the contract control account is entered and is not the same as the account we are maintaining */ protected boolean checkContractControlAccountNumberRequired(Account newAccount) { boolean result = true; // Contract Control account must either exist or be the same as account being maintained if (ObjectUtils.isNull(newAccount.getContractControlFinCoaCode())) { return result; } if (ObjectUtils.isNull(newAccount.getContractControlAccountNumber())) { return result; } if ((newAccount.getContractControlFinCoaCode().equals(newAccount.getChartOfAccountsCode())) && (newAccount.getContractControlAccountNumber().equals(newAccount.getAccountNumber()))) { return result; } // do an existence/active test DictionaryValidationService dvService = super.getDictionaryValidationService(); boolean referenceExists = dvService.validateReferenceExists(newAccount, "contractControlAccount"); if (!referenceExists) { putFieldError("contractControlAccountNumber", KFSKeyConstants.ERROR_EXISTENCE, "Contract Control Account: " + newAccount.getContractControlFinCoaCode() + "-" + newAccount.getContractControlAccountNumber()); result &= false; } return result; } /** * This method checks to see if any expiration date field rules were violated * * @param maintenanceDocument * @return false on rules violation */ protected boolean checkExpirationDate(MaintenanceDocument maintenanceDocument) { LOG.info("checkExpirationDate called"); boolean success = true; Date oldExpDate = oldAccount.getAccountExpirationDate(); Date newExpDate = newAccount.getAccountExpirationDate(); Date today = new Date(getDateTimeService().getCurrentTimestamp().getTime()); today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); // remove any time components // When updating an account expiration date, the date must be today or later // Only run this test if this maintenance doc // is an edit doc if (isUpdatedExpirationDateInvalid(maintenanceDocument)) { Account newAccount = (Account) maintenanceDocument.getNewMaintainableObject().getBusinessObject(); if(newAccount.isClosed()){ /*If the Account is being closed and the date is before today's date, the EXP date can only be today*/ putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); } else{ /*If the Account is not being closed and the date is before today's date, the EXP date can only be today or at a later date*/ putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER); } success &= false; } // a continuation account is required if the expiration date is completed. if (ObjectUtils.isNotNull(newExpDate)) { if (StringUtils.isBlank(newAccount.getContinuationAccountNumber())) { putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED); } if (StringUtils.isBlank(newAccount.getContinuationFinChrtOfAcctCd())) { putFieldError("continuationFinChrtOfAcctCd", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_FINCODE_REQD_IF_EXP_DATE_COMPLETED); success &= false; } } // If creating a new account if acct_expiration_dt is set then // the acct_expiration_dt must be changed to a date that is today or later if (maintenanceDocument.isNew() && ObjectUtils.isNotNull(newExpDate)) { Collection<String> fundGroups = SpringContext.getBean(ParameterService.class).getParameterValuesAsString(Account.class, KFSConstants.ChartApcParms.EXPIRATION_DATE_BACKDATING_FUND_GROUPS); if (fundGroups == null || ObjectUtils.isNull(newAccount.getSubFundGroup()) || !fundGroups.contains(newAccount.getSubFundGroup().getFundGroupCode())) { if (!newExpDate.after(today) && !newExpDate.equals(today)) { putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER); success &= false; } } } // acct_expiration_dt can not be before acct_effect_dt Date effectiveDate = newAccount.getAccountEffectiveDate(); if (ObjectUtils.isNotNull(effectiveDate) && ObjectUtils.isNotNull(newExpDate)) { if (newExpDate.before(effectiveDate)) { putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE); success &= false; } } return success; } /** * This method checks to see if the new expiration date is different from the old expiration and if it has if it is invalid * * @param maintDoc * @return true if expiration date has changed and is invalid */ protected boolean isUpdatedExpirationDateInvalid(MaintenanceDocument maintDoc) { // if this isn't an Edit document, we're not interested if (!maintDoc.isEdit()) { return false; } Date oldExpDate = oldAccount.getAccountExpirationDate(); Date newExpDate = newAccount.getAccountExpirationDate(); Date today = new Date(getDateTimeService().getCurrentDate().getTime()); today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); // remove any time components // if the date was valid upon submission, and this is an approval, // we're not interested unless the approver changed the value if (maintDoc.getDocumentHeader().getWorkflowDocument().isApprovalRequested()) { try { MaintenanceDocument oldMaintDoc = (MaintenanceDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(maintDoc.getDocumentNumber()); Account oldAccount = (Account)oldMaintDoc.getDocumentBusinessObject(); if (ObjectUtils.isNotNull(oldAccount.getAccountExpirationDate()) && oldAccount.getAccountExpirationDate().equals(newExpDate)) { return false; } } catch (WorkflowException ex) { LOG.warn( "Error retrieving maintenance doc for doc #" + maintDoc.getDocumentNumber()+ ". This shouldn't happen.", ex ); } } // When updating an account expiration date, the date must be today or later // Only run this test if this maintenance doc // is an edit doc boolean expDateHasChanged = false; // if the old version of the account had no expiration date, and the new // one has a date if (ObjectUtils.isNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) { expDateHasChanged = true; } // if there was an old and a new expDate, but they're different else if (ObjectUtils.isNotNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) { if (!oldExpDate.equals(newExpDate)) { expDateHasChanged = true; } } // if the expiration date hasn't changed, we're not interested if (!expDateHasChanged) { return false; } // make a shortcut to the newAccount Account newAccount = (Account) maintDoc.getNewMaintainableObject().getBusinessObject(); /* expirationDate must be today or later than today (cannot be before today). * This rule doesn't apply to certain fund groups (C&G perhaps) according to * the parameter. */ Collection<String> fundGroups = SpringContext.getBean(ParameterService.class).getParameterValuesAsString(Account.class, KFSConstants.ChartApcParms.EXPIRATION_DATE_BACKDATING_FUND_GROUPS); if (fundGroups != null && ObjectUtils.isNotNull(newAccount.getSubFundGroup()) && fundGroups.contains(newAccount.getSubFundGroup().getFundGroupCode())) { return false; } return newExpDate.before(today); } /** * This method checks to see if any Fund Group rules were violated Specifically: if we are dealing with a "GF" (General Fund) we * cannot have an account with a budget recording level of "M" (Mixed) * * @param maintenanceDocument * @return false on rules violation */ protected boolean checkFundGroup(MaintenanceDocument maintenanceDocument) { LOG.info("checkFundGroup called"); boolean success = true; SubFundGroup subFundGroup = newAccount.getSubFundGroup(); if (ObjectUtils.isNotNull(subFundGroup)) { // get values for fundGroupCode and restrictedStatusCode String fundGroupCode = ""; String restrictedStatusCode = ""; if (StringUtils.isNotBlank(subFundGroup.getFundGroupCode())) { fundGroupCode = subFundGroup.getFundGroupCode().trim(); } if (StringUtils.isNotBlank(newAccount.getAccountRestrictedStatusCode())) { restrictedStatusCode = newAccount.getAccountRestrictedStatusCode().trim(); } } return success; } /** * This method checks to see if any SubFund Group rules were violated Specifically: if SubFundGroup is empty or not "PFCMR" we * cannot have a campus code or building code if SubFundGroup is "PFCMR" then campus code and building code "must" be entered * and be valid codes * * @param maintenanceDocument * @return false on rules violation */ protected boolean checkSubFundGroup(MaintenanceDocument maintenanceDocument) { LOG.info("checkSubFundGroup called"); boolean success = true; String subFundGroupCode = newAccount.getSubFundGroupCode(); if (newAccount.getAccountDescription() != null) { String campusCode = newAccount.getAccountDescription().getCampusCode(); String buildingCode = newAccount.getAccountDescription().getBuildingCode(); // check if sub fund group code is blank if (StringUtils.isBlank(subFundGroupCode)) { // check if campus code and building code are NOT blank if (!StringUtils.isBlank(campusCode) || !StringUtils.isBlank(buildingCode)) { // if sub_fund_grp_cd is blank, campus code should NOT be entered if (!StringUtils.isBlank(campusCode)) { putFieldError("accountDescription.campusCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_BLANK_SUBFUNDGROUP_WITH_CAMPUS_CD_FOR_BLDG, subFundGroupCode); success &= false; } // if sub_fund_grp_cd is blank, then bldg_cd should NOT be entered if (!StringUtils.isBlank(buildingCode)) { putFieldError("accountDescription.buildingCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_BLANK_SUBFUNDGROUP_WITH_BUILDING_CD, subFundGroupCode); success &= false; } } else { // if all sub fund group, campus code, building code are all blank return true return success; } } else if (!StringUtils.isBlank(subFundGroupCode) && !ObjectUtils.isNull(newAccount.getSubFundGroup())) { // Attempt to get the right SubFundGroup code to check the following logic with. If the value isn't available, go // ahead // and die, as this indicates a mis-configured application, and important business rules wont be implemented without it. ParameterEvaluator evaluator = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(Account.class, ACCT_CAPITAL_SUBFUNDGROUP, subFundGroupCode.trim()); if (evaluator.evaluationSucceeds()) { // if sub_fund_grp_cd is 'PFCMR' then campus_cd must be entered if (StringUtils.isBlank(campusCode)) { putFieldError("accountDescription.campusCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CAMS_SUBFUNDGROUP_WITH_MISSING_CAMPUS_CD_FOR_BLDG, subFundGroupCode); success &= false; } // if sub_fund_grp_cd is 'PFCMR' then bldg_cd must be entered if (StringUtils.isBlank(buildingCode)) { putFieldError("accountDescription.buildingCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CAMS_SUBFUNDGROUP_WITH_MISSING_BUILDING_CD, subFundGroupCode); success &= false; } // the building object (campusCode & buildingCode) must exist in the DB if (!StringUtils.isBlank(campusCode) && !StringUtils.isBlank(buildingCode)) { // make sure that primary key fields are upper case org.kuali.rice.krad.service.DataDictionaryService dds = getDdService(); Boolean buildingCodeForceUppercase = dds.getAttributeForceUppercase(AccountDescription.class, KFSPropertyConstants.BUILDING_CODE); if (StringUtils.isNotBlank(buildingCode) && buildingCodeForceUppercase != null && buildingCodeForceUppercase.booleanValue() == true) { buildingCode = buildingCode.toUpperCase(); } Boolean campusCodeForceUppercase = dds.getAttributeForceUppercase(AccountDescription.class, KFSPropertyConstants.CAMPUS_CODE); if (StringUtils.isNotBlank(campusCode) && campusCodeForceUppercase != null && campusCodeForceUppercase.booleanValue() == true) { campusCode = campusCode.toUpperCase(); } Map<String, String> pkMap = new HashMap<String, String>(); pkMap.put("campusCode", campusCode); pkMap.put("buildingCode", buildingCode); Building building = getBoService().findByPrimaryKey(Building.class, pkMap); if (building == null) { putFieldError("accountDescription.campusCode", KFSKeyConstants.ERROR_EXISTENCE, campusCode); putFieldError("accountDescription.buildingCode", KFSKeyConstants.ERROR_EXISTENCE, buildingCode); success &= false; } } } else { // if sub_fund_grp_cd is NOT 'PFCMR', campus code should NOT be entered if (!StringUtils.isBlank(campusCode)) { putFieldError("accountDescription.campusCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_NONCAMS_SUBFUNDGROUP_WITH_CAMPUS_CD_FOR_BLDG, subFundGroupCode); success &= false; } // if sub_fund_grp_cd is NOT 'PFCMR' then bldg_cd should NOT be entered if (!StringUtils.isBlank(buildingCode)) { putFieldError("accountDescription.buildingCode", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_NONCAMS_SUBFUNDGROUP_WITH_BUILDING_CD, subFundGroupCode); success &= false; } } } } return success; } /** * the income stream account is required if account's sub fund group code's fund group code is either GF or CG. * * @param newAccount * @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) * else return false. */ protected boolean checkIncomeStreamAccountRule() { // KFSMI-4877: if fund group is in system parameter values then income stream account number must exist. if ( ObjectUtils.isNotNull(newAccount.getSubFundGroup()) && StringUtils.isNotBlank(newAccount.getSubFundGroup().getFundGroupCode())) { if (ObjectUtils.isNull(newAccount.getIncomeStreamAccount())) { String incomeStreamRequiringFundGroupCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(Account.class, KFSConstants.ChartApcParms.INCOME_STREAM_ACCOUNT_REQUIRING_FUND_GROUPS); if (StringUtils.containsIgnoreCase(newAccount.getSubFundGroup().getFundGroupCode(), incomeStreamRequiringFundGroupCode)) { GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_NUMBER, KFSKeyConstants.ERROR_DOCUMENT_BA_NO_INCOME_STREAM_ACCOUNT, newAccount.getAccountNumber()); return false; } } } return true; } /** * This method checks to see if the contracts and grants fields are filled in or not * * @param account * @param propertyName - property to attach error to * @return false if the contracts and grants fields are blank */ protected boolean checkCGFieldNotFilledIn(Account account, String propertyName) { boolean success = true; Object value = ObjectUtils.getPropertyValue(account, propertyName); if ((value instanceof String && !StringUtils.isBlank(value.toString())) || (value != null)) { success = false; putFieldError(propertyName, KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CG_FIELDS_FILLED_FOR_NON_CG_ACCOUNT, new String[] { account.getSubFundGroupCode() }); } return success; } /** * This method checks to see if account is allowed to cross chart; * and if not makes sure that the account number is unique in the whole system. * This checking is only needed when adding a new account, * since users are not allowed to change account numbers on editing. * * @param maintenanceDocument * @return false on account-cross-chart rule violation */ protected boolean checkUniqueAccountNumber(MaintenanceDocument maintenanceDocument) { boolean success = true; String accountNumber = newAccount.getAccountNumber(); if (maintenanceDocument.isNew() && // if adding a new account // while account is not allowed to cross chart !accountService.accountsCanCrossCharts() && // and with an account number that already exists !accountService.getAccountsForAccountNumber(accountNumber).isEmpty()) { // report error success = false; putFieldError("accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_NMBR_NOT_UNIQUE, accountNumber); } return success; } protected boolean checkOpenEncumbrances() { boolean success = true; if(!oldAccount.isClosed() && newAccount.isClosed()){ Map<String, String> pkMap = new HashMap<String, String>(); pkMap.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear().toString() ); pkMap.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, oldAccount.getChartOfAccountsCode()); pkMap.put(KFSPropertyConstants.ACCOUNT_NUMBER, oldAccount.getAccountNumber()); int encumbranceCount = getEncumbranceService().getOpenEncumbranceRecordCount(pkMap, false); /* Some encumbrances were still showing up as open because the old method * looked at a count of records instead of totaling the records together and only counting the * ones that had a net balance.*/ if ( getEncumbranceService().hasSummarizedOpenEncumbranceRecords(pkMap, false)){ success = false; putFieldError("closed", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CANNOT_CLOSE_OPEN_ENCUMBRANCE); } } return success; } /** * This method sets the generalLedgerPendingEntryService * * @param generalLedgerPendingEntryService */ public void setGeneralLedgerPendingEntryService(GeneralLedgerPendingEntryService generalLedgerPendingEntryService) { this.generalLedgerPendingEntryService = generalLedgerPendingEntryService; } /** * This method sets the balanceService * * @param balanceService */ public void setBalanceService(BalanceService balanceService) { this.balanceService = balanceService; } /** * Sets the accountService attribute value. * * @param accountService The accountService to set. */ public final void setAccountService(AccountService accountService) { this.accountService = accountService; } /** * Sets the contractsAndGrantsModuleService attribute value. * @param contractsAndGrantsModuleService The contractsAndGrantsModuleService to set. */ public void setContractsAndGrantsModuleService(ContractsAndGrantsModuleService contractsAndGrantsModuleService) { this.contractsAndGrantsModuleService = contractsAndGrantsModuleService; } public SubFundGroupService getSubFundGroupService() { if ( subFundGroupService == null ) { subFundGroupService = SpringContext.getBean(SubFundGroupService.class); } return subFundGroupService; } public ParameterService getParameterService() { if ( parameterService == null ) { parameterService = SpringContext.getBean(ParameterService.class); } return parameterService; } public EncumbranceService getEncumbranceService() { if ( encumbranceService == null ) { encumbranceService = SpringContext.getBean(EncumbranceService.class); } return encumbranceService; } }