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 }