/*
* 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.fp.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.businessobject.AccountingLineOverride;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocumentBase;
import org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer;
import org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase;
import org.kuali.kfs.sys.document.datadictionary.AccountingLineGroupDefinition;
import org.kuali.kfs.sys.document.datadictionary.FinancialSystemTransactionalDocumentEntry;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.krad.document.Document;
import org.kuali.rice.kns.rule.event.PromptBeforeValidationEvent;
import org.kuali.rice.kns.rules.PromptBeforeValidationBase;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
import org.kuali.kfs.fp.service.AccountingDocumentPreRuleService;
/**
* This service interface defines methods that a AccountingDocumentPreRuleService implementation must provide.
*/
public class AccountingDocumentPreRuleServiceImpl implements AccountingDocumentPreRuleService {
protected static Logger LOG = Logger.getLogger(AccountingDocumentPreRuleServiceImpl.class);
/**
* Access the account override question for all accounting document
*
* @param document
* @param preRule
* @return
*/
public boolean expiredAccountOverrideQuestion(AccountingDocumentBase document, PromptBeforeValidationBase preRule, PromptBeforeValidationEvent event) {
boolean tabStatesOK = true;
List<AccountingLine> accountLineList = getOverrideQuestionAccount(document);
if (accountLineList != null && !accountLineList.isEmpty()) {
String questionText = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(KFSKeyConstants.QUESTION_NEED_OVERRIDE_ACCOUNT_FOR_EXPIRED);
StringBuffer expiredAccounts = new StringBuffer();
for (AccountingLine accountingLine : accountLineList) {
expiredAccounts.append(accountingLine.getChartOfAccountsCode());
expiredAccounts.append("-");
expiredAccounts.append(accountingLine.getAccountNumber());
expiredAccounts.append(" ");
}
questionText = StringUtils.replace(questionText, "{0}", expiredAccounts.toString());
boolean overrideAccount = preRule.askOrAnalyzeYesNoQuestion(KFSConstants.OVERRIDE_ACCOUNT_FOR_EXPIRED_QUESTION_ID, questionText);
if (overrideAccount) {
Set overrideInputComponents = new HashSet();
overrideInputComponents.add(AccountingLineOverride.COMPONENT.EXPIRED_ACCOUNT);
for (AccountingLine accountingLine : accountLineList) {
setAccountOverride(document, accountingLine.getAccount(), AccountingLineOverride.valueOf(overrideInputComponents).getCode());
}
}
else {
// return to document if the user selects No
event.setActionForwardName(KFSConstants.MAPPING_BASIC);
tabStatesOK = false;
}
}
return tabStatesOK;
}
/**
* Set up override for all accounting line with the same account number
*
* @param document
* @param accountLine
* @param code
*/
protected void setAccountOverride(AccountingDocumentBase document, Account overrideAccount, String code) {
List accountLinesFromDoc = new ArrayList<AccountingLine>();
accountLinesFromDoc.addAll(document.getSourceAccountingLines());
accountLinesFromDoc.addAll(document.getTargetAccountingLines());
for (Iterator iter = accountLinesFromDoc.iterator(); iter.hasNext();) {
AccountingLine currentLine = (AccountingLine) iter.next();
if (overrideAccount.getChartOfAccountsCode().equals(currentLine.getChartOfAccountsCode()) && overrideAccount.getAccountNumber().equals(currentLine.getAccountNumber())) {
currentLine.setOverrideCode(code);
}
}
}
/**
* DTT-3163: Walk through all source and target accounting lines to identify if there is account which is expired and requires
* approver to override but approver does not have the edit permission
*
* @param document
* @return
*/
protected List<AccountingLine> getOverrideQuestionAccount(Document document) {
final Person currentUser = GlobalVariables.getUserSession().getPerson();
List<AccountingLine> questionAccounts = new ArrayList<AccountingLine>();
HashMap questionAccountsMap = new HashMap<String, Object>();
String accountKey = null;
// expiration warning should be triggered only when document is enrouting and waiting for approval; accounting
// line changed from active to inactive due to expiration date; the current approval does not have the permission on editing
// accounting line
if (ObjectUtils.isNotNull(document.getDocumentHeader())) {
WorkflowDocument workflowDoc = document.getDocumentHeader().getWorkflowDocument();
if (ObjectUtils.isNotNull(workflowDoc) && workflowDoc.isEnroute() && workflowDoc.isApprovalRequested()) {
AccountingDocumentBase acctDoc = (AccountingDocumentBase) document;
List accountLinesFromDoc = new ArrayList<AccountingLine>();
accountLinesFromDoc.addAll(acctDoc.getSourceAccountingLines());
accountLinesFromDoc.addAll(acctDoc.getTargetAccountingLines());
Map<String, AccountingLineAuthorizer> authorizerMap = new HashMap<String, AccountingLineAuthorizer>();
for (Iterator iter = accountLinesFromDoc.iterator(); iter.hasNext();) {
AccountingLine currentLine = (AccountingLine) iter.next();
accountKey = currentLine.getChartOfAccountsCode() + "-" + currentLine.getAccountNumber();
// if account is a known expired account, skip it.
if (questionAccountsMap.containsKey(accountKey)) {
continue;
}
AccountingLineOverride override = AccountingLineOverride.valueOf(currentLine.getOverrideCode());
if (AccountingLineOverride.needsExpiredAccountOverride(currentLine.getAccount()) && !override.hasComponent(AccountingLineOverride.COMPONENT.EXPIRED_ACCOUNT)) {
// check if the approver has the permission
String groupName = getGroupName(currentLine);
AccountingLineAuthorizer accountingLineAuthorizer = authorizerMap.get(groupName);
if (accountingLineAuthorizer == null) {
accountingLineAuthorizer = lookupAccountingLineAuthorizer(currentLine, document, groupName);
authorizerMap.put(groupName, accountingLineAuthorizer);
}
if (accountingLineAuthorizer != null) {
boolean lineIsAccessible = accountingLineAuthorizer.hasEditPermissionOnAccountingLine(acctDoc, currentLine, getAccountingLineCollectionProperty(currentLine), currentUser, true);
boolean isAccessible = accountingLineAuthorizer.hasEditPermissionOnField(acctDoc, currentLine, getAccountingLineCollectionProperty(currentLine), KFSPropertyConstants.ACCOUNT_NUMBER, lineIsAccessible, true, currentUser);
if (!isAccessible) {
questionAccounts.add(currentLine);
questionAccountsMap.put(accountKey, currentLine);
}
}
}
}
}
}
return questionAccounts;
}
/**
* Determines the property of the accounting line collection from the error prefixes
*
* @return the accounting line collection property
*/
protected String getAccountingLineCollectionProperty(AccountingLine account) {
String propertyName = null;
propertyName = account.isSourceAccountingLine() ? KFSConstants.PermissionAttributeValue.SOURCE_ACCOUNTING_LINES.value : KFSConstants.PermissionAttributeValue.TARGET_ACCOUNTING_LINES.value;
if (propertyName.equals("newSourceLine"))
return KFSConstants.PermissionAttributeValue.SOURCE_ACCOUNTING_LINES.value;
if (propertyName.equals("newTargetLine"))
return KFSConstants.PermissionAttributeValue.TARGET_ACCOUNTING_LINES.value;
return propertyName;
}
/**
* @param accountingLines
* @return Map containing accountingLines from the given List, indexed by their sequenceNumber
*/
protected Map buildAccountingLineMap(List accountingLines) {
Map lineMap = new HashMap();
for (Iterator i = accountingLines.iterator(); i.hasNext();) {
AccountingLine accountingLine = (AccountingLine) i.next();
Integer sequenceNumber = accountingLine.getSequenceNumber();
lineMap.put(sequenceNumber, accountingLine);
}
return lineMap;
}
/**
* @return hopefully, the best accounting line authorizer implementation to do the KIM check for to see if lines are accessible
*/
protected AccountingLineAuthorizer lookupAccountingLineAuthorizer(AccountingLine account, Document document, String groupName) {
Map<String, AccountingLineGroupDefinition> groups = ((FinancialSystemTransactionalDocumentEntry) SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getDictionaryObjectEntry(document.getClass().getName())).getAccountingLineGroups();
if (groups.isEmpty())
return new AccountingLineAuthorizerBase(); // no groups? just use the default...
if (groups.containsKey(groupName))
return groups.get(groupName).getAccountingLineAuthorizer(); // we've got the group
Set<String> groupNames = groups.keySet(); // we've got groups, just not the proper name; try our luck and get the
// first group iterator
Iterator<String> groupNameIterator = groupNames.iterator();
String firstGroupName = groupNameIterator.next();
return groups.get(firstGroupName).getAccountingLineAuthorizer();
}
/**
* Returns the name of the accounting line group which holds the proper authorizer to do the KIM check
*
* @return the name of the accouting line group to get the authorizer from
*/
protected String getGroupName(AccountingLine line) {
return (line.isSourceAccountingLine() ? KFSConstants.SOURCE_ACCOUNTING_LINES_GROUP_NAME : KFSConstants.TARGET_ACCOUNTING_LINES_GROUP_NAME);
}
}