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 = null; 243 if(vendor.getVendorAddresses()!=null && vendor.getVendorHeader()!=null && vendor.getVendorHeader().getVendorType()!=null && vendor.getVendorHeader().getVendorType().getAddressType()!=null && vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode()!=null){ 244 defaultAddress = vendorService.getVendorDefaultAddress(vendor.getVendorAddresses(), vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode(), ""); 245 } 246 if (ObjectUtils.isNotNull(defaultAddress)) { 247 248 if (ObjectUtils.isNotNull(defaultAddress.getVendorState())) { 249 vendor.setVendorStateForLookup(defaultAddress.getVendorState().getName()); 250 } else { 251 if ( LOG.isDebugEnabled() ) { 252 LOG.debug( "Warning - unable to retrieve state for " + defaultAddress.getVendorCountryCode() + " / " + defaultAddress.getVendorStateCode() ); 253 } 254 vendor.setVendorStateForLookup(""); 255 } 256 vendor.setDefaultAddressLine1(defaultAddress.getVendorLine1Address()); 257 vendor.setDefaultAddressLine2(defaultAddress.getVendorLine2Address()); 258 vendor.setDefaultAddressCity(defaultAddress.getVendorCityName()); 259 vendor.setDefaultAddressPostalCode(defaultAddress.getVendorZipCode()); 260 vendor.setDefaultAddressStateCode(defaultAddress.getVendorStateCode()); 261 vendor.setDefaultAddressInternationalProvince(defaultAddress.getVendorAddressInternationalProvinceName()); 262 vendor.setDefaultAddressCountryCode(defaultAddress.getVendorCountryCode()); 263 vendor.setDefaultFaxNumber(defaultAddress.getVendorFaxNumber()); 264 } else { 265 if ( LOG.isDebugEnabled() ) { 266 LOG.debug( "Warning - default vendor address was null for " + vendor.getVendorNumber() + " / " + vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode() ); 267 } 268 vendor.setVendorStateForLookup(""); 269 } 270 } 271 272 /** 273 * Overrides a method of the superclass and is now called instead of that one by the Search method of KualiLookupAction when the 274 * Lookupable is of this class. This method first calls the method from the superclass, which should do all the required field 275 * checking, and then goes through all the specific validations which aren't done in at the JSP level. Both the superclass 276 * method and the various validation methods side-effect the adding of errors to the global error map when the input is found to 277 * have an issue. 278 * 279 * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#validateSearchParameters(java.util.Map) 280 */ 281 @Override 282 public void validateSearchParameters(Map fieldValues) { 283 super.validateSearchParameters(fieldValues); 284 285 validateVendorNumber(fieldValues); 286 validateTaxNumber(fieldValues); 287 288 if (GlobalVariables.getMessageMap().hasErrors()) { 289 throw new ValidationException("Error(s) in search criteria"); 290 } 291 } 292 293 /** 294 * Validates that the Vendor Number has no more than one dash in it, and does not consist solely of one dash. Then it calls 295 * extractVendorNumberToVendorIds to obtain vendorHeaderGeneratedId and vendorDetailAssignedId and if either one of the ids 296 * cannot be converted to integers, it will add error that the vendor number must be numerics or numerics separated by a dash. 297 * 298 * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup 299 */ 300 private void validateVendorNumber(Map fieldValues) { 301 String vendorNumber = (String) fieldValues.get(VendorPropertyConstants.VENDOR_NUMBER); 302 if (StringUtils.isNotBlank(vendorNumber)) { 303 int dashPos1 = vendorNumber.indexOf(VendorConstants.DASH); 304 if (dashPos1 > -1) { // There's a dash in the number. 305 if (vendorNumber.indexOf(VendorConstants.DASH, dashPos1 + 1) > -1) { // There can't be more than one. 306 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_TOO_MANY_DASHES); 307 } 308 if (vendorNumber.matches("\\-*")) { 309 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_DASHES_ONLY); 310 } 311 } 312 extractVendorNumberToVendorIds(fieldValues, vendorNumber); 313 } 314 } 315 316 /** 317 * Parses the vendorNumber string into vendorHeaderGeneratedIdentifier and vendorDetailAssignedIdentifier, validates that both 318 * fields would be able to be converted into integers, if so it will add both fields into the search criterias map in the 319 * fieldValues and remove the vendorNumber from the fieldValues. If the two fields cannot be converted into integers, this 320 * method will add error message to the errorMap in GlobalVariables that the vendor number must be numeric or numerics separated 321 * by a dash. 322 * 323 * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup 324 * @param vendorNumber vendor number String 325 */ 326 private void extractVendorNumberToVendorIds(Map fieldValues, String vendorNumber) { 327 String vendorHeaderGeneratedIdentifier = null; 328 String vendorDetailAssignedIdentifier = null; 329 int indexOfDash = vendorNumber.indexOf(VendorConstants.DASH); 330 if (indexOfDash < 0) { 331 vendorHeaderGeneratedIdentifier = vendorNumber; 332 } 333 else { 334 vendorHeaderGeneratedIdentifier = vendorNumber.substring(0, indexOfDash); 335 vendorDetailAssignedIdentifier = vendorNumber.substring(indexOfDash + 1, vendorNumber.length()); 336 } 337 try { 338 if (StringUtils.isNotEmpty(vendorHeaderGeneratedIdentifier)) { 339 Integer.parseInt(vendorHeaderGeneratedIdentifier); 340 } 341 if (StringUtils.isNotEmpty(vendorDetailAssignedIdentifier)) { 342 Integer.parseInt(vendorDetailAssignedIdentifier); 343 } 344 fieldValues.remove(VendorPropertyConstants.VENDOR_NUMBER); 345 fieldValues.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendorHeaderGeneratedIdentifier); 346 if (StringUtils.isNotEmpty(vendorDetailAssignedIdentifier)) { 347 fieldValues.put(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID, vendorDetailAssignedIdentifier); 348 } 349 } 350 catch (NumberFormatException headerExc) { 351 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_NUMERIC_DASH_SEPARATED); 352 } 353 } 354 355 /** 356 * Validates that the tax number is 9 digits long. 357 * 358 * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup 359 */ 360 private void validateTaxNumber(Map fieldValues) { 361 String taxNumber = (String) fieldValues.get(VendorPropertyConstants.VENDOR_TAX_NUMBER); 362 if (StringUtils.isNotBlank(taxNumber) && (!StringUtils.isNumeric(taxNumber) || taxNumber.length() != 9)) { 363 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_TAX_NUM_INVALID); 364 } 365 } 366 367 public void setVendorService(VendorService vendorService) { 368 this.vendorService = vendorService; 369 } 370 371 @Override 372 public void setParameterService(ParameterService parameterService) { 373 this.parameterService = parameterService; 374 } 375 376}