0001: /*
0002: * Copyright 2005-2007 The Kuali Foundation.
0003: *
0004: * Licensed under the Educational Community License, Version 1.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.opensource.org/licenses/ecl1.php
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.kuali.module.chart.rules;
0017:
0018: import java.sql.Timestamp;
0019: import java.util.Calendar;
0020: import java.util.HashMap;
0021: import java.util.List;
0022: import java.util.Map;
0023:
0024: import org.apache.commons.lang.StringUtils;
0025: import org.apache.commons.lang.time.DateUtils;
0026: import org.kuali.core.bo.user.UniversalUser;
0027: import org.kuali.core.document.MaintenanceDocument;
0028: import org.kuali.core.exceptions.UserNotFoundException;
0029: import org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase;
0030: import org.kuali.core.service.DataDictionaryService;
0031: import org.kuali.core.service.DictionaryValidationService;
0032: import org.kuali.core.service.KualiConfigurationService;
0033: import org.kuali.core.util.ErrorMap;
0034: import org.kuali.core.util.GlobalVariables;
0035: import org.kuali.core.util.ObjectUtils;
0036: import org.kuali.kfs.KFSConstants;
0037: import org.kuali.kfs.KFSKeyConstants;
0038: import org.kuali.kfs.KFSPropertyConstants;
0039: import org.kuali.kfs.bo.Building;
0040: import org.kuali.kfs.context.SpringContext;
0041: import org.kuali.kfs.service.GeneralLedgerPendingEntryService;
0042: import org.kuali.kfs.service.ParameterService;
0043: import org.kuali.module.chart.bo.Account;
0044: import org.kuali.module.chart.bo.AccountDescription;
0045: import org.kuali.module.chart.bo.AccountGuideline;
0046: import org.kuali.module.chart.bo.ChartUser;
0047: import org.kuali.module.chart.bo.FundGroup;
0048: import org.kuali.module.chart.bo.IcrAutomatedEntry;
0049: import org.kuali.module.chart.bo.SubFundGroup;
0050: import org.kuali.module.chart.service.AccountService;
0051: import org.kuali.module.chart.service.SubFundGroupService;
0052: import org.kuali.module.gl.service.BalanceService;
0053: import org.kuali.module.labor.service.LaborLedgerPendingEntryService;
0054:
0055: /**
0056: * Business rule(s) applicable to AccountMaintenance documents.
0057: */
0058: public class AccountRule extends MaintenanceDocumentRuleBase {
0059:
0060: protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
0061: .getLogger(AccountRule.class);
0062:
0063: private static final String ACCT_PREFIX_RESTRICTION = "PREFIXES";
0064: private static final String ACCT_CAPITAL_SUBFUNDGROUP = "CAPITAL_SUB_FUND_GROUP";
0065:
0066: private static final String GENERAL_FUND_CD = "GF";
0067: private static final String RESTRICTED_FUND_CD = "RF";
0068: private static final String ENDOWMENT_FUND_CD = "EN";
0069: private static final String PLANT_FUND_CD = "PF";
0070:
0071: private static final String RESTRICTED_CD_RESTRICTED = "R";
0072: private static final String RESTRICTED_CD_UNRESTRICTED = "U";
0073: private static final String RESTRICTED_CD_TEMPORARILY_RESTRICTED = "T";
0074: private static final String SUB_FUND_GROUP_MEDICAL_PRACTICE_FUNDS = "MPRACT";
0075: private static final String BUDGET_RECORDING_LEVEL_MIXED = "M";
0076:
0077: private GeneralLedgerPendingEntryService generalLedgerPendingEntryService;
0078: private LaborLedgerPendingEntryService laborLedgerPendingEntryService;
0079: private BalanceService balanceService;
0080: private AccountService accountService;
0081:
0082: private Account oldAccount;
0083: private Account newAccount;
0084:
0085: public AccountRule() {
0086:
0087: // Pseudo-inject some services.
0088: //
0089: // This approach is being used to make it simpler to convert the Rule classes
0090: // to spring-managed with these services injected by Spring at some later date.
0091: // When this happens, just remove these calls to the setters with
0092: // SpringContext, and configure the bean defs for spring.
0093: this .setGeneralLedgerPendingEntryService(SpringContext
0094: .getBean(GeneralLedgerPendingEntryService.class));
0095: this .setBalanceService(SpringContext
0096: .getBean(BalanceService.class));
0097: this .setAccountService(SpringContext
0098: .getBean(AccountService.class));
0099: this .setLaborLedgerPendingEntryService(SpringContext
0100: .getBean(LaborLedgerPendingEntryService.class));
0101: }
0102:
0103: /**
0104: * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and
0105: * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
0106: * all sub-objects from the DB by their primary keys, if available.
0107: */
0108: public void setupConvenienceObjects() {
0109:
0110: // setup oldAccount convenience objects, make sure all possible sub-objects are populated
0111: oldAccount = (Account) super .getOldBo();
0112:
0113: // setup newAccount convenience objects, make sure all possible sub-objects are populated
0114: newAccount = (Account) super .getNewBo();
0115: }
0116:
0117: /**
0118: * This method calls the route rules but does not fail if any of them fail (this only happens on routing)
0119: *
0120: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
0121: */
0122: protected boolean processCustomSaveDocumentBusinessRules(
0123: MaintenanceDocument document) {
0124:
0125: LOG.info("processCustomSaveDocumentBusinessRules called");
0126: // call the route rules to report all of the messages, but ignore the result
0127: processCustomRouteDocumentBusinessRules(document);
0128:
0129: // Save always succeeds, even if there are business rule failures
0130: return true;
0131: }
0132:
0133: /**
0134: * This method calls the following rules: checkAccountGuidelinesValidation checkEmptyValues checkGeneralRules checkCloseAccount
0135: * checkContractsAndGrants checkExpirationDate checkFundGroup checkSubFundGroup checkFiscalOfficerIsValidKualiUser this rule
0136: * will fail on routing
0137: *
0138: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
0139: */
0140: protected boolean processCustomRouteDocumentBusinessRules(
0141: MaintenanceDocument document) {
0142:
0143: LOG.info("processCustomRouteDocumentBusinessRules called");
0144: setupConvenienceObjects();
0145:
0146: // default to success
0147: boolean success = true;
0148:
0149: // validate the embedded AccountGuideline object
0150: success &= checkAccountGuidelinesValidation(newAccount
0151: .getAccountGuideline());
0152:
0153: success &= checkEmptyValues(document);
0154: success &= checkGeneralRules(document);
0155: success &= checkCloseAccount(document);
0156: success &= checkContractsAndGrants(document);
0157: success &= checkExpirationDate(document);
0158: success &= checkFundGroup(document);
0159: success &= checkSubFundGroup(document);
0160: success &= checkFiscalOfficerIsValidKualiUser(newAccount
0161: .getAccountFiscalOfficerSystemIdentifier());
0162:
0163: return success;
0164: }
0165:
0166: /**
0167: * This method checks the basic rules for empty values in an account and associated objects with this account If guidelines are
0168: * required for this Business Object it checks to make sure that it is filled out It also checks for partially filled out
0169: * reference keys on the following: continuationAccount incomeStreamAccount endowmentIncomeAccount reportsToAccount
0170: * contractControlAccount indirectCostRecoveryAcct
0171: *
0172: * @param maintenanceDocument
0173: * @return false if any of these are empty
0174: */
0175: protected boolean checkEmptyValues(
0176: MaintenanceDocument maintenanceDocument) {
0177:
0178: LOG.info("checkEmptyValues called");
0179:
0180: boolean success = true;
0181:
0182: // guidelines are always required, except when the expirationDate is set, and its
0183: // earlier than today
0184: boolean guidelinesRequired = areGuidelinesRequired((Account) maintenanceDocument
0185: .getNewMaintainableObject().getBusinessObject());
0186:
0187: // confirm that required guidelines are entered, if required
0188: if (guidelinesRequired) {
0189: success &= checkEmptyBOField(
0190: "accountGuideline.accountExpenseGuidelineText",
0191: newAccount.getAccountGuideline()
0192: .getAccountExpenseGuidelineText(),
0193: "Expense Guideline");
0194: success &= checkEmptyBOField(
0195: "accountGuideline.accountIncomeGuidelineText",
0196: newAccount.getAccountGuideline()
0197: .getAccountIncomeGuidelineText(),
0198: "Income Guideline");
0199: success &= checkEmptyBOField(
0200: "accountGuideline.accountPurposeText", newAccount
0201: .getAccountGuideline()
0202: .getAccountPurposeText(), "Account Purpose");
0203: }
0204:
0205: // this set confirms that all fields which are grouped (ie, foreign keys of a reference
0206: // object), must either be none filled out, or all filled out.
0207: success &= checkForPartiallyFilledOutReferenceForeignKeys("continuationAccount");
0208: success &= checkForPartiallyFilledOutReferenceForeignKeys("incomeStreamAccount");
0209: success &= checkForPartiallyFilledOutReferenceForeignKeys("endowmentIncomeAccount");
0210: success &= checkForPartiallyFilledOutReferenceForeignKeys("reportsToAccount");
0211: success &= checkForPartiallyFilledOutReferenceForeignKeys("contractControlAccount");
0212: success &= checkForPartiallyFilledOutReferenceForeignKeys("indirectCostRecoveryAcct");
0213:
0214: return success;
0215: }
0216:
0217: /**
0218: * This method validates that the account guidelines object is valid
0219: *
0220: * @param accountGuideline
0221: * @return true if account guideline is valid
0222: */
0223: protected boolean checkAccountGuidelinesValidation(
0224: AccountGuideline accountGuideline) {
0225: ErrorMap map = GlobalVariables.getErrorMap();
0226: int errorCount = map.getErrorCount();
0227: GlobalVariables.getErrorMap().addToErrorPath(
0228: "document.newMaintainableObject.accountGuideline");
0229: dictionaryValidationService.validateBusinessObject(
0230: accountGuideline, false);
0231: GlobalVariables.getErrorMap().removeFromErrorPath(
0232: "document.newMaintainableObject.accountGuideline");
0233: return map.getErrorCount() == errorCount;
0234: }
0235:
0236: /**
0237: * This method determines whether the guidelines are required, based on business rules.
0238: *
0239: * @param account - the populated Account bo to be evaluated
0240: * @return true if guidelines are required, false otherwise
0241: */
0242: protected boolean areGuidelinesRequired(Account account) {
0243:
0244: boolean result = true;
0245:
0246: if (account.getAccountExpirationDate() != null) {
0247: Timestamp today = getDateTimeService()
0248: .getCurrentTimestamp();
0249: today.setTime(DateUtils.truncate(today,
0250: Calendar.DAY_OF_MONTH).getTime());
0251: if (account.getAccountExpirationDate().before(today)) {
0252: result = false;
0253: }
0254: }
0255: return result;
0256: }
0257:
0258: /**
0259: * This method tests whether the accountNumber passed in is prefixed with an allowed prefix, or an illegal one. The illegal
0260: * prefixes are passed in as an array of strings.
0261: *
0262: * @param accountNumber - The Account Number to be tested.
0263: * @param illegalValues - An Array of Strings of the unallowable prefixes.
0264: * @return false if the accountNumber starts with any of the illegalPrefixes, true otherwise
0265: */
0266: protected boolean accountNumberStartsWithAllowedPrefix(
0267: String accountNumber, List<String> illegalValues) {
0268: boolean result = true;
0269: for (String illegalValue : illegalValues) {
0270: if (accountNumber.startsWith(illegalValue)) {
0271: result = false;
0272: putFieldError(
0273: "accountNumber",
0274: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_NMBR_NOT_ALLOWED,
0275: new String[] { accountNumber, illegalValue });
0276: }
0277: }
0278: return result;
0279: }
0280:
0281: /**
0282: * This method tests whether an account is being ReOpened by anyone except a system supervisor. Only system supervisors may
0283: * reopen closed accounts.
0284: *
0285: * @param document - populated document containing the old and new accounts
0286: * @param user - the user who is trying to possibly reopen the account
0287: * @return true if: document is an edit document, old was closed and new is open, and the user is not one of the System
0288: * Supervisors
0289: */
0290: protected boolean isNonSystemSupervisorEditingAClosedAccount(
0291: MaintenanceDocument document, UniversalUser user) {
0292:
0293: boolean result = false;
0294:
0295: if (document.isEdit()) {
0296:
0297: // get local references
0298: Account oldAccount = (Account) document
0299: .getOldMaintainableObject().getBusinessObject();
0300: Account newAccount = (Account) document
0301: .getNewMaintainableObject().getBusinessObject();
0302:
0303: // do the test
0304: if (oldAccount.isAccountClosedIndicator()
0305: && !user.isSupervisorUser()) {
0306: result = true;
0307: }
0308: }
0309: return result;
0310: }
0311:
0312: /**
0313: * This method tests whether a given account has the T - Temporary value for Restricted Status Code, but does not have a
0314: * Restricted Status Date, which is required when the code is T.
0315: *
0316: * @param account
0317: * @return true if the account is temporarily restricted but the status date is empty
0318: */
0319: protected boolean hasTemporaryRestrictedStatusCodeButNoRestrictedStatusDate(
0320: Account account) {
0321:
0322: boolean result = false;
0323:
0324: if (StringUtils.isNotBlank(account
0325: .getAccountRestrictedStatusCode())) {
0326: if (RESTRICTED_CD_TEMPORARILY_RESTRICTED
0327: .equalsIgnoreCase(account
0328: .getAccountRestrictedStatusCode().trim())) {
0329: if (account.getAccountRestrictedStatusDate() == null) {
0330: result = true;
0331: }
0332: }
0333: }
0334: return result;
0335: }
0336:
0337: /**
0338: * Checks whether the account restricted status code is the default from the sub fund group.
0339: *
0340: * @param account
0341: * @return true if the restricted status code is the same as the sub fund group's
0342: */
0343: protected boolean hasDefaultRestrictedStatusCode(Account account) {
0344: boolean result = false;
0345:
0346: if (StringUtils.isNotBlank(account
0347: .getAccountRestrictedStatusCode())) {
0348: result = account.getAccountRestrictedStatusCode().equals(
0349: account.getSubFundGroup()
0350: .getAccountRestrictedStatusCode());
0351: }
0352:
0353: return result;
0354: }
0355:
0356: /**
0357: * This method checks some of the general business rules associated with this document Calls the following rules:
0358: * accountNumberStartsWithAllowedPrefix isNonSystemSupervisorEditingAClosedAccount
0359: * hasTemporaryRestrictedStatusCodeButNoRestrictedStatusDate checkFringeBenefitAccountRule checkUserStatusAndType (on fiscal
0360: * officer, supervisor and manager) ensures that the fiscal officer, supervisor and manager are not the same
0361: * isContinuationAccountExpired
0362: *
0363: * @param maintenanceDocument
0364: * @return false on rules violation
0365: */
0366: protected boolean checkGeneralRules(
0367: MaintenanceDocument maintenanceDocument) {
0368:
0369: LOG.info("checkGeneralRules called");
0370: UniversalUser fiscalOfficer = newAccount
0371: .getAccountFiscalOfficerUser();
0372: UniversalUser accountManager = newAccount
0373: .getAccountManagerUser();
0374: UniversalUser accountSupervisor = newAccount
0375: .getAccountSupervisoryUser();
0376:
0377: boolean success = true;
0378:
0379: // Enforce institutionally specified restrictions on account number prefixes
0380: // (e.g. the account number cannot begin with a 3 or with 00.)
0381: // Only bother trying if there is an account string to test
0382: if (!StringUtils.isBlank(newAccount.getAccountNumber())) {
0383: // test the number
0384: success &= accountNumberStartsWithAllowedPrefix(newAccount
0385: .getAccountNumber(), SpringContext.getBean(
0386: ParameterService.class).getParameterValues(
0387: Account.class, ACCT_PREFIX_RESTRICTION));
0388: }
0389:
0390: // only a FIS supervisor can reopen a closed account. (This is the central super user, not an account supervisor).
0391: // we need to get the old maintanable doc here
0392: if (isNonSystemSupervisorEditingAClosedAccount(
0393: maintenanceDocument, GlobalVariables.getUserSession()
0394: .getUniversalUser())) {
0395: success &= false;
0396: putFieldError(
0397: "accountClosedIndicator",
0398: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ONLY_SUPERVISORS_CAN_EDIT);
0399: }
0400:
0401: // when a restricted status code of 'T' (temporarily restricted) is selected, a restricted status
0402: // date must be supplied.
0403: if (hasTemporaryRestrictedStatusCodeButNoRestrictedStatusDate(newAccount)) {
0404: success &= false;
0405: putFieldError(
0406: "accountRestrictedStatusDate",
0407: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_RESTRICTED_STATUS_DT_REQ,
0408: newAccount.getAccountNumber());
0409: }
0410:
0411: // check FringeBenefit account rules
0412: success &= checkFringeBenefitAccountRule(newAccount);
0413:
0414: // the employee type for fiscal officer, account manager, and account supervisor must be 'P' – professional.
0415: success &= checkUserStatusAndType(
0416: "accountFiscalOfficerUser.personUserIdentifier",
0417: fiscalOfficer);
0418: success &= checkUserStatusAndType(
0419: "accountSupervisoryUser.personUserIdentifier",
0420: accountSupervisor);
0421: success &= checkUserStatusAndType(
0422: "accountManagerUser.personUserIdentifier",
0423: accountManager);
0424:
0425: // the supervisor cannot be the same as the fiscal officer or account manager.
0426: if (isSupervisorSameAsFiscalOfficer(newAccount)) {
0427: success &= false;
0428: putFieldError(
0429: "accountsSupervisorySystemsIdentifier",
0430: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_FISCAL_OFFICER);
0431: }
0432: if (isSupervisorSameAsManager(newAccount)) {
0433: success &= false;
0434: putFieldError(
0435: "accountManagerSystemIdentifier",
0436: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_ACCT_MGR);
0437: }
0438:
0439: // disallow continuation account being expired
0440: if (isContinuationAccountExpired(newAccount)) {
0441: success &= false;
0442: putFieldError(
0443: "continuationAccountNumber",
0444: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_EXPIRED_CONTINUATION);
0445: }
0446:
0447: return success;
0448: }
0449:
0450: /**
0451: * This method tests whether the continuation account entered (if any) has expired or not.
0452: *
0453: * @param newAccount
0454: * @return true if continuation account has expired
0455: */
0456: protected boolean isContinuationAccountExpired(Account newAccount) {
0457:
0458: boolean result = false;
0459:
0460: String chartCode = newAccount.getContinuationFinChrtOfAcctCd();
0461: String accountNumber = newAccount
0462: .getContinuationAccountNumber();
0463:
0464: // if either chartCode or accountNumber is not entered, then we
0465: // cant continue, so exit
0466: if (StringUtils.isBlank(chartCode)
0467: || StringUtils.isBlank(accountNumber)) {
0468: return result;
0469: }
0470:
0471: // attempt to retrieve the continuation account from the DB
0472: Account continuation = null;
0473: Map pkMap = new HashMap();
0474: pkMap.put("chartOfAccountsCode", chartCode);
0475: pkMap.put("accountNumber", accountNumber);
0476: continuation = (Account) super .getBoService().findByPrimaryKey(
0477: Account.class, pkMap);
0478:
0479: // if the object doesnt exist, then we cant continue, so exit
0480: if (ObjectUtils.isNull(continuation)) {
0481: return result;
0482: }
0483:
0484: // at this point, we have a valid continuation account, so we just need to
0485: // know whether its expired or not
0486: result = continuation.isExpired();
0487:
0488: return result;
0489: }
0490:
0491: /**
0492: * the fringe benefit account (otherwise known as the reportsToAccount) is required if the fringe benefit code is set to N. The
0493: * fringe benefit code of the account designated to accept the fringes must be Y.
0494: *
0495: * @param newAccount
0496: * @return
0497: */
0498: protected boolean checkFringeBenefitAccountRule(Account newAccount) {
0499:
0500: boolean result = true;
0501:
0502: // if this account is selected as a Fringe Benefit Account, then we have nothing
0503: // to test, so exit
0504: if (newAccount.isAccountsFringesBnftIndicator()) {
0505: return true;
0506: }
0507:
0508: // if fringe benefit is not selected ... continue processing
0509:
0510: // fringe benefit account number is required
0511: if (StringUtils.isBlank(newAccount.getReportsToAccountNumber())) {
0512: putFieldError(
0513: "reportsToAccountNumber",
0514: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_REQUIRED_IF_FRINGEBENEFIT_FALSE);
0515: result &= false;
0516: }
0517:
0518: // fringe benefit chart of accounts code is required
0519: if (StringUtils.isBlank(newAccount
0520: .getReportsToChartOfAccountsCode())) {
0521: putFieldError(
0522: "reportsToChartOfAccountsCode",
0523: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_REQUIRED_IF_FRINGEBENEFIT_FALSE);
0524: result &= false;
0525: }
0526:
0527: // if either of the fringe benefit account fields are not present, then we're done
0528: if (result == false) {
0529: return result;
0530: }
0531:
0532: // attempt to load the fringe benefit account
0533: Account fringeBenefitAccount = accountService.getByPrimaryId(
0534: newAccount.getReportsToChartOfAccountsCode(),
0535: newAccount.getReportsToAccountNumber());
0536:
0537: // fringe benefit account must exist
0538: if (fringeBenefitAccount == null) {
0539: putFieldError("reportsToAccountNumber",
0540: KFSKeyConstants.ERROR_EXISTENCE, getFieldLabel(
0541: Account.class, "reportsToAccountNumber"));
0542: return false;
0543: }
0544:
0545: // fringe benefit account must be active
0546: if (fringeBenefitAccount.isAccountClosedIndicator()) {
0547: putFieldError("reportsToAccountNumber",
0548: KFSKeyConstants.ERROR_INACTIVE, getFieldLabel(
0549: Account.class, "reportsToAccountNumber"));
0550: result &= false;
0551: }
0552:
0553: // make sure the fringe benefit account specified is set to fringe benefits = Y
0554: if (!fringeBenefitAccount.isAccountsFringesBnftIndicator()) {
0555: putFieldError(
0556: "reportsToAccountNumber",
0557: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_RPTS_TO_ACCT_MUST_BE_FLAGGED_FRINGEBENEFIT,
0558: fringeBenefitAccount.getChartOfAccountsCode() + "-"
0559: + fringeBenefitAccount.getAccountNumber());
0560: result &= false;
0561: }
0562:
0563: return result;
0564: }
0565:
0566: /**
0567: * This method is a helper method for checking if the supervisor user is the same as the fiscal officer Calls
0568: * {@link AccountRule#areTwoUsersTheSame(UniversalUser, UniversalUser)}
0569: *
0570: * @param accountGlobals
0571: * @return true if the two users are the same
0572: */
0573: protected boolean isSupervisorSameAsFiscalOfficer(Account account) {
0574: return areTwoUsersTheSame(account.getAccountSupervisoryUser(),
0575: account.getAccountFiscalOfficerUser());
0576: }
0577:
0578: /**
0579: * This method is a helper method for checking if the supervisor user is the same as the manager Calls
0580: * {@link AccountRule#areTwoUsersTheSame(UniversalUser, UniversalUser)}
0581: *
0582: * @param accountGlobals
0583: * @return true if the two users are the same
0584: */
0585: protected boolean isSupervisorSameAsManager(Account account) {
0586: return areTwoUsersTheSame(account.getAccountSupervisoryUser(),
0587: account.getAccountManagerUser());
0588: }
0589:
0590: /**
0591: * This method checks to see if two users are the same BusinessObject using their identifiers
0592: *
0593: * @param user1
0594: * @param user2
0595: * @return true if these two users are the same
0596: */
0597: protected boolean areTwoUsersTheSame(UniversalUser user1,
0598: UniversalUser user2) {
0599: if (ObjectUtils.isNull(user1)) {
0600: return false;
0601: }
0602: if (ObjectUtils.isNull(user2)) {
0603: return false;
0604: }
0605: if (ObjectUtils.equalByKeys(user1, user2)) {
0606: return true;
0607: } else {
0608: return false;
0609: }
0610: }
0611:
0612: /**
0613: * This method checks to see if the user passed in is of the type requested. If so, it returns true. If not, it returns false,
0614: * and adds an error to the GlobalErrors.
0615: *
0616: * @param propertyName - property to attach error to
0617: * @param user - UniversalUser to be tested
0618: * @return true if user is of the requested employee type, false if not, true if the user object is null
0619: */
0620: protected boolean checkUserStatusAndType(String propertyName,
0621: UniversalUser user) {
0622:
0623: boolean success = true;
0624:
0625: // if the user isn't populated, exit with success
0626: // the actual existence check is performed in the general rules so not testing here
0627: if (ObjectUtils.isNull(user)) {
0628: return success;
0629: }
0630:
0631: // user must be of the allowable statuses (A - Active)
0632: if (!SpringContext
0633: .getBean(ParameterService.class)
0634: .getParameterEvaluator(
0635: Account.class,
0636: KFSConstants.ChartApcParms.ACCOUNT_USER_EMP_STATUSES,
0637: user.getEmployeeStatusCode())
0638: .evaluationSucceeds()) {
0639: success &= false;
0640: putFieldError(
0641: propertyName,
0642: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACTIVE_REQD_FOR_EMPLOYEE,
0643: getDdService().getAttributeLabel(Account.class,
0644: propertyName));
0645: }
0646:
0647: // user must be of the allowable types (P - Professional)
0648: if (!SpringContext
0649: .getBean(ParameterService.class)
0650: .getParameterEvaluator(
0651: Account.class,
0652: KFSConstants.ChartApcParms.ACCOUNT_USER_EMP_TYPES,
0653: user.getEmployeeTypeCode())
0654: .evaluationSucceeds()) {
0655: success &= false;
0656: putFieldError(
0657: propertyName,
0658: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_PRO_TYPE_REQD_FOR_EMPLOYEE,
0659: getDdService().getAttributeLabel(Account.class,
0660: propertyName));
0661: }
0662:
0663: return success;
0664: }
0665:
0666: /**
0667: * 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
0668: * additional rule checkAccountExpirationDateValidTodayOrEarlier
0669: *
0670: * @param maintenanceDocument
0671: * @return false on rules violation
0672: */
0673: protected boolean checkCloseAccount(
0674: MaintenanceDocument maintenanceDocument) {
0675:
0676: LOG.info("checkCloseAccount called");
0677:
0678: boolean success = true;
0679: boolean isBeingClosed = false;
0680:
0681: // if the account isnt being closed, then dont bother processing the rest of
0682: // the method
0683: if (!oldAccount.isAccountClosedIndicator()
0684: && newAccount.isAccountClosedIndicator()) {
0685: isBeingClosed = true;
0686: }
0687:
0688: if (!isBeingClosed) {
0689: return true;
0690: }
0691:
0692: // on an account being closed, the expiration date must be
0693: success &= checkAccountExpirationDateValidTodayOrEarlier(newAccount);
0694:
0695: // when closing an account, a continuation account is required
0696: if (StringUtils.isBlank(newAccount
0697: .getContinuationAccountNumber())) {
0698: putFieldError(
0699: "continuationAccountNumber",
0700: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CLOSE_CONTINUATION_ACCT_REQD);
0701: success &= false;
0702: }
0703: if (StringUtils.isBlank(newAccount
0704: .getContinuationFinChrtOfAcctCd())) {
0705: putFieldError(
0706: "continuationFinChrtOfAcctCd",
0707: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CLOSE_CONTINUATION_ACCT_REQD);
0708: success &= false;
0709: }
0710:
0711: // must have no pending ledger entries
0712: if (generalLedgerPendingEntryService
0713: .hasPendingGeneralLedgerEntry(newAccount)) {
0714: putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_PENDING_LEDGER_ENTRIES);
0715: success &= false;
0716: }
0717:
0718: // beginning balance must be loaded in order to close account
0719: if (!balanceService.beginningBalanceLoaded(newAccount)) {
0720: putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_NO_LOADED_BEGINNING_BALANCE);
0721: success &= false;
0722: }
0723:
0724: // must have no base budget, must have no open encumbrances, must have no asset, liability or fund balance balances other
0725: // than object code 9899
0726: // (9899 is fund balance for us), and the process of closing income and expense into 9899 must take the 9899 balance to
0727: // zero.
0728: if (balanceService
0729: .hasAssetLiabilityFundBalanceBalances(newAccount)) {
0730: putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_NO_FUND_BALANCES);
0731: success &= false;
0732: }
0733:
0734: // We must not have any pending labor ledger entries
0735: if (laborLedgerPendingEntryService
0736: .hasPendingLaborLedgerEntry(newAccount)) {
0737: putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_CLOSED_PENDING_LABOR_LEDGER_ENTRIES);
0738: success &= false;
0739: }
0740:
0741: return success;
0742: }
0743:
0744: /**
0745: * This method checsk to see if the account expiration date is today's date or earlier
0746: *
0747: * @param newAccount
0748: * @return fails if the expiration date is null or after today's date
0749: */
0750: protected boolean checkAccountExpirationDateValidTodayOrEarlier(
0751: Account newAccount) {
0752:
0753: // get today's date, with no time component
0754: Timestamp todaysDate = getDateTimeService()
0755: .getCurrentTimestamp();
0756: todaysDate.setTime(DateUtils.truncate(todaysDate,
0757: Calendar.DAY_OF_MONTH).getTime());
0758: // TODO: convert this to using Wes' kuali DateUtils once we're using Date's instead of Timestamp
0759:
0760: // get the expiration date, if any
0761: Timestamp expirationDate = newAccount
0762: .getAccountExpirationDate();
0763: if (ObjectUtils.isNull(expirationDate)) {
0764: putFieldError(
0765: "accountExpirationDate",
0766: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID);
0767: return false;
0768: }
0769:
0770: // when closing an account, the account expiration date must be the current date or earlier
0771: expirationDate.setTime(DateUtils.truncate(expirationDate,
0772: Calendar.DAY_OF_MONTH).getTime());
0773: if (expirationDate.after(todaysDate)) {
0774: putFieldError(
0775: "accountExpirationDate",
0776: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID);
0777: return false;
0778: }
0779:
0780: return true;
0781: }
0782:
0783: /**
0784: * This method checks to see if any Contracts and Grants business rules were violated Calls the following sub-rules:
0785: * checkCgRequiredFields checkCgIncomeStreamRequired
0786: *
0787: * @param maintenanceDocument
0788: * @return false on rules violation
0789: */
0790: protected boolean checkContractsAndGrants(
0791: MaintenanceDocument maintenanceDocument) {
0792:
0793: LOG.info("checkContractsAndGrants called");
0794:
0795: boolean success = true;
0796:
0797: // Certain C&G fields are required if the Account belongs to the CG Fund Group
0798: success &= checkCgRequiredFields(newAccount);
0799:
0800: // Income Stream account is required if this account is CG fund group,
0801: // or GF (general fund) fund group (with some exceptions)
0802: success &= checkCgIncomeStreamRequired(newAccount);
0803:
0804: return success;
0805: }
0806:
0807: /**
0808: * This method checks to see if the income stream account is required
0809: *
0810: * @param newAccount
0811: * @return fails if it is required and not entered, or not valid
0812: */
0813: protected boolean checkCgIncomeStreamRequired(Account newAccount) {
0814:
0815: boolean result = true;
0816: boolean required = false;
0817:
0818: // if the subFundGroup object is null, we cant test, so exit
0819: if (ObjectUtils.isNull(newAccount.getSubFundGroup())) {
0820: return result;
0821: }
0822:
0823: // retrieve the subfundcode and fundgroupcode
0824: String subFundGroupCode = newAccount.getSubFundGroupCode()
0825: .trim();
0826: String fundGroupCode = newAccount.getSubFundGroup()
0827: .getFundGroupCode().trim();
0828:
0829: String requiredByValue = "";
0830: String requiredByLabel = "";
0831:
0832: // if this is a CG fund group, then its required
0833: if (SpringContext.getBean(SubFundGroupService.class)
0834: .isForContractsAndGrants(newAccount.getSubFundGroup())) {
0835: required = true;
0836: requiredByLabel = SpringContext.getBean(
0837: SubFundGroupService.class)
0838: .getContractsAndGrantsDenotingAttributeLabel();
0839: requiredByValue = SpringContext.getBean(
0840: SubFundGroupService.class)
0841: .getContractsAndGrantsDenotingValue();
0842: }
0843:
0844: // if this is a general fund group, then its required
0845: else if (GENERAL_FUND_CD.equalsIgnoreCase(fundGroupCode)) {
0846: // unless its part of the MPRACT subfundgroup
0847: if (!SUB_FUND_GROUP_MEDICAL_PRACTICE_FUNDS
0848: .equalsIgnoreCase(subFundGroupCode)) {
0849: required = true;
0850: requiredByLabel = getDdService().getAttributeLabel(
0851: FundGroup.class,
0852: KFSConstants.FUND_GROUP_CODE_PROPERTY_NAME);
0853: requiredByValue = GENERAL_FUND_CD;
0854: }
0855: }
0856:
0857: // if the income stream account is not required, then we're done
0858: if (!required) {
0859: return result;
0860: }
0861:
0862: DictionaryValidationService dvService = super
0863: .getDictionaryValidationService();
0864:
0865: // make sure both coaCode and accountNumber are filled out
0866: boolean incomeStreamAccountIsValid = true;
0867: if (!checkEmptyValue(newAccount
0868: .getIncomeStreamFinancialCoaCode())) {
0869: putFieldError(
0870: "incomeStreamFinancialCoaCode",
0871: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_INCOME_STREAM_ACCT_COA_CANNOT_BE_EMPTY,
0872: new String[] { requiredByLabel, requiredByValue });
0873: incomeStreamAccountIsValid = false;
0874: }
0875: if (!checkEmptyValue(newAccount.getIncomeStreamAccountNumber())) {
0876: putFieldError(
0877: "incomeStreamAccountNumber",
0878: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_INCOME_STREAM_ACCT_NBR_CANNOT_BE_EMPTY,
0879: new String[] { requiredByLabel, requiredByValue });
0880: incomeStreamAccountIsValid = false;
0881: } else {
0882: // validate that the income stream account exists
0883: if (null != newAccount.getIncomeStreamAccountNumber()
0884: && null != newAccount
0885: .getIncomeStreamFinancialCoaCode()) {
0886: if (newAccount.getIncomeStreamAccountNumber().equals(
0887: newAccount.getAccountNumber())
0888: && newAccount
0889: .getIncomeStreamFinancialCoaCode()
0890: .equals(
0891: newAccount
0892: .getChartOfAccountsCode())) {
0893: // income stream account is valid
0894: } else if (!dvService.validateReferenceExists(
0895: newAccount, "incomeStreamAccount")) {
0896: putFieldError(
0897: "incomeStreamAccountNumber",
0898: KFSKeyConstants.ERROR_EXISTENCE,
0899: "Income Stream Account: "
0900: + newAccount
0901: .getIncomeStreamFinancialCoaCode()
0902: + "-"
0903: + newAccount
0904: .getIncomeStreamAccountNumber());
0905: incomeStreamAccountIsValid = false;
0906: }
0907: }
0908: }
0909:
0910: if (incomeStreamAccountIsValid) {
0911: result = true;
0912: } else {
0913: result = null != newAccount.getAccountNumber()
0914: && null != newAccount
0915: .getIncomeStreamAccountNumber();
0916: if (result) {
0917: result &= newAccount.getAccountNumber().equals(
0918: newAccount.getIncomeStreamAccountNumber());
0919: result &= newAccount.getChartOfAccountsCode().equals(
0920: newAccount.getIncomeStreamFinancialCoaCode());
0921: }
0922: }
0923:
0924: return result;
0925: }
0926:
0927: /**
0928: * This method checks to make sure that if the contracts and grants fields are required they are entered correctly
0929: *
0930: * @param newAccount
0931: * @return
0932: */
0933: protected boolean checkCgRequiredFields(Account newAccount) {
0934:
0935: boolean result = true;
0936:
0937: // Certain C&G fields are required if the Account belongs to the CG Fund Group
0938: if (ObjectUtils.isNotNull(newAccount.getSubFundGroup())) {
0939: if (SpringContext.getBean(SubFundGroupService.class)
0940: .isForContractsAndGrants(
0941: newAccount.getSubFundGroup())) {
0942: result &= checkEmptyBOField(
0943: "acctIndirectCostRcvyTypeCd",
0944: newAccount.getAcctIndirectCostRcvyTypeCd(),
0945: replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_TYPE_CODE_CANNOT_BE_EMPTY));
0946: result &= checkEmptyBOField(
0947: "financialIcrSeriesIdentifier",
0948: newAccount.getFinancialIcrSeriesIdentifier(),
0949: replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_SERIES_IDENTIFIER_CANNOT_BE_EMPTY));
0950:
0951: // Validation for financialIcrSeriesIdentifier
0952: if (checkEmptyBOField(
0953: "financialIcrSeriesIdentifier",
0954: newAccount.getFinancialIcrSeriesIdentifier(),
0955: replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_SERIES_IDENTIFIER_CANNOT_BE_EMPTY))) {
0956: Map pkMap = new HashMap();
0957: pkMap.put("financialIcrSeriesIdentifier",
0958: newAccount
0959: .getFinancialIcrSeriesIdentifier());
0960: if (getBoService().countMatching(
0961: IcrAutomatedEntry.class, pkMap) == 0) {
0962: putFieldError("financialIcrSeriesIdentifier",
0963: KFSKeyConstants.ERROR_EXISTENCE,
0964: "financialIcrSeriesIdentifier");
0965: result &= false;
0966: }
0967: }
0968:
0969: result &= checkEmptyBOField(
0970: "indirectCostRcvyFinCoaCode",
0971: newAccount.getIndirectCostRcvyFinCoaCode(),
0972: replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_CHART_CODE_CANNOT_BE_EMPTY));
0973: result &= checkEmptyBOField(
0974: "indirectCostRecoveryAcctNbr",
0975: newAccount.getIndirectCostRecoveryAcctNbr(),
0976: replaceTokens(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ICR_ACCOUNT_CANNOT_BE_EMPTY));
0977: result &= checkContractControlAccountNumberRequired(newAccount);
0978: } else {
0979: // this is not a C&G fund group. So users should not fill in any fields in the C&G tab.
0980: result &= checkCGFieldNotFilledIn(newAccount,
0981: "acctIndirectCostRcvyTypeCd");
0982: result &= checkCGFieldNotFilledIn(newAccount,
0983: "financialIcrSeriesIdentifier");
0984: result &= checkCGFieldNotFilledIn(newAccount,
0985: "indirectCostRcvyFinCoaCode");
0986: result &= checkCGFieldNotFilledIn(newAccount,
0987: "indirectCostRecoveryAcctNbr");
0988: }
0989: }
0990: return result;
0991: }
0992:
0993: /**
0994: * This method is a helper method that replaces error tokens with values for contracts and grants labels
0995: *
0996: * @param errorConstant
0997: * @return error string that has had tokens "{0}" and "{1}" replaced
0998: */
0999: private String replaceTokens(String errorConstant) {
1000: String cngLabel = SpringContext.getBean(
1001: SubFundGroupService.class)
1002: .getContractsAndGrantsDenotingAttributeLabel();
1003: String cngValue = SpringContext.getBean(
1004: SubFundGroupService.class)
1005: .getContractsAndGrantsDenotingValue();
1006: String result = SpringContext.getBean(
1007: KualiConfigurationService.class).getPropertyString(
1008: errorConstant);
1009: result = StringUtils.replace(result, "{0}", cngLabel);
1010: result = StringUtils.replace(result, "{1}", cngValue);
1011: return result;
1012: }
1013:
1014: /**
1015: * This method checks to make sure that if the contract control account exists it is the same as the Account that we are working
1016: * on
1017: *
1018: * @param newAccount
1019: * @return false if the contract control account is entered and is not the same as the account we are maintaining
1020: */
1021: protected boolean checkContractControlAccountNumberRequired(
1022: Account newAccount) {
1023:
1024: boolean result = true;
1025:
1026: // Contract Control account must either exist or be the same as account being maintained
1027:
1028: if (ObjectUtils.isNull(newAccount
1029: .getContractControlFinCoaCode())) {
1030: return result;
1031: }
1032: if (ObjectUtils.isNull(newAccount
1033: .getContractControlAccountNumber())) {
1034: return result;
1035: }
1036: if ((newAccount.getContractControlFinCoaCode()
1037: .equals(newAccount.getChartOfAccountsCode()))
1038: && (newAccount.getContractControlAccountNumber()
1039: .equals(newAccount.getAccountNumber()))) {
1040: return result;
1041: }
1042:
1043: // do an existence/active test
1044: DictionaryValidationService dvService = super
1045: .getDictionaryValidationService();
1046: boolean referenceExists = dvService.validateReferenceExists(
1047: newAccount, "contractControlAccount");
1048: if (!referenceExists) {
1049: putFieldError("contractControlAccountNumber",
1050: KFSKeyConstants.ERROR_EXISTENCE,
1051: "Contract Control Account: "
1052: + newAccount.getContractControlFinCoaCode()
1053: + "-"
1054: + newAccount
1055: .getContractControlAccountNumber());
1056: result &= false;
1057: }
1058:
1059: return result;
1060: }
1061:
1062: /**
1063: * This method checks to see if any expiration date field rules were violated
1064: *
1065: * @param maintenanceDocument
1066: * @return false on rules violation
1067: */
1068: protected boolean checkExpirationDate(
1069: MaintenanceDocument maintenanceDocument) {
1070:
1071: LOG.info("checkExpirationDate called");
1072:
1073: boolean success = true;
1074:
1075: Timestamp oldExpDate = oldAccount.getAccountExpirationDate();
1076: Timestamp newExpDate = newAccount.getAccountExpirationDate();
1077: Timestamp today = getDateTimeService().getCurrentTimestamp();
1078: today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH)
1079: .getTime()); // remove any time components
1080:
1081: // When updating an account expiration date, the date must be today or later
1082: // Only run this test if this maint doc
1083: // is an edit doc
1084: if (isUpdatedExpirationDateInvalid(maintenanceDocument)) {
1085: putFieldError(
1086: "accountExpirationDate",
1087: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
1088: success &= false;
1089: }
1090:
1091: // a continuation account is required if the expiration date is completed.
1092: if (ObjectUtils.isNotNull(newExpDate)) {
1093: if (StringUtils.isBlank(newAccount
1094: .getContinuationAccountNumber())) {
1095: putFieldError(
1096: "continuationAccountNumber",
1097: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED);
1098: }
1099: if (StringUtils.isBlank(newAccount
1100: .getContinuationFinChrtOfAcctCd())) {
1101: putFieldError(
1102: "continuationFinChrtOfAcctCd",
1103: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_FINCODE_REQD_IF_EXP_DATE_COMPLETED);
1104: // putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED);
1105: success &= false;
1106: }
1107: }
1108:
1109: // If creating a new account if acct_expiration_dt is set then
1110: // the acct_expiration_dt must be changed to a date that is today or later
1111: if (maintenanceDocument.isNew()
1112: && ObjectUtils.isNotNull(newExpDate)) {
1113: if (!newExpDate.after(today) && !newExpDate.equals(today)) {
1114: putFieldError(
1115: "accountExpirationDate",
1116: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
1117: // putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
1118: success &= false;
1119: }
1120: }
1121:
1122: // acct_expiration_dt can not be before acct_effect_dt
1123: Timestamp effectiveDate = newAccount.getAccountEffectiveDate();
1124: if (ObjectUtils.isNotNull(effectiveDate)
1125: && ObjectUtils.isNotNull(newExpDate)) {
1126: if (newExpDate.before(effectiveDate)) {
1127: putFieldError(
1128: "accountExpirationDate",
1129: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE);
1130: // putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE);
1131: success &= false;
1132: }
1133: }
1134:
1135: return success;
1136: }
1137:
1138: /**
1139: * This method checks to see if the new expiration date is different from the old expiration and if it has if it is invalid
1140: *
1141: * @param maintDoc
1142: * @return true if expiration date has changed and is invalid
1143: */
1144: protected boolean isUpdatedExpirationDateInvalid(
1145: MaintenanceDocument maintDoc) {
1146:
1147: // if this isnt an Edit document, we're not interested
1148: if (!maintDoc.isEdit()) {
1149: return false;
1150: }
1151:
1152: Timestamp oldExpDate = oldAccount.getAccountExpirationDate();
1153: Timestamp newExpDate = newAccount.getAccountExpirationDate();
1154: Timestamp today = getDateTimeService().getCurrentTimestamp();
1155: today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH)
1156: .getTime()); // remove any time components
1157:
1158: // When updating an account expiration date, the date must be today or later
1159: // Only run this test if this maint doc
1160: // is an edit doc
1161: boolean expDateHasChanged = false;
1162:
1163: // if the old version of the account had no expiration date, and the new
1164: // one has a date
1165: if (ObjectUtils.isNull(oldExpDate)
1166: && ObjectUtils.isNotNull(newExpDate)) {
1167: expDateHasChanged = true;
1168: }
1169:
1170: // if there was an old and a new expDate, but they're different
1171: else if (ObjectUtils.isNotNull(oldExpDate)
1172: && ObjectUtils.isNotNull(newExpDate)) {
1173: if (!oldExpDate.equals(newExpDate)) {
1174: expDateHasChanged = true;
1175: }
1176: }
1177:
1178: // if the expiration date hasnt changed, we're not interested
1179: if (!expDateHasChanged) {
1180: return false;
1181: }
1182:
1183: // make a shortcut to the newAccount
1184: Account newAccount = (Account) maintDoc
1185: .getNewMaintainableObject().getBusinessObject();
1186:
1187: // expirationDate must be today or later than today (cannot be before today)
1188: if (newExpDate.equals(today) || newExpDate.after(today)) {
1189: return false;
1190: } else
1191: return true;
1192: }
1193:
1194: /**
1195: * This method checks to see if any Fund Group rules were violated Specifically: if we are dealing with a "GF" (General Fund) we
1196: * cannot have an account with a budget recording level of "M" (Mixed)
1197: *
1198: * @param maintenanceDocument
1199: * @return false on rules violation
1200: */
1201: protected boolean checkFundGroup(
1202: MaintenanceDocument maintenanceDocument) {
1203:
1204: LOG.info("checkFundGroup called");
1205:
1206: boolean success = true;
1207: SubFundGroup subFundGroup = newAccount.getSubFundGroup();
1208:
1209: if (ObjectUtils.isNotNull(subFundGroup)) {
1210:
1211: // get values for fundGroupCode and restrictedStatusCode
1212: String fundGroupCode = "";
1213: String restrictedStatusCode = "";
1214: if (StringUtils.isNotBlank(subFundGroup.getFundGroupCode())) {
1215: fundGroupCode = subFundGroup.getFundGroupCode().trim();
1216: }
1217: if (StringUtils.isNotBlank(newAccount
1218: .getAccountRestrictedStatusCode())) {
1219: restrictedStatusCode = newAccount
1220: .getAccountRestrictedStatusCode().trim();
1221: }
1222:
1223: // an account in the general fund fund group cannot have a budget recording level of mixed.
1224: if (fundGroupCode.equalsIgnoreCase(GENERAL_FUND_CD)) {
1225: String budgetRecordingLevelCode = newAccount
1226: .getBudgetRecordingLevelCode();
1227: if (StringUtils.isNotEmpty(budgetRecordingLevelCode)) {
1228: if (budgetRecordingLevelCode
1229: .equalsIgnoreCase(BUDGET_RECORDING_LEVEL_MIXED)) {
1230: putFieldError(
1231: "budgetRecordingLevelCode",
1232: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_GF_BUDGET_RECORD_LVL_MIXED);
1233: success &= false;
1234: }
1235: }
1236: }
1237: }
1238:
1239: return success;
1240: }
1241:
1242: /**
1243: * This method insures the fiscal officer is a valid Kuali User
1244: *
1245: * @param fiscalOfficerUserId
1246: * @return true if they are a valid Kuali user
1247: */
1248: protected boolean checkFiscalOfficerIsValidKualiUser(
1249: String fiscalOfficerUserId) {
1250: boolean result = true;
1251: try {
1252: UniversalUser fiscalOfficer = getUniversalUserService()
1253: .getUniversalUser(fiscalOfficerUserId);
1254: if (fiscalOfficer != null
1255: && !fiscalOfficer
1256: .isActiveForModule(ChartUser.MODULE_ID)) {
1257: result = false;
1258: putFieldError(
1259: "accountFiscalOfficerUser.personUserIdentifier",
1260: KFSKeyConstants.ERROR_DOCUMENT_ACCOUNT_FISCAL_OFFICER_MUST_BE_KUALI_USER);
1261: }
1262: } catch (UserNotFoundException e) {
1263: result = false;
1264: }
1265:
1266: return result;
1267: }
1268:
1269: /**
1270: * This method checks to see if any SubFund Group rules were violated Specifically: if SubFundGroup is empty or not "PFCMR" we
1271: * cannot have a campus code or building code if SubFundGroup is "PFCMR" then campus code and building code "must" be entered
1272: * and be valid codes
1273: *
1274: * @param maintenanceDocument
1275: * @return false on rules violation
1276: */
1277: protected boolean checkSubFundGroup(
1278: MaintenanceDocument maintenanceDocument) {
1279:
1280: LOG.info("checkSubFundGroup called");
1281:
1282: boolean success = true;
1283:
1284: String subFundGroupCode = newAccount.getSubFundGroupCode();
1285:
1286: if (newAccount.getAccountDescription() != null) {
1287:
1288: String campusCode = newAccount.getAccountDescription()
1289: .getCampusCode();
1290: String buildingCode = newAccount.getAccountDescription()
1291: .getBuildingCode();
1292:
1293: // check if sub fund group code is blank
1294: if (StringUtils.isBlank(subFundGroupCode)) {
1295:
1296: // check if campus code and building code are NOT blank
1297: if (!StringUtils.isBlank(campusCode)
1298: || !StringUtils.isBlank(buildingCode)) {
1299:
1300: // if sub_fund_grp_cd is blank, campus code should NOT be entered
1301: if (!StringUtils.isBlank(campusCode)) {
1302: putFieldError(
1303: "accountDescription.campusCode",
1304: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_BLANK_SUBFUNDGROUP_WITH_CAMPUS_CD_FOR_BLDG,
1305: subFundGroupCode);
1306: success &= false;
1307: }
1308:
1309: // if sub_fund_grp_cd is blank, then bldg_cd should NOT be entered
1310: if (!StringUtils.isBlank(buildingCode)) {
1311: putFieldError(
1312: "accountDescription.buildingCode",
1313: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_BLANK_SUBFUNDGROUP_WITH_BUILDING_CD,
1314: subFundGroupCode);
1315: success &= false;
1316: }
1317:
1318: } else {
1319:
1320: // if all sub fund group, campus code, building code are all blank return true
1321: return success;
1322: }
1323:
1324: } else if (!StringUtils.isBlank(subFundGroupCode)
1325: && !ObjectUtils
1326: .isNull(newAccount.getSubFundGroup())) {
1327:
1328: // Attempt to get the right SubFundGroup code to check the following logic with. If the value isn't available, go
1329: // ahead
1330: // and die, as this indicates a misconfigured app, and important business rules wont be implemented without it.
1331: String capitalSubFundGroup = SpringContext.getBean(
1332: ParameterService.class).getParameterValue(
1333: Account.class, ACCT_CAPITAL_SUBFUNDGROUP);
1334:
1335: if (capitalSubFundGroup
1336: .equalsIgnoreCase(subFundGroupCode.trim())) {
1337:
1338: // if sub_fund_grp_cd is 'PFCMR' then campus_cd must be entered
1339: if (StringUtils.isBlank(campusCode)) {
1340: putFieldError(
1341: "accountDescription.campusCode",
1342: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CAMS_SUBFUNDGROUP_WITH_MISSING_CAMPUS_CD_FOR_BLDG);
1343: success &= false;
1344: }
1345:
1346: // if sub_fund_grp_cd is 'PFCMR' then bldg_cd must be entered
1347: if (StringUtils.isBlank(buildingCode)) {
1348: putFieldError(
1349: "accountDescription.buildingCode",
1350: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CAMS_SUBFUNDGROUP_WITH_MISSING_BUILDING_CD);
1351: success &= false;
1352: }
1353:
1354: // the building object (campusCode & buildingCode) must exist in the DB
1355: if (!StringUtils.isBlank(campusCode)
1356: && !StringUtils.isBlank(buildingCode)) {
1357:
1358: // make sure that primary key fields are upper case
1359: DataDictionaryService dds = SpringContext
1360: .getBean(DataDictionaryService.class);
1361: Boolean buildingCodeForceUppercase = dds
1362: .getAttributeForceUppercase(
1363: AccountDescription.class,
1364: KFSPropertyConstants.BUILDING_CODE);
1365: if (StringUtils.isNotBlank(buildingCode)
1366: && buildingCodeForceUppercase != null
1367: && buildingCodeForceUppercase
1368: .booleanValue() == true) {
1369: buildingCode = buildingCode.toUpperCase();
1370: }
1371:
1372: Boolean campusCodeForceUppercase = dds
1373: .getAttributeForceUppercase(
1374: AccountDescription.class,
1375: KFSPropertyConstants.CAMPUS_CODE);
1376: if (StringUtils.isNotBlank(campusCode)
1377: && campusCodeForceUppercase != null
1378: && campusCodeForceUppercase
1379: .booleanValue() == true) {
1380: campusCode = campusCode.toUpperCase();
1381: }
1382:
1383: Map pkMap = new HashMap();
1384: pkMap.put("campusCode", campusCode);
1385: pkMap.put("buildingCode", buildingCode);
1386:
1387: Building building = (Building) getBoService()
1388: .findByPrimaryKey(Building.class, pkMap);
1389: if (building == null) {
1390: putFieldError(
1391: "accountDescription.campusCode",
1392: KFSKeyConstants.ERROR_EXISTENCE,
1393: campusCode);
1394: putFieldError(
1395: "accountDescription.buildingCode",
1396: KFSKeyConstants.ERROR_EXISTENCE,
1397: buildingCode);
1398: success &= false;
1399: }
1400: }
1401: } else {
1402:
1403: // if sub_fund_grp_cd is NOT 'PFCMR', campus code should NOT be entered
1404: if (!StringUtils.isBlank(campusCode)) {
1405: putFieldError(
1406: "accountDescription.campusCode",
1407: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_NONCAMS_SUBFUNDGROUP_WITH_CAMPUS_CD_FOR_BLDG,
1408: subFundGroupCode);
1409: success &= false;
1410: }
1411:
1412: // if sub_fund_grp_cd is NOT 'PFCMR' then bldg_cd should NOT be entered
1413: if (!StringUtils.isBlank(buildingCode)) {
1414: putFieldError(
1415: "accountDescription.buildingCode",
1416: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_NONCAMS_SUBFUNDGROUP_WITH_BUILDING_CD,
1417: subFundGroupCode);
1418: success &= false;
1419: }
1420: }
1421: }
1422:
1423: }
1424:
1425: return success;
1426: }
1427:
1428: /**
1429: * This method checks to see if the contracts and grants fields are filled in or not
1430: *
1431: * @param account
1432: * @param propertyName - property to attach error to
1433: * @return false if the contracts and grants fields are blank
1434: */
1435: protected boolean checkCGFieldNotFilledIn(Account account,
1436: String propertyName) {
1437: boolean success = true;
1438: Object value = ObjectUtils.getPropertyValue(account,
1439: propertyName);
1440: if ((value instanceof String && !StringUtils.isBlank(value
1441: .toString()))
1442: || (value != null)) {
1443: success = false;
1444: putFieldError(
1445: propertyName,
1446: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CG_FIELDS_FILLED_FOR_NON_CG_ACCOUNT,
1447: new String[] { account.getSubFundGroupCode() });
1448: }
1449:
1450: return success;
1451: }
1452:
1453: /**
1454: * This method sets the generalLedgerPendingEntryService
1455: *
1456: * @param generalLedgerPendingEntryService
1457: */
1458: public void setGeneralLedgerPendingEntryService(
1459: GeneralLedgerPendingEntryService generalLedgerPendingEntryService) {
1460: this .generalLedgerPendingEntryService = generalLedgerPendingEntryService;
1461: }
1462:
1463: /**
1464: * This method sets the laborLedgerPendingEntryService
1465: *
1466: * @param laborLedgerPendingEntryService
1467: */
1468: public void setLaborLedgerPendingEntryService(
1469: LaborLedgerPendingEntryService laborLedgerPendingEntryService) {
1470: this .laborLedgerPendingEntryService = laborLedgerPendingEntryService;
1471: }
1472:
1473: /**
1474: * This method sets the balanceService
1475: *
1476: * @param balanceService
1477: */
1478: public void setBalanceService(BalanceService balanceService) {
1479: this .balanceService = balanceService;
1480: }
1481:
1482: /**
1483: * Sets the accountService attribute value.
1484: *
1485: * @param accountService The accountService to set.
1486: */
1487: public final void setAccountService(AccountService accountService) {
1488: this.accountService = accountService;
1489: }
1490:
1491: }
|