001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.kuali.kfs.document.authorization;
017:
018: import java.util.ArrayList;
019: import java.util.Arrays;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Map;
024:
025: import org.apache.commons.collections.Closure;
026: import org.apache.commons.collections.CollectionUtils;
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029: import org.kuali.core.authorization.AuthorizationConstants;
030: import org.kuali.core.bo.user.UniversalUser;
031: import org.kuali.core.document.Document;
032: import org.kuali.core.document.TransactionalDocument;
033: import org.kuali.core.document.authorization.TransactionalDocumentAuthorizerBase;
034: import org.kuali.core.util.ObjectUtils;
035: import org.kuali.core.workflow.service.KualiWorkflowDocument;
036: import org.kuali.kfs.bo.AccountingLine;
037: import org.kuali.kfs.context.SpringContext;
038: import org.kuali.kfs.document.AccountingDocument;
039: import org.kuali.module.chart.bo.Account;
040: import org.kuali.module.chart.bo.ChartUser;
041: import org.kuali.module.chart.service.AccountService;
042: import org.kuali.workflow.KualiWorkflowUtils.RouteLevelNames;
043:
044: import edu.iu.uis.eden.exception.WorkflowException;
045:
046: /**
047: * DocumentAuthorizer containing common, reusable document-level authorization code for financial (i.e. Transactional) documents
048: */
049: public class AccountingDocumentAuthorizerBase extends
050: TransactionalDocumentAuthorizerBase implements
051: AccountingDocumentAuthorizer {
052: private static Log LOG = LogFactory
053: .getLog(AccountingDocumentAuthorizerBase.class);
054:
055: /**
056: * @see org.kuali.core.authorization.FinancialDocumentAuthorizer#getAccountingLineEditableFields(org.kuali.core.document.Document,
057: * org.kuali.core.bo.user.KualiUser)
058: */
059: public Map getAccountingLineEditableFields(Document document,
060: UniversalUser user) {
061: return new HashMap();
062: }
063:
064: /**
065: * The Kuali user interface code automatically reconstructs a complete document from a form's contents when a form is submitted,
066: * even if that form's contents are invalid (specifically, the Source and TargetAccountingLines from the form are used even if
067: * they contain invalid accountNumbers). Since the accounts referenced in the source and targetAccountingLines are used to
068: * determine editMode, when a) the document is enroute; b) the user is an FO, and receives the document for approval; and c) the
069: * user changes the accountNumbers on all accountingLines for which they are FO to something different or invalid, the form
070: * builds a Document for which the user is FO of none of the accountingLines, which makes the editMode computation incorrectly
071: * shift from EXPENSE_ENTRY to ACCOUNT_REVIEW, which makes it impossible for the user to restore the lines which they just
072: * rendered invalid. The simplest way to avoid that is to allow the UI to pass in the baselineSource and target accountingLines,
073: * so that the editability can be determined from them (since they are, by definition, valid and authoritative).
074: * </p>
075: * Note that document types which route straight to final will get an edit mode of VIEW_ONLY or FULL_ENTRY, never EXPENSE_ENTRY,
076: * because even if the state is briefly enroute, the route level is never ORG_REVIEW or ACCOUNT_REVIEW.
077: *
078: * @see org.kuali.module.financial.document.authorization.FinancialDocumentAuthorizer#getEditMode(org.kuali.core.document.Document,
079: * org.kuali.core.bo.user.UniversalUser, java.util.List, java.util.List)
080: */
081: public Map getEditMode(Document document, UniversalUser user,
082: List sourceAccountingLines, List targetAccountingLines) {
083: ChartUser chartUser = (ChartUser) user
084: .getModuleUser(ChartUser.MODULE_ID);
085:
086: String editMode = AuthorizationConstants.TransactionalEditMode.VIEW_ONLY;
087:
088: KualiWorkflowDocument workflowDocument = document
089: .getDocumentHeader().getWorkflowDocument();
090:
091: if (workflowDocument.stateIsCanceled()
092: || (document.getDocumentHeader()
093: .getFinancialDocumentInErrorNumber() != null)) {
094: editMode = AuthorizationConstants.TransactionalEditMode.VIEW_ONLY;
095: } else if (workflowDocument.stateIsInitiated()
096: || workflowDocument.stateIsSaved()) {
097: if (workflowDocument.userIsInitiator(user)) {
098: editMode = AuthorizationConstants.TransactionalEditMode.FULL_ENTRY;
099: }
100: } else if (workflowDocument.stateIsEnroute()) {
101: List currentRouteLevels = getCurrentRouteLevels(workflowDocument);
102:
103: if (currentRouteLevels.contains(RouteLevelNames.ORG_REVIEW)) {
104: // The routing level should be linear for Kuali, i.e., assert currentRouteLevels.size() == 1,
105: // but in case it becomes parallel, don't allow an account review while an org review is underway.
106: editMode = AuthorizationConstants.TransactionalEditMode.VIEW_ONLY;
107: } else if (currentRouteLevels
108: .contains(RouteLevelNames.ACCOUNT_REVIEW)) {
109: List lineList = new ArrayList();
110: lineList.addAll(sourceAccountingLines);
111: lineList.addAll(targetAccountingLines);
112:
113: if (workflowDocument.isApprovalRequested()
114: && userOwnsAnyAccountingLine(chartUser,
115: lineList)) {
116: editMode = AuthorizationConstants.TransactionalEditMode.EXPENSE_ENTRY;
117: }
118: }
119: }
120:
121: Map editModeMap = new HashMap();
122: editModeMap.put(editMode, "TRUE");
123:
124: return editModeMap;
125: }
126:
127: /**
128: * A helper method for determining the route levels for a given document.
129: *
130: * @param workflowDocument
131: * @return List
132: */
133: protected static List getCurrentRouteLevels(
134: KualiWorkflowDocument workflowDocument) {
135: try {
136: return Arrays.asList(workflowDocument.getNodeNames());
137: } catch (WorkflowException e) {
138: throw new RuntimeException(e);
139: }
140: }
141:
142: /**
143: * @param accountingLines
144: * @param user
145: * @return true if the given user is responsible for any accounting line of the given transactionalDocument
146: */
147: protected boolean userOwnsAnyAccountingLine(ChartUser user,
148: List accountingLines) {
149: for (Iterator i = accountingLines.iterator(); i.hasNext();) {
150: AccountingLine accountingLine = (AccountingLine) i.next();
151: String chartCode = accountingLine.getChartOfAccountsCode();
152: String accountNumber = accountingLine.getAccountNumber();
153:
154: if (user.isResponsibleForAccount(chartCode, accountNumber)) {
155: return true;
156: }
157: }
158: return false;
159: }
160:
161: /**
162: * This class, a simple closure, decides if an account belongs in the editableAccounts map or not: if it does not exist, or if
163: * the account is editable, it will go into the map.
164: */
165: private class AccountResponsibilityClosure implements Closure {
166: private Map editableAccounts;
167: private ChartUser currentUser;
168: private AccountService accountService;
169:
170: public AccountResponsibilityClosure(Map editableAccounts,
171: ChartUser currentUser, AccountService accountService) {
172: this .editableAccounts = editableAccounts;
173: this .currentUser = currentUser;
174: this .accountService = accountService;
175: }
176:
177: public void execute(Object input) {
178: AccountingLine acctLine = (AccountingLine) input;
179: Account acct = accountService.getByPrimaryId(acctLine
180: .getChartOfAccountsCode(), acctLine
181: .getAccountNumber());
182: if (ObjectUtils.isNotNull(acct)) {
183: if (accountService.hasResponsibilityOnAccount(
184: currentUser.getUniversalUser(), acct)) {
185: editableAccounts
186: .put(acctLine.getAccountKey(), acct);
187: }
188: } else {
189: editableAccounts.put(acctLine.getAccountKey(), acctLine
190: .getAccountKey());
191: }
192: }
193:
194: }
195:
196: /**
197: * @see org.kuali.module.financial.document.authorization.FinancialDocumentAuthorizer#getEditableAccounts(org.kuali.core.document.TransactionalDocument,
198: * org.kuali.module.chart.bo.ChartUser)
199: */
200: public Map getEditableAccounts(TransactionalDocument document,
201: ChartUser user) {
202:
203: Map editableAccounts = new HashMap();
204: AccountingDocument acctDoc = (AccountingDocument) document;
205: AccountResponsibilityClosure accountResponsibilityClosure = new AccountResponsibilityClosure(
206: editableAccounts, user, SpringContext
207: .getBean(AccountService.class));
208:
209: // for every source accounting line, decide if account should be in map
210: CollectionUtils.forAllDo(acctDoc.getSourceAccountingLines(),
211: accountResponsibilityClosure);
212:
213: // for every target accounting line, decide if account should be in map
214: CollectionUtils.forAllDo(acctDoc.getTargetAccountingLines(),
215: accountResponsibilityClosure);
216:
217: return editableAccounts;
218: }
219:
220: /**
221: * @see org.kuali.kfs.document.authorization.AccountingDocumentAuthorizer#getEditableAccounts(java.util.List,
222: * org.kuali.module.chart.bo.ChartUser)
223: */
224: public Map getEditableAccounts(List<AccountingLine> lines,
225: ChartUser user) {
226: Map editableAccounts = new HashMap();
227: AccountResponsibilityClosure accountResponsibilityClosure = new AccountResponsibilityClosure(
228: editableAccounts, user, SpringContext
229: .getBean(AccountService.class));
230:
231: CollectionUtils.forAllDo(lines, accountResponsibilityClosure);
232:
233: return editableAccounts;
234: }
235: }
|