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.fp.businessobject.lookup;
017    
018    import java.util.ArrayList;
019    import java.util.Collection;
020    import java.util.Collections;
021    import java.util.HashMap;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.kuali.kfs.fp.businessobject.DisbursementPayee;
027    import org.kuali.kfs.fp.document.service.DisbursementVoucherPaymentReasonService;
028    import org.kuali.kfs.sys.KFSConstants;
029    import org.kuali.kfs.sys.KFSKeyConstants;
030    import org.kuali.kfs.sys.KFSPropertyConstants;
031    import org.kuali.kfs.sys.context.SpringContext;
032    import org.kuali.kfs.vnd.VendorConstants;
033    import org.kuali.kfs.vnd.VendorPropertyConstants;
034    import org.kuali.kfs.vnd.businessobject.VendorDetail;
035    import org.kuali.rice.kim.bo.Person;
036    import org.kuali.rice.kim.service.KIMServiceLocator;
037    import org.kuali.rice.kim.util.KIMPropertyConstants;
038    import org.kuali.rice.kns.bo.BusinessObject;
039    import org.kuali.rice.kns.exception.ValidationException;
040    import org.kuali.rice.kns.lookup.CollectionIncomplete;
041    import org.kuali.rice.kns.lookup.KualiLookupableHelperServiceImpl;
042    import org.kuali.rice.kns.lookup.Lookupable;
043    import org.kuali.rice.kns.service.DataDictionaryService;
044    import org.kuali.rice.kns.util.BeanPropertyComparator;
045    import org.kuali.rice.kns.util.GlobalVariables;
046    import org.kuali.rice.kns.util.MessageList;
047    import org.kuali.rice.kns.web.struts.form.LookupForm;
048    import org.kuali.rice.kns.web.ui.ResultRow;
049    import org.kuali.rice.kns.datadictionary.AttributeDefinition;
050    import org.kuali.rice.kns.datadictionary.AttributeSecurity;
051    
052    public class DisbursementPayeeLookupableHelperServiceImpl extends KualiLookupableHelperServiceImpl {
053        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DisbursementPayeeLookupableHelperServiceImpl.class);
054    
055        private Lookupable vendorLookupable;
056        private DisbursementVoucherPaymentReasonService disbursementVoucherPaymentReasonService;
057        
058        private static final int NAME_REQUIRED_FILLED_WITH_WILDCARD = 4;
059    
060        /**
061         * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#performLookup(org.kuali.rice.kns.web.struts.form.LookupForm,
062         *      java.util.Collection, boolean)
063         */
064        @Override
065        public Collection performLookup(LookupForm lookupForm, Collection resultTable, boolean bounded) {
066            Map<String, String> fieldValues = lookupForm.getFieldsForLookup();
067            String paymentReasonCode = fieldValues.get(KFSPropertyConstants.PAYMENT_REASON_CODE);
068    
069            List<DisbursementPayee> displayList = (List<DisbursementPayee>) super.performLookup(lookupForm, resultTable, bounded);
070    
071            this.filterReturnUrl((List<ResultRow>) resultTable, displayList, paymentReasonCode);
072    
073            MessageList messageList = GlobalVariables.getMessageList();
074            disbursementVoucherPaymentReasonService.postPaymentReasonCodeUsage(paymentReasonCode, messageList);
075    
076            return displayList;
077        }
078    
079        /**
080         * @see org.kuali.rice.kns.lookup.KualiLookupableHelperServiceImpl#getSearchResults(java.util.Map)
081         */
082        @Override
083        public List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues) {
084            List<DisbursementPayee> searchResults = new ArrayList<DisbursementPayee>();
085    
086            if (StringUtils.isNotBlank(fieldValues.get(KFSPropertyConstants.VENDOR_NUMBER)) || StringUtils.isNotBlank(fieldValues.get(KFSPropertyConstants.VENDOR_NAME))) {
087                searchResults.addAll(this.getVendorsAsPayees(fieldValues));
088            }
089            else if (StringUtils.isNotBlank(fieldValues.get(KIMPropertyConstants.Person.EMPLOYEE_ID))) {
090                searchResults.addAll(this.getPersonAsPayees(fieldValues));
091            }
092            else {
093                searchResults.addAll(this.getVendorsAsPayees(fieldValues));
094                searchResults.addAll(this.getPersonAsPayees(fieldValues));
095            }
096    
097            CollectionIncomplete results = new CollectionIncomplete(searchResults, Long.valueOf(searchResults.size()));
098    
099            // sort list if default sort column given
100            List<String> defaultSortColumns = getDefaultSortColumns();
101            if (defaultSortColumns.size() > 0) {
102                Collections.sort(results, new BeanPropertyComparator(getDefaultSortColumns(), true));
103            }
104    
105            return results;
106        }
107    
108        /**
109         * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#validateSearchParameters(java.util.Map)
110         */
111        @Override
112        public void validateSearchParameters(Map fieldValues) {
113            super.validateSearchParameters(fieldValues);
114            
115            final String vendorName = (String) fieldValues.get(KFSPropertyConstants.VENDOR_NAME);
116            final String vendorNumber = (String) fieldValues.get(KFSPropertyConstants.VENDOR_NUMBER);
117            final String employeeId = (String) fieldValues.get(KIMPropertyConstants.Person.EMPLOYEE_ID);
118            final String firstName = (String)fieldValues.get(KIMPropertyConstants.Person.FIRST_NAME);
119            final String lastName = (String)fieldValues.get(KIMPropertyConstants.Person.LAST_NAME);
120            final String vendorTaxNumber = (String)fieldValues.get(KFSPropertyConstants.TAX_NUMBER);
121            final String vendorTaxNumberLabel = this.getAttributeLabel(KFSPropertyConstants.TAX_NUMBER);        
122            
123            final String employeeIdLabel = this.getAttributeLabel(KIMPropertyConstants.Person.EMPLOYEE_ID);
124            
125            if (StringUtils.isBlank(vendorNumber) && StringUtils.isBlank(employeeId) && StringUtils.isBlank(firstName) && StringUtils.isBlank(lastName) && StringUtils.isBlank(vendorName)) {
126                final String vendorNumberLabel = this.getAttributeLabel(KFSPropertyConstants.VENDOR_NUMBER);
127                final String vendorNameLabel = this.getAttributeLabel(KFSPropertyConstants.VENDOR_NAME);
128                final String firstNameLabel = this.getAttributeLabel(KIMPropertyConstants.Person.FIRST_NAME);
129                final String lastNameLabel = this.getAttributeLabel(KIMPropertyConstants.Person.LAST_NAME);
130                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.VENDOR_NUMBER, KFSKeyConstants.ERROR_DV_LOOKUP_NEEDS_SOME_FIELD, new String[] {vendorNumberLabel, employeeIdLabel, vendorNameLabel, firstNameLabel, lastNameLabel});
131            } else {
132                final boolean isVendorInfoEntered = StringUtils.isNotBlank(vendorName) || StringUtils.isNotBlank(vendorNumber);
133                if (isVendorInfoEntered && StringUtils.isNotBlank(employeeId)) {
134                    // only can use the vendor name and vendor number fields or the employee id field, but not both.
135                    String messageKey = KFSKeyConstants.ERROR_DV_VENDOR_EMPLOYEE_CONFUSION;
136                    String vendorNameLabel = this.getAttributeLabel(KFSPropertyConstants.VENDOR_NAME);
137                    String vendorNumberLabel = this.getAttributeLabel(KFSPropertyConstants.VENDOR_NUMBER);
138                    GlobalVariables.getMessageMap().putError(KIMPropertyConstants.Person.EMPLOYEE_ID, messageKey, employeeIdLabel, vendorNameLabel, vendorNumberLabel);
139                }
140                if (StringUtils.isBlank(vendorNumber) && !StringUtils.isBlank(vendorName) && !filledEnough(vendorName)) {
141                    final String vendorNameLabel = this.getAttributeLabel(KFSPropertyConstants.VENDOR_NAME);
142                    GlobalVariables.getMessageMap().putError(KFSPropertyConstants.VENDOR_NAME, KFSKeyConstants.ERROR_DV_NAME_NOT_FILLED_ENOUGH, new String[] {vendorNameLabel, Integer.toString(getNameLengthWithWildcardRequirement())});
143                }
144        
145                final boolean isPersonNameEntered = StringUtils.isNotBlank(firstName) || StringUtils.isNotBlank(lastName);
146                final String employeeFirstNameLabel = this.getAttributeLabel(KIMPropertyConstants.Person.FIRST_NAME);
147                final String employeeLastNameLabel = this.getAttributeLabel(KIMPropertyConstants.Person.LAST_NAME);
148                
149                // we do not use to use Tax Number field for the lookup on the person...
150                if(StringUtils.isNotBlank(vendorTaxNumber) && StringUtils.isNotBlank(firstName)) {
151                    fieldValues.remove(KFSPropertyConstants.TAX_NUMBER);
152                    String messageKey = KFSKeyConstants.ERROR_DV_LOOKUP_TAX_NUMBER_EMPLOYEE_DETAILS_CONFUSION;
153                    GlobalVariables.getMessageMap().putError(KIMPropertyConstants.Person.FIRST_NAME, messageKey, vendorTaxNumberLabel, employeeFirstNameLabel);                
154                }
155                // if tax number and employee last name entered then send an error message...
156                if(StringUtils.isNotBlank(vendorTaxNumber) && StringUtils.isNotBlank(lastName)) {
157                    fieldValues.remove(KFSPropertyConstants.TAX_NUMBER);
158                    String messageKey = KFSKeyConstants.ERROR_DV_LOOKUP_TAX_NUMBER_EMPLOYEE_DETAILS_CONFUSION;
159                    GlobalVariables.getMessageMap().putError(KIMPropertyConstants.Person.LAST_NAME, messageKey, vendorTaxNumberLabel, employeeLastNameLabel);                
160                }
161                // if tax number and employee id entered then send an error message...
162                if(StringUtils.isNotBlank(vendorTaxNumber) && StringUtils.isNotBlank(employeeId)) {
163                    fieldValues.remove(KFSPropertyConstants.TAX_NUMBER);
164                    String messageKey = KFSKeyConstants.ERROR_DV_LOOKUP_TAX_NUMBER_EMPLOYEE_DETAILS_CONFUSION;
165                    GlobalVariables.getMessageMap().putError(KIMPropertyConstants.Person.EMPLOYEE_ID, messageKey, employeeIdLabel, vendorTaxNumberLabel);                
166                }            
167                
168                if (isPersonNameEntered && StringUtils.isNotBlank(vendorName)) {
169                    // only can use the person first and last name fields or the vendor name field, but not both.
170                    String messageKey = KFSKeyConstants.ERROR_DV_VENDOR_NAME_PERSON_NAME_CONFUSION;
171        
172                    String vendorNameLabel = this.getAttributeLabel(KFSPropertyConstants.VENDOR_NAME);
173                    String firstNameLabel = this.getAttributeLabel(KIMPropertyConstants.Person.FIRST_NAME);
174                    String lastNameLabel = this.getAttributeLabel(KIMPropertyConstants.Person.LAST_NAME);
175        
176                    GlobalVariables.getMessageMap().putError(KFSPropertyConstants.VENDOR_NAME, messageKey, vendorNameLabel, firstNameLabel, lastNameLabel);
177                }
178                if (StringUtils.isBlank(employeeId)) {
179                    if (StringUtils.isBlank(firstName) && !StringUtils.isBlank(lastName) && !filledEnough(lastName)) {
180                        final String label = getAttributeLabel(KIMPropertyConstants.Person.LAST_NAME);
181                        GlobalVariables.getMessageMap().putError(KIMPropertyConstants.Person.LAST_NAME, KFSKeyConstants.ERROR_DV_NAME_NOT_FILLED_ENOUGH, new String[] { label, Integer.toString(getNameLengthWithWildcardRequirement() ) } );
182                    } else if (StringUtils.isBlank(lastName) && !StringUtils.isBlank(firstName) && !filledEnough(firstName)) {
183                        final String label = getAttributeLabel(KIMPropertyConstants.Person.FIRST_NAME);
184                        GlobalVariables.getMessageMap().putError(KIMPropertyConstants.Person.FIRST_NAME, KFSKeyConstants.ERROR_DV_NAME_NOT_FILLED_ENOUGH, new String[] { label, Integer.toString(getNameLengthWithWildcardRequirement() ) } );
185                    }
186                }
187            }
188            
189            if (GlobalVariables.getMessageMap().hasErrors()) {
190                throw new ValidationException("errors in search criteria");
191            }
192        }
193        
194        /**
195         * Determines if a String is "filled enough" - ie, is not null, has a length greater than zero and if a wildcard is present, has a length greater than 4 (3 characters, plus a wildcard)
196         * @param s the String to test
197         * @return true if the given String is "filled" by the definition above, false otherwise
198         */
199        protected boolean filledEnough(String s) {
200            final boolean containsWildcard = containsLookupWildcard(s);
201            return s != null && s.length() > 0 && ((containsWildcard && s.length() >= getNameLengthWithWildcardRequirement()) || !containsWildcard);
202        }
203        
204        /**
205         * @return the number of characters a name field must be filled in for the search to be valid
206         */
207        protected int getNameLengthWithWildcardRequirement() {
208            return DisbursementPayeeLookupableHelperServiceImpl.NAME_REQUIRED_FILLED_WITH_WILDCARD;
209        }
210        
211        /**
212         * Determines if the given String contains a lookup wildcard
213         * @param s the String to test
214         * @return true if a lookup wildcard is in the String, false otherwise
215         */
216        protected boolean containsLookupWildcard(String s) {
217            return s != null && (s.indexOf('*') > -1 || s.indexOf('%') > -1); 
218        }
219    
220        // get the label for the given attribute of the current business object
221        protected String getAttributeLabel(String attributeName) {
222            return this.getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName);
223        }
224    
225        // perform vendor search
226        protected List<DisbursementPayee> getVendorsAsPayees(Map<String, String> fieldValues) {
227            List<DisbursementPayee> payeeList = new ArrayList<DisbursementPayee>();
228    
229            Map<String, String> fieldsForLookup = this.getVendorFieldValues(fieldValues);
230            vendorLookupable.setBusinessObjectClass(VendorDetail.class);
231            vendorLookupable.validateSearchParameters(fieldsForLookup);
232    
233            List<BusinessObject> vendorList = vendorLookupable.getSearchResults(fieldsForLookup);
234            for (BusinessObject vendor : vendorList) {
235                VendorDetail vendorDetail = (VendorDetail) vendor;
236                DisbursementPayee payee = getPayeeFromVendor(vendorDetail, fieldValues);
237                payeeList.add(payee);
238            }
239            
240            return payeeList;
241        }
242    
243        protected DisbursementPayee getPayeeFromVendor(VendorDetail vendorDetail, Map<String, String> fieldValues) {
244            DisbursementPayee payee = DisbursementPayee.getPayeeFromVendor(vendorDetail);
245            payee.setPaymentReasonCode(fieldValues.get(KFSPropertyConstants.PAYMENT_REASON_CODE));
246            
247            //KFSMI-5497
248            //get the attributeSecurity property and mask the field so that on results screen will be shown masked.        
249            DataDictionaryService dataDictionaryService = SpringContext.getBean(DataDictionaryService.class);
250            AttributeSecurity attributeSecurity =  dataDictionaryService.getAttributeSecurity(DisbursementPayee.class.getName(), "taxNumber");
251            attributeSecurity.setMask(true);
252            
253            return payee;
254        }
255    
256        // get the search criteria valid for vendor lookup
257        private Map<String, String> getVendorFieldValues(Map<String, String> fieldValues) {
258            Map<String, String> vendorFieldValues = new HashMap<String, String>();
259            vendorFieldValues.putAll(fieldValues);
260    
261            Map<String, String> fieldConversionMap = DisbursementPayee.getFieldConversionBetweenPayeeAndVendor();
262            this.replaceFieldKeys(vendorFieldValues, fieldConversionMap);
263    
264            String vendorName = this.getVendorName(vendorFieldValues);
265            if (StringUtils.isNotBlank(vendorName)) {
266                vendorFieldValues.put(KFSPropertyConstants.VENDOR_NAME, vendorName);
267            }
268    
269            vendorFieldValues.remove(VendorPropertyConstants.VENDOR_FIRST_NAME);
270            vendorFieldValues.remove(VendorPropertyConstants.VENDOR_LAST_NAME);
271    
272            return vendorFieldValues;
273        }
274    
275        // get the vendor name from the given field value map
276        private String getVendorName(Map<String, String> vendorFieldValues) {
277            String firstName = vendorFieldValues.get(VendorPropertyConstants.VENDOR_FIRST_NAME);
278            String lastName = vendorFieldValues.get(VendorPropertyConstants.VENDOR_LAST_NAME);
279    
280            if (StringUtils.isNotBlank(lastName)) {
281                return lastName + VendorConstants.NAME_DELIM + firstName;
282            }
283            else if (StringUtils.isNotBlank(firstName)) {
284                return KFSConstants.WILDCARD_CHARACTER + VendorConstants.NAME_DELIM + firstName;
285            }
286    
287            return StringUtils.EMPTY;
288        }
289    
290        // perform person search
291        protected List<DisbursementPayee> getPersonAsPayees(Map<String, String> fieldValues) {
292            List<DisbursementPayee> payeeList = new ArrayList<DisbursementPayee>();
293            
294            Map<String, String> fieldsForLookup = this.getPersonFieldValues(fieldValues);                
295            List<? extends Person> persons = KIMServiceLocator.getPersonService().findPeople(fieldsForLookup);   
296            
297            for (Person personDetail : persons) {            
298                DisbursementPayee payee = getPayeeFromPerson(personDetail, fieldValues);
299                payeeList.add(payee);
300            }
301            
302            return payeeList;
303        }
304    
305        protected DisbursementPayee getPayeeFromPerson(Person personDetail, Map<String, String> fieldValues) {
306            DisbursementPayee payee = DisbursementPayee.getPayeeFromPerson(personDetail);
307            payee.setPaymentReasonCode(fieldValues.get(KFSPropertyConstants.PAYMENT_REASON_CODE));
308    
309            //KFSMI-5497
310            //get the attributeSecurity property and unmask the field so that on results screen, it will set as blank.
311            DataDictionaryService dataDictionaryService = SpringContext.getBean(DataDictionaryService.class);
312            AttributeSecurity attributeSecurity =  dataDictionaryService.getAttributeSecurity(DisbursementPayee.class.getName(), "taxNumber");
313            attributeSecurity.setMask(false);
314            
315            return payee;
316        }
317    
318        // get the search criteria valid for person lookup
319        private Map<String, String> getPersonFieldValues(Map<String, String> fieldValues) {
320            Map<String, String> personFieldValues = new HashMap<String, String>();
321            personFieldValues.putAll(fieldValues);
322    
323            Map<String, String> fieldConversionMap = DisbursementPayee.getFieldConversionBetweenPayeeAndPerson();
324            this.replaceFieldKeys(personFieldValues, fieldConversionMap);
325    
326            personFieldValues.put(KIMPropertyConstants.Person.EMPLOYEE_STATUS_CODE, KFSConstants.EMPLOYEE_ACTIVE_STATUS);
327    
328            return personFieldValues;
329        }
330    
331        // replace the keys in fieldValues with the corresponding values defined in fieldConversionMap
332        private void replaceFieldKeys(Map<String, String> fieldValues, Map<String, String> fieldConversionMap) {
333            for (String key : fieldConversionMap.keySet()) {
334                if (fieldValues.containsKey(key)) {
335                    String value = fieldValues.get(key);
336                    String newKey = fieldConversionMap.get(key);
337    
338                    fieldValues.remove(key);
339                    fieldValues.put(newKey, value);
340                }
341            }
342        }
343    
344        // remove its return URLs if a row is not qualified for returning
345        protected void filterReturnUrl(List<ResultRow> resultRowList, List<DisbursementPayee> payeeList, String paymentReasonCode) {
346            List<String> payeeTypeCodes = disbursementVoucherPaymentReasonService.getPayeeTypesByPaymentReason(paymentReasonCode);
347            if (payeeTypeCodes == null || payeeTypeCodes.isEmpty()) {
348                return;
349            }
350    
351            for (int index = 0; index < payeeList.size(); index++) {
352                DisbursementPayee payee = payeeList.get(index);
353    
354                boolean isQualified = disbursementVoucherPaymentReasonService.isPayeeQualifiedForPayment(payee, paymentReasonCode, payeeTypeCodes);
355                if (!isQualified) {
356                    resultRowList.get(index).setReturnUrl(StringUtils.EMPTY);
357                }
358            }
359        }
360    
361        /**
362         * Sets the vendorLookupable attribute value.
363         * 
364         * @param vendorLookupable The vendorLookupable to set.
365         */
366        public void setVendorLookupable(Lookupable vendorLookupable) {
367            this.vendorLookupable = vendorLookupable;
368        }
369    
370        /**
371         * Sets the disbursementVoucherPaymentReasonService attribute value.
372         * 
373         * @param disbursementVoucherPaymentReasonService The disbursementVoucherPaymentReasonService to set.
374         */
375        public void setDisbursementVoucherPaymentReasonService(DisbursementVoucherPaymentReasonService disbursementVoucherPaymentReasonService) {
376            this.disbursementVoucherPaymentReasonService = disbursementVoucherPaymentReasonService;
377        }
378    }