View Javadoc
1   /*
2    * The Kuali Financial System, a comprehensive financial management system for higher education.
3    * 
4    * Copyright 2005-2014 The Kuali Foundation
5    * 
6    * This program is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Affero General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   * 
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Affero General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Affero General Public License
17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package org.kuali.kfs.module.purap.document.authorization;
20  
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.List;
24  import java.util.Set;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.kuali.kfs.module.purap.PurapParameterConstants;
28  import org.kuali.kfs.module.purap.PurapPropertyConstants;
29  import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
30  import org.kuali.kfs.module.purap.businessobject.PurApItem;
31  import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
32  import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
33  import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
34  import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase;
35  import org.kuali.kfs.module.purap.document.RequisitionDocument;
36  import org.kuali.kfs.module.purap.document.service.PurapService;
37  import org.kuali.kfs.sys.KFSPropertyConstants;
38  import org.kuali.kfs.sys.businessobject.AccountingLine;
39  import org.kuali.kfs.sys.context.SpringContext;
40  import org.kuali.kfs.sys.document.AccountingDocument;
41  import org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase;
42  import org.kuali.kfs.sys.document.authorization.FinancialSystemTransactionalDocumentAuthorizerBase;
43  import org.kuali.kfs.sys.document.authorization.FinancialSystemTransactionalDocumentPresentationController;
44  import org.kuali.kfs.sys.document.web.AccountingLineRenderingContext;
45  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
46  import org.kuali.rice.kim.api.identity.Person;
47  import org.kuali.rice.kns.datadictionary.TransactionalDocumentEntry;
48  import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
49  import org.kuali.rice.kns.service.DataDictionaryService;
50  import org.kuali.rice.krad.document.DocumentPresentationController;
51  
52  /**
53   * Authorizer which deals with financial processing document issues, specifically sales tax lines on documents
54   * This class utilizes the new accountingLine model.
55   */
56  public class PurapAccountingLineAuthorizer extends AccountingLineAuthorizerBase {
57  
58      /**
59       * Overrides the method in AccountingLineAuthorizerBase so that the add button would
60       * have the line item number in addition to the rest of the insertxxxx String for
61       * methodToCall when the user clicks on the add button.
62       *
63       * @param accountingLine
64       * @param accountingLineProperty
65       * @return
66       */
67      @Override
68      protected String getAddMethod(AccountingLine accountingLine, String accountingLineProperty) {
69          final String infix = getActionInfixForNewAccountingLine(accountingLine, accountingLineProperty);
70          String lineNumber = null;
71          if (accountingLineProperty.equals(PurapPropertyConstants.ACCOUNT_DISTRIBUTION_NEW_SRC_LINE)) {
72              lineNumber = "-2";
73          }
74          else {
75          lineNumber = StringUtils.substringBetween(accountingLineProperty, "[", "]");
76          }
77          return "insert"+infix + "Line.line" + lineNumber + "." + "anchoraccounting"+infix+"Anchor";
78      }
79  
80      /**
81       * Overrides the method in AccountingLineAuthorizerBase so that the delete button would have both
82       * the line item number and the accounting line number for methodToCall when the user clicks on
83       * the delete button.
84       *
85       * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase#getDeleteLineMethod(org.kuali.kfs.sys.businessobject.AccountingLine, java.lang.String, java.lang.Integer)
86       */
87      @Override
88      protected String getDeleteLineMethod(AccountingLine accountingLine, String accountingLineProperty, Integer accountingLineIndex) {
89          final String infix = getActionInfixForExtantAccountingLine(accountingLine, accountingLineProperty);
90          String lineNumber = StringUtils.substringBetween(accountingLineProperty, "item[", "].sourceAccountingLine");
91          if (lineNumber == null) {
92              lineNumber = "-2";
93          }
94          String accountingLineNumber = StringUtils.substringBetween(accountingLineProperty, "sourceAccountingLine[", "]");
95          return "delete"+infix+"Line.line"+ lineNumber + ":" + accountingLineNumber + ".anchoraccounting"+infix+"Anchor";
96      }
97  
98      /**
99       * Overrides the method in AccountingLineAuthorizerBase so that the balance inquiry button would
100      * have both the line item number and the accounting line number for methodToCall when the user
101      * clicks on the balance inquiry button.
102      * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase#getBalanceInquiryMethod(org.kuali.kfs.sys.businessobject.AccountingLine, java.lang.String, java.lang.Integer)
103      */
104     @Override
105     protected String getBalanceInquiryMethod(AccountingLine accountingLine, String accountingLineProperty, Integer accountingLineIndex) {
106         final String infix = getActionInfixForNewAccountingLine(accountingLine, accountingLineProperty);
107         String lineNumber = StringUtils.substringBetween(accountingLineProperty, "item[", "].sourceAccountingLine");
108         if (lineNumber == null) {
109             lineNumber = "-2";
110         }
111         String accountingLineNumber = StringUtils.substringBetween(accountingLineProperty, "sourceAccountingLine[", "]");
112         return "performBalanceInquiryFor"+infix+"Line.line"+ ":" + lineNumber + ":" + accountingLineNumber + ".anchoraccounting"+infix+ "existingLineLineAnchor"+accountingLineNumber;
113     }
114 
115     /**
116      * @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.api.identity.Person)
117      */
118     @Override
119     public Set<String> getUnviewableBlocks(AccountingDocument accountingDocument, AccountingLine accountingLine, boolean newLine, Person currentUser) {
120         Set<String> unviewableBlocks = super.getUnviewableBlocks(accountingDocument, accountingLine, newLine, currentUser);
121         if (showAmountOnly(accountingDocument)) {
122             unviewableBlocks.add(KFSPropertyConstants.PERCENT);
123         }
124         else {
125             unviewableBlocks.add(KFSPropertyConstants.AMOUNT);
126         }
127         return unviewableBlocks;
128     }
129 
130     private boolean showAmountOnly(AccountingDocument accountingDocument) {
131         PurapService purapService = SpringContext.getBean(PurapService.class);
132         if (accountingDocument instanceof PurchasingAccountsPayableDocument) {
133             if (purapService.isFullDocumentEntryCompleted((PurchasingAccountsPayableDocument)accountingDocument)) {
134                 return true;
135             }
136         }
137         return false;
138     }
139 
140     /**
141      *
142      * @param accountingDocument
143      * @return
144      */
145     private FinancialSystemTransactionalDocumentPresentationController getPresentationController(AccountingDocument accountingDocument) {
146         final Class<? extends DocumentPresentationController> presentationControllerClass = ((TransactionalDocumentEntry)SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getDictionaryObjectEntry(accountingDocument.getClass().getName())).getDocumentPresentationControllerClass();
147         FinancialSystemTransactionalDocumentPresentationController presentationController = null;
148         try {
149             presentationController = (FinancialSystemTransactionalDocumentPresentationController)presentationControllerClass.newInstance();
150         }
151         catch (InstantiationException ie) {
152             throw new RuntimeException("Cannot instantiate instance of presentation controller for "+accountingDocument.getClass().getName(), ie);
153         }
154         catch (IllegalAccessException iae) {
155             throw new RuntimeException("Cannot instantiate instance of presentation controller for "+accountingDocument.getClass().getName(), iae);
156         }
157         return presentationController;
158     }
159 
160     /**
161      *
162      * @param accountingDocument
163      * @return
164      */
165     @Override
166     protected FinancialSystemTransactionalDocumentAuthorizerBase getDocumentAuthorizer(AccountingDocument accountingDocument) {
167         final Class<? extends DocumentAuthorizer> documentAuthorizerClass = ((TransactionalDocumentEntry)SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getDictionaryObjectEntry(accountingDocument.getClass().getName())).getDocumentAuthorizerClass();
168         FinancialSystemTransactionalDocumentAuthorizerBase documentAuthorizer = null;
169         try {
170             documentAuthorizer = (FinancialSystemTransactionalDocumentAuthorizerBase)documentAuthorizerClass.newInstance();
171         }
172         catch (InstantiationException ie) {
173             throw new RuntimeException("Cannot instantiate instance of document authorizer for "+accountingDocument.getClass().getName(), ie);
174         }
175         catch (IllegalAccessException iae) {
176             throw new RuntimeException("Cannot instantiate instance of document authorizer for "+accountingDocument.getClass().getName(), iae);
177         }
178         return documentAuthorizer;
179     }
180 
181     @Override
182     public boolean isGroupEditable(AccountingDocument accountingDocument,
183                                    List<? extends AccountingLineRenderingContext> accountingLineRenderingContexts,
184                                    Person currentUser) {
185 
186         boolean isEditable = super.isGroupEditable(accountingDocument, accountingLineRenderingContexts, currentUser);
187 
188         if (isEditable){
189             if (accountingLineRenderingContexts.size() == 0) {
190                 return false;
191             }
192             isEditable = allowAccountingLinesAreEditable(accountingDocument,accountingLineRenderingContexts.get(0).getAccountingLine());
193         }
194 
195         return isEditable;
196     }
197 
198     @Override
199     public boolean determineEditPermissionOnField(AccountingDocument accountingDocument,
200                                                   AccountingLine accountingLine,
201                                                   String accountingLineCollectionProperty,
202                                                   String fieldName,
203                                                   boolean editablePage) {
204 
205         // the fields in a new line should be always editable
206         if (accountingLine.getSequenceNumber() == null) {
207             return true;
208         }
209 
210         boolean isEditable = super.determineEditPermissionOnField(accountingDocument, accountingLine, accountingLineCollectionProperty,fieldName,editablePage);
211 
212         if (isEditable){
213             isEditable = allowAccountingLinesAreEditable(accountingDocument,accountingLine);
214         }
215 
216         return isEditable;
217     }
218 
219     @Override
220     public boolean determineEditPermissionOnLine(AccountingDocument accountingDocument,
221                                                  AccountingLine accountingLine,
222                                                  String accountingLineCollectionProperty,
223                                                  boolean currentUserIsDocumentInitiator,
224                                                  boolean pageIsEditable) {
225 
226         boolean isEditable = super.determineEditPermissionOnLine(accountingDocument, accountingLine, accountingLineCollectionProperty, currentUserIsDocumentInitiator, pageIsEditable);
227 
228         if (isEditable){
229             isEditable = allowAccountingLinesAreEditable(accountingDocument,accountingLine);
230         }
231 
232         return (isEditable && pageIsEditable);
233     }
234 
235     /**
236      * This method checks whether the accounting lines are editable for a specific item type.
237      *
238      */
239     protected boolean allowAccountingLinesAreEditable(AccountingDocument accountingDocument,
240                                                             AccountingLine accountingLine){
241 
242         PurApAccountingLine purapAccount = (PurApAccountingLine)accountingLine;
243         @SuppressWarnings("rawtypes")
244         Class clazz = getPurapDocumentClass(accountingDocument);
245         if (clazz == null){
246             return true;
247         }
248 
249         //if not calculated yet then the line is editable
250         PurchasingAccountsPayableDocumentBase purapDoc = (PurchasingAccountsPayableDocumentBase) accountingDocument;
251         if (!purapDoc.isCalculated()) {
252             return true;
253         }
254 
255         Collection<String> restrictedItemTypesList = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString(clazz, PurapParameterConstants.PURAP_ITEM_TYPES_RESTRICTING_ACCOUNT_EDIT) );
256 
257         // This call determines a new special case in which an item marked for trade-in cannot have editable accounting lines
258         // once the calculate button image is clicked, even if the accounting line has not been saved yet.
259         boolean retval = true;
260         retval = isEditableBasedOnTradeInRestriction(accountingDocument, accountingLine);
261 
262         if (restrictedItemTypesList != null && purapAccount.getPurapItem() != null){
263             return (!restrictedItemTypesList.contains(purapAccount.getPurapItem().getItemTypeCode()) && retval);
264         } else if (restrictedItemTypesList != null && purapAccount.getPurapItem() == null) {
265             return retval;
266         } else {
267             return true;
268         }
269     }
270 
271     /**
272      * Find the item to which an accounting line belongs. Convenience/Utility method.
273      *
274      * Some methods that require an accounting line with a purApItem attached were getting accounting lines
275      * passed in that did not yet have a purApItem. I needed a way to match the accounting line to the
276      * proper item.
277      *
278      * @param accountingDocument the document holding both the accounting line and the item to which the
279      * accounting line is attached
280      * @param accountingLine the accounting line of interest, for which a containing item should be found
281      * @return the item to which the incoming accounting line is attached
282      */
283     protected PurApItem findTheItemForAccountingLine(AccountingDocument accountingDocument, AccountingLine accountingLine) {
284         PurApItem retval = null;
285         List<PurApItem> listItems = null;
286 
287         scan: {
288             if (accountingDocument instanceof PurchasingAccountsPayableDocumentBase) {
289                 listItems = ((PurchasingAccountsPayableDocumentBase) accountingDocument).getItems();
290 
291                 // loop through all items in the document to see if the item has an accounting line that
292                 // matches the one passed in
293                 for (PurApItem oneItem : listItems) {
294                     List<PurApAccountingLine> acctLines = oneItem.getSourceAccountingLines();
295                     for (PurApAccountingLine oneAcctLine : acctLines) {
296                         // we want to compare the exact same memory location, not the contents
297                         if (oneAcctLine == accountingLine) {
298                             retval = oneItem;
299 
300                             // we found it, so I can stop altogether.
301                             break scan;
302                         }
303                     }
304                 }
305             }
306         }
307 
308         return retval;
309     }
310 
311     /**
312      * Handles a restriction on accounting lines assigned to trade-in items.
313      * If the accounting Line is for a trade-in item type, and the accounting line has contents,
314      * the user is not allowed to change the contents of the calculated values.
315      *
316      * The trade-in may not yet have a sequence number, so the old way of relying solely on sequence
317      * number (in method super.approvedForUnqualifiedEditing() is incomplete, and needs this extra check
318      * for trade-ins.
319      *
320      * @param accountingLine the accounting line being examined
321      * @return whether the accounting line is editable according to the trade-in/non-empty restriction
322      */
323     private boolean isEditableBasedOnTradeInRestriction(AccountingDocument accountingDocument, AccountingLine accountingLine) {
324         boolean retval = true;
325         // if the accounting Line is for a trade-In, and the line has contents, the user is not allowed to
326         // change the contents of the calculated values
327         if ((accountingLine != null) && (accountingLine instanceof PurApAccountingLine)) {
328             PurApItem purApItem = (((PurApAccountingLine) accountingLine)).getPurapItem();
329 
330             // this small block is to allow for another way to get to the purApItem if the
331             // incoming accounting line does not yet have a purApItem attached. Calling it is not
332             // completely necessary any more, unless/until the functional team members decide to
333             // add more item types to the read-only accounting lines list.
334             if (purApItem == null) {
335                 purApItem = findTheItemForAccountingLine(accountingDocument, accountingLine);
336             }
337 
338             if (purApItem != null) {
339                 String itemTypeCode = purApItem.getItemTypeCode();
340                 if (itemTypeCode.toUpperCase().equalsIgnoreCase(PurapParameterConstants.PURAP_ITEM_TYPE_TRDI)) {
341                     // does the line have any contents? if so, then the user cannot edit them
342                     if ((accountingLine.getChartOfAccountsCode() != null) || (accountingLine.getAccountNumber() != null) || (accountingLine.getFinancialObjectCode() != null)) {
343                         retval = false;
344                     }
345                 }
346                 // there has been a call to "if (purApItem.getItemAssignedToTradeInIndicator()) {" here
347                 // that required the earlier use of findTheItemForAccountingLine()
348             }
349         }
350         return retval;
351     }
352 
353     @SuppressWarnings("rawtypes")
354     private Class getPurapDocumentClass(AccountingDocument accountingDocument){
355         if (accountingDocument instanceof RequisitionDocument){
356             return RequisitionDocument.class;
357         }else if (accountingDocument instanceof PurchaseOrderDocument){
358             return PurchaseOrderDocument.class;
359         }else if (accountingDocument instanceof PaymentRequestDocument){
360             return PaymentRequestDocument.class;
361         }else{
362             return null;
363         }
364     }
365 
366     /**
367      * Determines if the given line is editable, no matter what a KIM check would say about line editability.  In the default case,
368      * any accounting line is editable - minus KIM check - when the document is PreRoute, or if the line is a new line
369      *
370      * This overriding implementation is required because the Purap module has a new restriction that requires
371      * that an accounting line for a Trade-In item cannot be manually editable, even if not yet saved ("not yet saved" means it has
372      * no sequence number). Therefore, the base implementation that determines editability on the sequence number alone has to be
373      * preceded by a check that declares ineligible for editing if it is a trade-in.
374      *
375      * @see org.kuali.kfs.module.purap.document.authorization.PurapAccountingLineAuthorizer#allowAccountingLinesAreEditable(AccountingDocument, AccountingLine)
376      *
377      * @param accountingDocument the accounting document the line is or wants to be associated with
378      * @param accountingLine the accounting line itself
379      * @param accountingLineCollectionProperty the collection the accounting line is or would be part of
380      * @param currentUserIsDocumentInitiator is the current user the initiator of the document?
381      * @return true if the line as a whole can be edited without the KIM check, false otherwise
382      */
383     @Override
384     protected boolean approvedForUnqualifiedEditing(AccountingDocument accountingDocument, AccountingLine accountingLine, String accountingLineCollectionProperty, boolean currentUserIsDocumentInitiator) {
385         boolean retval = true;
386 
387         retval = isEditableBasedOnTradeInRestriction(accountingDocument, accountingLine);
388 
389         if (retval) {
390             retval = super.approvedForUnqualifiedEditing(accountingDocument, accountingLine, accountingLineCollectionProperty, currentUserIsDocumentInitiator);
391         }
392         return retval;
393     }
394 }
395