/*
* 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.Timestamp;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.AccountDelegate;
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.kfs.sys.document.service.FinancialSystemDocumentTypeService;
import org.kuali.kfs.sys.document.validation.impl.KfsMaintenanceDocumentRuleBase;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kns.document.MaintenanceDocument;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* Validates content of a <code>{@link AccountDelegate}</code> maintenance document upon triggering of a approve, save, or route
* event.
*/
public class DelegateRule extends KfsMaintenanceDocumentRuleBase {
protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DelegateRule.class);
protected AccountDelegate oldDelegate;
protected AccountDelegate newDelegate;
/**
* This runs specific rules that are called when a document is saved:
* <ul>
* <li>{@link DelegateRule#checkSimpleRules()}</li>
* <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
* <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
* </ul>
*
* @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
* @return doesn't fail on save, even if sub-rules fail
*/
@Override
protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
LOG.debug("Entering processCustomSaveDocumentBusinessRules()");
setupConvenienceObjects(document);
// check simple rules
checkSimpleRules();
// disallow more than one PrimaryRoute for a given Chart/Account/Doctype
checkOnlyOnePrimaryRoute(document);
// delegate user must be Active and Professional
checkDelegateUserRules(document);
return true;
}
/**
* This runs specific rules that are called when a document is routed:
* <ul>
* <li>{@link DelegateRule#checkSimpleRules()}</li>
* <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
* <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
* </ul>
*
* @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
* @return fails if sub-rules fail
*/
@Override
protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
LOG.debug("Entering processCustomRouteDocumentBusinessRules()");
setupConvenienceObjects(document);
// check simple rules
boolean success = checkSimpleRules();
// disallow more than one PrimaryRoute for a given Chart/Account/Doctype
success &= checkOnlyOnePrimaryRoute(document);
// delegate user must be Active and Professional
success &= checkDelegateUserRules(document);
return success;
}
/**
* This runs specific rules that are called when a document is approved:
* <ul>
* <li>{@link DelegateRule#checkSimpleRules()}</li>
* <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
* <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
* </ul>
*
* @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
*/
@Override
protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
boolean success = true;
LOG.debug("Entering processCustomApproveDocumentBusinessRules()");
setupConvenienceObjects(document);
// check simple rules
success &= checkSimpleRules();
success &= checkOnlyOnePrimaryRoute(document);
// delegate user must be Active and Professional
success &= checkDelegateUserRules(document);
return success;
}
/**
* This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and
* old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
* all sub-objects from the DB by their primary keys, if available.
*
* @param document - the maintenanceDocument being evaluated
*/
protected void setupConvenienceObjects(MaintenanceDocument document) {
// setup oldAccount convenience objects, make sure all possible sub-objects are populated
oldDelegate = (AccountDelegate) super.getOldBo();
// setup newAccount convenience objects, make sure all possible sub-objects are populated
newDelegate = (AccountDelegate) super.getNewBo();
}
/**
* This checks to see if
* <ul>
* <li>the delegate start date is valid and they are active</li>
* <li>from amount is >= 0</li>
* <li>to amount cannot be empty when from amount is filled out</li>
* <li>to amount is >= from amount</li>
* <li>account cannot be closed</li>
* </ul>
*
* @return
*/
protected boolean checkSimpleRules() {
boolean success = true;
Map<String, String> fieldValues = new HashMap<String, String>();
fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, newDelegate.getChartOfAccountsCode());
fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, newDelegate.getAccountNumber());
int accountExist = getBoService().countMatching(Account.class, fieldValues);
if (accountExist<=0) {
putFieldError(KFSPropertyConstants.ACCOUNT_NUMBER, KFSKeyConstants.ERROR_EXISTENCE, newDelegate.getAccountNumber());
success &= false;
}
// FROM amount must be >= 0 (may not be negative)
KualiDecimal fromAmount = newDelegate.getFinDocApprovalFromThisAmt();
if (ObjectUtils.isNotNull(fromAmount)) {
if (fromAmount.isLessThan(KualiDecimal.ZERO)) {
putFieldError(KFSPropertyConstants.FIN_DOC_APPROVAL_FROM_THIS_AMT, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_FROM_AMOUNT_NONNEGATIVE);
success &= false;
}
}
// TO amount must be >= FROM amount or Zero
KualiDecimal toAmount = newDelegate.getFinDocApprovalToThisAmount();
if (ObjectUtils.isNotNull(toAmount) && !toAmount.equals(KualiDecimal.ZERO)) {
// case if FROM amount is non-null and positive, disallow TO amount being less
if (fromAmount != null && toAmount.isLessThan(fromAmount)) {
putFieldError(KFSPropertyConstants.FIN_DOC_APPROVAL_TO_THIS_AMOUNT, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
success &= false;
} else if (toAmount.isLessThan(KualiDecimal.ZERO)) {
putFieldError(KFSPropertyConstants.FIN_DOC_APPROVAL_TO_THIS_AMOUNT, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
success &= false;
}
}
// do we have a good document type?
final FinancialSystemDocumentTypeService documentService = SpringContext.getBean(FinancialSystemDocumentTypeService.class);
if (!documentService.isFinancialSystemDocumentType(newDelegate.getFinancialDocumentTypeCode())) {
putFieldError(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_INVALID_DOC_TYPE, new String[] { newDelegate.getFinancialDocumentTypeCode(), KFSConstants.ROOT_DOCUMENT_TYPE });
success = false;
}
return success;
}
/**
* This checks to see if there is already a record for the primary route
*
* @param document
* @return false if there is a record
*/
protected boolean checkOnlyOnePrimaryRoute(MaintenanceDocument document) {
boolean success = true;
boolean checkDb = false;
boolean newPrimary;
boolean newActive;
boolean blockingDocumentExists;
// exit out immediately if this doc is not requesting a primary route
newPrimary = newDelegate.isAccountsDelegatePrmrtIndicator();
if (!newPrimary) {
return success;
}
// exit if new document not active
newActive = newDelegate.isActive();
if (!newActive) {
return success;
}
// if its a new document, we are only interested if they have chosen this one
// to be a primary route
if (document.isNew()) {
if (newPrimary) {
checkDb = true;
}
}
// handle an edit, where all we care about is that someone might change it
// from NOT a primary TO a primary
if (document.isEdit()) {
boolean oldPrimary = oldDelegate.isAccountsDelegatePrmrtIndicator();
if (!oldPrimary && newPrimary) {
checkDb = true;
}
}
// if we dont want to check the db for another primary, then exit
if (!checkDb) {
return success;
}
// if a primary already exists for a document type (including ALL), throw an error. However, current business rules
// should allow a primary for other document types, even if a primary for ALL already exists.
Map whereMap = new HashMap();
whereMap.put("chartOfAccountsCode", newDelegate.getChartOfAccountsCode());
whereMap.put("accountNumber", newDelegate.getAccountNumber());
whereMap.put("accountsDelegatePrmrtIndicator", Boolean.TRUE);
whereMap.put("financialDocumentTypeCode", newDelegate.getFinancialDocumentTypeCode());
whereMap.put("active", Boolean.TRUE);
// find all the matching records
Collection primaryRoutes = getBoService().findMatching(AccountDelegate.class, whereMap);
// if there is at least one result, then this business rule is tripped
if (primaryRoutes.size() > 0) {
putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_PRIMARY_ROUTE_ALREADY_EXISTS_FOR_DOCTYPE);
success &= false;
}
return success;
}
/**
* This checks to see if the user is valid and active for this module
*
* @param document
* @return false if this user is not valid or active for being a delegate
*/
protected boolean checkDelegateUserRules(MaintenanceDocument document) {
boolean success = true;
final Person accountDelegate = newDelegate.getAccountDelegate();
// if the user doesn't exist, then do nothing, it'll fail the existence test elsewhere
if (ObjectUtils.isNull(accountDelegate)) {
return success;
}
// if the document is inactivating an account delegate, don't bother validating the user
if (document.getOldMaintainableObject() != null && document.getOldMaintainableObject().getBusinessObject() != null && ((AccountDelegate)document.getOldMaintainableObject().getBusinessObject()).isActive() && !((AccountDelegate)document.getNewMaintainableObject().getBusinessObject()).isActive()) {
return success;
}
if (StringUtils.isBlank(accountDelegate.getEntityId()) || !getDocumentHelperService().getDocumentAuthorizer(document).isAuthorized(document, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.name, accountDelegate.getPrincipalId())) {
super.putFieldError("accountDelegate.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountDelegate.getName(), KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.name});
success = false;
}
return success;
}
}