001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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.module.vendor.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.core.bo.BusinessObject;
028: import org.kuali.core.exceptions.ValidationException;
029: import org.kuali.core.lookup.AbstractLookupableHelperServiceImpl;
030: import org.kuali.core.lookup.CollectionIncomplete;
031: import org.kuali.core.util.BeanPropertyComparator;
032: import org.kuali.core.util.GlobalVariables;
033: import org.kuali.core.util.ObjectUtils;
034: import org.kuali.core.util.UrlFactory;
035: import org.kuali.core.web.format.Formatter;
036: import org.kuali.kfs.KFSConstants;
037: import org.kuali.kfs.service.ParameterService;
038: import org.kuali.module.vendor.VendorConstants;
039: import org.kuali.module.vendor.VendorKeyConstants;
040: import org.kuali.module.vendor.VendorParameterConstants;
041: import org.kuali.module.vendor.VendorPropertyConstants;
042: import org.kuali.module.vendor.bo.VendorAddress;
043: import org.kuali.module.vendor.bo.VendorDetail;
044: import org.kuali.module.vendor.service.VendorService;
045:
046: public class VendorLookupableHelperServiceImpl extends
047: AbstractLookupableHelperServiceImpl {
048: private VendorService vendorService;
049: private ParameterService parameterService;
050:
051: private static String VNDR_LOOKUP_MIN_NAME_LENGTH;
052: private static String VNDR_MIN_NUM_LOOKUP_CRITERIA;
053:
054: /**
055: * Allows only active parent vendors to create new divisions
056: *
057: * @see org.kuali.core.lookup.AbstractLookupableHelperServiceImpl#getActionUrls(org.kuali.core.bo.BusinessObject)
058: */
059: @Override
060: public String getActionUrls(BusinessObject bo) {
061: VendorDetail vendor = (VendorDetail) bo;
062: StringBuffer actions = new StringBuffer(super .getActionUrls(bo));
063: actions.append(" ");
064: if (vendor.isVendorParentIndicator()
065: && vendor.isActiveIndicator()) {
066: // only allow active parent vendors to create new divisions
067: actions.append(getMaintenanceUrl(bo,
068: KFSConstants.MAINTENANCE_NEWWITHEXISTING_ACTION));
069: }
070:
071: return actions.toString();
072: }
073:
074: /**
075: * 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.
076: * 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
077: * link to create a new division only if the vendor is a parent vendor, and also remove the vendor detail assigned id from the
078: * 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
079: * 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").
080: * We'll always add the vendor header id in the query string in all links.
081: *
082: * @see org.kuali.core.lookup.AbstractLookupableHelperServiceImpl#getMaintenanceUrl(org.kuali.core.bo.BusinessObject,
083: * java.lang.String)
084: */
085: @Override
086: public String getMaintenanceUrl(BusinessObject bo,
087: String methodToCall) {
088: if (!methodToCall.equals(KFSConstants.COPY_METHOD)) {
089: Properties parameters = new Properties();
090: parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER,
091: methodToCall);
092: parameters.put(
093: KFSConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, bo
094: .getClass().getName());
095:
096: List pkNames = getPersistenceStructureService()
097: .listPrimaryKeyFieldNames(getBusinessObjectClass());
098: for (Iterator<String> iter = pkNames.iterator(); iter
099: .hasNext();) {
100: String fieldNm = iter.next();
101: if (!fieldNm
102: .equals(VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID)
103: || !((VendorDetail) bo)
104: .isVendorParentIndicator()
105: || (((VendorDetail) bo)
106: .isVendorParentIndicator())
107: && !methodToCall
108: .equals(KFSConstants.MAINTENANCE_NEWWITHEXISTING_ACTION)) {
109: Object fieldVal = ObjectUtils.getPropertyValue(bo,
110: fieldNm);
111: if (fieldVal == null) {
112: fieldVal = KFSConstants.EMPTY_STRING;
113: }
114: if (fieldVal instanceof java.sql.Date) {
115: String formattedString = KFSConstants.EMPTY_STRING;
116: if (Formatter
117: .findFormatter(fieldVal.getClass()) != null) {
118: Formatter formatter = Formatter
119: .getFormatter(fieldVal.getClass());
120: formattedString = (String) formatter
121: .format(fieldVal);
122: fieldVal = formattedString;
123: }
124: }
125: parameters.put(fieldNm, fieldVal.toString());
126: }
127: }
128: if (methodToCall
129: .equals(KFSConstants.MAINTENANCE_NEWWITHEXISTING_ACTION)) {
130: methodToCall = VendorConstants.CREATE_NEW_DIVISION;
131: }
132: String url = UrlFactory.parameterizeUrl(
133: KFSConstants.MAINTENANCE_ACTION, parameters);
134: url = "<a href=\"" + url + "\">" + methodToCall + "</a>";
135: return url;
136: } else {
137:
138: return KFSConstants.EMPTY_STRING;
139: }
140: }
141:
142: /**
143: * Overrides the getSearchResults in the super class so that we can do some customization in our vendor lookup. For example, for
144: * vendor name as the search criteria, we want to search both the vendor detail table and the vendor alias table for the vendor
145: * name. Display the vendor's default address state in the search results.
146: *
147: * @see org.kuali.core.lookup.Lookupable#getSearchResults(java.util.Map)
148: */
149: @Override
150: public List<BusinessObject> getSearchResults(
151: Map<String, String> fieldValues) {
152: boolean unbounded = false;
153: super .setBackLocation((String) fieldValues
154: .get(KFSConstants.BACK_LOCATION));
155: super .setDocFormKey((String) fieldValues
156: .get(KFSConstants.DOC_FORM_KEY));
157:
158: String vendorName = fieldValues
159: .get(VendorPropertyConstants.VENDOR_NAME);
160:
161: List<BusinessObject> searchResults = (List) getLookupService()
162: .findCollectionBySearchHelper(getBusinessObjectClass(),
163: fieldValues, unbounded);
164:
165: // re-run the query against the vendor name alias field if necessary and merge the results
166: // this could double the returned results for the search, but there is no alternative at present
167: // without refactoring of the lookup service
168: if (StringUtils.isNotEmpty(vendorName)) {
169: // if searching by vendorName, also search in list of alias names
170: fieldValues
171: .put(
172: VendorPropertyConstants.VENDOR_ALIAS_NAME_FULL_PATH,
173: vendorName);
174: // also make sure that we only use active aliases to match the query string
175: fieldValues.put(
176: VendorPropertyConstants.VENDOR_ALIAS_ACTIVE, "Y");
177: fieldValues.remove(VendorPropertyConstants.VENDOR_NAME);
178: List<BusinessObject> searchResults2 = (List) getLookupService()
179: .findCollectionBySearchHelper(
180: getBusinessObjectClass(), fieldValues,
181: unbounded);
182:
183: searchResults.addAll(searchResults2);
184: if (searchResults instanceof CollectionIncomplete
185: && searchResults2 instanceof CollectionIncomplete) {
186: ((CollectionIncomplete) searchResults)
187: .setActualSizeIfTruncated(((CollectionIncomplete) searchResults)
188: .getActualSizeIfTruncated().longValue()
189: + ((CollectionIncomplete) searchResults2)
190: .getActualSizeIfTruncated()
191: .longValue());
192: }
193: }
194:
195: List<BusinessObject> processedSearchResults = new ArrayList();
196:
197: // loop through results
198: for (BusinessObject bo : searchResults) {
199: VendorDetail vendor = (VendorDetail) bo;
200:
201: // if its a top level vendor, search for its divisions and add them to the appropriate list then add the vendor to the
202: // return results
203: // if its a division, see if we already have the parent and if not, retrieve it and its divisions then add the parent to
204: // the return results
205:
206: // If this vendor is not already in the processedSearchResults, let's do further processing (e.g. setting the state for
207: // lookup from default address, etc)
208: // and then add it in the processedSearchResults.
209: if (!processedSearchResults.contains(vendor)) {
210: Map<String, String> tmpValues = new HashMap<String, String>();
211: List<VendorDetail> relatedVendors = new ArrayList();
212: tmpValues
213: .put(
214: VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID,
215: vendor
216: .getVendorHeaderGeneratedIdentifier()
217: .toString());
218: relatedVendors = (List) getLookupService()
219: .findCollectionBySearchHelper(
220: getBusinessObjectClass(), tmpValues,
221: unbounded);
222:
223: for (VendorDetail tmpVendor : relatedVendors) {
224: if (tmpVendor != null
225: && !processedSearchResults
226: .contains(tmpVendor)) {
227: // populate state from default address
228: updatedefaultVendorAddress(tmpVendor);
229: processedSearchResults.add(tmpVendor);
230: }
231: }
232:
233: if (!processedSearchResults.contains(vendor)) {
234: updatedefaultVendorAddress(vendor);
235: processedSearchResults.add(vendor);
236: }
237: }
238: }
239:
240: for (BusinessObject bo : processedSearchResults) {
241: VendorDetail vendor = (VendorDetail) bo;
242: if (!vendor.isVendorParentIndicator()) {
243: // find the parent object in the details collection and add that§
244: for (BusinessObject tmpObject : processedSearchResults) {
245: VendorDetail tmpVendor = (VendorDetail) tmpObject;
246: if (tmpVendor
247: .getVendorHeaderGeneratedIdentifier()
248: .equals(
249: vendor
250: .getVendorHeaderGeneratedIdentifier())
251: && tmpVendor
252: .getVendorDetailAssignedIdentifier() == 0) {
253: vendor.setVendorName(tmpVendor.getVendorName()
254: + " > " + vendor.getVendorName());
255: break;
256: }
257: }
258: }
259: }
260:
261: searchResults.clear();
262: searchResults.addAll(processedSearchResults);
263:
264: // sort list if default sort column given
265: List<String> defaultSortColumns = getDefaultSortColumns();
266: if (defaultSortColumns.size() > 0) {
267: Collections.sort(searchResults, new BeanPropertyComparator(
268: getDefaultSortColumns(), true));
269: }
270:
271: return searchResults;
272: }
273:
274: /**
275: * Populates address fields from default address
276: *
277: * @param vendor venodrDetail
278: */
279: private void updatedefaultVendorAddress(VendorDetail vendor) {
280: VendorAddress defaultAddress = vendorService
281: .getVendorDefaultAddress(vendor.getVendorAddresses(),
282: vendor.getVendorHeader().getVendorType()
283: .getAddressType()
284: .getVendorAddressTypeCode(), "");
285: if (defaultAddress != null
286: && defaultAddress.getVendorState() != null) {
287: vendor.setVendorStateForLookup(defaultAddress
288: .getVendorState().getPostalStateName());
289: vendor.setDefaultAddressLine1(defaultAddress
290: .getVendorLine1Address());
291: vendor.setDefaultAddressLine2(defaultAddress
292: .getVendorLine2Address());
293: vendor.setDefaultAddressCity(defaultAddress
294: .getVendorCityName());
295: vendor.setDefaultAddressPostalCode(defaultAddress
296: .getVendorZipCode());
297: vendor.setDefaultAddressStateCode(defaultAddress
298: .getVendorStateCode());
299: vendor.setDefaultAddressCountryCode(defaultAddress
300: .getVendorCountryCode());
301: }
302: }
303:
304: /**
305: * Overrides a method of the superclass and is now called instead of that one by the Search method of KualiLookupAction when the
306: * Lookupable is of this class. This method first calls the method from the superclass, which should do all the required field
307: * checking, and then orchestrates all the specific validations which aren't done in at the JSP level. Both the superclass
308: * method and the various validation methods side-effect the adding of errors to the global error map when the input is found to
309: * have an issue.
310: *
311: * @see org.kuali.core.lookup.AbstractLookupableHelperServiceImpl#validateSearchParameters(java.util.Map)
312: */
313: @Override
314: public void validateSearchParameters(Map fieldValues) {
315: super .validateSearchParameters(fieldValues);
316:
317: validateVendorNumber(fieldValues);
318: validateVendorName(fieldValues);
319: validateTaxNumber(fieldValues);
320:
321: if (!GlobalVariables.getErrorMap().isEmpty()) {
322: throw new ValidationException("Error(s) in search criteria");
323: }
324: }
325:
326: /**
327: * Ensures that if a string is entered in the Vendor Name field, it is at least the minimum number of characters in length.
328: *
329: * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup
330: */
331: private void validateVendorName(Map fieldValues) {
332: String vendorName = (String) fieldValues
333: .get(VendorPropertyConstants.VENDOR_NAME);
334: if (StringUtils.isNotBlank(vendorName)) {
335: if (ObjectUtils.isNull(VNDR_LOOKUP_MIN_NAME_LENGTH)) {
336: VNDR_LOOKUP_MIN_NAME_LENGTH = parameterService
337: .getParameterValue(
338: VendorDetail.class,
339: VendorParameterConstants.PURAP_VNDR_LOOKUP_MIN_NAME_LENGTH);
340: }
341: if (vendorName.length() < Integer
342: .parseInt(VNDR_LOOKUP_MIN_NAME_LENGTH)) {
343: GlobalVariables
344: .getErrorMap()
345: .putError(
346: VendorPropertyConstants.VENDOR_NAME,
347: VendorKeyConstants.ERROR_VENDOR_LOOKUP_NAME_TOO_SHORT,
348: VNDR_LOOKUP_MIN_NAME_LENGTH);
349: }
350: }
351: }
352:
353: /**
354: * Validates that the Vendor Number has no more than one dash in it, and does not consist solely of one dash. Then it calls
355: * extractVendorNumberToVendorIds to obtain vendorHeaderGeneratedId and vendorDetailAssignedId and if either one of the ids
356: * cannot be converted to integers, it will add error that the vendor number must be numerics or numerics separated by a dash.
357: *
358: * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup
359: */
360: private void validateVendorNumber(Map fieldValues) {
361: String vendorNumber = (String) fieldValues
362: .get(VendorPropertyConstants.VENDOR_NUMBER);
363: if (StringUtils.isNotBlank(vendorNumber)) {
364: int dashPos1 = vendorNumber.indexOf(VendorConstants.DASH);
365: if (dashPos1 > -1) { // There's a dash in the number.
366: if (vendorNumber.indexOf(VendorConstants.DASH,
367: dashPos1 + 1) > -1) { // There can't be more than one.
368: GlobalVariables
369: .getErrorMap()
370: .putError(
371: VendorPropertyConstants.VENDOR_NUMBER,
372: VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_TOO_MANY_DASHES);
373: }
374: if (vendorNumber.matches("\\-*")) {
375: GlobalVariables
376: .getErrorMap()
377: .putError(
378: VendorPropertyConstants.VENDOR_NUMBER,
379: VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_DASHES_ONLY);
380: }
381: }
382: extractVendorNumberToVendorIds(fieldValues, vendorNumber);
383: }
384: }
385:
386: /**
387: * Parses the vendorNumber string into vendorHeaderGeneratedIdentifier and vendorDetailAssignedIdentifier, validates that both
388: * fields would be able to be converted into integers, if so it will add both fields into the search criterias map in the
389: * fieldValues and remove the vendorNumber from the fieldValues. If the two fields cannot be converted into integers, this
390: * method will add error message to the errorMap in GlobalVariables that the vendor number must be numeric or numerics separated
391: * by a dash.
392: *
393: * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup
394: * @param vendorNumber venodr number String
395: */
396: private void extractVendorNumberToVendorIds(Map fieldValues,
397: String vendorNumber) {
398: String vendorHeaderGeneratedIdentifier = null;
399: String vendorDetailAssignedIdentifier = null;
400: int indexOfDash = vendorNumber.indexOf(VendorConstants.DASH);
401: if (indexOfDash < 0) {
402: vendorHeaderGeneratedIdentifier = vendorNumber;
403: } else {
404: vendorHeaderGeneratedIdentifier = vendorNumber.substring(0,
405: indexOfDash);
406: vendorDetailAssignedIdentifier = vendorNumber.substring(
407: indexOfDash + 1, vendorNumber.length());
408: }
409: try {
410: if (StringUtils.isNotEmpty(vendorHeaderGeneratedIdentifier)) {
411: Integer.parseInt(vendorHeaderGeneratedIdentifier);
412: }
413: if (StringUtils.isNotEmpty(vendorDetailAssignedIdentifier)) {
414: Integer.parseInt(vendorDetailAssignedIdentifier);
415: }
416: fieldValues.remove(VendorPropertyConstants.VENDOR_NUMBER);
417: fieldValues.put(
418: VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID,
419: vendorHeaderGeneratedIdentifier);
420: fieldValues.put(
421: VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID,
422: vendorDetailAssignedIdentifier);
423: } catch (NumberFormatException headerExc) {
424: GlobalVariables
425: .getErrorMap()
426: .putError(
427: VendorPropertyConstants.VENDOR_NUMBER,
428: VendorKeyConstants.ERROR_VENDOR_LOOKUP_VNDR_NUM_NUMERIC_DASH_SEPARATED);
429: }
430: }
431:
432: /**
433: * Validates that the tax number is 9 digits long.
434: *
435: * @param fieldValues a Map containing only those key-value pairs that have been filled in on the lookup
436: */
437: private void validateTaxNumber(Map fieldValues) {
438: String taxNumber = (String) fieldValues
439: .get(VendorPropertyConstants.VENDOR_TAX_NUMBER);
440: if (StringUtils.isNotBlank(taxNumber)
441: && (!StringUtils.isNumeric(taxNumber) || taxNumber
442: .length() != 9)) {
443: GlobalVariables
444: .getErrorMap()
445: .putError(
446: VendorPropertyConstants.VENDOR_TAX_NUMBER,
447: VendorKeyConstants.ERROR_VENDOR_LOOKUP_TAX_NUM_INVALID);
448: }
449: }
450:
451: public void setVendorService(VendorService vendorService) {
452: this .vendorService = vendorService;
453: }
454:
455: public void setParameterService(ParameterService parameterService) {
456: this.parameterService = parameterService;
457: }
458:
459: }
|