/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.coa.document.validation.impl;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.AccountGlobal;
import org.kuali.kfs.coa.businessobject.AccountGlobalDetail;
import org.kuali.kfs.coa.businessobject.SubFundGroup;
import org.kuali.kfs.coa.service.OrganizationService;
import org.kuali.kfs.coa.service.SubFundGroupService;
import org.kuali.kfs.integration.cg.ContractsAndGrantsCfda;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kns.document.MaintenanceDocument;
import org.kuali.rice.kns.service.DictionaryValidationService;
import org.kuali.rice.krad.bo.PersistableBusinessObject;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentService;
import org.kuali.rice.krad.service.KualiModuleService;
import org.kuali.rice.krad.service.ModuleService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* This class represents the business rules for the maintenance of {@link AccountGlobal} business objects
*/
public class AccountGlobalRule extends GlobalDocumentRuleBase {
protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountGlobalRule.class);
// These constants should not be here - need to be in system parameters
@Deprecated
protected static final String GENERAL_FUND_CD = "GF";
@Deprecated
protected static final String SUB_FUND_GROUP_MEDICAL_PRACTICE_FUNDS = "MPRACT";
protected AccountGlobal newAccountGlobal;
protected Timestamp today;
/**
* This method sets the convenience objects like newAccountGlobal and oldAccount, so you have short and easy handles to the new
* and old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to
* load all sub-objects from the DB by their primary keys, if available.
*/
@Override
public void setupConvenienceObjects() {
// setup newDelegateGlobal convenience objects, make sure all possible sub-objects are populated
newAccountGlobal = (AccountGlobal) super.getNewBo();
today = getDateTimeService().getCurrentTimestamp();
today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); // remove any time components
}
/**
* This method checks the following rules: checkEmptyValues checkGeneralRules checkContractsAndGrants checkExpirationDate
* checkOnlyOneChartErrorWrapper checkFiscalOfficerIsValidKualiUser but does not fail if any of them fail (this only happens on
* routing)
*
* @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
*/
@Override
protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
LOG.info("processCustomSaveDocumentBusinessRules called");
setupConvenienceObjects();
checkEmptyValues();
checkGeneralRules(document);
checkOrganizationValidity(newAccountGlobal);
checkContractsAndGrants();
checkExpirationDate(document);
checkOnlyOneChartErrorWrapper(newAccountGlobal.getAccountGlobalDetails());
// checkFundGroup(document);
// checkSubFundGroup(document);
// Save always succeeds, even if there are business rule failures
return true;
}
/**
* This method checks the following rules: checkEmptyValues checkGeneralRules checkContractsAndGrants checkExpirationDate
* checkOnlyOneChartErrorWrapper checkFiscalOfficerIsValidKualiUser but does fail if any of these rule checks fail
*
* @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
*/
@Override
protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
LOG.info("processCustomRouteDocumentBusinessRules called");
setupConvenienceObjects();
// default to success
boolean success = true;
success &= checkEmptyValues();
success &= checkGeneralRules(document);
success &= checkContractsAndGrants();
success &= checkExpirationDate(document);
success &= checkAccountDetails(document, newAccountGlobal.getAccountGlobalDetails());
// success &= checkFundGroup(document);
// success &= checkSubFundGroup(document);
return success;
}
/**
* This method loops through the list of {@link AccountGlobalDetail}s and passes them off to checkAccountDetails for further
* rule analysis One rule it does check is checkOnlyOneChartErrorWrapper
*
* @param document
* @param details
* @return true if the collection of {@link AccountGlobalDetail}s passes the sub-rules
*/
public boolean checkAccountDetails(MaintenanceDocument document, List<AccountGlobalDetail> details) {
boolean success = true;
// check if there are any accounts
if (details.size() == 0) {
putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + "accountGlobalDetails.accountNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_NO_ACCOUNTS);
success = false;
}
else {
// check each account
int index = 0;
for (AccountGlobalDetail dtl : details) {
String errorPath = MAINTAINABLE_ERROR_PREFIX + "accountGlobalDetails[" + index + "]";
GlobalVariables.getMessageMap().addToErrorPath(errorPath);
success &= checkAccountDetails(dtl);
GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
index++;
}
success &= checkOnlyOneChartErrorWrapper(details);
}
return success;
}
/**
* This method ensures that each {@link AccountGlobalDetail} is valid and has a valid account number
*
* @param dtl
* @return true if the detail object contains a valid account
*/
public boolean checkAccountDetails(AccountGlobalDetail dtl) {
boolean success = true;
int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount();
getDictionaryValidationService().validateBusinessObject(dtl);
if (StringUtils.isNotBlank(dtl.getAccountNumber()) && StringUtils.isNotBlank(dtl.getChartOfAccountsCode())) {
dtl.refreshReferenceObject("account");
if (ObjectUtils.isNull(dtl.getAccount())) {
GlobalVariables.getMessageMap().putError("accountNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ACCOUNT, new String[] { dtl.getChartOfAccountsCode(), dtl.getAccountNumber() });
}
}
success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount;
return success;
}
/**
* This method checks the basic rules for empty reference key values on a continuation account and an income stream account
*
* @return true if no empty values or partially filled out reference keys
*/
protected boolean checkEmptyValues() {
LOG.info("checkEmptyValues called");
boolean success = true;
// this set confirms that all fields which are grouped (ie, foreign keys of a referenc
// object), must either be none filled out, or all filled out.
success &= checkForPartiallyFilledOutReferenceForeignKeys("continuationAccount");
success &= checkForPartiallyFilledOutReferenceForeignKeys("incomeStreamAccount");
return success;
}
/**
* This method checks some of the general business rules associated with this document Such as: valid user for fiscal officer,
* supervisor or account manager (and not the same individual) are they trying to use an expired continuation account
*
* @param maintenanceDocument
* @return false on rules violation
*/
protected boolean checkGeneralRules(MaintenanceDocument maintenanceDocument) {
LOG.info("checkGeneralRules called");
Person fiscalOfficer = newAccountGlobal.getAccountFiscalOfficerUser();
Person accountManager = newAccountGlobal.getAccountManagerUser();
Person accountSupervisor = newAccountGlobal.getAccountSupervisoryUser();
boolean success = true;
if (!StringUtils.isBlank(newAccountGlobal.getAccountFiscalOfficerSystemIdentifier()) && (ObjectUtils.isNull(fiscalOfficer) || StringUtils.isEmpty(fiscalOfficer.getPrincipalId()) || !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.name, fiscalOfficer.getPrincipalId()))) {
final String fiscalOfficerName = fiscalOfficer != null ? fiscalOfficer.getName() : newAccountGlobal.getAccountFiscalOfficerSystemIdentifier();
super.putFieldError("accountFiscalOfficerUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {fiscalOfficerName, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER.name});
success = false;
} else if ((ObjectUtils.isNotNull(fiscalOfficer) && !StringUtils.isBlank(fiscalOfficer.getPrincipalName()) && ObjectUtils.isNull(fiscalOfficer.getPrincipalId()))) {
super.putFieldError("accountFiscalOfficerUser.principalName", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_PRINCPAL_NAME_FISCAL_OFFICER_SUPER_INVALID);
success = false;
}
if (!StringUtils.isBlank(newAccountGlobal.getAccountsSupervisorySystemsIdentifier()) && (ObjectUtils.isNull(accountSupervisor) || StringUtils.isEmpty(accountSupervisor.getPrincipalId()) || !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.namespace, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.name, accountSupervisor.getPrincipalId()))) {
final String accountSupervisorName = accountSupervisor != null ? accountSupervisor.getName() : newAccountGlobal.getAccountsSupervisorySystemsIdentifier();
super.putFieldError("accountSupervisoryUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountSupervisorName, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.namespace, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_SUPERVISOR.name});
success = false;
} else if (ObjectUtils.isNotNull(accountSupervisor) && !StringUtils.isBlank(accountSupervisor.getPrincipalName()) && ObjectUtils.isNull(accountSupervisor.getPrincipalId())) {
super.putFieldError("accountSupervisoryUser.principalName", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_PRINCPAL_NAME_ACCOUNT_SUPER_INVALID);
success = false;
}
if (!StringUtils.isBlank(newAccountGlobal.getAccountManagerSystemIdentifier()) && (ObjectUtils.isNull(accountManager) || StringUtils.isEmpty(accountManager.getPrincipalId()) || !getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument).isAuthorized(maintenanceDocument, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.namespace, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.name, accountManager.getPrincipalId()))) {
final String accountManagerName = accountManager != null ? accountManager.getName() : newAccountGlobal.getAccountManagerSystemIdentifier();
super.putFieldError("accountManagerUser.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountManagerName, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.namespace, KFSConstants.PermissionNames.SERVE_AS_ACCOUNT_MANAGER.name});
success = false;
} else if (ObjectUtils.isNotNull(accountManager) && !StringUtils.isBlank(accountManager.getPrincipalName()) && ObjectUtils.isNull(accountManager.getPrincipalId())) {
super.putFieldError("accountManagerUser.principalName", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_PRINCPAL_NAME_ACCOUNT_MANAGER_INVALID);
success = false;
}
// the supervisor cannot be the same as the fiscal officer or account manager.
if (isSupervisorSameAsFiscalOfficer(newAccountGlobal)) {
success &= false;
putFieldError("accountsSupervisorySystemsIdentifier", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_FISCAL_OFFICER);
}
if (isSupervisorSameAsManager(newAccountGlobal)) {
success &= false;
putFieldError("accountManagerSystemIdentifier", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_BE_ACCT_MGR);
}
// disallow continuation account being expired
if (isContinuationAccountExpired(newAccountGlobal)) {
success &= false;
putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCOUNT_EXPIRED_CONTINUATION);
}
// loop over change detail objects to test if the supervisor/FO/mgr restrictions are in place
// only need to do this check if the entered information does not already violate the rules
if (!isSupervisorSameAsFiscalOfficer(newAccountGlobal) && !isSupervisorSameAsManager(newAccountGlobal)) {
success &= checkAllAccountUsers(newAccountGlobal, fiscalOfficer, accountManager, accountSupervisor);
}
success &= checkCfda( newAccountGlobal.getAccountCfdaNumber());
return success;
}
private boolean checkCfda(String accountCfdaNumber) {
boolean success = true;
ContractsAndGrantsCfda cfda = null;
if (! StringUtils.isEmpty(accountCfdaNumber)) {
ModuleService moduleService = SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService(ContractsAndGrantsCfda.class);
if ( moduleService != null ) {
Map<String,Object> keys = new HashMap<String, Object>(1);
keys.put(KFSPropertyConstants.CFDA_NUMBER, accountCfdaNumber);
cfda = moduleService.getExternalizableBusinessObject(ContractsAndGrantsCfda.class, keys);
} else {
throw new RuntimeException( "CONFIGURATION ERROR: No responsible module found for EBO class. Unable to proceed." );
}
success = (ObjectUtils.isNull(cfda)) ? false : true;
if (!success) {
putFieldError("accountCfdaNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_CFDA_NUMBER_INVALID);
}
}
return success;
}
/**
* This method checks to make sure that if the users are filled out (fiscal officer, supervisor, manager) that they are not the
* same individual Only need to check this if these are new users that override existing users on the {@link Account} object
*
* @param doc
* @param newFiscalOfficer
* @param newManager
* @param newSupervisor
* @return true if the users are either not changed or pass the sub-rules
*/
protected boolean checkAllAccountUsers(AccountGlobal doc, Person newFiscalOfficer, Person newManager, Person newSupervisor) {
boolean success = true;
if (LOG.isDebugEnabled()) {
LOG.debug("newSupervisor: " + newSupervisor);
LOG.debug("newFiscalOfficer: " + newFiscalOfficer);
LOG.debug("newManager: " + newManager);
}
// only need to do this check if at least one of the user fields is
// non null
if (newSupervisor != null || newFiscalOfficer != null || newManager != null) {
// loop over all AccountGlobalDetail records
int index = 0;
for (AccountGlobalDetail detail : doc.getAccountGlobalDetails()) {
success &= checkAccountUsers(detail, newFiscalOfficer, newManager, newSupervisor, index);
index++;
}
}
return success;
}
/**
* This method checks that the new users (fiscal officer, supervisor, manager) are not the same individual for the
* {@link Account} being changed (contained in the {@link AccountGlobalDetail})
*
* @param detail - where the Account information is stored
* @param newFiscalOfficer
* @param newManager
* @param newSupervisor
* @param index - for storing the error line
* @return true if the new users pass this sub-rule
*/
protected boolean checkAccountUsers(AccountGlobalDetail detail, Person newFiscalOfficer, Person newManager, Person newSupervisor, int index) {
boolean success = true;
// only need to do this check if at least one of the user fields is non null
if (newSupervisor != null || newFiscalOfficer != null || newManager != null) {
// loop over all AccountGlobalDetail records
detail.refreshReferenceObject("account");
Account account = detail.getAccount();
if (ObjectUtils.isNotNull(account)){
if (LOG.isDebugEnabled()) {
LOG.debug("old-Supervisor: " + account.getAccountSupervisoryUser());
LOG.debug("old-FiscalOfficer: " + account.getAccountFiscalOfficerUser());
LOG.debug("old-Manager: " + account.getAccountManagerUser());
}
// only need to check if they are not being overridden by the change document
if (newSupervisor != null && newSupervisor.getPrincipalId() != null) {
if (areTwoUsersTheSame(newSupervisor, account.getAccountFiscalOfficerUser())) {
success = false;
putFieldError("accountGlobalDetails[" + index + "].accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_EQUAL_EXISTING_FISCAL_OFFICER, new String[] { account.getAccountFiscalOfficerUser().getPrincipalName(), "Fiscal Officer", detail.getAccountNumber() });
}
if (areTwoUsersTheSame(newSupervisor, account.getAccountManagerUser())) {
success = false;
putFieldError("accountGlobalDetails[" + index + "].accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_SUPER_CANNOT_EQUAL_EXISTING_ACCT_MGR, new String[] { account.getAccountManagerUser().getPrincipalName(), "Account Manager", detail.getAccountNumber() });
}
}
if (newManager != null && newManager.getPrincipalId() != null) {
if (areTwoUsersTheSame(newManager, account.getAccountSupervisoryUser())) {
success = false;
putFieldError("accountGlobalDetails[" + index + "].accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_MGR_CANNOT_EQUAL_EXISTING_ACCT_SUPERVISOR, new String[] { account.getAccountSupervisoryUser().getPrincipalName(), "Account Supervisor", detail.getAccountNumber() });
}
}
if (newFiscalOfficer != null && newFiscalOfficer.getPrincipalId() != null) {
if (areTwoUsersTheSame(newFiscalOfficer, account.getAccountSupervisoryUser())) {
success = false;
putFieldError("accountGlobalDetails[" + index + "].accountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_FISCAL_OFFICER_CANNOT_EQUAL_EXISTING_ACCT_SUPERVISOR, new String[] { account.getAccountSupervisoryUser().getPrincipalName(), "Account Supervisor", detail.getAccountNumber() });
}
}
}
else {
LOG.warn("AccountGlobalDetail object has null account object:" + detail.getChartOfAccountsCode() + "-" + detail.getAccountNumber());
}
}
return success;
}
/**
* This method is a helper method for checking if the supervisor user is the same as the fiscal officer Calls
* {@link AccountGlobalRule#areTwoUsersTheSame(Person, Person)}
*
* @param accountGlobals
* @return true if the two users are the same
*/
protected boolean isSupervisorSameAsFiscalOfficer(AccountGlobal accountGlobals) {
return areTwoUsersTheSame(accountGlobals.getAccountSupervisoryUser(), accountGlobals.getAccountFiscalOfficerUser());
}
/**
* This method is a helper method for checking if the supervisor user is the same as the manager Calls
* {@link AccountGlobalRule#areTwoUsersTheSame(Person, Person)}
*
* @param accountGlobals
* @return true if the two users are the same
*/
protected boolean isSupervisorSameAsManager(AccountGlobal accountGlobals) {
return areTwoUsersTheSame(accountGlobals.getAccountSupervisoryUser(), accountGlobals.getAccountManagerUser());
}
/**
* This method checks to see if two users are the same Person using their identifiers
*
* @param user1
* @param user2
* @return true if these two users are the same
*/
protected boolean areTwoUsersTheSame(Person user1, Person user2) {
if (ObjectUtils.isNull(user1) || user1.getPrincipalId() == null ) {
return false;
}
if (ObjectUtils.isNull(user2) || user2.getPrincipalId() == null ) {
return false;
}
return user1.getPrincipalId().equals(user2.getPrincipalId());
}
/**
* This method checks to see if any expiration date field rules were violated Loops through each detail object and calls
* {@link AccountGlobalRule#checkExpirationDate(MaintenanceDocument, AccountGlobalDetail)}
*
* @param maintenanceDocument
* @return false on rules violation
*/
protected boolean checkExpirationDate(MaintenanceDocument maintenanceDocument) {
LOG.info("checkExpirationDate called");
boolean success = true;
Date newExpDate = newAccountGlobal.getAccountExpirationDate();
// If creating a new account if acct_expiration_dt is set then
// the acct_expiration_dt must be changed to a date that is today or later
// unless the date was valid upon submission, this is an approval action
// and the approver hasn't changed the value
if (maintenanceDocument.isNew() && ObjectUtils.isNotNull(newExpDate)) {
Date oldExpDate = null;
if (maintenanceDocument.getDocumentHeader().getWorkflowDocument().isApprovalRequested()) {
try {
MaintenanceDocument oldMaintDoc = (MaintenanceDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(maintenanceDocument.getDocumentNumber());
AccountGlobal oldAccountGlobal = (AccountGlobal)oldMaintDoc.getDocumentBusinessObject();
if (ObjectUtils.isNotNull(oldAccountGlobal)) {
oldExpDate = oldAccountGlobal.getAccountExpirationDate();
}
}
catch (WorkflowException ex) {
LOG.warn( "Error retrieving maintenance doc for doc #" + maintenanceDocument.getDocumentNumber()+ ". This shouldn't happen.", ex );
}
}
if (ObjectUtils.isNull(oldExpDate) || !oldExpDate.equals(newExpDate)) {
if (!newExpDate.after(today) && !newExpDate.equals(today)) {
putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
success &= false;
}
}
}
// a continuation account is required if the expiration date is completed.
success &= checkContinuationAccount(maintenanceDocument, newExpDate);
for (AccountGlobalDetail detail : newAccountGlobal.getAccountGlobalDetails()) {
success &= checkExpirationDate(maintenanceDocument, detail);
}
return success;
}
/**
* This method checks to see if any expiration date field rules were violated in relation to the given detail record
*
* @param maintenanceDocument
* @param detail - the account detail we are investigating
* @return false on rules violation
*/
protected boolean checkExpirationDate(MaintenanceDocument maintenanceDocument, AccountGlobalDetail detail) {
boolean success = true;
Date newExpDate = newAccountGlobal.getAccountExpirationDate();
Date prevExpDate = null;
// get previous expiration date for possible check later
if (maintenanceDocument.getDocumentHeader().getWorkflowDocument().isApprovalRequested()) {
try {
MaintenanceDocument oldMaintDoc = (MaintenanceDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(maintenanceDocument.getDocumentNumber());
AccountGlobal oldAccountGlobal = (AccountGlobal)oldMaintDoc.getDocumentBusinessObject();
if (ObjectUtils.isNotNull(oldAccountGlobal)) {
prevExpDate = oldAccountGlobal.getAccountExpirationDate();
}
}
catch (WorkflowException ex) {
LOG.warn( "Error retrieving maintenance doc for doc #" + maintenanceDocument.getDocumentNumber()+ ". This shouldn't happen.", ex );
}
}
// load the object by keys
Account account = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Account.class, detail.getPrimaryKeys());
if (ObjectUtils.isNotNull(account)) {
Date oldExpDate = account.getAccountExpirationDate();
// When updating an account expiration date, the date must be today or later
// (except for C&G accounts). Only run this test if this maint doc
// is an edit doc
if (isUpdatedExpirationDateInvalid(account, newAccountGlobal)) {
// if the date was valid upon submission, and this is an approval,
// we're not interested unless the approver changed the value
if (ObjectUtils.isNull(prevExpDate) || !prevExpDate.equals(newExpDate)) {
putFieldError("accountExpirationDate", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
success &= false;
}
}
// If creating a new account if acct_expiration_dt is set and the fund_group is not "CG" then
// the acct_expiration_dt must be changed to a date that is today or later
// unless the date was valid upon submission, this is an approval action
// and the approver hasn't changed the value
if (maintenanceDocument.isNew() && ObjectUtils.isNotNull(newExpDate)) {
if (ObjectUtils.isNull(prevExpDate) || !prevExpDate.equals(newExpDate)) {
if (ObjectUtils.isNotNull(newExpDate) && ObjectUtils.isNull(newAccountGlobal.getSubFundGroup())) {
if (ObjectUtils.isNotNull(account.getSubFundGroup())) {
if (!account.isForContractsAndGrants()) {
if (!newExpDate.after(today) && !newExpDate.equals(today)) {
putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_TODAY_LATER);
success &= false;
}
}
}
}
}
}
// acct_expiration_dt can not be before acct_effect_dt
Date effectiveDate = account.getAccountEffectiveDate();
if (ObjectUtils.isNotNull(effectiveDate) && ObjectUtils.isNotNull(newExpDate)) {
if (newExpDate.before(effectiveDate)) {
putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_EXP_DATE_CANNOT_BE_BEFORE_EFFECTIVE_DATE);
success &= false;
}
}
}
return success;
}
/*
* protected boolean checkAccountExpirationDateValidTodayOrEarlier(Account newAccount) { // get today's date, with no time
* component Timestamp todaysDate = getDateTimeService().getCurrentTimestamp();
* todaysDate.setTime(KfsDateUtils.truncate(todaysDate, Calendar.DAY_OF_MONTH).getTime()); // TODO: convert this to using Wes'
* kuali KfsDateUtils once we're using Date's instead of Timestamp // get the expiration date, if any Timestamp expirationDate =
* newAccount.getAccountExpirationDate(); if (ObjectUtils.isNull(expirationDate)) { putFieldError("accountExpirationDate",
* KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); return false; } // when closing an account,
* the account expiration date must be the current date or earlier expirationDate.setTime(KfsDateUtils.truncate(expirationDate,
* Calendar.DAY_OF_MONTH).getTime()); if (expirationDate.after(todaysDate)) { putFieldError("accountExpirationDate",
* KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_ACCT_CANNOT_BE_CLOSED_EXP_DATE_INVALID); return false; } return true; }
*/
/**
* This method checks to see if the updated expiration is not a valid one Only gets checked for specific {@link SubFundGroup}s
*
* @param oldAccount
* @param newAccountGlobal
* @return true if date has changed and is invalid
*/
protected boolean isUpdatedExpirationDateInvalid(Account oldAccount, AccountGlobal newAccountGlobal) {
Date oldExpDate = oldAccount.getAccountExpirationDate();
Date newExpDate = newAccountGlobal.getAccountExpirationDate();
// When updating an account expiration date, the date must be today or later
// (except for C&G accounts). Only run this test if this maint doc
// is an edit doc
boolean expDateHasChanged = false;
// if the old version of the account had no expiration date, and the new
// one has a date
if (ObjectUtils.isNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) {
expDateHasChanged = true;
}
// if there was an old and a new expDate, but they're different
else if (ObjectUtils.isNotNull(oldExpDate) && ObjectUtils.isNotNull(newExpDate)) {
if (!oldExpDate.equals(newExpDate)) {
expDateHasChanged = true;
}
}
// if the expiration date hasnt changed, we're not interested
if (!expDateHasChanged) {
return false;
}
// if a subFundGroup isnt present, we cannot continue the testing
SubFundGroup subFundGroup = newAccountGlobal.getSubFundGroup();
if (ObjectUtils.isNull(subFundGroup)) {
return false;
}
// get the fundGroup code
String fundGroupCode = newAccountGlobal.getSubFundGroup().getFundGroupCode().trim();
// if the account is part of the CG fund group, then this rule does not
// apply, so we're done
if (SpringContext.getBean(SubFundGroupService.class).isForContractsAndGrants(newAccountGlobal.getSubFundGroup())) {
return false;
}
// at this point, we know its not a CG fund group, so we must apply the rule
// expirationDate must be today or later than today (cannot be before today)
if (newExpDate.equals(today) || newExpDate.after(today)) {
return false;
}
else {
return true;
}
}
/**
* This method tests whether the continuation account entered (if any) has expired or not.
*
* @param accountGlobals
* @return true if the continuation account has expired
*/
protected boolean isContinuationAccountExpired(AccountGlobal accountGlobals) {
boolean result = false;
String chartCode = accountGlobals.getContinuationFinChrtOfAcctCd();
String accountNumber = accountGlobals.getContinuationAccountNumber();
// if either chartCode or accountNumber is not entered, then we
// cant continue, so exit
if (StringUtils.isBlank(chartCode) || StringUtils.isBlank(accountNumber)) {
return result;
}
// attempt to retrieve the continuation account from the DB
Account continuation = null;
Map<String,String> pkMap = new HashMap<String,String>();
pkMap.put("chartOfAccountsCode", chartCode);
pkMap.put("accountNumber", accountNumber);
continuation = super.getBoService().findByPrimaryKey(Account.class, pkMap);
// if the object doesnt exist, then we cant continue, so exit
if (ObjectUtils.isNull(continuation)) {
return result;
}
// at this point, we have a valid continuation account, so we just need to
// know whether its expired or not
result = continuation.isExpired();
return result;
}
/**
* This method checks to see if any Contracts and Grants business rules were violated
*
* @return false on rules violation
*/
protected boolean checkContractsAndGrants() {
LOG.info("checkContractsAndGrants called");
boolean success = true;
// Income Stream account is required if this account is CG fund group,
// or GF (general fund) fund group (with some exceptions)
success &= checkCgIncomeStreamRequired(newAccountGlobal);
return success;
}
/**
* This method checks to see if the contracts and grants income stream account is required
*
* @param accountGlobals
* @return false if it is required (and not entered) or invalid/inactive
*/
protected boolean checkCgIncomeStreamRequired(AccountGlobal accountGlobals) {
boolean result = true;
boolean required = false;
// if the subFundGroup object is null, we cant test, so exit
if (ObjectUtils.isNull(accountGlobals.getSubFundGroup())) {
return result;
}
// retrieve the subfundcode and fundgroupcode
String subFundGroupCode = accountGlobals.getSubFundGroupCode().trim();
String fundGroupCode = accountGlobals.getSubFundGroup().getFundGroupCode().trim();
// if this is a CG fund group, then its required
if (SpringContext.getBean(SubFundGroupService.class).isForContractsAndGrants(accountGlobals.getSubFundGroup())) {
required = true;
}
// if this is a general fund group, then its required
else if (GENERAL_FUND_CD.equalsIgnoreCase(fundGroupCode)) {
// unless its part of the MPRACT subfundgroup
if (!SUB_FUND_GROUP_MEDICAL_PRACTICE_FUNDS.equalsIgnoreCase(subFundGroupCode)) {
required = true;
}
}
// if the income stream account is not required, then we're done
if (!required) {
return result;
}
// make sure both coaCode and accountNumber are filled out
result &= checkEmptyBOField("incomeStreamAccountNumber", accountGlobals.getIncomeStreamAccountNumber(), "When Fund Group is CG or GF, Income Stream Account Number");
result &= checkEmptyBOField("incomeStreamFinancialCoaCode", accountGlobals.getIncomeStreamFinancialCoaCode(), "When Fund Group is CG or GF, Income Stream Chart Of Accounts Code");
// if both fields arent present, then we're done
if (result == false) {
return result;
}
// do an existence/active test
DictionaryValidationService dvService = super.getDictionaryValidationService();
boolean referenceExists = dvService.validateReferenceExists(accountGlobals, "incomeStreamAccount");
if (!referenceExists) {
putFieldError("incomeStreamAccount", KFSKeyConstants.ERROR_EXISTENCE, "Income Stream Account: " + accountGlobals.getIncomeStreamFinancialCoaCode() + "-" + accountGlobals.getIncomeStreamAccountNumber());
result &= false;
}
return result;
}
/**
* This method calls checkAccountDetails checkExpirationDate checkOnlyOneChartAddLineErrorWrapper whenever a new
* {@link AccountGlobalDetail} is added to this global
*
* @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument,
* java.lang.String, org.kuali.rice.krad.bo.PersistableBusinessObject)
*/
@Override
public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) {
AccountGlobalDetail detail = (AccountGlobalDetail) bo;
boolean success = true;
success &= checkAccountDetails(detail);
success &= checkExpirationDate(document, detail);
success &= checkOnlyOneChartAddLineErrorWrapper(detail, newAccountGlobal.getAccountGlobalDetails());
return success;
}
/**
* This method validates that a continuation account is required and that the values provided exist
*
* @param document An instance of the maintenance document being validated.
* @param newExpDate The expiration date assigned to the account being validated for submission.
* @return True if the continuation account values are valid for the associated account, false otherwise.
*/
protected boolean checkContinuationAccount(MaintenanceDocument document, Date newExpDate) {
LOG.info("checkContinuationAccount called");
boolean result = true;
boolean continuationAccountIsValid = true;
// make sure both coaCode and accountNumber are filled out
if (ObjectUtils.isNotNull(newExpDate)) {
if (!checkEmptyValue(newAccountGlobal.getContinuationAccountNumber())) {
putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_ACCT_REQD_IF_EXP_DATE_COMPLETED);
continuationAccountIsValid = false;
}
if (!checkEmptyValue(newAccountGlobal.getContinuationFinChrtOfAcctCd())) {
putFieldError("continuationFinChrtOfAcctCd", KFSKeyConstants.ERROR_DOCUMENT_ACCMAINT_CONTINUATION_FINCODE_REQD_IF_EXP_DATE_COMPLETED);
continuationAccountIsValid = false;
}
}
// if both fields aren't present, then we're done
if (continuationAccountIsValid && ObjectUtils.isNotNull(newAccountGlobal.getContinuationAccountNumber()) && ObjectUtils.isNotNull(newAccountGlobal.getContinuationFinChrtOfAcctCd())) {
// do an existence/active test
DictionaryValidationService dvService = super.getDictionaryValidationService();
boolean referenceExists = dvService.validateReferenceExists(newAccountGlobal, "continuationAccount");
if (!referenceExists) {
putFieldError("continuationAccountNumber", KFSKeyConstants.ERROR_EXISTENCE, "Continuation Account: " + newAccountGlobal.getContinuationFinChrtOfAcctCd() + "-" + newAccountGlobal.getContinuationAccountNumber());
continuationAccountIsValid = false;
}
}
if (continuationAccountIsValid) {
result = true;
}
else {
List<AccountGlobalDetail> gAcctDetails = newAccountGlobal.getAccountGlobalDetails();
for (AccountGlobalDetail detail : gAcctDetails) {
if (null != detail.getAccountNumber() && null != newAccountGlobal.getContinuationAccountNumber()) {
result &= detail.getAccountNumber().equals(newAccountGlobal.getContinuationAccountNumber());
result &= detail.getChartOfAccountsCode().equals(newAccountGlobal.getContinuationFinChrtOfAcctCd());
}
}
}
return result;
}
/**
* Validate that the object code on the form (if entered) is valid for all charts used in the detail sections.
*
* @param acctGlobal
* @return
*/
protected boolean checkOrganizationValidity( AccountGlobal acctGlobal ) {
boolean result = true;
// check that an org has been entered
if ( StringUtils.isNotBlank( acctGlobal.getOrganizationCode() ) ) {
// get all distinct charts
HashSet<String> charts = new HashSet<String>(10);
for ( AccountGlobalDetail acct : acctGlobal.getAccountGlobalDetails() ) {
charts.add( acct.getChartOfAccountsCode() );
}
OrganizationService orgService = SpringContext.getBean(OrganizationService.class);
// test for an invalid organization
for ( String chartCode : charts ) {
if ( StringUtils.isNotBlank(chartCode) ) {
if ( null == orgService.getByPrimaryIdWithCaching( chartCode, acctGlobal.getOrganizationCode() ) ) {
result = false;
putFieldError("organizationCode", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ORG, new String[] { chartCode, acctGlobal.getOrganizationCode() } );
break;
}
}
}
}
return result;
}
}