001 /*
002 * Copyright 2011 The Kuali Foundation.
003 *
004 * Licensed under the Educational Community License, Version 2.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/ecl2.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.module.purap.document.authorization;
017
018 import java.util.List;
019 import java.util.Set;
020
021 import org.apache.commons.lang.StringUtils;
022 import org.kuali.kfs.module.purap.PurapAuthorizationConstants;
023 import org.kuali.kfs.module.purap.PurapParameterConstants;
024 import org.kuali.kfs.module.purap.PurapPropertyConstants;
025 import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
026 import org.kuali.kfs.module.purap.businessobject.PurApItem;
027 import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
028 import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
029 import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase;
030 import org.kuali.kfs.module.purap.document.RequisitionDocument;
031 import org.kuali.kfs.sys.KFSPropertyConstants;
032 import org.kuali.kfs.sys.businessobject.AccountingLine;
033 import org.kuali.kfs.sys.context.SpringContext;
034 import org.kuali.kfs.sys.document.AccountingDocument;
035 import org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase;
036 import org.kuali.kfs.sys.document.authorization.FinancialSystemTransactionalDocumentAuthorizerBase;
037 import org.kuali.kfs.sys.document.authorization.FinancialSystemTransactionalDocumentPresentationController;
038 import org.kuali.kfs.sys.document.web.AccountingLineRenderingContext;
039 import org.kuali.rice.kim.bo.Person;
040 import org.kuali.rice.kns.datadictionary.TransactionalDocumentEntry;
041 import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
042 import org.kuali.rice.kns.document.authorization.DocumentPresentationController;
043 import org.kuali.rice.kns.service.DataDictionaryService;
044 import org.kuali.rice.kns.service.ParameterService;
045 import org.kuali.rice.kns.util.GlobalVariables;
046
047 /**
048 * Authorizer which deals with financial processing document issues, specifically sales tax lines on documents
049 * This class utilizes the new accountingLine model.
050 */
051 public class PurapAccountingLineAuthorizer extends AccountingLineAuthorizerBase {
052
053 /**
054 * Overrides the method in AccountingLineAuthorizerBase so that the add button would
055 * have the line item number in addition to the rest of the insertxxxx String for
056 * methodToCall when the user clicks on the add button.
057 *
058 * @param accountingLine
059 * @param accountingLineProperty
060 * @return
061 */
062 @Override
063 protected String getAddMethod(AccountingLine accountingLine, String accountingLineProperty) {
064 final String infix = getActionInfixForNewAccountingLine(accountingLine, accountingLineProperty);
065 String lineNumber = null;
066 if (accountingLineProperty.equals(PurapPropertyConstants.ACCOUNT_DISTRIBUTION_NEW_SRC_LINE)) {
067 lineNumber = "-2";
068 }
069 else {
070 lineNumber = StringUtils.substringBetween(accountingLineProperty, "[", "]");
071 }
072 return "insert"+infix + "Line.line" + lineNumber + "." + "anchoraccounting"+infix+"Anchor";
073 }
074
075 /**
076 * Overrides the method in AccountingLineAuthorizerBase so that the delete button would have both
077 * the line item number and the accounting line number for methodToCall when the user clicks on
078 * the delete button.
079 *
080 * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase#getDeleteLineMethod(org.kuali.kfs.sys.businessobject.AccountingLine, java.lang.String, java.lang.Integer)
081 */
082 @Override
083 protected String getDeleteLineMethod(AccountingLine accountingLine, String accountingLineProperty, Integer accountingLineIndex) {
084 final String infix = getActionInfixForExtantAccountingLine(accountingLine, accountingLineProperty);
085 String lineNumber = StringUtils.substringBetween(accountingLineProperty, "item[", "].sourceAccountingLine");
086 if (lineNumber == null) {
087 lineNumber = "-2";
088 }
089 String accountingLineNumber = StringUtils.substringBetween(accountingLineProperty, "sourceAccountingLine[", "]");
090 return "delete"+infix+"Line.line"+ lineNumber + ":" + accountingLineNumber + ".anchoraccounting"+infix+"Anchor";
091 }
092
093 /**
094 * Overrides the method in AccountingLineAuthorizerBase so that the balance inquiry button would
095 * have both the line item number and the accounting line number for methodToCall when the user
096 * clicks on the balance inquiry button.
097 * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase#getBalanceInquiryMethod(org.kuali.kfs.sys.businessobject.AccountingLine, java.lang.String, java.lang.Integer)
098 */
099 @Override
100 protected String getBalanceInquiryMethod(AccountingLine accountingLine, String accountingLineProperty, Integer accountingLineIndex) {
101 final String infix = getActionInfixForNewAccountingLine(accountingLine, accountingLineProperty);
102 String lineNumber = StringUtils.substringBetween(accountingLineProperty, "item[", "].sourceAccountingLine");
103 if (lineNumber == null) {
104 lineNumber = "-2";
105 }
106 String accountingLineNumber = StringUtils.substringBetween(accountingLineProperty, "sourceAccountingLine[", "]");
107 return "performBalanceInquiryFor"+infix+"Line.line"+ ":" + lineNumber + ":" + accountingLineNumber + ".anchoraccounting"+infix+ "existingLineLineAnchor"+accountingLineNumber;
108 }
109
110 /**
111 * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase#getUnviewableBlocks(org.kuali.kfs.sys.document.AccountingDocument, org.kuali.kfs.sys.businessobject.AccountingLine, boolean, org.kuali.rice.kim.bo.Person)
112 */
113 @Override
114 public Set<String> getUnviewableBlocks(AccountingDocument accountingDocument, AccountingLine accountingLine, boolean newLine, Person currentUser) {
115 Set<String> unviewableBlocks = super.getUnviewableBlocks(accountingDocument, accountingLine, newLine, currentUser);
116 if (showAmountOnly(accountingDocument)) {
117 unviewableBlocks.add(KFSPropertyConstants.PERCENT);
118 }
119 else {
120 unviewableBlocks.add(KFSPropertyConstants.AMOUNT);
121 }
122 return unviewableBlocks;
123 }
124
125 private boolean showAmountOnly(AccountingDocument accountingDocument) {
126 final FinancialSystemTransactionalDocumentPresentationController presentationController = getPresentationController(accountingDocument);
127 final FinancialSystemTransactionalDocumentAuthorizerBase authorizer = getDocumentAuthorizer(accountingDocument);
128 if (presentationController == null || authorizer == null) {
129 throw new RuntimeException("Null presentation controller or document authorizer for document "+accountingDocument.getClass().getName());
130 }
131 Set<String> editModes = presentationController.getEditModes(accountingDocument);
132 editModes = authorizer.getEditModes(accountingDocument, GlobalVariables.getUserSession().getPerson(), editModes);
133 return editModes.contains(PurapAuthorizationConstants.PaymentRequestEditMode.FULL_DOCUMENT_ENTRY_COMPLETED);
134 }
135
136 /**
137 *
138 * @param accountingDocument
139 * @return
140 */
141 private FinancialSystemTransactionalDocumentPresentationController getPresentationController(AccountingDocument accountingDocument) {
142 final Class<? extends DocumentPresentationController> presentationControllerClass = ((TransactionalDocumentEntry)SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getDictionaryObjectEntry(accountingDocument.getClass().getName())).getDocumentPresentationControllerClass();
143 FinancialSystemTransactionalDocumentPresentationController presentationController = null;
144 try {
145 presentationController = (FinancialSystemTransactionalDocumentPresentationController)presentationControllerClass.newInstance();
146 }
147 catch (InstantiationException ie) {
148 throw new RuntimeException("Cannot instantiate instance of presentation controller for "+accountingDocument.getClass().getName(), ie);
149 }
150 catch (IllegalAccessException iae) {
151 throw new RuntimeException("Cannot instantiate instance of presentation controller for "+accountingDocument.getClass().getName(), iae);
152 }
153 return presentationController;
154 }
155
156 /**
157 *
158 * @param accountingDocument
159 * @return
160 */
161 private FinancialSystemTransactionalDocumentAuthorizerBase getDocumentAuthorizer(AccountingDocument accountingDocument) {
162 final Class<? extends DocumentAuthorizer> documentAuthorizerClass = ((TransactionalDocumentEntry)SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getDictionaryObjectEntry(accountingDocument.getClass().getName())).getDocumentAuthorizerClass();
163 FinancialSystemTransactionalDocumentAuthorizerBase documentAuthorizer = null;
164 try {
165 documentAuthorizer = (FinancialSystemTransactionalDocumentAuthorizerBase)documentAuthorizerClass.newInstance();
166 }
167 catch (InstantiationException ie) {
168 throw new RuntimeException("Cannot instantiate instance of document authorizer for "+accountingDocument.getClass().getName(), ie);
169 }
170 catch (IllegalAccessException iae) {
171 throw new RuntimeException("Cannot instantiate instance of document authorizer for "+accountingDocument.getClass().getName(), iae);
172 }
173 return documentAuthorizer;
174 }
175
176 @Override
177 public boolean isGroupEditable(AccountingDocument accountingDocument,
178 List<? extends AccountingLineRenderingContext> accountingLineRenderingContexts,
179 Person currentUser) {
180
181 boolean isEditable = super.isGroupEditable(accountingDocument, accountingLineRenderingContexts, currentUser);
182
183 if (isEditable){
184 if (accountingLineRenderingContexts.size() == 0) {
185 return false;
186 }
187 isEditable = allowAccountingLinesAreEditable(accountingDocument,accountingLineRenderingContexts.get(0).getAccountingLine());
188 }
189
190 return isEditable;
191 }
192
193 @Override
194 public boolean determineEditPermissionOnField(AccountingDocument accountingDocument,
195 AccountingLine accountingLine,
196 String accountingLineCollectionProperty,
197 String fieldName,
198 boolean editablePage) {
199
200 boolean isEditable = super.determineEditPermissionOnField(accountingDocument, accountingLine, accountingLineCollectionProperty,fieldName,editablePage);
201
202 if (isEditable){
203 isEditable = allowAccountingLinesAreEditable(accountingDocument,accountingLine);
204 }
205
206 return isEditable;
207 }
208
209 @Override
210 public boolean determineEditPermissionOnLine(AccountingDocument accountingDocument,
211 AccountingLine accountingLine,
212 String accountingLineCollectionProperty,
213 boolean currentUserIsDocumentInitiator,
214 boolean pageIsEditable) {
215
216 boolean isEditable = super.determineEditPermissionOnLine(accountingDocument, accountingLine, accountingLineCollectionProperty, currentUserIsDocumentInitiator, pageIsEditable);
217
218 if (isEditable){
219 isEditable = allowAccountingLinesAreEditable(accountingDocument,accountingLine);
220 }
221
222 return (isEditable && pageIsEditable);
223 }
224
225 /**
226 * This method checks whether the accounting lines are editable for a specific item type.
227 *
228 */
229 protected boolean allowAccountingLinesAreEditable(AccountingDocument accountingDocument,
230 AccountingLine accountingLine){
231
232 PurApAccountingLine purapAccount = (PurApAccountingLine)accountingLine;
233 @SuppressWarnings("rawtypes")
234 Class clazz = getPurapDocumentClass(accountingDocument);
235 if (clazz == null){
236 return true;
237 }
238
239 List<String> restrictedItemTypesList = SpringContext.getBean(ParameterService.class).getParameterValues(clazz, PurapParameterConstants.PURAP_ITEM_TYPES_RESTRICTING_ACCOUNT_EDIT);
240
241 // This call determines a new special case in which an item marked for trade-in cannot have editable accounting lines
242 // once the calculate button image is clicked, even if the accounting line has not been saved yet.
243 boolean retval = true;
244 retval = isEditableBasedOnTradeInRestriction(accountingDocument, accountingLine);
245
246 if (restrictedItemTypesList != null && purapAccount.getPurapItem() != null){
247 return (!restrictedItemTypesList.contains(((PurApItem) purapAccount.getPurapItem()).getItemTypeCode()) && retval);
248 } else if (restrictedItemTypesList != null && purapAccount.getPurapItem() == null) {
249 return retval;
250 } else {
251 return true;
252 }
253 }
254
255 /**
256 * Find the item to which an accounting line belongs. Convenience/Utility method.
257 *
258 * Some methods that require an accounting line with a purApItem attached were getting accounting lines
259 * passed in that did not yet have a purApItem. I needed a way to match the accounting line to the
260 * proper item.
261 *
262 * @param accountingDocument the document holding both the accounting line and the item to which the
263 * accounting line is attached
264 * @param accountingLine the accounting line of interest, for which a containing item should be found
265 * @return the item to which the incoming accounting line is attached
266 */
267 protected PurApItem findTheItemForAccountingLine(AccountingDocument accountingDocument, AccountingLine accountingLine) {
268 PurApItem retval = null;
269 List<PurApItem> listItems = null;
270
271 scan: {
272 if (accountingDocument instanceof PurchasingAccountsPayableDocumentBase) {
273 listItems = ((PurchasingAccountsPayableDocumentBase) accountingDocument).getItems();
274
275 // loop through all items in the document to see if the item has an accounting line that
276 // matches the one passed in
277 for (PurApItem oneItem : listItems) {
278 List<PurApAccountingLine> acctLines = oneItem.getSourceAccountingLines();
279 for (PurApAccountingLine oneAcctLine : acctLines) {
280 // we want to compare the exact same memory location, not the contents
281 if (oneAcctLine == accountingLine) {
282 retval = oneItem;
283
284 // we found it, so I can stop altogether.
285 break scan;
286 }
287 }
288 }
289 }
290 }
291
292 return retval;
293 }
294
295 /**
296 * Handles a restriction on accounting lines assigned to trade-in items.
297 * If the accounting Line is for a trade-in item type, and the accounting line has contents,
298 * the user is not allowed to change the contents of the calculated values.
299 *
300 * The trade-in may not yet have a sequence number, so the old way of relying solely on sequence
301 * number (in method super.approvedForUnqualifiedEditing() is incomplete, and needs this extra check
302 * for trade-ins.
303 *
304 * @param accountingLine the accounting line being examined
305 * @return whether the accounting line is editable according to the trade-in/non-empty restriction
306 */
307 private boolean isEditableBasedOnTradeInRestriction(AccountingDocument accountingDocument, AccountingLine accountingLine) {
308 boolean retval = true;
309 // if the accounting Line is for a trade-In, and the line has contents, the user is not allowed to
310 // change the contents of the calculated values
311 if ((accountingLine != null) && (accountingLine instanceof PurApAccountingLine)) {
312 PurApItem purApItem = (((PurApAccountingLine) accountingLine)).getPurapItem();
313
314 // this small block is to allow for another way to get to the purApItem if the
315 // incoming accounting line does not yet have a purApItem attached. Calling it is not
316 // completely necessary any more, unless/until the functional team members decide to
317 // add more item types to the read-only accounting lines list.
318 if (purApItem == null) {
319 purApItem = findTheItemForAccountingLine(accountingDocument, accountingLine);
320 }
321
322 if (purApItem != null) {
323 String itemTypeCode = purApItem.getItemTypeCode();
324 if (itemTypeCode.toUpperCase().equalsIgnoreCase(PurapParameterConstants.PURAP_ITEM_TYPE_TRDI)) {
325 // does the line have any contents? if so, then the user cannot edit them
326 if ((accountingLine.getChartOfAccountsCode() != null) || (accountingLine.getAccountNumber() != null) || (accountingLine.getFinancialObjectCode() != null)) {
327 retval = false;
328 }
329 }
330 // there has been a call to "if (purApItem.getItemAssignedToTradeInIndicator()) {" here
331 // that required the earlier use of findTheItemForAccountingLine()
332 }
333 }
334 return retval;
335 }
336
337 @SuppressWarnings("rawtypes")
338 private Class getPurapDocumentClass(AccountingDocument accountingDocument){
339 if (accountingDocument instanceof RequisitionDocument){
340 return RequisitionDocument.class;
341 }else if (accountingDocument instanceof PurchaseOrderDocument){
342 return PurchaseOrderDocument.class;
343 }else if (accountingDocument instanceof PaymentRequestDocument){
344 return PaymentRequestDocument.class;
345 }else{
346 return null;
347 }
348 }
349
350 /**
351 * Determines if the given line is editable, no matter what a KIM check would say about line editability. In the default case,
352 * any accounting line is editable - minus KIM check - when the document is PreRoute, or if the line is a new line
353 *
354 * This overriding implementation is required because the Purap module has a new restriction that requires
355 * that an accounting line for a Trade-In item cannot be manually editable, even if not yet saved ("not yet saved" means it has
356 * no sequence number). Therefore, the base implementation that determines editability on the sequence number alone has to be
357 * preceded by a check that declares ineligible for editing if it is a trade-in.
358 *
359 * @see org.kuali.kfs.module.purap.document.authorization.PurapAccountingLineAuthorizer#allowAccountingLinesAreEditable(AccountingDocument, AccountingLine)
360 *
361 * @param accountingDocument the accounting document the line is or wants to be associated with
362 * @param accountingLine the accounting line itself
363 * @param accountingLineCollectionProperty the collection the accounting line is or would be part of
364 * @param currentUserIsDocumentInitiator is the current user the initiator of the document?
365 * @return true if the line as a whole can be edited without the KIM check, false otherwise
366 */
367 @Override
368 protected boolean approvedForUnqualifiedEditing(AccountingDocument accountingDocument, AccountingLine accountingLine, String accountingLineCollectionProperty, boolean currentUserIsDocumentInitiator) {
369 boolean retval = true;
370
371 retval = isEditableBasedOnTradeInRestriction(accountingDocument, accountingLine);
372
373 if (retval) {
374 retval = super.approvedForUnqualifiedEditing(accountingDocument, accountingLine, accountingLineCollectionProperty, currentUserIsDocumentInitiator);
375 }
376 return retval;
377 }
378 }
379