0001: /*
0002: * Copyright 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.HashSet;
0022: import java.util.List;
0023: import java.util.Map;
0024:
0025: import org.apache.commons.lang.StringUtils;
0026: import org.apache.commons.lang.time.DateUtils;
0027: import org.kuali.core.bo.PersistableBusinessObject;
0028: import org.kuali.core.bo.user.UniversalUser;
0029: import org.kuali.core.document.MaintenanceDocument;
0030: import org.kuali.core.exceptions.UserNotFoundException;
0031: import org.kuali.core.service.BusinessObjectService;
0032: import org.kuali.core.service.DictionaryValidationService;
0033: import org.kuali.core.util.GlobalVariables;
0034: import org.kuali.core.util.ObjectUtils;
0035: import org.kuali.kfs.KFSConstants;
0036: import org.kuali.kfs.KFSKeyConstants;
0037: import org.kuali.kfs.context.SpringContext;
0038: import org.kuali.kfs.service.ParameterService;
0039: import org.kuali.module.chart.bo.Account;
0040: import org.kuali.module.chart.bo.AccountGlobal;
0041: import org.kuali.module.chart.bo.AccountGlobalDetail;
0042: import org.kuali.module.chart.bo.ChartUser;
0043: import org.kuali.module.chart.bo.SubFundGroup;
0044: import org.kuali.module.chart.service.OrganizationService;
0045: import org.kuali.module.chart.service.SubFundGroupService;
0046:
0047: /**
0048: * This class represents the business rules for the maintenance of {@link AccountGlobal} business objects
0049: */
0050: public class AccountGlobalRule extends GlobalDocumentRuleBase {
0051: private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
0052: .getLogger(AccountGlobalRule.class);
0053:
0054: private static final String GENERAL_FUND_CD = "GF";
0055: private static final String RESTRICTED_FUND_CD = "RF";
0056: private static final String ENDOWMENT_FUND_CD = "EN";
0057: private static final String PLANT_FUND_CD = "PF";
0058:
0059: private static final String RESTRICTED_CD_RESTRICTED = "R";
0060: private static final String RESTRICTED_CD_UNRESTRICTED = "U";
0061: private static final String RESTRICTED_CD_TEMPORARILY_RESTRICTED = "T";
0062:
0063: private static final String SUB_FUND_GROUP_MEDICAL_PRACTICE_FUNDS = "MPRACT";
0064:
0065: private AccountGlobal newAccountGlobal;
0066: private Timestamp today;
0067:
0068: public AccountGlobalRule() {
0069: super ();
0070: }
0071:
0072: /**
0073: * This method sets the convenience objects like newAccountGlobal and oldAccount, so you have short and easy handles to the new
0074: * and old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to
0075: * load all sub-objects from the DB by their primary keys, if available.
0076: */
0077: @Override
0078: public void setupConvenienceObjects() {
0079:
0080: // setup newDelegateGlobal convenience objects, make sure all possible sub-objects are populated
0081: newAccountGlobal = (AccountGlobal) super .getNewBo();
0082: today = getDateTimeService().getCurrentTimestamp();
0083: today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH)
0084: .getTime()); // remove any time components
0085: }
0086:
0087: /**
0088: * This method checks the following rules: checkEmptyValues checkGeneralRules checkContractsAndGrants checkExpirationDate
0089: * checkOnlyOneChartErrorWrapper checkFiscalOfficerIsValidKualiUser but does not fail if any of them fail (this only happens on
0090: * routing)
0091: *
0092: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
0093: */
0094: protected boolean processCustomSaveDocumentBusinessRules(
0095: MaintenanceDocument document) {
0096:
0097: LOG.info("processCustomSaveDocumentBusinessRules called");
0098: setupConvenienceObjects();
0099:
0100: checkEmptyValues();
0101: checkGeneralRules(document);
0102: checkOrganizationValidity(newAccountGlobal);
0103: checkContractsAndGrants();
0104: checkExpirationDate(document);
0105: checkOnlyOneChartErrorWrapper(newAccountGlobal
0106: .getAccountGlobalDetails());
0107: checkFiscalOfficerIsValidKualiUser(newAccountGlobal
0108: .getAccountFiscalOfficerSystemIdentifier());
0109: // checkFundGroup(document);
0110: // checkSubFundGroup(document);
0111:
0112: // Save always succeeds, even if there are business rule failures
0113: return true;
0114: }
0115:
0116: /**
0117: * This method checks the following rules: checkEmptyValues checkGeneralRules checkContractsAndGrants checkExpirationDate
0118: * checkOnlyOneChartErrorWrapper checkFiscalOfficerIsValidKualiUser but does fail if any of these rule checks fail
0119: *
0120: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.core.document.MaintenanceDocument)
0121: */
0122: protected boolean processCustomRouteDocumentBusinessRules(
0123: MaintenanceDocument document) {
0124:
0125: LOG.info("processCustomRouteDocumentBusinessRules called");
0126: setupConvenienceObjects();
0127:
0128: // default to success
0129: boolean success = true;
0130:
0131: success &= checkEmptyValues();
0132: success &= checkGeneralRules(document);
0133: success &= checkContractsAndGrants();
0134: success &= checkExpirationDate(document);
0135: success &= checkAccountDetails(document, newAccountGlobal
0136: .getAccountGlobalDetails());
0137: if (!StringUtils.isEmpty(newAccountGlobal
0138: .getAccountFiscalOfficerSystemIdentifier())) {
0139: success &= checkFiscalOfficerIsValidKualiUser(newAccountGlobal
0140: .getAccountFiscalOfficerSystemIdentifier());
0141: }
0142: // success &= checkFundGroup(document);
0143: // success &= checkSubFundGroup(document);
0144:
0145: return success;
0146: }
0147:
0148: /**
0149: * This method loops through the list of {@link AccountGlobalDetail}s and passes them off to checkAccountDetails for further
0150: * rule analysis One rule it does check is checkOnlyOneChartErrorWrapper
0151: *
0152: * @param document
0153: * @param details
0154: * @return true if the collection of {@link AccountGlobalDetail}s passes the sub-rules
0155: */
0156: public boolean checkAccountDetails(MaintenanceDocument document,
0157: List<AccountGlobalDetail> details) {
0158: boolean success = true;
0159:
0160: // check if there are any accounts
0161: if (details.size() == 0) {
0162:
0163: putFieldError(
0164: KFSConstants.MAINTENANCE_ADD_PREFIX
0165: + "accountGlobalDetails.accountNumber",
0166: KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_NO_ACCOUNTS);
0167:
0168: success = false;
0169: } else {
0170: // check each account
0171: int index = 0;
0172: for (AccountGlobalDetail dtl : details) {
0173: String errorPath = MAINTAINABLE_ERROR_PREFIX
0174: + "accountGlobalDetails[" + index + "]";
0175: GlobalVariables.getErrorMap().addToErrorPath(errorPath);
0176: success &= checkAccountDetails(dtl);
0177: GlobalVariables.getErrorMap().removeFromErrorPath(
0178: errorPath);
0179: index++;
0180: }
0181: success &= checkOnlyOneChartErrorWrapper(details);
0182: }
0183:
0184: return success;
0185: }
0186:
0187: /**
0188: * This method ensures that each {@link AccountGlobalDetail} is valid and has a valid account number
0189: *
0190: * @param dtl
0191: * @return true if the detail object contains a valid account
0192: */
0193: public boolean checkAccountDetails(AccountGlobalDetail dtl) {
0194: boolean success = true;
0195: int originalErrorCount = GlobalVariables.getErrorMap()
0196: .getErrorCount();
0197: getDictionaryValidationService().validateBusinessObject(dtl);
0198: if (StringUtils.isNotBlank(dtl.getAccountNumber())
0199: && StringUtils.isNotBlank(dtl.getChartOfAccountsCode())) {
0200: dtl.refreshReferenceObject("account");
0201: if (dtl.getAccount() == null) {
0202: GlobalVariables
0203: .getErrorMap()
0204: .putError(
0205: "accountNumber",
0206: KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ACCOUNT,
0207: new String[] {
0208: dtl.getChartOfAccountsCode(),
0209: dtl.getAccountNumber() });
0210: }
0211: }
0212: success &= GlobalVariables.getErrorMap().getErrorCount() == originalErrorCount;
0213:
0214: return success;
0215: }
0216:
0217: /**
0218: * This method checks the basic rules for empty reference key values on a continuation account and an income stream account
0219: *
0220: * @return true if no empty values or partially filled out reference keys
0221: */
0222: protected boolean checkEmptyValues() {
0223:
0224: LOG.info("checkEmptyValues called");
0225:
0226: boolean success = true;
0227:
0228: // this set confirms that all fields which are grouped (ie, foreign keys of a referenc
0229: // object), must either be none filled out, or all filled out.
0230: success &= checkForPartiallyFilledOutReferenceForeignKeys("continuationAccount");
0231: success &= checkForPartiallyFilledOutReferenceForeignKeys("incomeStreamAccount");
0232:
0233: return success;
0234: }
0235:
0236: /**
0237: * This method checks some of the general business rules associated with this document Such as: valid user for fiscal officer,
0238: * supervisor or account manager (and not the same individual) are they trying to use an expired continuation account
0239: *
0240: * @param maintenanceDocument
0241: * @return false on rules violation
0242: */
0243: protected boolean checkGeneralRules(
0244: MaintenanceDocument maintenanceDocument) {
0245:
0246: LOG.info("checkGeneralRules called");
0247: UniversalUser fiscalOfficer = newAccountGlobal
0248: .getAccountFiscalOfficerUser();
0249: UniversalUser accountManager = newAccountGlobal
0250: .getAccountManagerUser();
0251: UniversalUser accountSupervisor = newAccountGlobal
0252: .getAccountSupervisoryUser();
0253:
0254: boolean success = true;
0255:
0256: // the employee type for fiscal officer, account manager, and account supervisor must be 'P' – professional.
0257: success &= checkUserStatusAndType(
0258: "accountFiscalOfficerUser.personUserIdentifier",
0259: fiscalOfficer);
0260: success &= checkUserStatusAndType(
0261: "accountSupervisoryUser.personUserIdentifier",
0262: accountSupervisor);
0263: success &= checkUserStatusAndType(
0264: "accountManagerUser.personUserIdentifier",
0265: accountManager);
0266:
0267: // the supervisor cannot be the same as the fiscal officer or account manager.
0268: if (isSupervisorSameAsFiscalOfficer(newAccountGlobal)) {
0269: success &= false;
0270: putFieldError(
0271: "accountsSupervisorySystemsIdentifier",
0272: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_FISCAL_OFFICER);
0273: }
0274: if (isSupervisorSameAsManager(newAccountGlobal)) {
0275: success &= false;
0276: putFieldError(
0277: "accountManagerSystemIdentifier",
0278: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_ACCT_MGR);
0279: }
0280:
0281: // disallow continuation account being expired
0282: if (isContinuationAccountExpired(newAccountGlobal)) {
0283: success &= false;
0284: putFieldError(
0285: "continuationAccountNumber",
0286: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_EXPIRED_CONTINUATION);
0287: }
0288:
0289: // loop over change detail objects to test if the supervisor/FO/mgr restrictions are in place
0290: // only need to do this check if the entered information does not already violate the rules
0291: if (!isSupervisorSameAsFiscalOfficer(newAccountGlobal)
0292: && !isSupervisorSameAsManager(newAccountGlobal)) {
0293: success &= checkAllAccountUsers(newAccountGlobal,
0294: fiscalOfficer, accountManager, accountSupervisor);
0295: }
0296:
0297: return success;
0298: }
0299:
0300: /**
0301: * This method checks to make sure that if the users are filled out (fiscal officer, supervisor, manager) that they are not the
0302: * same individual Only need to check this if these are new users that override existing users on the {@link Account} object
0303: *
0304: * @param doc
0305: * @param newFiscalOfficer
0306: * @param newManager
0307: * @param newSupervisor
0308: * @return true if the users are either not changed or pass the sub-rules
0309: */
0310: protected boolean checkAllAccountUsers(AccountGlobal doc,
0311: UniversalUser newFiscalOfficer, UniversalUser newManager,
0312: UniversalUser newSupervisor) {
0313: boolean success = true;
0314:
0315: if (LOG.isDebugEnabled()) {
0316: LOG.debug("newSupervisor: " + newSupervisor);
0317: LOG.debug("newFiscalOfficer: " + newFiscalOfficer);
0318: LOG.debug("newManager: " + newManager);
0319: }
0320: // only need to do this check if at least one of the user fields is
0321: // non null
0322: if (newSupervisor != null || newFiscalOfficer != null
0323: || newManager != null) {
0324: // loop over all AccountGlobalDetail records
0325: int index = 0;
0326: for (AccountGlobalDetail detail : doc
0327: .getAccountGlobalDetails()) {
0328: success &= checkAccountUsers(detail, newFiscalOfficer,
0329: newManager, newSupervisor, index);
0330: index++;
0331: }
0332: }
0333:
0334: return success;
0335: }
0336:
0337: /**
0338: * This method checks that the new users (fiscal officer, supervisor, manager) are not the same individual for the
0339: * {@link Account} being changed (contained in the {@link AccountGlobalDetail})
0340: *
0341: * @param detail - where the Account information is stored
0342: * @param newFiscalOfficer
0343: * @param newManager
0344: * @param newSupervisor
0345: * @param index - for storing the error line
0346: * @return true if the new users pass this sub-rule
0347: */
0348: protected boolean checkAccountUsers(AccountGlobalDetail detail,
0349: UniversalUser newFiscalOfficer, UniversalUser newManager,
0350: UniversalUser newSupervisor, int index) {
0351: boolean success = true;
0352:
0353: // only need to do this check if at least one of the user fields is non null
0354: if (newSupervisor != null || newFiscalOfficer != null
0355: || newManager != null) {
0356: // loop over all AccountGlobalDetail records
0357: detail.refreshReferenceObject("account");
0358: Account account = detail.getAccount();
0359: if (account != null) {
0360: if (LOG.isDebugEnabled()) {
0361: LOG.debug("old-Supervisor: "
0362: + account.getAccountSupervisoryUser());
0363: LOG.debug("old-FiscalOfficer: "
0364: + account.getAccountFiscalOfficerUser());
0365: LOG.debug("old-Manager: "
0366: + account.getAccountManagerUser());
0367: }
0368: // only need to check if they are not being overridden by the change document
0369: if (newSupervisor != null
0370: && newSupervisor.getPersonUniversalIdentifier() != null) {
0371: if (areTwoUsersTheSame(newSupervisor, account
0372: .getAccountFiscalOfficerUser())) {
0373: success = false;
0374: putFieldError(
0375: "accountGlobalDetails[" + index
0376: + "].accountNumber",
0377: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_EQUAL_EXISTING_FISCAL_OFFICER,
0378: new String[] {
0379: account
0380: .getAccountFiscalOfficerUserPersonUserIdentifier(),
0381: "Fiscal Officer",
0382: detail.getAccountNumber() });
0383: }
0384: if (areTwoUsersTheSame(newSupervisor, account
0385: .getAccountManagerUser())) {
0386: success = false;
0387: putFieldError(
0388: "accountGlobalDetails[" + index
0389: + "].accountNumber",
0390: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_EQUAL_EXISTING_ACCT_MGR,
0391: new String[] {
0392: account
0393: .getAccountManagerUserPersonUserIdentifier(),
0394: "Account Manager",
0395: detail.getAccountNumber() });
0396: }
0397: }
0398: if (newManager != null
0399: && newManager.getPersonUniversalIdentifier() != null) {
0400: if (areTwoUsersTheSame(newManager, account
0401: .getAccountSupervisoryUser())) {
0402: success = false;
0403: putFieldError(
0404: "accountGlobalDetails[" + index
0405: + "].accountNumber",
0406: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_MGR_CANNOT_EQUAL_EXISTING_ACCT_SUPERVISOR,
0407: new String[] {
0408: account
0409: .getAccountSupervisoryUserPersonUserIdentifier(),
0410: "Account Supervisor",
0411: detail.getAccountNumber() });
0412: }
0413: }
0414: if (newFiscalOfficer != null
0415: && newFiscalOfficer
0416: .getPersonUniversalIdentifier() != null) {
0417: if (areTwoUsersTheSame(newFiscalOfficer, account
0418: .getAccountSupervisoryUser())) {
0419: success = false;
0420: putFieldError(
0421: "accountGlobalDetails[" + index
0422: + "].accountNumber",
0423: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_FISCAL_OFFICER_CANNOT_EQUAL_EXISTING_ACCT_SUPERVISOR,
0424: new String[] {
0425: account
0426: .getAccountSupervisoryUserPersonUserIdentifier(),
0427: "Account Supervisor",
0428: detail.getAccountNumber() });
0429: }
0430: }
0431: } else {
0432: LOG
0433: .warn("AccountGlobalDetail object has null account object:"
0434: + detail.getChartOfAccountsCode()
0435: + "-"
0436: + detail.getAccountNumber());
0437: }
0438: }
0439:
0440: return success;
0441: }
0442:
0443: /**
0444: * This method is a helper method for checking if the supervisor user is the same as the fiscal officer Calls
0445: * {@link AccountGlobalRule#areTwoUsersTheSame(UniversalUser, UniversalUser)}
0446: *
0447: * @param accountGlobals
0448: * @return true if the two users are the same
0449: */
0450: protected boolean isSupervisorSameAsFiscalOfficer(
0451: AccountGlobal accountGlobals) {
0452: return areTwoUsersTheSame(accountGlobals
0453: .getAccountSupervisoryUser(), accountGlobals
0454: .getAccountFiscalOfficerUser());
0455: }
0456:
0457: /**
0458: * This method is a helper method for checking if the supervisor user is the same as the manager Calls
0459: * {@link AccountGlobalRule#areTwoUsersTheSame(UniversalUser, UniversalUser)}
0460: *
0461: * @param accountGlobals
0462: * @return true if the two users are the same
0463: */
0464: protected boolean isSupervisorSameAsManager(
0465: AccountGlobal accountGlobals) {
0466: return areTwoUsersTheSame(accountGlobals
0467: .getAccountSupervisoryUser(), accountGlobals
0468: .getAccountManagerUser());
0469: }
0470:
0471: /**
0472: * This method checks to see if two users are the same BusinessObject using their identifiers
0473: *
0474: * @param user1
0475: * @param user2
0476: * @return true if these two users are the same
0477: */
0478: protected boolean areTwoUsersTheSame(UniversalUser user1,
0479: UniversalUser user2) {
0480: if (ObjectUtils.isNull(user1)) {
0481: return false;
0482: }
0483: if (ObjectUtils.isNull(user2)) {
0484: return false;
0485: }
0486: // not a real person object - fail the comparison
0487: if (user1.getPersonUniversalIdentifier() == null
0488: || user2.getPersonUniversalIdentifier() == null) {
0489: return false;
0490: }
0491: if (ObjectUtils.equalByKeys(user1, user2)) {
0492: return true;
0493: } else {
0494: return false;
0495: }
0496: }
0497:
0498: /**
0499: * 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,
0500: * and adds an error to the GlobalErrors.
0501: *
0502: * @param propertyName
0503: * @param user - UniversalUser to be tested
0504: * @return true if user is of the requested employee type, false if not, true if the user object is null
0505: */
0506: protected boolean checkUserStatusAndType(String propertyName,
0507: UniversalUser user) {
0508:
0509: boolean success = true;
0510:
0511: // if the user isnt populated, exit with success
0512: // the actual existence check is performed in the general rules so not testing here
0513: if (ObjectUtils.isNull(user)
0514: || user.getPersonUniversalIdentifier() == null) {
0515: return success;
0516: }
0517:
0518: // user must be of the allowable statuses (A - Active)
0519: if (!SpringContext
0520: .getBean(ParameterService.class)
0521: .getParameterEvaluator(
0522: Account.class,
0523: KFSConstants.ChartApcParms.ACCOUNT_USER_EMP_STATUSES,
0524: user.getEmployeeStatusCode())
0525: .evaluationSucceeds()) {
0526: success = false;
0527: putFieldError(
0528: propertyName,
0529: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACTIVE_REQD_FOR_EMPLOYEE,
0530: getDdService().getAttributeLabel(Account.class,
0531: propertyName));
0532: }
0533:
0534: // user must be of the allowable types (P - Professional)
0535: if (!SpringContext
0536: .getBean(ParameterService.class)
0537: .getParameterEvaluator(
0538: Account.class,
0539: KFSConstants.ChartApcParms.ACCOUNT_USER_EMP_TYPES,
0540: user.getEmployeeTypeCode())
0541: .evaluationSucceeds()) {
0542: success = false;
0543: putFieldError(
0544: propertyName,
0545: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_PRO_TYPE_REQD_FOR_EMPLOYEE,
0546: getDdService().getAttributeLabel(Account.class,
0547: propertyName));
0548: }
0549:
0550: return success;
0551: }
0552:
0553: /**
0554: * This method insures the fiscal officer is a valid Kuali User
0555: *
0556: * @param fiscalOfficerUserId
0557: * @return true if fiscal officer is a valid KualiUser
0558: */
0559: protected boolean checkFiscalOfficerIsValidKualiUser(
0560: String fiscalOfficerUserId) {
0561: boolean result = true;
0562: try {
0563: UniversalUser fiscalOfficer = getUniversalUserService()
0564: .getUniversalUser(fiscalOfficerUserId);
0565: if (fiscalOfficer != null
0566: && !fiscalOfficer
0567: .isActiveForModule(ChartUser.MODULE_ID)) {
0568: result = false;
0569: putFieldError(
0570: "accountFiscalOfficerUser.personUserIdentifier",
0571: KFSKeyConstants.ERROR_DOCUMENT_ACCOUNT_FISCAL_OFFICER_MUST_BE_KUALI_USER);
0572: }
0573: } catch (UserNotFoundException e) {
0574: result = false;
0575: }
0576:
0577: return result;
0578: }
0579:
0580: /**
0581: * This method checks to see if any expiration date field rules were violated Loops through each detail object and calls
0582: * {@link AccountGlobalRule#checkExpirationDate(MaintenanceDocument, AccountGlobalDetail)}
0583: *
0584: * @param maintenanceDocument
0585: * @return false on rules violation
0586: */
0587: protected boolean checkExpirationDate(
0588: MaintenanceDocument maintenanceDocument) {
0589: LOG.info("checkExpirationDate called");
0590:
0591: boolean success = true;
0592: Timestamp newExpDate = newAccountGlobal
0593: .getAccountExpirationDate();
0594:
0595: // If creating a new account if acct_expiration_dt is set and the fund_group is not "CG" then
0596: // the acct_expiration_dt must be changed to a date that is today or later
0597: if (ObjectUtils.isNotNull(newExpDate)) {
0598: if (ObjectUtils.isNotNull(newAccountGlobal
0599: .getSubFundGroup())) {
0600: if (!SpringContext.getBean(SubFundGroupService.class)
0601: .isForContractsAndGrants(
0602: newAccountGlobal.getSubFundGroup())) {
0603: if (!newExpDate.after(today)
0604: && !newExpDate.equals(today)) {
0605: putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
0606: success &= false;
0607: }
0608: }
0609: }
0610: }
0611:
0612: // a continuation account is required if the expiration date is completed.
0613: success &= checkContinuationAccount(maintenanceDocument,
0614: newExpDate);
0615:
0616: for (AccountGlobalDetail detail : newAccountGlobal
0617: .getAccountGlobalDetails()) {
0618: success &= checkExpirationDate(maintenanceDocument, detail);
0619: }
0620: return success;
0621: }
0622:
0623: /**
0624: * This method checks to see if any expiration date field rules were violated in relation to the given detail record
0625: *
0626: * @param maintenanceDocument
0627: * @param detail - the account detail we are investigating
0628: * @return false on rules violation
0629: */
0630: protected boolean checkExpirationDate(
0631: MaintenanceDocument maintenanceDocument,
0632: AccountGlobalDetail detail) {
0633: boolean success = true;
0634: Timestamp newExpDate = newAccountGlobal
0635: .getAccountExpirationDate();
0636:
0637: // load the object by keys
0638: Account account = (Account) SpringContext.getBean(
0639: BusinessObjectService.class).findByPrimaryKey(
0640: Account.class, detail.getPrimaryKeys());
0641: if (account != null) {
0642: Timestamp oldExpDate = account.getAccountExpirationDate();
0643:
0644: // When updating an account expiration date, the date must be today or later
0645: // (except for C&G accounts). Only run this test if this maint doc
0646: // is an edit doc
0647: if (isUpdatedExpirationDateInvalid(account,
0648: newAccountGlobal)) {
0649: putFieldError(
0650: "accountExpirationDate",
0651: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
0652: success &= false;
0653: }
0654:
0655: // If creating a new account if acct_expiration_dt is set and the fund_group is not "CG" then
0656: // the acct_expiration_dt must be changed to a date that is today or later
0657: if (ObjectUtils.isNotNull(newExpDate)
0658: && ObjectUtils.isNull(newAccountGlobal
0659: .getSubFundGroup())) {
0660: if (ObjectUtils.isNotNull(account.getSubFundGroup())) {
0661: if (!account.isForContractsAndGrants()) {
0662: if (!newExpDate.after(today)
0663: && !newExpDate.equals(today)) {
0664: putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
0665: success &= false;
0666: }
0667: }
0668: }
0669: }
0670: // acct_expiration_dt can not be before acct_effect_dt
0671: Timestamp effectiveDate = account.getAccountEffectiveDate();
0672: if (ObjectUtils.isNotNull(effectiveDate)
0673: && ObjectUtils.isNotNull(newExpDate)) {
0674: if (newExpDate.before(effectiveDate)) {
0675: putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE);
0676: success &= false;
0677: }
0678: }
0679: }
0680:
0681: return success;
0682: }
0683:
0684: /*
0685: * protected boolean checkAccountExpirationDateValidTodayOrEarlier(Account newAccount) { // get today's date, with no time
0686: * component Timestamp todaysDate = getDateTimeService().getCurrentTimestamp();
0687: * todaysDate.setTime(DateUtils.truncate(todaysDate, Calendar.DAY_OF_MONTH).getTime()); // TODO: convert this to using Wes'
0688: * kuali DateUtils once we're using Date's instead of Timestamp // get the expiration date, if any Timestamp expirationDate =
0689: * newAccount.getAccountExpirationDate(); if (ObjectUtils.isNull(expirationDate)) { putFieldError("accountExpirationDate",
0690: * KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); return false; } // when closing an account,
0691: * the account expiration date must be the current date or earlier expirationDate.setTime(DateUtils.truncate(expirationDate,
0692: * Calendar.DAY_OF_MONTH).getTime()); if (expirationDate.after(todaysDate)) { putFieldError("accountExpirationDate",
0693: * KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); return false; } return true; }
0694: */
0695:
0696: /**
0697: * This method checks to see if the updated expiration is not a valid one Only gets checked for specific {@link SubFundGroup}s
0698: *
0699: * @param oldAccount
0700: * @param newAccountGlobal
0701: * @return true if date has changed and is invalid
0702: */
0703: protected boolean isUpdatedExpirationDateInvalid(
0704: Account oldAccount, AccountGlobal newAccountGlobal) {
0705:
0706: Timestamp oldExpDate = oldAccount.getAccountExpirationDate();
0707: Timestamp newExpDate = newAccountGlobal
0708: .getAccountExpirationDate();
0709:
0710: // When updating an account expiration date, the date must be today or later
0711: // (except for C&G accounts). Only run this test if this maint doc
0712: // is an edit doc
0713: boolean expDateHasChanged = false;
0714:
0715: // if the old version of the account had no expiration date, and the new
0716: // one has a date
0717: if (ObjectUtils.isNull(oldExpDate)
0718: && ObjectUtils.isNotNull(newExpDate)) {
0719: expDateHasChanged = true;
0720: }
0721:
0722: // if there was an old and a new expDate, but they're different
0723: else if (ObjectUtils.isNotNull(oldExpDate)
0724: && ObjectUtils.isNotNull(newExpDate)) {
0725: if (!oldExpDate.equals(newExpDate)) {
0726: expDateHasChanged = true;
0727: }
0728: }
0729:
0730: // if the expiration date hasnt changed, we're not interested
0731: if (!expDateHasChanged) {
0732: return false;
0733: }
0734:
0735: // if a subFundGroup isnt present, we cannot continue the testing
0736: SubFundGroup subFundGroup = newAccountGlobal.getSubFundGroup();
0737: if (ObjectUtils.isNull(subFundGroup)) {
0738: return false;
0739: }
0740:
0741: // get the fundGroup code
0742: String fundGroupCode = newAccountGlobal.getSubFundGroup()
0743: .getFundGroupCode().trim();
0744:
0745: // if the account is part of the CG fund group, then this rule does not
0746: // apply, so we're done
0747: if (SpringContext.getBean(SubFundGroupService.class)
0748: .isForContractsAndGrants(
0749: newAccountGlobal.getSubFundGroup())) {
0750: return false;
0751: }
0752:
0753: // at this point, we know its not a CG fund group, so we must apply the rule
0754:
0755: // expirationDate must be today or later than today (cannot be before today)
0756: if (newExpDate.equals(today) || newExpDate.after(today)) {
0757: return false;
0758: } else
0759: return true;
0760: }
0761:
0762: /**
0763: * This method tests whether the continuation account entered (if any) has expired or not.
0764: *
0765: * @param accountGlobals
0766: * @return true if the continuation account has expired
0767: */
0768: protected boolean isContinuationAccountExpired(
0769: AccountGlobal accountGlobals) {
0770:
0771: boolean result = false;
0772:
0773: String chartCode = accountGlobals
0774: .getContinuationFinChrtOfAcctCd();
0775: String accountNumber = accountGlobals
0776: .getContinuationAccountNumber();
0777:
0778: // if either chartCode or accountNumber is not entered, then we
0779: // cant continue, so exit
0780: if (StringUtils.isBlank(chartCode)
0781: || StringUtils.isBlank(accountNumber)) {
0782: return result;
0783: }
0784:
0785: // attempt to retrieve the continuation account from the DB
0786: Account continuation = null;
0787: Map pkMap = new HashMap();
0788: pkMap.put("chartOfAccountsCode", chartCode);
0789: pkMap.put("accountNumber", accountNumber);
0790: continuation = (Account) super .getBoService().findByPrimaryKey(
0791: Account.class, pkMap);
0792:
0793: // if the object doesnt exist, then we cant continue, so exit
0794: if (ObjectUtils.isNull(continuation)) {
0795: return result;
0796: }
0797:
0798: // at this point, we have a valid continuation account, so we just need to
0799: // know whether its expired or not
0800: result = continuation.isExpired();
0801:
0802: return result;
0803: }
0804:
0805: /**
0806: * This method checks to see if any Contracts and Grants business rules were violated
0807: *
0808: * @return false on rules violation
0809: */
0810: protected boolean checkContractsAndGrants() {
0811:
0812: LOG.info("checkContractsAndGrants called");
0813:
0814: boolean success = true;
0815:
0816: // Income Stream account is required if this account is CG fund group,
0817: // or GF (general fund) fund group (with some exceptions)
0818: success &= checkCgIncomeStreamRequired(newAccountGlobal);
0819:
0820: return success;
0821: }
0822:
0823: /**
0824: * This method checks to see if the contracts and grants income stream account is required
0825: *
0826: * @param accountGlobals
0827: * @return false if it is required (and not entered) or invalid/inactive
0828: */
0829: protected boolean checkCgIncomeStreamRequired(
0830: AccountGlobal accountGlobals) {
0831:
0832: boolean result = true;
0833: boolean required = false;
0834:
0835: // if the subFundGroup object is null, we cant test, so exit
0836: if (ObjectUtils.isNull(accountGlobals.getSubFundGroup())) {
0837: return result;
0838: }
0839:
0840: // retrieve the subfundcode and fundgroupcode
0841: String subFundGroupCode = accountGlobals.getSubFundGroupCode()
0842: .trim();
0843: String fundGroupCode = accountGlobals.getSubFundGroup()
0844: .getFundGroupCode().trim();
0845:
0846: // if this is a CG fund group, then its required
0847: if (SpringContext.getBean(SubFundGroupService.class)
0848: .isForContractsAndGrants(
0849: accountGlobals.getSubFundGroup())) {
0850: required = true;
0851: }
0852:
0853: // if this is a general fund group, then its required
0854: else if (GENERAL_FUND_CD.equalsIgnoreCase(fundGroupCode)) {
0855: // unless its part of the MPRACT subfundgroup
0856: if (!SUB_FUND_GROUP_MEDICAL_PRACTICE_FUNDS
0857: .equalsIgnoreCase(subFundGroupCode)) {
0858: required = true;
0859: }
0860: }
0861:
0862: // if the income stream account is not required, then we're done
0863: if (!required) {
0864: return result;
0865: }
0866:
0867: // make sure both coaCode and accountNumber are filled out
0868: result &= checkEmptyBOField("incomeStreamAccountNumber",
0869: accountGlobals.getIncomeStreamAccountNumber(),
0870: "When Fund Group is CG or GF, Income Stream Account Number");
0871: result &= checkEmptyBOField("incomeStreamFinancialCoaCode",
0872: accountGlobals.getIncomeStreamFinancialCoaCode(),
0873: "When Fund Group is CG or GF, Income Stream Chart Of Accounts Code");
0874:
0875: // if both fields arent present, then we're done
0876: if (result == false) {
0877: return result;
0878: }
0879:
0880: // do an existence/active test
0881: DictionaryValidationService dvService = super
0882: .getDictionaryValidationService();
0883: boolean referenceExists = dvService.validateReferenceExists(
0884: accountGlobals, "incomeStreamAccount");
0885: if (!referenceExists) {
0886: putFieldError("incomeStreamAccount",
0887: KFSKeyConstants.ERROR_EXISTENCE,
0888: "Income Stream Account: "
0889: + accountGlobals
0890: .getIncomeStreamFinancialCoaCode()
0891: + "-"
0892: + accountGlobals
0893: .getIncomeStreamAccountNumber());
0894: result &= false;
0895: }
0896:
0897: return result;
0898: }
0899:
0900: /**
0901: * This method calls checkAccountDetails checkExpirationDate checkOnlyOneChartAddLineErrorWrapper whenever a new
0902: * {@link AccountGlobalDetail} is added to this global
0903: *
0904: * @see org.kuali.core.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.core.document.MaintenanceDocument,
0905: * java.lang.String, org.kuali.core.bo.PersistableBusinessObject)
0906: */
0907: public boolean processCustomAddCollectionLineBusinessRules(
0908: MaintenanceDocument document, String collectionName,
0909: PersistableBusinessObject bo) {
0910: AccountGlobalDetail detail = (AccountGlobalDetail) bo;
0911: boolean success = true;
0912:
0913: success &= checkAccountDetails(detail);
0914: success &= checkExpirationDate(document, detail);
0915: success &= checkOnlyOneChartAddLineErrorWrapper(detail,
0916: newAccountGlobal.getAccountGlobalDetails());
0917:
0918: return success;
0919: }
0920:
0921: /**
0922: * This method validates that a continuation account is required and that the values provided exist
0923: *
0924: * @param document An instance of the maintenance document being validated.
0925: * @param newExpDate The expiration date assigned to the account being validated for submission.
0926: * @return True if the continuation account values are valid for the associated account, false otherwise.
0927: */
0928: protected boolean checkContinuationAccount(
0929: MaintenanceDocument document, Timestamp newExpDate) {
0930: LOG.info("checkContinuationAccount called");
0931:
0932: boolean result = true;
0933: boolean continuationAccountIsValid = true;
0934:
0935: // make sure both coaCode and accountNumber are filled out
0936: if (ObjectUtils.isNotNull(newExpDate)) {
0937: if (!checkEmptyValue(newAccountGlobal
0938: .getContinuationAccountNumber())) {
0939: putFieldError(
0940: "continuationAccountNumber",
0941: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED);
0942: continuationAccountIsValid = false;
0943: }
0944: if (!checkEmptyValue(newAccountGlobal
0945: .getContinuationFinChrtOfAcctCd())) {
0946: putFieldError(
0947: "continuationFinChrtOfAcctCd",
0948: KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_FINCODE_REQD_IF_EXP_DATE_COMPLETED);
0949: continuationAccountIsValid = false;
0950: }
0951: }
0952:
0953: // if both fields aren't present, then we're done
0954: if (continuationAccountIsValid
0955: && ObjectUtils.isNotNull(newAccountGlobal
0956: .getContinuationAccountNumber())
0957: && ObjectUtils.isNotNull(newAccountGlobal
0958: .getContinuationFinChrtOfAcctCd())) {
0959: // do an existence/active test
0960: DictionaryValidationService dvService = super
0961: .getDictionaryValidationService();
0962: boolean referenceExists = dvService
0963: .validateReferenceExists(newAccountGlobal,
0964: "continuationAccount");
0965: if (!referenceExists) {
0966: putFieldError(
0967: "continuationAccountNumber",
0968: KFSKeyConstants.ERROR_EXISTENCE,
0969: "Continuation Account: "
0970: + newAccountGlobal
0971: .getContinuationFinChrtOfAcctCd()
0972: + "-"
0973: + newAccountGlobal
0974: .getContinuationAccountNumber());
0975: continuationAccountIsValid = false;
0976: }
0977: }
0978:
0979: if (continuationAccountIsValid) {
0980: result = true;
0981: } else {
0982: List<AccountGlobalDetail> gAcctDetails = newAccountGlobal
0983: .getAccountGlobalDetails();
0984: for (AccountGlobalDetail detail : gAcctDetails) {
0985: if (null != detail.getAccountNumber()
0986: && null != newAccountGlobal
0987: .getContinuationAccountNumber()) {
0988: result &= detail.getAccountNumber().equals(
0989: newAccountGlobal
0990: .getContinuationAccountNumber());
0991: result &= detail.getChartOfAccountsCode().equals(
0992: newAccountGlobal
0993: .getContinuationFinChrtOfAcctCd());
0994: }
0995: }
0996: }
0997:
0998: return result;
0999: }
1000:
1001: /**
1002: * Validate that the object code on the form (if entered) is valid for all charts used in the detail sections.
1003: *
1004: * @param acctGlobal
1005: * @return
1006: */
1007: protected boolean checkOrganizationValidity(AccountGlobal acctGlobal) {
1008: boolean result = true;
1009:
1010: // check that an org has been entered
1011: if (StringUtils.isNotBlank(acctGlobal.getOrganizationCode())) {
1012: // get all distinct charts
1013: HashSet<String> charts = new HashSet<String>(10);
1014: for (AccountGlobalDetail acct : acctGlobal
1015: .getAccountGlobalDetails()) {
1016: charts.add(acct.getChartOfAccountsCode());
1017: }
1018: OrganizationService orgService = SpringContext
1019: .getBean(OrganizationService.class);
1020: // test for an invalid organization
1021: for (String chartCode : charts) {
1022: if (StringUtils.isNotBlank(chartCode)) {
1023: if (null == orgService
1024: .getByPrimaryIdWithCaching(chartCode,
1025: acctGlobal.getOrganizationCode())) {
1026: result = false;
1027: putFieldError(
1028: "organizationCode",
1029: KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ORG,
1030: new String[] {
1031: chartCode,
1032: acctGlobal
1033: .getOrganizationCode() });
1034: break;
1035: }
1036: }
1037: }
1038: }
1039:
1040: return result;
1041: }
1042: }
|