001: /*
002: * $Id: ShippingEvents.java,v 1.10 2004/01/24 14:51:40 ajzeneski Exp $
003: *
004: * Copyright (c) 2001, 2002 The Open For Business Project - www.ofbiz.org
005: *
006: * Permission is hereby granted, free of charge, to any person obtaining a
007: * copy of this software and associated documentation files (the "Software"),
008: * to deal in the Software without restriction, including without limitation
009: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
010: * and/or sell copies of the Software, and to permit persons to whom the
011: * Software is furnished to do so, subject to the following conditions:
012: *
013: * The above copyright notice and this permission notice shall be included
014: * in all copies or substantial portions of the Software.
015: *
016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
017: * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
018: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
019: * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
020: * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
021: * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
022: * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
023: */
024: package org.ofbiz.order.shoppingcart.shipping;
025:
026: import java.util.*;
027:
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.ofbiz.base.util.Debug;
032: import org.ofbiz.base.util.UtilMisc;
033: import org.ofbiz.base.util.UtilValidate;
034: import org.ofbiz.entity.GenericDelegator;
035: import org.ofbiz.entity.GenericEntityException;
036: import org.ofbiz.entity.GenericValue;
037: import org.ofbiz.entity.util.EntityUtil;
038: import org.ofbiz.order.order.OrderReadHelper;
039: import org.ofbiz.order.shoppingcart.ShoppingCart;
040: import org.ofbiz.service.ModelService;
041: import org.ofbiz.service.ServiceUtil;
042: import org.ofbiz.common.geo.GeoWorker;
043:
044: /**
045: * ShippingEvents - Events used for processing shipping fees
046: *
047: * @author <a href="mailto:jaz@ofbiz.org">Andy Zeneski</a>
048: * @version $Revision: 1.10 $
049: * @since 2.0
050: */
051: public class ShippingEvents {
052:
053: public static final String module = ShippingEvents.class.getName();
054:
055: public static String getShipEstimate(HttpServletRequest request,
056: HttpServletResponse response) {
057: ShoppingCart cart = (ShoppingCart) request.getSession()
058: .getAttribute("shoppingCart");
059: GenericDelegator delegator = (GenericDelegator) request
060: .getAttribute("delegator");
061: Map result = getShipEstimate(delegator, cart, null);
062: ServiceUtil.getMessages(request, result, null, "<li>", "</li>",
063: "<ul>", "</ul>", null, null);
064: if (result.get(ModelService.RESPONSE_MESSAGE).equals(
065: ModelService.RESPOND_ERROR)) {
066: return "error";
067: }
068:
069: Double shippingTotal = (Double) result.get("shippingTotal");
070: // remove old shipping adjustments if there
071: cart.removeAdjustmentByType("SHIPPING_CHARGES");
072:
073: // creat the new adjustment and add it to the cart
074: if (shippingTotal != null && shippingTotal.doubleValue() > 0) {
075: GenericValue orderAdjustment = delegator.makeValue(
076: "OrderAdjustment", UtilMisc
077: .toMap("orderAdjustmentTypeId",
078: "SHIPPING_CHARGES", "amount",
079: shippingTotal));
080: cart.addAdjustment(orderAdjustment);
081: }
082:
083: // all done
084: return "success";
085: }
086:
087: public static Map getShipEstimate(GenericDelegator delegator,
088: ShoppingCart cart, String shippingMethod) {
089: String shipmentMethodTypeId = null;
090: String carrierPartyId = null;
091: if (UtilValidate.isNotEmpty(shippingMethod)) {
092: int delimiterPos = shippingMethod.indexOf('@');
093: if (delimiterPos > 0) {
094: shipmentMethodTypeId = shippingMethod.substring(0,
095: delimiterPos);
096: carrierPartyId = shippingMethod
097: .substring(delimiterPos + 1);
098: }
099: } else {
100: shipmentMethodTypeId = cart.getShipmentMethodTypeId();
101: carrierPartyId = cart.getCarrierPartyId();
102: }
103: return getShipEstimate(delegator, cart.getOrderType(),
104: shipmentMethodTypeId, carrierPartyId, cart
105: .getShippingContactMechId(), cart
106: .getProductStoreId(), cart.getShippableSizes(),
107: cart.getFeatureIdQtyMap(), cart.getShippableWeight(),
108: cart.getShippableQuantity(), cart.getShippableTotal());
109: }
110:
111: public static Map getShipEstimate(GenericDelegator delegator,
112: OrderReadHelper orh) {
113: String shippingMethod = orh.getShippingMethodCode();
114: String shipmentMethodTypeId = null;
115: String carrierPartyId = null;
116: if (UtilValidate.isNotEmpty(shippingMethod)) {
117: int delimiterPos = shippingMethod.indexOf('@');
118: if (delimiterPos > 0) {
119: shipmentMethodTypeId = shippingMethod.substring(0,
120: delimiterPos);
121: carrierPartyId = shippingMethod
122: .substring(delimiterPos + 1);
123: }
124: }
125: GenericValue shipAddr = orh.getShippingAddress();
126: String contactMechId = shipAddr.getString("contactMechId");
127: return getShipEstimate(delegator, orh.getOrderTypeId(),
128: shipmentMethodTypeId, carrierPartyId, contactMechId,
129: orh.getProductStoreId(), orh.getShippableSizes(), orh
130: .getFeatureIdQtyMap(),
131: orh.getShippableWeight(), orh.getShippableQuantity(),
132: orh.getShippableTotal());
133: }
134:
135: public static Map getShipEstimate(GenericDelegator delegator,
136: String orderTypeId, String shipmentMethodTypeId,
137: String carrierPartyId, String shippingContactMechId,
138: String productStoreId, List itemSizes, Map featureMap,
139: double shippableWeight, double shippableQuantity,
140: double shippableTotal) {
141: String standardMessage = "A problem occurred calculating shipping. Fees will be calculated offline.";
142: List errorMessageList = new ArrayList();
143: StringBuffer errorMessage = new StringBuffer();
144:
145: if (shipmentMethodTypeId == null || carrierPartyId == null) {
146: if ("SALES_ORDER".equals(orderTypeId)) {
147: errorMessageList
148: .add("Please Select Your Shipping Method.");
149: return ServiceUtil.returnError(errorMessageList);
150: } else {
151: return ServiceUtil.returnSuccess();
152: }
153: }
154:
155: if (shippingContactMechId == null) {
156: errorMessageList
157: .add("Please Select Your Shipping Address.");
158: return ServiceUtil.returnError(errorMessageList);
159: }
160:
161: if (Debug.verboseOn()) {
162: Debug.logVerbose("Shippable Weight : " + shippableWeight,
163: module);
164: Debug.logVerbose("Shippable Qty : " + shippableQuantity,
165: module);
166: Debug.logVerbose("Shippable Total : " + shippableTotal,
167: module);
168: }
169:
170: // no shippable items; we won't change any shipping at all
171: if (shippableQuantity == 0) {
172: Map result = ServiceUtil.returnSuccess();
173: result.put("shippingTotal", new Double(0));
174: return result;
175: }
176:
177: // Get the ShipmentCostEstimate(s)
178: Collection estimates = null;
179:
180: try {
181: Map fields = UtilMisc.toMap("productStoreId",
182: productStoreId, "shipmentMethodTypeId",
183: shipmentMethodTypeId, "carrierPartyId",
184: carrierPartyId, "carrierRoleTypeId", "CARRIER");
185:
186: estimates = delegator.findByAnd("ShipmentCostEstimate",
187: fields);
188: if (Debug.verboseOn())
189: Debug.logVerbose("Estimate fields: " + fields, module);
190: if (Debug.verboseOn())
191: Debug.logVerbose("Estimate(s): " + estimates, module);
192: } catch (GenericEntityException e) {
193: Debug
194: .logError(
195: "[ShippingEvents.getShipEstimate] Cannot get shipping estimates.",
196: module);
197: return ServiceUtil.returnSuccess(standardMessage);
198: }
199: if (estimates == null || estimates.size() < 1) {
200: Debug
201: .logInfo(
202: "[ShippingEvents.getShipEstimate] No shipping estimate found.",
203: module);
204: return ServiceUtil.returnSuccess(standardMessage);
205: }
206:
207: if (Debug.verboseOn())
208: Debug.logVerbose(
209: "[ShippingEvents.getShipEstimate] Estimates begin size: "
210: + estimates.size(), module);
211:
212: // Get the PostalAddress
213: GenericValue shipAddress = null;
214:
215: try {
216: shipAddress = delegator.findByPrimaryKey("PostalAddress",
217: UtilMisc.toMap("contactMechId",
218: shippingContactMechId));
219: } catch (GenericEntityException e) {
220: Debug
221: .logError(
222: "[ShippingEvents.getShipEstimate] Cannot get shipping address entity.",
223: module);
224: return ServiceUtil.returnSuccess(standardMessage);
225: }
226:
227: // Get the possible estimates.
228: ArrayList estimateList = new ArrayList();
229: Iterator i = estimates.iterator();
230:
231: while (i.hasNext()) {
232: GenericValue this Estimate = (GenericValue) i.next();
233: String toGeo = this Estimate.getString("geoIdTo");
234: List toGeoList = GeoWorker.expandGeoGroup(toGeo, delegator);
235:
236: // Make sure we have a valid GEOID.
237: if (toGeoList == null
238: || toGeoList.size() == 0
239: || GeoWorker.containsGeo(toGeoList, shipAddress
240: .getString("countryGeoId"), delegator)
241: || GeoWorker
242: .containsGeo(toGeoList, shipAddress
243: .getString("stateProvinceGeoId"),
244: delegator)
245: || GeoWorker.containsGeo(toGeoList, shipAddress
246: .getString("postalCodeGeoId"), delegator)) {
247:
248: /*
249: if (toGeo == null || toGeo.equals("") || toGeo.equals(shipAddress.getString("countryGeoId")) ||
250: toGeo.equals(shipAddress.getString("stateProvinceGeoId")) ||
251: toGeo.equals(shipAddress.getString("postalCodeGeoId"))) {
252: */
253:
254: GenericValue wv = null;
255: GenericValue qv = null;
256: GenericValue pv = null;
257:
258: try {
259: wv = this Estimate
260: .getRelatedOne("WeightQuantityBreak");
261: } catch (GenericEntityException e) {
262: }
263: try {
264: qv = this Estimate
265: .getRelatedOne("QuantityQuantityBreak");
266: } catch (GenericEntityException e) {
267: }
268: try {
269: pv = this Estimate
270: .getRelatedOne("PriceQuantityBreak");
271: } catch (GenericEntityException e) {
272: }
273: if (wv == null && qv == null && pv == null) {
274: estimateList.add(this Estimate);
275: } else {
276: // Do some testing.
277: boolean useWeight = false;
278: boolean weightValid = false;
279: boolean useQty = false;
280: boolean qtyValid = false;
281: boolean usePrice = false;
282: boolean priceValid = false;
283:
284: if (wv != null) {
285: useWeight = true;
286: double min = 0.0001;
287: double max = 0.0001;
288:
289: try {
290: min = wv.getDouble("fromQuantity")
291: .doubleValue();
292: max = wv.getDouble("thruQuantity")
293: .doubleValue();
294: } catch (Exception e) {
295: }
296: if (shippableWeight >= min
297: && (max == 0 || shippableWeight <= max))
298: weightValid = true;
299: }
300: if (qv != null) {
301: useQty = true;
302: double min = 0.0001;
303: double max = 0.0001;
304:
305: try {
306: min = qv.getDouble("fromQuantity")
307: .doubleValue();
308: max = qv.getDouble("thruQuantity")
309: .doubleValue();
310: } catch (Exception e) {
311: }
312: if (shippableQuantity >= min
313: && (max == 0 || shippableQuantity <= max))
314: qtyValid = true;
315: }
316: if (pv != null) {
317: usePrice = true;
318: double min = 0.0001;
319: double max = 0.0001;
320:
321: try {
322: min = pv.getDouble("fromQuantity")
323: .doubleValue();
324: max = pv.getDouble("thruQuantity")
325: .doubleValue();
326: } catch (Exception e) {
327: }
328: if (shippableTotal >= min
329: && (max == 0 || shippableTotal <= max))
330: priceValid = true;
331: }
332: // Now check the tests.
333: if ((useWeight && weightValid)
334: || (useQty && qtyValid)
335: || (usePrice && priceValid))
336: estimateList.add(this Estimate);
337: }
338: }
339: }
340:
341: if (Debug.verboseOn())
342: Debug.logVerbose(
343: "[ShippingEvents.getShipEstimate] Estimates left after GEO filter: "
344: + estimateList.size(), module);
345:
346: if (estimateList.size() < 1) {
347: Debug
348: .logInfo(
349: "[ShippingEvents.getShipEstimate] No shipping estimate found.",
350: module);
351: return ServiceUtil.returnSuccess(standardMessage);
352: }
353:
354: // Calculate priority based on available data.
355: double PRIORITY_PARTY = 9;
356: double PRIORITY_ROLE = 8;
357: double PRIORITY_GEO = 4;
358: double PRIORITY_WEIGHT = 1;
359: double PRIORITY_QTY = 1;
360: double PRIORITY_PRICE = 1;
361:
362: int estimateIndex = 0;
363:
364: if (estimateList.size() > 1) {
365: TreeMap estimatePriority = new TreeMap();
366: //int estimatePriority[] = new int[estimateList.size()];
367:
368: for (int x = 0; x < estimateList.size(); x++) {
369: GenericValue currentEstimate = (GenericValue) estimateList
370: .get(x);
371:
372: int prioritySum = 0;
373: if (UtilValidate.isNotEmpty(currentEstimate
374: .getString("partyId")))
375: prioritySum += PRIORITY_PARTY;
376: if (UtilValidate.isNotEmpty(currentEstimate
377: .getString("roleTypeId")))
378: prioritySum += PRIORITY_ROLE;
379: if (UtilValidate.isNotEmpty(currentEstimate
380: .getString("geoIdTo")))
381: prioritySum += PRIORITY_GEO;
382: if (UtilValidate.isNotEmpty(currentEstimate
383: .getString("weightBreakId")))
384: prioritySum += PRIORITY_WEIGHT;
385: if (UtilValidate.isNotEmpty(currentEstimate
386: .getString("quantityBreakId")))
387: prioritySum += PRIORITY_QTY;
388: if (UtilValidate.isNotEmpty(currentEstimate
389: .getString("priceBreakId")))
390: prioritySum += PRIORITY_PRICE;
391:
392: // there will be only one of each priority; latest will replace
393: estimatePriority.put(new Integer(prioritySum),
394: currentEstimate);
395: }
396:
397: // locate the highest priority estimate; or the latest entered
398: Object[] estimateArray = estimatePriority.values()
399: .toArray();
400: estimateIndex = estimateList
401: .indexOf(estimateArray[estimateArray.length - 1]);
402: }
403:
404: // Grab the estimate and work with it.
405: GenericValue estimate = (GenericValue) estimateList
406: .get(estimateIndex);
407:
408: //Debug.log("[ShippingEvents.getShipEstimate] Working with estimate [" + estimateIndex + "]: " + estimate, module);
409:
410: // flat fees
411: double orderFlat = 0.00;
412: if (estimate.getDouble("orderFlatPrice") != null)
413: orderFlat = estimate.getDouble("orderFlatPrice")
414: .doubleValue();
415:
416: double orderItemFlat = 0.00;
417: if (estimate.getDouble("orderItemFlatPrice") != null)
418: orderItemFlat = estimate.getDouble("orderItemFlatPrice")
419: .doubleValue();
420:
421: double orderPercent = 0.00;
422: if (estimate.getDouble("orderPricePercent") != null)
423: orderPercent = estimate.getDouble("orderPricePercent")
424: .doubleValue();
425:
426: double itemFlatAmount = shippableQuantity * orderItemFlat;
427: double orderPercentage = shippableTotal * (orderPercent / 100);
428:
429: // flat total
430: double flatTotal = orderFlat + itemFlatAmount + orderPercentage;
431:
432: // spans
433: double weightUnit = 0.00;
434: if (estimate.getDouble("weightUnitPrice") != null)
435: weightUnit = estimate.getDouble("weightUnitPrice")
436: .doubleValue();
437:
438: double qtyUnit = 0.00;
439: if (estimate.getDouble("quantityUnitPrice") != null)
440: qtyUnit = estimate.getDouble("quantityUnitPrice")
441: .doubleValue();
442:
443: double priceUnit = 0.00;
444: if (estimate.getDouble("priceUnitPrice") != null)
445: priceUnit = estimate.getDouble("priceUnitPrice")
446: .doubleValue();
447:
448: double weightAmount = shippableWeight * weightUnit;
449: double quantityAmount = shippableQuantity * qtyUnit;
450: double priceAmount = shippableTotal * priceUnit;
451:
452: // span total
453: double spanTotal = weightAmount + quantityAmount + priceAmount;
454:
455: // feature surcharges
456: double featureSurcharge = 0.00;
457: String featureGroupId = estimate
458: .getString("productFeatureGroupId");
459: Double featurePercent = estimate.getDouble("featurePercent");
460: Double featurePrice = estimate.getDouble("featurePrice");
461: if (featurePercent == null) {
462: featurePercent = new Double(0);
463: }
464: if (featurePrice == null) {
465: featurePrice = new Double(0.00);
466: }
467:
468: if (featureGroupId != null && featureGroupId.length() > 0
469: && featureMap != null) {
470: Iterator fii = featureMap.keySet().iterator();
471: while (fii.hasNext()) {
472: String featureId = (String) fii.next();
473: Double quantity = (Double) featureMap.get(featureId);
474: GenericValue appl = null;
475: Map fields = UtilMisc.toMap("productFeatureGroupId",
476: featureGroupId, "productFeatureId", featureId);
477: try {
478: List appls = delegator.findByAndCache(
479: "ProductFeatureGroupAppl", fields);
480: appls = EntityUtil.filterByDate(appls);
481: appl = EntityUtil.getFirst(appls);
482: } catch (GenericEntityException e) {
483: Debug.logError(e, "Unable to lookup feature/group"
484: + fields, module);
485: }
486: if (appl != null) {
487: featureSurcharge += (shippableTotal
488: * (featurePercent.doubleValue() / 100) * quantity
489: .doubleValue());
490: featureSurcharge += featurePrice.doubleValue()
491: * quantity.doubleValue();
492: }
493: }
494: }
495:
496: // size surcharges
497: double sizeSurcharge = 0.00;
498: Double sizeUnit = estimate.getDouble("oversizeUnit");
499: Double sizePrice = estimate.getDouble("oversizePrice");
500: if (sizeUnit != null && sizeUnit.doubleValue() > 0) {
501: if (itemSizes != null) {
502: Iterator isi = itemSizes.iterator();
503: while (isi.hasNext()) {
504: Double size = (Double) isi.next();
505: if (size != null
506: && size.doubleValue() >= sizeUnit
507: .doubleValue()) {
508: sizeSurcharge += sizePrice.doubleValue();
509: }
510: }
511: }
512: }
513:
514: // surcharges total
515: double surchargeTotal = featureSurcharge + sizeSurcharge;
516:
517: // shipping total
518: double shippingTotal = spanTotal + flatTotal + surchargeTotal;
519:
520: if (Debug.verboseOn())
521: Debug.logVerbose(
522: "[ShippingEvents.getShipEstimate] Setting shipping amount : "
523: + shippingTotal, module);
524:
525: Map responseResult = ServiceUtil.returnSuccess();
526: responseResult.put("shippingTotal", new Double(shippingTotal));
527: return responseResult;
528: }
529:
530: /*
531: * Reserved for future use.
532: *
533: private static double getUPSRate(ShoppingCart cart, String fromZip, String upsMethod) {
534: HttpClient req = new HttpClient();
535: HashMap arguments = new HashMap();
536: double totalWeight = 0.00000;
537: double upsRate = 0.00;
538:
539: HashMap services = new HashMap();
540: services.put("1DA","Next Day Air");
541: services.put("1DM","Next Day Air Early");
542: services.put("1DP","Next Day Air Saver");
543: services.put("1DAPI","Next Day Air Intra (Puerto Rico)");
544: services.put("2DA","2nd Day Air");
545: services.put("2DM","2nd Day Air A.M.");
546: services.put("3DS","3rd Day");
547: services.put("GND","Ground Service");
548: services.put("STD","Canada Standard");
549: services.put("XPR","Worldwide Express");
550: services.put("XDM","Worldwide Express Plus");
551: services.put("XPD","Worldwide Expedited");
552:
553: if ( !services.containsKey(upsMethod) )
554: return 0.00;
555:
556: // Get the total weight from the cart.
557: Iterator cartItemIterator = cart.iterator();
558: while ( cartItemIterator.hasNext() ) {
559: ShoppingCartItem item = (ShoppingCartItem) cartItemIterator.next();
560: totalWeight += (item.getWeight() * item.getQuantity());
561: }
562: String weightString = new Double(totalWeight).toString();
563: if (Debug.infoOn()) Debug.logInfo("[ShippingEvents.getUPSRate] Total Weight: " + weightString, module);
564:
565: // Set up the UPS arguments.
566: arguments.put("AppVersion","1.2");
567: arguments.put("ResponseType","application/x-ups-rss");
568: arguments.put("AcceptUPSLicenseAgreement","yes");
569:
570: arguments.put("RateChart","Regular Daily Pickup"); // ?
571: arguments.put("PackagingType","00"); // Using own container
572: arguments.put("ResidentialInd","1"); // Assume residential
573:
574: arguments.put("ShipperPostalCode",fromZip); // Ship From ZipCode
575: arguments.put("ConsigneeCountry","US"); // 2 char country ISO
576: arguments.put("ConsigneePostalCode","27703"); // Ship TO ZipCode
577: arguments.put("PackageActualWeight",weightString); // Total shipment weight
578:
579: arguments.put("ActionCode","3"); // Specify the shipping type. (4) to get all
580: arguments.put("ServiceLevelCode",upsMethod); // User's shipping choice (or 1DA for ActionCode 4)
581:
582: String upsResponse = null;
583: try {
584: req.setUrl(UPS_RATES_URL);
585: req.setLineFeed(false);
586: req.setParameters(arguments);
587: upsResponse = req.get();
588: }
589: catch ( HttpClientException e ) {
590: Debug.logError("[ShippingEvents.getUPSRate] Problems getting UPS Rate Infomation.", module);
591: return -1;
592: }
593:
594: if ( upsResponse.indexOf("application/x-ups-error") != -1 ) {
595: // get the error message
596: }
597: else if ( upsResponse.indexOf("application/x-ups-rss") != -1 ) {
598: // get the content
599: upsResponse = upsResponse.substring(upsResponse.indexOf("UPSOnLine"));
600: upsResponse = upsResponse.substring(0,upsResponse.indexOf("--UPSBOUNDARY--") -1 );
601: ArrayList respList = new ArrayList();
602: while ( upsResponse.indexOf("%") != -1 ) {
603: respList.add(upsResponse.substring(0,upsResponse.indexOf("%")));
604: upsResponse = upsResponse.substring(upsResponse.indexOf("%") + 1);
605: if ( upsResponse.indexOf("%") == -1 )
606: respList.add(upsResponse);
607: }
608:
609: // Debug:
610: Iterator i = respList.iterator();
611: while ( i.hasNext() ) {
612: String value = (String) i.next();
613: if (Debug.infoOn()) Debug.logInfo("[ShippingEvents.getUPSRate] Resp List: " + value, module);
614: }
615:
616: // Shipping method is index 5
617: // Shipping rate is index 12
618: if ( !respList.get(5).equals(upsMethod) )
619: Debug.logInfo("[ShippingEvents.getUPSRate] Shipping method does not match.", module);
620: try {
621: upsRate = Double.parseDouble((String)respList.get(12));
622: }
623: catch ( NumberFormatException nfe ) {
624: Debug.logError("[ShippingEvents.getUPSRate] Problems parsing rate value.", module);
625: }
626: }
627:
628: return upsRate;
629: }
630: */
631:
632: }
|