/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.vnd.businessobject.lookup; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.integration.purap.PurchasingAccountsPayableModuleService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.vnd.VendorConstants; import org.kuali.kfs.vnd.VendorKeyConstants; import org.kuali.kfs.vnd.VendorPropertyConstants; import org.kuali.kfs.vnd.businessobject.VendorAddress; import org.kuali.kfs.vnd.businessobject.VendorDetail; import org.kuali.kfs.vnd.document.service.VendorService; import org.kuali.rice.core.web.format.Formatter; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl; import org.kuali.rice.kns.lookup.HtmlData; import org.kuali.rice.kns.lookup.HtmlData.AnchorHtmlData; import org.kuali.rice.krad.bo.BusinessObject; import org.kuali.rice.krad.exception.ValidationException; import org.kuali.rice.krad.lookup.CollectionIncomplete; import org.kuali.rice.krad.util.BeanPropertyComparator; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.KRADConstants; import org.kuali.rice.krad.util.ObjectUtils; import org.kuali.rice.krad.util.UrlFactory; public class VendorLookupableHelperServiceImpl extends AbstractLookupableHelperServiceImpl { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(VendorLookupableHelperServiceImpl.class); protected VendorService vendorService; protected ParameterService parameterService; /** * Add custom links to the vendor search results. One to Allow only active parent vendors to create new divisions. Another to * create a link for B2B shopping if PURAP service has been setup to allow for that. * * @see org.kuali.rice.kns.lookup.LookupableHelperService#getCustomActionUrls(org.kuali.rice.krad.bo.BusinessObject, * java.util.List, java.util.List pkNames) */ @Override public List<HtmlData> getCustomActionUrls(BusinessObject businessObject, List pkNames) { VendorDetail vendor = (VendorDetail) businessObject; List<HtmlData> anchorHtmlDataList = new ArrayList<HtmlData>(); AnchorHtmlData anchorHtmlData = super.getUrlData(businessObject, KRADConstants.MAINTENANCE_EDIT_METHOD_TO_CALL, pkNames); anchorHtmlDataList.add(anchorHtmlData); if (vendor.isVendorParentIndicator() && vendor.isActiveIndicator()) { // only allow active parent vendors to create new divisions anchorHtmlDataList.add(super.getUrlData(businessObject, KFSConstants.MAINTENANCE_NEWWITHEXISTING_ACTION, VendorConstants.CREATE_DIVISION, pkNames)); } //Adding a "Shopping" link for B2B vendors. String b2bUrlString = SpringContext.getBean(PurchasingAccountsPayableModuleService.class).getB2BUrlString(); if (vendor.isB2BVendor() && StringUtils.isNotBlank(b2bUrlString)) { Properties theProperties = new Properties(); theProperties.put("channelTitle", "Shop Catalogs"); String backLocation = this.getBackLocation(); int lastSlash = backLocation.lastIndexOf("/"); String returnUrlForShop = backLocation.substring(0, lastSlash+1) + "portal.do"; String href = UrlFactory.parameterizeUrl(returnUrlForShop, theProperties); anchorHtmlDataList.add(new AnchorHtmlData(href + b2bUrlString, null, "shop")); } return anchorHtmlDataList; } /** * 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. * 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 * link to create a new division only if the vendor is a parent vendor, and also remove the vendor detail assigned id from the * 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 * 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"). * We'll always add the vendor header id in the query string in all links. * * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#getActionUrlHref(org.kuali.rice.krad.bo.BusinessObject, java.lang.String, java.util.List) */ @Override protected String getActionUrlHref(BusinessObject businessObject, String methodToCall, List pkNames){ if (!methodToCall.equals(KFSConstants.COPY_METHOD)) { Properties parameters = new Properties(); parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, methodToCall); parameters.put(KFSConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, businessObject.getClass().getName()); for (Iterator<String> iter = pkNames.iterator(); iter.hasNext();) { String fieldNm = iter.next(); if (!fieldNm.equals(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID) || !((VendorDetail) businessObject).isVendorParentIndicator() || (((VendorDetail) businessObject).isVendorParentIndicator()) && !methodToCall.equals(KFSConstants.MAINTENANCE_NEWWITHEXISTING_ACTION)) { Object fieldVal = ObjectUtils.getPropertyValue(businessObject, fieldNm); if (fieldVal == null) { fieldVal = KFSConstants.EMPTY_STRING; } if (fieldVal instanceof java.sql.Date) { String formattedString = KFSConstants.EMPTY_STRING; if (Formatter.findFormatter(fieldVal.getClass()) != null) { Formatter formatter = Formatter.getFormatter(fieldVal.getClass()); formattedString = (String) formatter.format(fieldVal); fieldVal = formattedString; } } parameters.put(fieldNm, fieldVal.toString()); } } return UrlFactory.parameterizeUrl(KFSConstants.MAINTENANCE_ACTION, parameters); } else { return KFSConstants.EMPTY_STRING; } } /** * Overrides the getSearchResults in the super class so that we can do some customization in our vendor lookup. For example, for * vendor name as the search criteria, we want to search both the vendor detail table and the vendor alias table for the vendor * name. Display the vendor's default address state in the search results. * * @see org.kuali.rice.kns.lookup.Lookupable#getSearchResults(java.util.Map) */ @Override public List<BusinessObject> getSearchResults(Map<String, String> fieldValues) { boolean unbounded = false; super.setBackLocation(fieldValues.get(KFSConstants.BACK_LOCATION)); super.setDocFormKey(fieldValues.get(KFSConstants.DOC_FORM_KEY)); String vendorName = fieldValues.get(VendorPropertyConstants.VENDOR_NAME); List<BusinessObject> searchResults = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), fieldValues, unbounded); // re-run the query against the vendor name alias field if necessary and merge the results // this could double the returned results for the search, but there is no alternative at present // without refactoring of the lookup service if (StringUtils.isNotEmpty(vendorName)) { // if searching by vendorName, also search in list of alias names fieldValues.put(VendorPropertyConstants.VENDOR_ALIAS_NAME_FULL_PATH, vendorName); // also make sure that we only use active aliases to match the query string fieldValues.put(VendorPropertyConstants.VENDOR_ALIAS_ACTIVE, "Y"); fieldValues.remove(VendorPropertyConstants.VENDOR_NAME); List<BusinessObject> searchResults2 = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), fieldValues, unbounded); searchResults.addAll(searchResults2); if (searchResults instanceof CollectionIncomplete && searchResults2 instanceof CollectionIncomplete) { ((CollectionIncomplete) searchResults).setActualSizeIfTruncated(((CollectionIncomplete) searchResults).getActualSizeIfTruncated().longValue() + ((CollectionIncomplete) searchResults2).getActualSizeIfTruncated().longValue()); } } List<BusinessObject> processedSearchResults = new ArrayList(); // loop through results for (BusinessObject businessObject : searchResults) { VendorDetail vendor = (VendorDetail) businessObject; // if its a top level vendor, search for its divisions and add them to the appropriate list then add the vendor to the // return results // if its a division, see if we already have the parent and if not, retrieve it and its divisions then add the parent to // the return results // If this vendor is not already in the processedSearchResults, let's do further processing (e.g. setting the state for // lookup from default address, etc) // and then add it in the processedSearchResults. if (!processedSearchResults.contains(vendor)) { Map<String, String> tmpValues = new HashMap<String, String>(); List<VendorDetail> relatedVendors = new ArrayList(); tmpValues.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendor.getVendorHeaderGeneratedIdentifier().toString()); relatedVendors = (List) getLookupService().findCollectionBySearchHelper(getBusinessObjectClass(), tmpValues, unbounded); for (VendorDetail tmpVendor : relatedVendors) { if (ObjectUtils.isNotNull(tmpVendor) && !processedSearchResults.contains(tmpVendor)) { // populate state from default address updateDefaultVendorAddress(tmpVendor); processedSearchResults.add(tmpVendor); } } if (!processedSearchResults.contains(vendor)) { updateDefaultVendorAddress(vendor); processedSearchResults.add(vendor); } } } for (BusinessObject businessObject : processedSearchResults) { VendorDetail vendor = (VendorDetail) businessObject; if (!vendor.isVendorParentIndicator()) { // find the parent object in the details collection and add that for (BusinessObject tmpObject : processedSearchResults) { VendorDetail tmpVendor = (VendorDetail) tmpObject; if (tmpVendor.getVendorHeaderGeneratedIdentifier().equals(vendor.getVendorHeaderGeneratedIdentifier()) && tmpVendor.isVendorParentIndicator()) { vendor.setVendorName(tmpVendor.getVendorName() + " > " + vendor.getVendorName()); break; } } } } searchResults.clear(); searchResults.addAll(processedSearchResults); // sort list if default sort column given List<String> defaultSortColumns = getDefaultSortColumns(); if (defaultSortColumns.size() > 0) { Collections.sort(searchResults, new BeanPropertyComparator(getDefaultSortColumns(), true)); } return searchResults; } /** * Populates address fields from default address * * @param vendor venodrDetail */ private void updateDefaultVendorAddress(VendorDetail vendor) { VendorAddress defaultAddress = vendorService.getVendorDefaultAddress(vendor.getVendorAddresses(), vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode(), ""); if (ObjectUtils.isNotNull(defaultAddress)) { if (ObjectUtils.isNotNull(defaultAddress.getVendorState())) { vendor.setVendorStateForLookup(defaultAddress.getVendorState().getName()); } else { if ( LOG.isDebugEnabled() ) { LOG.debug( "Warning - unable to retrieve state for " + defaultAddress.getVendorCountryCode() + " / " + defaultAddress.getVendorStateCode() ); } vendor.setVendorStateForLookup(""); } vendor.setDefaultAddressLine1(defaultAddress.getVendorLine1Address()); vendor.setDefaultAddressLine2(defaultAddress.getVendorLine2Address()); vendor.setDefaultAddressCity(defaultAddress.getVendorCityName()); vendor.setDefaultAddressPostalCode(defaultAddress.getVendorZipCode()); vendor.setDefaultAddressStateCode(defaultAddress.getVendorStateCode()); vendor.setDefaultAddressInternationalProvince(defaultAddress.getVendorAddressInternationalProvinceName()); vendor.setDefaultAddressCountryCode(defaultAddress.getVendorCountryCode()); vendor.setDefaultFaxNumber(defaultAddress.getVendorFaxNumber()); } else { if ( LOG.isDebugEnabled() ) { LOG.debug( "Warning - default vendor address was null for " + vendor.getVendorNumber() + " / " + vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode() ); } vendor.setVendorStateForLookup(""); } } /** * Overrides a method of the superclass and is now called instead of that one by the Search method of KualiLookupAction when the * Lookupable is of this class. This method first calls the method from the superclass, which should do all the required field * checking, and then goes through all the specific validations which aren't done in at the JSP level. Both the superclass * method and the various validation methods side-effect the adding of errors to the global error map when the input is found to * have an issue. * * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#validateSearchParameters(java.util.Map) */ @Override public void validateSearchParameters(Map fieldValues) { super.validateSearchParameters(fieldValues); validateVendorNumber(fieldValues); validateTaxNumber(fieldValues); if (GlobalVariables.getMessageMap().hasErrors()) { throw new ValidationException("Error(s) in search criteria"); } } /** * Validates that the Vendor Number has no more than one dash in it, and does not consist solely of one dash. Then it calls * extractVendorNumberToVendorIds to obtain vendorHeaderGeneratedId and vendorDetailAssignedId and if either one of the ids * cannot be converted to integers, it will add error that the vendor number must be numerics or numerics separated by a dash. * * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup */ private void validateVendorNumber(Map fieldValues) { String vendorNumber = (String) fieldValues.get(VendorPropertyConstants.VENDOR_NUMBER); if (StringUtils.isNotBlank(vendorNumber)) { int dashPos1 = vendorNumber.indexOf(VendorConstants.DASH); if (dashPos1 > -1) { // There's a dash in the number. if (vendorNumber.indexOf(VendorConstants.DASH, dashPos1 + 1) > -1) { // There can't be more than one. GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_TOO_MANY_DASHES); } if (vendorNumber.matches("\\-*")) { GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_DASHES_ONLY); } } extractVendorNumberToVendorIds(fieldValues, vendorNumber); } } /** * Parses the vendorNumber string into vendorHeaderGeneratedIdentifier and vendorDetailAssignedIdentifier, validates that both * fields would be able to be converted into integers, if so it will add both fields into the search criterias map in the * fieldValues and remove the vendorNumber from the fieldValues. If the two fields cannot be converted into integers, this * method will add error message to the errorMap in GlobalVariables that the vendor number must be numeric or numerics separated * by a dash. * * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup * @param vendorNumber vendor number String */ private void extractVendorNumberToVendorIds(Map fieldValues, String vendorNumber) { String vendorHeaderGeneratedIdentifier = null; String vendorDetailAssignedIdentifier = null; int indexOfDash = vendorNumber.indexOf(VendorConstants.DASH); if (indexOfDash < 0) { vendorHeaderGeneratedIdentifier = vendorNumber; } else { vendorHeaderGeneratedIdentifier = vendorNumber.substring(0, indexOfDash); vendorDetailAssignedIdentifier = vendorNumber.substring(indexOfDash + 1, vendorNumber.length()); } try { if (StringUtils.isNotEmpty(vendorHeaderGeneratedIdentifier)) { Integer.parseInt(vendorHeaderGeneratedIdentifier); } if (StringUtils.isNotEmpty(vendorDetailAssignedIdentifier)) { Integer.parseInt(vendorDetailAssignedIdentifier); } fieldValues.remove(VendorPropertyConstants.VENDOR_NUMBER); fieldValues.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendorHeaderGeneratedIdentifier); if (StringUtils.isNotEmpty(vendorDetailAssignedIdentifier)) { fieldValues.put(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID, vendorDetailAssignedIdentifier); } } catch (NumberFormatException headerExc) { GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_NUMERIC_DASH_SEPARATED); } } /** * Validates that the tax number is 9 digits long. * * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup */ private void validateTaxNumber(Map fieldValues) { String taxNumber = (String) fieldValues.get(VendorPropertyConstants.VENDOR_TAX_NUMBER); if (StringUtils.isNotBlank(taxNumber) && (!StringUtils.isNumeric(taxNumber) || taxNumber.length() != 9)) { GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_VENDOR_LOOKUP_TAX_NUM_INVALID); } } public void setVendorService(VendorService vendorService) { this.vendorService = vendorService; } @Override public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } }