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.vnd.businessobject.lookup; 017 018 import java.util.ArrayList; 019 import java.util.Collections; 020 import java.util.HashMap; 021 import java.util.Iterator; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.Properties; 025 026 import org.apache.commons.lang.StringUtils; 027 import org.kuali.kfs.integration.purap.PurchasingAccountsPayableModuleService; 028 import org.kuali.kfs.sys.KFSConstants; 029 import org.kuali.kfs.sys.context.SpringContext; 030 import org.kuali.kfs.vnd.VendorConstants; 031 import org.kuali.kfs.vnd.VendorKeyConstants; 032 import org.kuali.kfs.vnd.VendorParameterConstants; 033 import org.kuali.kfs.vnd.VendorPropertyConstants; 034 import org.kuali.kfs.vnd.businessobject.VendorAddress; 035 import org.kuali.kfs.vnd.businessobject.VendorDetail; 036 import org.kuali.kfs.vnd.document.service.VendorService; 037 import org.kuali.rice.kns.bo.BusinessObject; 038 import org.kuali.rice.kns.exception.ValidationException; 039 import org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl; 040 import org.kuali.rice.kns.lookup.CollectionIncomplete; 041 import org.kuali.rice.kns.lookup.HtmlData; 042 import org.kuali.rice.kns.lookup.HtmlData.AnchorHtmlData; 043 import org.kuali.rice.kns.service.ParameterService; 044 import org.kuali.rice.kns.util.BeanPropertyComparator; 045 import org.kuali.rice.kns.util.GlobalVariables; 046 import org.kuali.rice.kns.util.KNSConstants; 047 import org.kuali.rice.kns.util.ObjectUtils; 048 import org.kuali.rice.kns.util.UrlFactory; 049 import org.kuali.rice.kns.web.format.Formatter; 050 051 public class VendorLookupableHelperServiceImpl extends AbstractLookupableHelperServiceImpl { 052 private VendorService vendorService; 053 private ParameterService parameterService; 054 055 /** 056 * Add custom links to the vendor search results. One to Allow only active parent vendors to create new divisions. Another to 057 * create a link for B2B shopping if PURAP service has been setup to allow for that. 058 * 059 * @see org.kuali.rice.kns.lookup.LookupableHelperService#getCustomActionUrls(org.kuali.rice.kns.bo.BusinessObject, 060 * java.util.List, java.util.List pkNames) 061 */ 062 @Override 063 public List<HtmlData> getCustomActionUrls(BusinessObject businessObject, List pkNames) { 064 VendorDetail vendor = (VendorDetail) businessObject; 065 List<HtmlData> anchorHtmlDataList = new ArrayList<HtmlData>(); 066 067 AnchorHtmlData anchorHtmlData = super.getUrlData(businessObject, KNSConstants.MAINTENANCE_EDIT_METHOD_TO_CALL, pkNames); 068 anchorHtmlDataList.add(anchorHtmlData); 069 if (vendor.isVendorParentIndicator() && vendor.isActiveIndicator()) { 070 // only allow active parent vendors to create new divisions 071 anchorHtmlDataList.add(super.getUrlData(businessObject, KFSConstants.MAINTENANCE_NEWWITHEXISTING_ACTION, VendorConstants.CREATE_DIVISION, pkNames)); 072 } 073 074 //Adding a "Shopping" link for B2B vendors. 075 String b2bUrlString = SpringContext.getBean(PurchasingAccountsPayableModuleService.class).getB2BUrlString(); 076 if (vendor.isB2BVendor() && StringUtils.isNotBlank(b2bUrlString)) { 077 Properties theProperties = new Properties(); 078 theProperties.put("channelTitle", "Shop Catalogs"); 079 String backLocation = this.getBackLocation(); 080 int lastSlash = backLocation.lastIndexOf("/"); 081 String returnUrlForShop = backLocation.substring(0, lastSlash+1) + "portal.do"; 082 String href = UrlFactory.parameterizeUrl(returnUrlForShop, theProperties); 083 anchorHtmlDataList.add(new AnchorHtmlData(href + b2bUrlString, null, "shop")); 084 } 085 return anchorHtmlDataList; 086 } 087 088 /** 089 * 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. 090 * 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 091 * link to create a new division only if the vendor is a parent vendor, and also remove the vendor detail assigned id from the 092 * 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 093 * 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"). 094 * We'll always add the vendor header id in the query string in all links. 095 * 096 * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#getActionUrlHref(org.kuali.rice.kns.bo.BusinessObject, java.lang.String, java.util.List) 097 */ 098 @Override 099 protected String getActionUrlHref(BusinessObject businessObject, String methodToCall, List pkNames){ 100 if (!methodToCall.equals(KFSConstants.COPY_METHOD)) { 101 Properties parameters = new Properties(); 102 parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, methodToCall); 103 parameters.put(KFSConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, businessObject.getClass().getName()); 104 105 for (Iterator<String> iter = pkNames.iterator(); iter.hasNext();) { 106 String fieldNm = iter.next(); 107 if (!fieldNm.equals(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID) || 108 !((VendorDetail) businessObject).isVendorParentIndicator() 109 || (((VendorDetail) businessObject).isVendorParentIndicator()) 110 && !methodToCall.equals(KFSConstants.MAINTENANCE_NEWWITHEXISTING_ACTION)) { 111 Object fieldVal = ObjectUtils.getPropertyValue(businessObject, fieldNm); 112 if (fieldVal == null) { 113 fieldVal = KFSConstants.EMPTY_STRING; 114 } 115 if (fieldVal instanceof java.sql.Date) { 116 String formattedString = KFSConstants.EMPTY_STRING; 117 if (Formatter.findFormatter(fieldVal.getClass()) != null) { 118 Formatter formatter = Formatter.getFormatter(fieldVal.getClass()); 119 formattedString = (String) formatter.format(fieldVal); 120 fieldVal = formattedString; 121 } 122 } 123 parameters.put(fieldNm, fieldVal.toString()); 124 } 125 } 126 return UrlFactory.parameterizeUrl(KFSConstants.MAINTENANCE_ACTION, parameters); 127 } else { 128 return KFSConstants.EMPTY_STRING; 129 } 130 } 131 132 /** 133 * Overrides the getSearchResults in the super class so that we can do some customization in our vendor lookup. For example, for 134 * vendor name as the search criteria, we want to search both the vendor detail table and the vendor alias table for the vendor 135 * name. Display the vendor's default address state in the search results. 136 * 137 * @see org.kuali.rice.kns.lookup.Lookupable#getSearchResults(java.util.Map) 138 */ 139 @Override 140 public List<BusinessObject> getSearchResults(Map<String, String> fieldValues) { 141 boolean unbounded = false; 142 super.setBackLocation((String) fieldValues.get(KFSConstants.BACK_LOCATION)); 143 super.setDocFormKey((String) fieldValues.get(KFSConstants.DOC_FORM_KEY)); 144 145 String vendorName = fieldValues.get(VendorPropertyConstants.VENDOR_NAME); 146 147 List<BusinessObject> searchResults = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), fieldValues, unbounded); 148 149 // re-run the query against the vendor name alias field if necessary and merge the results 150 // this could double the returned results for the search, but there is no alternative at present 151 // without refactoring of the lookup service 152 if (StringUtils.isNotEmpty(vendorName)) { 153 // if searching by vendorName, also search in list of alias names 154 fieldValues.put(VendorPropertyConstants.VENDOR_ALIAS_NAME_FULL_PATH, vendorName); 155 // also make sure that we only use active aliases to match the query string 156 fieldValues.put(VendorPropertyConstants.VENDOR_ALIAS_ACTIVE, "Y"); 157 fieldValues.remove(VendorPropertyConstants.VENDOR_NAME); 158 List<BusinessObject> searchResults2 = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), fieldValues, unbounded); 159 160 searchResults.addAll(searchResults2); 161 if (searchResults instanceof CollectionIncomplete && searchResults2 instanceof CollectionIncomplete) { 162 ((CollectionIncomplete) searchResults).setActualSizeIfTruncated(((CollectionIncomplete) searchResults).getActualSizeIfTruncated().longValue() + ((CollectionIncomplete) searchResults2).getActualSizeIfTruncated().longValue()); 163 } 164 } 165 166 List<BusinessObject> processedSearchResults = new ArrayList(); 167 168 // loop through results 169 for (BusinessObject businessObject : searchResults) { 170 VendorDetail vendor = (VendorDetail) businessObject; 171 172 // if its a top level vendor, search for its divisions and add them to the appropriate list then add the vendor to the 173 // return results 174 // if its a division, see if we already have the parent and if not, retrieve it and its divisions then add the parent to 175 // the return results 176 177 178 // If this vendor is not already in the processedSearchResults, let's do further processing (e.g. setting the state for 179 // lookup from default address, etc) 180 // and then add it in the processedSearchResults. 181 if (!processedSearchResults.contains(vendor)) { 182 Map<String, String> tmpValues = new HashMap<String, String>(); 183 List<VendorDetail> relatedVendors = new ArrayList(); 184 tmpValues.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendor.getVendorHeaderGeneratedIdentifier().toString()); 185 relatedVendors = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), tmpValues, unbounded); 186 187 for (VendorDetail tmpVendor : relatedVendors) { 188 if (tmpVendor != null && !processedSearchResults.contains(tmpVendor)) { 189 // populate state from default address 190 updateDefaultVendorAddress(tmpVendor); 191 processedSearchResults.add(tmpVendor); 192 } 193 } 194 195 if (!processedSearchResults.contains(vendor)) { 196 updateDefaultVendorAddress(vendor); 197 processedSearchResults.add(vendor); 198 } 199 } 200 } 201 202 for (BusinessObject businessObject : processedSearchResults) { 203 VendorDetail vendor = (VendorDetail) businessObject; 204 if (!vendor.isVendorParentIndicator()) { 205 // find the parent object in the details collection and add that 206 for (BusinessObject tmpObject : processedSearchResults) { 207 VendorDetail tmpVendor = (VendorDetail) tmpObject; 208 if (tmpVendor.getVendorHeaderGeneratedIdentifier().equals(vendor.getVendorHeaderGeneratedIdentifier()) && tmpVendor.isVendorParentIndicator()) { 209 vendor.setVendorName(tmpVendor.getVendorName() + " > " + vendor.getVendorName()); 210 break; 211 } 212 } 213 } 214 } 215 216 searchResults.clear(); 217 searchResults.addAll(processedSearchResults); 218 219 // sort list if default sort column given 220 List<String> defaultSortColumns = getDefaultSortColumns(); 221 if (defaultSortColumns.size() > 0) { 222 Collections.sort(searchResults, new BeanPropertyComparator(getDefaultSortColumns(), true)); 223 } 224 225 return searchResults; 226 } 227 228 /** 229 * Populates address fields from default address 230 * 231 * @param vendor venodrDetail 232 */ 233 private void updateDefaultVendorAddress(VendorDetail vendor) { 234 VendorAddress defaultAddress = vendorService.getVendorDefaultAddress(vendor.getVendorAddresses(), vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode(), ""); 235 if (defaultAddress != null ) { 236 if (defaultAddress.getVendorState() != null) { 237 vendor.setVendorStateForLookup(defaultAddress.getVendorState().getPostalStateName()); 238 } 239 vendor.setDefaultAddressLine1(defaultAddress.getVendorLine1Address()); 240 vendor.setDefaultAddressLine2(defaultAddress.getVendorLine2Address()); 241 vendor.setDefaultAddressCity(defaultAddress.getVendorCityName()); 242 vendor.setDefaultAddressPostalCode(defaultAddress.getVendorZipCode()); 243 vendor.setDefaultAddressStateCode(defaultAddress.getVendorStateCode()); 244 vendor.setDefaultAddressInternationalProvince(defaultAddress.getVendorAddressInternationalProvinceName()); 245 vendor.setDefaultAddressCountryCode(defaultAddress.getVendorCountryCode()); 246 vendor.setDefaultFaxNumber(defaultAddress.getVendorFaxNumber()); 247 } 248 } 249 250 /** 251 * Overrides a method of the superclass and is now called instead of that one by the Search method of KualiLookupAction when the 252 * Lookupable is of this class. This method first calls the method from the superclass, which should do all the required field 253 * checking, and then goes through all the specific validations which aren't done in at the JSP level. Both the superclass 254 * method and the various validation methods side-effect the adding of errors to the global error map when the input is found to 255 * have an issue. 256 * 257 * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#validateSearchParameters(java.util.Map) 258 */ 259 @Override 260 public void validateSearchParameters(Map fieldValues) { 261 super.validateSearchParameters(fieldValues); 262 263 validateVendorNumber(fieldValues); 264 validateTaxNumber(fieldValues); 265 266 if (!GlobalVariables.getMessageMap().isEmpty()) { 267 throw new ValidationException("Error(s) in search criteria"); 268 } 269 } 270 271 /** 272 * Validates that the Vendor Number has no more than one dash in it, and does not consist solely of one dash. Then it calls 273 * extractVendorNumberToVendorIds to obtain vendorHeaderGeneratedId and vendorDetailAssignedId and if either one of the ids 274 * cannot be converted to integers, it will add error that the vendor number must be numerics or numerics separated by a dash. 275 * 276 * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup 277 */ 278 private void validateVendorNumber(Map fieldValues) { 279 String vendorNumber = (String) fieldValues.get(VendorPropertyConstants.VENDOR_NUMBER); 280 if (StringUtils.isNotBlank(vendorNumber)) { 281 int dashPos1 = vendorNumber.indexOf(VendorConstants.DASH); 282 if (dashPos1 > -1) { // There's a dash in the number. 283 if (vendorNumber.indexOf(VendorConstants.DASH, dashPos1 + 1) > -1) { // There can't be more than one. 284 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_TOO_MANY_DASHES); 285 } 286 if (vendorNumber.matches("\\-*")) { 287 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_DASHES_ONLY); 288 } 289 } 290 extractVendorNumberToVendorIds(fieldValues, vendorNumber); 291 } 292 } 293 294 /** 295 * Parses the vendorNumber string into vendorHeaderGeneratedIdentifier and vendorDetailAssignedIdentifier, validates that both 296 * fields would be able to be converted into integers, if so it will add both fields into the search criterias map in the 297 * fieldValues and remove the vendorNumber from the fieldValues. If the two fields cannot be converted into integers, this 298 * method will add error message to the errorMap in GlobalVariables that the vendor number must be numeric or numerics separated 299 * by a dash. 300 * 301 * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup 302 * @param vendorNumber vendor number String 303 */ 304 private void extractVendorNumberToVendorIds(Map fieldValues, String vendorNumber) { 305 String vendorHeaderGeneratedIdentifier = null; 306 String vendorDetailAssignedIdentifier = null; 307 int indexOfDash = vendorNumber.indexOf(VendorConstants.DASH); 308 if (indexOfDash < 0) { 309 vendorHeaderGeneratedIdentifier = vendorNumber; 310 } 311 else { 312 vendorHeaderGeneratedIdentifier = vendorNumber.substring(0, indexOfDash); 313 vendorDetailAssignedIdentifier = vendorNumber.substring(indexOfDash + 1, vendorNumber.length()); 314 } 315 try { 316 if (StringUtils.isNotEmpty(vendorHeaderGeneratedIdentifier)) { 317 Integer.parseInt(vendorHeaderGeneratedIdentifier); 318 } 319 if (StringUtils.isNotEmpty(vendorDetailAssignedIdentifier)) { 320 Integer.parseInt(vendorDetailAssignedIdentifier); 321 } 322 fieldValues.remove(VendorPropertyConstants.VENDOR_NUMBER); 323 fieldValues.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendorHeaderGeneratedIdentifier); 324 fieldValues.put(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID, vendorDetailAssignedIdentifier); 325 } 326 catch (NumberFormatException headerExc) { 327 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_NUMERIC_DASH_SEPARATED); 328 } 329 } 330 331 /** 332 * Validates that the tax number is 9 digits long. 333 * 334 * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup 335 */ 336 private void validateTaxNumber(Map fieldValues) { 337 String taxNumber = (String) fieldValues.get(VendorPropertyConstants.VENDOR_TAX_NUMBER); 338 if (StringUtils.isNotBlank(taxNumber) && (!StringUtils.isNumeric(taxNumber) || taxNumber.length() != 9)) { 339 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_TAX_NUM_INVALID); 340 } 341 } 342 343 public void setVendorService(VendorService vendorService) { 344 this.vendorService = vendorService; 345 } 346 347 public void setParameterService(ParameterService parameterService) { 348 this.parameterService = parameterService; 349 } 350 351 }