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