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