View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.vnd.businessobject.lookup;
17  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Properties;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.kuali.ole.integration.purap.PurchasingAccountsPayableModuleService;
28  import org.kuali.ole.sys.OLEConstants;
29  import org.kuali.ole.sys.context.SpringContext;
30  import org.kuali.ole.vnd.VendorConstants;
31  import org.kuali.ole.vnd.VendorKeyConstants;
32  import org.kuali.ole.vnd.VendorPropertyConstants;
33  import org.kuali.ole.vnd.businessobject.VendorAddress;
34  import org.kuali.ole.vnd.businessobject.VendorDetail;
35  import org.kuali.ole.vnd.document.service.VendorService;
36  import org.kuali.rice.core.web.format.Formatter;
37  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
38  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
39  import org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl;
40  import org.kuali.rice.kns.lookup.HtmlData;
41  import org.kuali.rice.kns.lookup.HtmlData.AnchorHtmlData;
42  import org.kuali.rice.krad.bo.BusinessObject;
43  import org.kuali.rice.krad.exception.ValidationException;
44  import org.kuali.rice.krad.lookup.CollectionIncomplete;
45  import org.kuali.rice.krad.util.BeanPropertyComparator;
46  import org.kuali.rice.krad.util.GlobalVariables;
47  import org.kuali.rice.krad.util.KRADConstants;
48  import org.kuali.rice.krad.util.ObjectUtils;
49  import org.kuali.rice.krad.util.UrlFactory;
50  
51  public class VendorLookupableHelperServiceImpl extends AbstractLookupableHelperServiceImpl {
52      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(VendorLookupableHelperServiceImpl.class);
53  
54      protected VendorService vendorService;
55      protected ParameterService parameterService;
56  
57      /**
58       * Add custom links to the vendor search results. One to Allow only active parent vendors to create new divisions. Another to
59       * create a link for B2B shopping if PURAP service has been setup to allow for that.
60       *
61       * @see org.kuali.rice.kns.lookup.LookupableHelperService#getCustomActionUrls(org.kuali.rice.krad.bo.BusinessObject,
62       *      java.util.List, java.util.List pkNames)
63       */
64      @Override
65      public List<HtmlData> getCustomActionUrls(BusinessObject businessObject, List pkNames) {
66          VendorDetail vendor = (VendorDetail) businessObject;
67          List<HtmlData> anchorHtmlDataList = new ArrayList<HtmlData>();
68  
69          AnchorHtmlData anchorHtmlData = super.getUrlData(businessObject, KRADConstants.MAINTENANCE_EDIT_METHOD_TO_CALL, pkNames);
70          anchorHtmlDataList.add(anchorHtmlData);
71          String documentTypeName = OLEConstants.Vendor.DOCUMENT_TYPE;
72          String nameSpaceCode = OLEConstants.Vendor.VENDOR_NAMESPACE;
73  
74          boolean hasPermission = KimApiServiceLocator.getPermissionService().isAuthorized(
75                  GlobalVariables.getUserSession().getPerson().getPrincipalId(), nameSpaceCode,
76                  OLEConstants.Vendor.CREATE_VENDOR_DIVISION, Collections.<String, String> emptyMap());
77          if (vendor.isVendorParentIndicator() && vendor.isActiveIndicator() && hasPermission ) {
78              // only allow active parent vendors to create new divisions
79              anchorHtmlDataList.add(super.getUrlData(businessObject, OLEConstants.MAINTENANCE_NEWWITHEXISTING_ACTION, VendorConstants.CREATE_DIVISION, pkNames));
80          }
81  
82          //Adding a "Shopping" link for B2B vendors.
83          String b2bUrlString = SpringContext.getBean(PurchasingAccountsPayableModuleService.class).getB2BUrlString();
84          if (vendor.isB2BVendor() && StringUtils.isNotBlank(b2bUrlString)) {
85              Properties theProperties = new Properties();
86              theProperties.put("channelTitle", "Shop Catalogs");
87              String backLocation = this.getBackLocation();
88              int lastSlash = backLocation.lastIndexOf("/");
89              String returnUrlForShop = backLocation.substring(0, lastSlash+1) + "portal.do";
90              String href = UrlFactory.parameterizeUrl(returnUrlForShop, theProperties);
91              anchorHtmlDataList.add(new AnchorHtmlData(href + b2bUrlString, null, "shop"));
92          }
93          return anchorHtmlDataList;
94      }
95  
96      /**
97       * Used by getActionUrls to print the url on the Vendor Lookup page for the links to edit a Vendor or to create a new division.
98       * We won't provide a link to copy a vendor because we decided it wouldn't make sense to copy a vendor. We should display the
99       * link to create a new division only if the vendor is a parent vendor, and also remove the vendor detail assigned id from the
100      * query string in the link to create a new division. We'll add the vendor detail assigned id in the query string if the vendor
101      * is not a parent, or if the vendor is a parent and the link is not the create new division link (i.e. if the link is "edit").
102      * We'll always add the vendor header id in the query string in all links.
103      *
104      * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#getActionUrlHref(org.kuali.rice.krad.bo.BusinessObject, java.lang.String, java.util.List)
105      */
106     @Override
107     protected String getActionUrlHref(BusinessObject businessObject, String methodToCall, List pkNames){
108         if (!methodToCall.equals(OLEConstants.COPY_METHOD)) {
109             Properties parameters = new Properties();
110             parameters.put(OLEConstants.DISPATCH_REQUEST_PARAMETER, methodToCall);
111             parameters.put(OLEConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, businessObject.getClass().getName());
112 
113             for (Iterator<String> iter = pkNames.iterator(); iter.hasNext();) {
114                 String fieldNm = iter.next();
115                 if (!fieldNm.equals(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID) ||
116                         !((VendorDetail) businessObject).isVendorParentIndicator()
117                         || (((VendorDetail) businessObject).isVendorParentIndicator())
118                         && !methodToCall.equals(OLEConstants.MAINTENANCE_NEWWITHEXISTING_ACTION)) {
119                     Object fieldVal = ObjectUtils.getPropertyValue(businessObject, fieldNm);
120                     if (fieldVal == null) {
121                         fieldVal = OLEConstants.EMPTY_STRING;
122                     }
123                     if (fieldVal instanceof java.sql.Date) {
124                         String formattedString = OLEConstants.EMPTY_STRING;
125                         if (Formatter.findFormatter(fieldVal.getClass()) != null) {
126                             Formatter formatter = Formatter.getFormatter(fieldVal.getClass());
127                             formattedString = (String) formatter.format(fieldVal);
128                             fieldVal = formattedString;
129                         }
130                     }
131                     parameters.put(fieldNm, fieldVal.toString());
132                 }
133             }
134             return UrlFactory.parameterizeUrl(OLEConstants.MAINTENANCE_ACTION, parameters);
135         } else {
136             return OLEConstants.EMPTY_STRING;
137         }
138     }
139 
140     /**
141      * Overrides the getSearchResults in the super class so that we can do some customization in our vendor lookup. For example, for
142      * vendor name as the search criteria, we want to search both the vendor detail table and the vendor alias table for the vendor
143      * name. Display the vendor's default address state in the search results.
144      *
145      * @see org.kuali.rice.kns.lookup.Lookupable#getSearchResults(java.util.Map)
146      */
147     @Override
148     public List<BusinessObject> getSearchResults(Map<String, String> fieldValues) {
149         boolean unbounded = false;
150         super.setBackLocation(fieldValues.get(OLEConstants.BACK_LOCATION));
151         super.setDocFormKey(fieldValues.get(OLEConstants.DOC_FORM_KEY));
152 
153         String vendorName = fieldValues.get(VendorPropertyConstants.VENDOR_NAME);
154 
155         List<BusinessObject> searchResults = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), fieldValues, unbounded);
156 
157         // re-run the query against the vendor name alias field if necessary and merge the results
158         // this could double the returned results for the search, but there is no alternative at present
159         // without refactoring of the lookup service
160         if (StringUtils.isNotEmpty(vendorName)) {
161             // if searching by vendorName, also search in list of alias names
162             fieldValues.put(VendorPropertyConstants.VENDOR_ALIAS_NAME_FULL_PATH, vendorName);
163             // also make sure that we only use active aliases to match the query string
164             fieldValues.put(VendorPropertyConstants.VENDOR_ALIAS_ACTIVE, "Y");
165             fieldValues.remove(VendorPropertyConstants.VENDOR_NAME);
166             List<BusinessObject> searchResults2 = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), fieldValues, unbounded);
167 
168             searchResults.addAll(searchResults2);
169             if (searchResults instanceof CollectionIncomplete && searchResults2 instanceof CollectionIncomplete) {
170                 ((CollectionIncomplete) searchResults).setActualSizeIfTruncated(((CollectionIncomplete) searchResults).getActualSizeIfTruncated().longValue() + ((CollectionIncomplete) searchResults2).getActualSizeIfTruncated().longValue());
171             }
172         }
173 
174         List<BusinessObject> processedSearchResults = new ArrayList();
175 
176         // loop through results
177         for (BusinessObject businessObject : searchResults) {
178             VendorDetail vendor = (VendorDetail) businessObject;
179 
180             // if its a top level vendor, search for its divisions and add them to the appropriate list then add the vendor to the
181             // return results
182             // if its a division, see if we already have the parent and if not, retrieve it and its divisions then add the parent to
183             // the return results
184 
185 
186             // If this vendor is not already in the processedSearchResults, let's do further processing (e.g. setting the state for
187             // lookup from default address, etc)
188             // and then add it in the processedSearchResults.
189             if (!processedSearchResults.contains(vendor)) {
190                 Map<String, String> tmpValues = new HashMap<String, String>();
191                 List<VendorDetail> relatedVendors = new ArrayList();
192                 tmpValues.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendor.getVendorHeaderGeneratedIdentifier().toString());
193                 relatedVendors = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), tmpValues, unbounded);
194 
195                 for (VendorDetail tmpVendor : relatedVendors) {
196                     if (ObjectUtils.isNotNull(tmpVendor) && !processedSearchResults.contains(tmpVendor)) {
197                         // populate state from default address
198                         updateDefaultVendorAddress(tmpVendor);
199                         processedSearchResults.add(tmpVendor);
200                     }
201                 }
202 
203                 if (!processedSearchResults.contains(vendor)) {
204                     updateDefaultVendorAddress(vendor);
205                     processedSearchResults.add(vendor);
206                 }
207             }
208         }
209 
210         for (BusinessObject businessObject : processedSearchResults) {
211             VendorDetail vendor = (VendorDetail) businessObject;
212             if (!vendor.isVendorParentIndicator()) {
213                 // find the parent object in the details collection and add that
214                 for (BusinessObject tmpObject : processedSearchResults) {
215                     VendorDetail tmpVendor = (VendorDetail) tmpObject;
216                     if (tmpVendor.getVendorHeaderGeneratedIdentifier().equals(vendor.getVendorHeaderGeneratedIdentifier()) && tmpVendor.isVendorParentIndicator()) {
217                         vendor.setVendorName(tmpVendor.getVendorName() + " > " + vendor.getVendorName());
218                         break;
219                     }
220                 }
221             }
222         }
223 
224         searchResults.clear();
225         searchResults.addAll(processedSearchResults);
226 
227         // sort list if default sort column given
228         List<String> defaultSortColumns = getDefaultSortColumns();
229         if (defaultSortColumns.size() > 0) {
230             Collections.sort(searchResults, new BeanPropertyComparator(getDefaultSortColumns(), true));
231         }
232 
233         return searchResults;
234     }
235 
236     /**
237      * Populates address fields from default address
238      *
239      * @param vendor venodrDetail
240      */
241     private void updateDefaultVendorAddress(VendorDetail vendor) {
242         VendorAddress defaultAddress = vendorService.getVendorDefaultAddress(vendor.getVendorAddresses(), vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode(), "");
243         if (ObjectUtils.isNotNull(defaultAddress)) {
244 
245             if (ObjectUtils.isNotNull(defaultAddress.getVendorState())) {
246                 vendor.setVendorStateForLookup(defaultAddress.getVendorState().getName());
247             } else {
248                 if ( LOG.isDebugEnabled() ) {
249                     LOG.debug( "Warning - unable to retrieve state for " + defaultAddress.getVendorCountryCode() + " / " + defaultAddress.getVendorStateCode() );
250                 }
251                 vendor.setVendorStateForLookup("");
252             }
253             vendor.setDefaultAddressLine1(defaultAddress.getVendorLine1Address());
254             vendor.setDefaultAddressLine2(defaultAddress.getVendorLine2Address());
255             vendor.setDefaultAddressCity(defaultAddress.getVendorCityName());
256             vendor.setDefaultAddressPostalCode(defaultAddress.getVendorZipCode());
257             vendor.setDefaultAddressStateCode(defaultAddress.getVendorStateCode());
258             vendor.setDefaultAddressInternationalProvince(defaultAddress.getVendorAddressInternationalProvinceName());
259             vendor.setDefaultAddressCountryCode(defaultAddress.getVendorCountryCode());
260             vendor.setDefaultFaxNumber(defaultAddress.getVendorFaxNumber());
261         } else {
262             if ( LOG.isDebugEnabled() ) {
263                 LOG.debug( "Warning - default vendor address was null for " + vendor.getVendorNumber() + " / " + vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode() );
264             }
265             vendor.setVendorStateForLookup("");
266         }
267     }
268 
269     /**
270      * Overrides a method of the superclass and is now called instead of that one by the Search method of KualiLookupAction when the
271      * Lookupable is of this class. This method first calls the method from the superclass, which should do all the required field
272      * checking, and then goes through all the specific validations which aren't done in at the JSP level. Both the superclass
273      * method and the various validation methods side-effect the adding of errors to the global error map when the input is found to
274      * have an issue.
275      *
276      * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#validateSearchParameters(java.util.Map)
277      */
278     @Override
279     public void validateSearchParameters(Map fieldValues) {
280         super.validateSearchParameters(fieldValues);
281 
282         validateVendorNumber(fieldValues);
283         validateTaxNumber(fieldValues);
284 
285         if (GlobalVariables.getMessageMap().hasErrors()) {
286             throw new ValidationException("Error(s) in search criteria");
287         }
288     }
289 
290     /**
291      * Validates that the Vendor Number has no more than one dash in it, and does not consist solely of one dash. Then it calls
292      * extractVendorNumberToVendorIds to obtain vendorHeaderGeneratedId and vendorDetailAssignedId and if either one of the ids
293      * cannot be converted to integers, it will add error that the vendor number must be numerics or numerics separated by a dash.
294      *
295      * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup
296      */
297     private void validateVendorNumber(Map fieldValues) {
298         String vendorNumber = (String) fieldValues.get(VendorPropertyConstants.VENDOR_NUMBER);
299         if (StringUtils.isNotBlank(vendorNumber)) {
300             int dashPos1 = vendorNumber.indexOf(VendorConstants.DASH);
301             if (dashPos1 > -1) { // There's a dash in the number.
302                 if (vendorNumber.indexOf(VendorConstants.DASH, dashPos1 + 1) > -1) { // There can't be more than one.
303                     GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_TOO_MANY_DASHES);
304                 }
305                 if (vendorNumber.matches("\\-*")) {
306                     GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_DASHES_ONLY);
307                 }
308             }
309             extractVendorNumberToVendorIds(fieldValues, vendorNumber);
310         }
311     }
312 
313     /**
314      * Parses the vendorNumber string into vendorHeaderGeneratedIdentifier and vendorDetailAssignedIdentifier, validates that both
315      * fields would be able to be converted into integers, if so it will add both fields into the search criterias map in the
316      * fieldValues and remove the vendorNumber from the fieldValues. If the two fields cannot be converted into integers, this
317      * method will add error message to the errorMap in GlobalVariables that the vendor number must be numeric or numerics separated
318      * by a dash.
319      *
320      * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup
321      * @param vendorNumber vendor number String
322      */
323     private void extractVendorNumberToVendorIds(Map fieldValues, String vendorNumber) {
324         String vendorHeaderGeneratedIdentifier = null;
325         String vendorDetailAssignedIdentifier = null;
326         int indexOfDash = vendorNumber.indexOf(VendorConstants.DASH);
327         if (indexOfDash < 0) {
328             vendorHeaderGeneratedIdentifier = vendorNumber;
329         }
330         else {
331             vendorHeaderGeneratedIdentifier = vendorNumber.substring(0, indexOfDash);
332             vendorDetailAssignedIdentifier = vendorNumber.substring(indexOfDash + 1, vendorNumber.length());
333         }
334         try {
335             if (StringUtils.isNotEmpty(vendorHeaderGeneratedIdentifier)) {
336                 Integer.parseInt(vendorHeaderGeneratedIdentifier);
337             }
338             if (StringUtils.isNotEmpty(vendorDetailAssignedIdentifier)) {
339                 Integer.parseInt(vendorDetailAssignedIdentifier);
340             }
341             fieldValues.remove(VendorPropertyConstants.VENDOR_NUMBER);
342             fieldValues.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendorHeaderGeneratedIdentifier);
343             if (StringUtils.isNotEmpty(vendorDetailAssignedIdentifier)) {
344             fieldValues.put(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID, vendorDetailAssignedIdentifier);
345         }
346         }
347         catch (NumberFormatException headerExc) {
348             GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_NUMERIC_DASH_SEPARATED);
349         }
350     }
351 
352     /**
353      * Validates that the tax number is 9 digits long.
354      *
355      * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup
356      */
357     private void validateTaxNumber(Map fieldValues) {
358         String taxNumber = (String) fieldValues.get(VendorPropertyConstants.VENDOR_TAX_NUMBER);
359         if (StringUtils.isNotBlank(taxNumber) && (!StringUtils.isNumeric(taxNumber) || taxNumber.length() != 9)) {
360             GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_TAX_NUM_INVALID);
361         }
362     }
363 
364     public void setVendorService(VendorService vendorService) {
365         this.vendorService = vendorService;
366     }
367 
368     @Override
369     public void setParameterService(ParameterService parameterService) {
370         this.parameterService = parameterService;
371     }
372 
373 }