001/* 002 * Copyright 2007 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 */ 016package org.kuali.ole.vnd.businessobject.lookup; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024import java.util.Properties; 025 026import org.apache.commons.lang.StringUtils; 027import org.kuali.ole.integration.purap.PurchasingAccountsPayableModuleService; 028import org.kuali.ole.sys.OLEConstants; 029import org.kuali.ole.sys.context.SpringContext; 030import org.kuali.ole.vnd.VendorConstants; 031import org.kuali.ole.vnd.VendorKeyConstants; 032import org.kuali.ole.vnd.VendorPropertyConstants; 033import org.kuali.ole.vnd.businessobject.VendorAddress; 034import org.kuali.ole.vnd.businessobject.VendorDetail; 035import org.kuali.ole.vnd.document.service.VendorService; 036import org.kuali.rice.core.web.format.Formatter; 037import org.kuali.rice.coreservice.framework.parameter.ParameterService; 038import org.kuali.rice.kim.api.services.KimApiServiceLocator; 039import org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl; 040import org.kuali.rice.kns.lookup.HtmlData; 041import org.kuali.rice.kns.lookup.HtmlData.AnchorHtmlData; 042import org.kuali.rice.krad.bo.BusinessObject; 043import org.kuali.rice.krad.exception.ValidationException; 044import org.kuali.rice.krad.lookup.CollectionIncomplete; 045import org.kuali.rice.krad.util.BeanPropertyComparator; 046import org.kuali.rice.krad.util.GlobalVariables; 047import org.kuali.rice.krad.util.KRADConstants; 048import org.kuali.rice.krad.util.ObjectUtils; 049import org.kuali.rice.krad.util.UrlFactory; 050 051public class VendorLookupableHelperServiceImpl extends AbstractLookupableHelperServiceImpl { 052 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(VendorLookupableHelperServiceImpl.class); 053 054 protected VendorService vendorService; 055 protected ParameterService parameterService; 056 057 /** 058 * Add custom links to the vendor search results. One to Allow only active parent vendors to create new divisions. Another to 059 * create a link for B2B shopping if PURAP service has been setup to allow for that. 060 * 061 * @see org.kuali.rice.kns.lookup.LookupableHelperService#getCustomActionUrls(org.kuali.rice.krad.bo.BusinessObject, 062 * java.util.List, java.util.List pkNames) 063 */ 064 @Override 065 public List<HtmlData> getCustomActionUrls(BusinessObject businessObject, List pkNames) { 066 VendorDetail vendor = (VendorDetail) businessObject; 067 List<HtmlData> anchorHtmlDataList = new ArrayList<HtmlData>(); 068 069 AnchorHtmlData anchorHtmlData = super.getUrlData(businessObject, KRADConstants.MAINTENANCE_EDIT_METHOD_TO_CALL, pkNames); 070 anchorHtmlDataList.add(anchorHtmlData); 071 String documentTypeName = OLEConstants.Vendor.DOCUMENT_TYPE; 072 String nameSpaceCode = OLEConstants.Vendor.VENDOR_NAMESPACE; 073 074 boolean hasPermission = KimApiServiceLocator.getPermissionService().isAuthorized( 075 GlobalVariables.getUserSession().getPerson().getPrincipalId(), nameSpaceCode, 076 OLEConstants.Vendor.CREATE_VENDOR_DIVISION, Collections.<String, String> emptyMap()); 077 if (vendor.isVendorParentIndicator() && vendor.isActiveIndicator() && hasPermission ) { 078 // only allow active parent vendors to create new divisions 079 anchorHtmlDataList.add(super.getUrlData(businessObject, OLEConstants.MAINTENANCE_NEWWITHEXISTING_ACTION, VendorConstants.CREATE_DIVISION, pkNames)); 080 } 081 082 //Adding a "Shopping" link for B2B vendors. 083 String b2bUrlString = SpringContext.getBean(PurchasingAccountsPayableModuleService.class).getB2BUrlString(); 084 if (vendor.isB2BVendor() && StringUtils.isNotBlank(b2bUrlString)) { 085 Properties theProperties = new Properties(); 086 theProperties.put("channelTitle", "Shop Catalogs"); 087 String backLocation = this.getBackLocation(); 088 int lastSlash = backLocation.lastIndexOf("/"); 089 String returnUrlForShop = backLocation.substring(0, lastSlash+1) + "portal.do"; 090 String href = UrlFactory.parameterizeUrl(returnUrlForShop, theProperties); 091 anchorHtmlDataList.add(new AnchorHtmlData(href + b2bUrlString, null, "shop")); 092 } 093 return anchorHtmlDataList; 094 } 095 096 /** 097 * 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. 098 * 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 099 * 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}