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.validation.impl;
017    
018    import java.lang.reflect.InvocationTargetException;
019    import java.util.LinkedList;
020    import java.util.Queue;
021    
022    import org.apache.commons.beanutils.PropertyUtils;
023    import org.apache.commons.lang.StringUtils;
024    import org.kuali.kfs.module.purap.PurapConstants.PaymentRequestStatuses;
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.sys.businessobject.AccountingLine;
029    import org.kuali.kfs.sys.document.AccountingDocument;
030    import org.kuali.kfs.sys.document.validation.BranchingValidation;
031    import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
032    import org.kuali.rice.kns.bo.PersistableBusinessObject;
033    import org.kuali.rice.kns.service.ParameterService;
034    import org.kuali.rice.kns.util.ObjectUtils;
035    
036    /**
037     * A validation which uses parameters to determine if a value on an accounting line is valid.
038     */
039    public class PurchasingAccountsPayableObjectCodeOverrideBranchingValidation extends BranchingValidation {
040        protected String propertyPath;
041        protected String parameterToCheckAgainst;
042        protected ParameterService parameterService;
043        protected String responsibleProperty;
044        protected AccountingDocument accountingDocumentForValidation;
045        protected AccountingLine accountingLineForValidation;
046    
047        protected final static String OBJECT_CODE_OVERRIDEN = "ObjectCodeOverriden"; 
048        protected final static String OBJECT_CODE_NOT_OVERRIDEN = "ObjectCodeNotOverriden";
049        
050        @Override
051        protected String determineBranch(AttributedDocumentEvent event) {
052            if (!StringUtils.isBlank(propertyPath)) {
053                refreshByPath(accountingLineForValidation);
054            }
055            
056            boolean isTaxApproval = false;
057            //if payment request, skip object code check when this is a tax approval, 
058            // or if this accounting line is from a Tax Charge line.
059            if (accountingDocumentForValidation instanceof PaymentRequestDocument) {
060                PaymentRequestDocument preq = (PaymentRequestDocument)accountingDocumentForValidation;
061                PurApAccountingLine purapAccountingLine = (PurApAccountingLine)accountingLineForValidation;
062                PurApItem item = purapAccountingLine.getPurapItem();
063                
064                if (StringUtils.equals(PaymentRequestStatuses.AWAITING_TAX_REVIEW, preq.getStatusCode())){                
065                    isTaxApproval = true;
066                }else if(StringUtils.equals(PaymentRequestStatuses.DEPARTMENT_APPROVED, preq.getStatusCode()) &&
067                         (ObjectUtils.isNotNull(item) && item.getItemType().getIsTaxCharge()) ){
068                    isTaxApproval = true;
069                }
070            }
071            
072            if (isTaxApproval) {
073                return null;
074            } else if (isAccountingLineValueAllowed(accountingDocumentForValidation.getClass(), accountingLineForValidation, parameterToCheckAgainst, propertyPath, (responsibleProperty != null ? responsibleProperty : propertyPath))){
075                return OBJECT_CODE_OVERRIDEN;
076            } else {
077                return OBJECT_CODE_NOT_OVERRIDEN;
078            }                
079        }
080        
081        /**
082         * Checks that a value on an accounting line is valid, based on parameters, for a document of the given class
083         * @param documentClass the class of the document to check
084         * @param accountingLine the accounting line to check
085         * @param parameterName the name of the parameter to check
086         * @param propertyName the name of the property to check
087         * @param userEnteredPropertyName the value the user entered on the line
088         * @return true if this passes validation, false otherwise
089         */
090        protected boolean isAccountingLineValueAllowed(Class documentClass, AccountingLine accountingLine, String parameterName, String propertyName, String userEnteredPropertyName) {
091            boolean isAllowed = false;
092            String exceptionMessage = "Invalid property name provided to PurchasingAccountsPayableObjectCodeOverrideBranchingValidation isAccountingLineValueAllowed method: " + propertyName;
093            try {
094                String propertyValue = (String) PropertyUtils.getProperty(accountingLine, propertyName);
095                if (getParameterService().parameterExists(documentClass, parameterName)) {
096                    isAllowed = getParameterService().getParameterEvaluator(documentClass, parameterName, propertyValue).evaluationSucceeds();
097                }
098            }
099            catch (IllegalAccessException e) {
100                throw new RuntimeException(exceptionMessage, e);
101            }
102            catch (InvocationTargetException e) {
103                throw new RuntimeException(exceptionMessage, e);
104            }
105            catch (NoSuchMethodException e) {
106                throw new RuntimeException(exceptionMessage, e);
107            }
108            return isAllowed;
109        }
110        
111        /**
112         * Refreshes a value on the accounting line, using the propertyPath to decided what to refresh
113         * @param line the accounting line to refresh a property on
114         */
115        public void refreshByPath(AccountingLine line) {
116            refreshByQueue(line, convertPathToQueue(propertyPath));
117        }
118        
119        /**
120         * Creates a Queue which represents a FIFO path of what properties to visit, based on the given property path
121         * @param path the path to convert to a Queue
122         * @return a Queue representing the path
123         */
124        protected Queue<String> convertPathToQueue(String path) {
125            Queue<String> pathQueue = new LinkedList<String>();
126            for (String property: path.split("\\.")) {
127                pathQueue.add(property);
128            }
129            return pathQueue;
130        }
131        
132        /**
133         * Recursively refreshes a property given by the queue path
134         * @param bo the business object to refresh
135         * @param path the path, in Queue form, of properties to refresh
136         */
137        protected void refreshByQueue(PersistableBusinessObject bo, Queue<String> path) {
138            if (path.size() > 1) { // we know that the last thing on our list is a code. why refresh that?
139                String currentProperty = path.remove();
140                bo.refreshReferenceObject(currentProperty);
141                PersistableBusinessObject childBO = (PersistableBusinessObject)ObjectUtils.getPropertyValue(bo, currentProperty);
142                if (!ObjectUtils.isNull(childBO)) {       
143                    refreshByQueue(childBO, path);
144                }
145            }
146        }
147    
148        /**
149         * Gets the propertyPath attribute. This is the path to the value to check, e. g. "accountNumber.subFundGroup.fundGroupCode"
150         * @return Returns the propertyPath.
151         */
152        public String getPropertyPath() {
153            return propertyPath;
154        }
155    
156        /**
157         * Sets the propertyPath attribute value. This is the path to the value to check, e. g. "accountNumber.subFundGroup.fundGroupCode"
158         * @param propertyPath The propertyPath to set.
159         */
160        public void setPropertyPath(String refreshPath) {
161            this.propertyPath = refreshPath;
162        }
163    
164        /**
165         * Gets the parameterService attribute. 
166         * @return Returns the parameterService.
167         */
168        public ParameterService getParameterService() {
169            return parameterService;
170        }
171    
172        /**
173         * Sets the parameterService attribute value.
174         * @param parameterService The parameterService to set.
175         */
176        public void setParameterService(ParameterService parameterService) {
177            this.parameterService = parameterService;
178        }
179    
180        /**
181         * Gets the parameterToCheckAgainst attribute. This is the name of the parameter which has the values to validate against.
182         * @return Returns the parameterToCheckAgainst.
183         */
184        public String getParameterToCheckAgainst() {
185            return parameterToCheckAgainst;
186        }
187    
188        /**
189         * Sets the parameterToCheckAgainst attribute value.  This is the name of the parameter which has the values to validate against.
190         * @param parameterToCheckAgainst The parameterToCheckAgainst to set.
191         */
192        public void setParameterToCheckAgainst(String parameterToCheckAgainst) {
193            this.parameterToCheckAgainst = parameterToCheckAgainst;
194        }
195    
196        /**
197         * Gets the responsibleProperty attribute. This is the property on the accounting line to show the error on.
198         * @return Returns the responsibleProperty.
199         */
200        public String getResponsibleProperty() {
201            return responsibleProperty;
202        }
203    
204        /**
205         * Sets the responsibleProperty attribute value. This is the property on the accounting line to show the error on.
206         * @param responsibleProperty The responsibleProperty to set.
207         */
208        public void setResponsibleProperty(String responsibleProperty) {
209            this.responsibleProperty = responsibleProperty;
210        }
211    
212        /**
213         * Gets the accountingDocumentForValidation attribute. 
214         * @return Returns the accountingDocumentForValidation.
215         */
216        public AccountingDocument getAccountingDocumentForValidation() {
217            return accountingDocumentForValidation;
218        }
219    
220        /**
221         * Sets the accountingDocumentForValidation attribute value.
222         * @param accountingDocumentForValidation The accountingDocumentForValidation to set.
223         */
224        public void setAccountingDocumentForValidation(AccountingDocument accountingDocumentForValidation) {
225            this.accountingDocumentForValidation = accountingDocumentForValidation;
226        }
227    
228        /**
229         * Gets the accountingLineForValidation attribute. 
230         * @return Returns the accountingLineForValidation.
231         */
232        public AccountingLine getAccountingLineForValidation() {
233            return accountingLineForValidation;
234        }
235    
236        /**
237         * Sets the accountingLineForValidation attribute value.
238         * @param accountingLineForValidation The accountingLineForValidation to set.
239         */
240        public void setAccountingLineForValidation(AccountingLine accountingLineForValidation) {
241            this.accountingLineForValidation = accountingLineForValidation;
242        }
243    
244    }