You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by le...@apache.org on 2009/08/03 06:57:59 UTC
svn commit: r800198 - in /ofbiz/trunk/applications/product:
config/shipment.properties src/org/ofbiz/product/product/ProductWorker.java
src/org/ofbiz/shipment/shipment/ShipmentWorker.java
src/org/ofbiz/shipment/thirdparty/usps/UspsServices.java
Author: lektran
Date: Mon Aug 3 04:57:57 2009
New Revision: 800198
URL: http://svn.apache.org/viewvc?rev=800198&view=rev
Log:
Some fixes to the usps priority mail international rate estimate service
First pass implementation of a usps priority mail international shipping label service
Added:
ofbiz/trunk/applications/product/src/org/ofbiz/shipment/shipment/ShipmentWorker.java (with props)
Modified:
ofbiz/trunk/applications/product/config/shipment.properties
ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductWorker.java
ofbiz/trunk/applications/product/src/org/ofbiz/shipment/thirdparty/usps/UspsServices.java
Modified: ofbiz/trunk/applications/product/config/shipment.properties
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/config/shipment.properties?rev=800198&r1=800197&r2=800198&view=diff
==============================================================================
--- ofbiz/trunk/applications/product/config/shipment.properties (original)
+++ ofbiz/trunk/applications/product/config/shipment.properties Mon Aug 3 04:57:57 2009
@@ -130,8 +130,16 @@
# USPS Webtools API Configuration
############################################
+# USPS configuration indicator
+shipment.usps.shipping=Y
+# Testing indicator, currently only used for printing labels since USPS doesn't
+# use a separate url for testing
+shipment.usps.test=Y
+
# USPS Connection URL & timeout in seconds
shipment.usps.connect.url=http://localhost:8080/facility/ShippingAPI.dll
+# Url for labels differs in that the url is the same whether testing or not
+shipment.usps.connect.url.labels=http://localhost:8080/facility/ShippingAPI.dll
shipment.usps.connect.timeout=60
# USPS Credentials
Modified: ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductWorker.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductWorker.java?rev=800198&r1=800197&r2=800198&view=diff
==============================================================================
--- ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductWorker.java (original)
+++ ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductWorker.java Mon Aug 3 04:57:57 2009
@@ -31,6 +31,7 @@
import javax.servlet.jsp.PageContext;
import javolution.util.FastList;
+import javolution.util.FastMap;
import javolution.util.FastSet;
import org.ofbiz.base.util.Debug;
@@ -834,6 +835,49 @@
return null;
}
+ /*
+ * Returns the product's unit weight converted to the desired Uom. If the weight is null,
+ * then a check is made for an associated virtual product to retrieve the weight from. If the
+ * weight is still null then null is returned. If a weight is found and a desiredUomId has
+ * been supplied and the product specifies a weightUomId then an attempt will be made to
+ * convert the value otherwise the weight is returned as is.
+ */
+ public static BigDecimal getProductWeight(GenericValue product, String desiredUomId, GenericDelegator delegator, LocalDispatcher dispatcher) {
+ BigDecimal weight = product.getBigDecimal("weight");
+ String weightUomId = product.getString("weightUomId");
+
+ if (weight == null) {
+ GenericValue parentProduct = getParentProduct(product.getString("productId"), delegator);
+ if (parentProduct != null) {
+ weight = parentProduct.getBigDecimal("weight");
+ weightUomId = parentProduct.getString("weightUomId");
+ }
+ }
+
+ if (weight == null) {
+ return null;
+ } else {
+ // attempt a conversion if necessary
+ if (desiredUomId != null && product.get("weightUomId") != null && !desiredUomId.equals(product.get("weightUomId"))) {
+ Map<String, Object> result = FastMap.newInstance();
+ try {
+ result = dispatcher.runSync("convertUom", UtilMisc.<String, Object>toMap("uomId", weightUomId, "uomIdTo", "WT_lb", "originalValue", weight));
+ } catch (GenericServiceException e) {
+ Debug.logError(e, module);
+ }
+
+ if (result.get(ModelService.RESPONSE_MESSAGE).equals(ModelService.RESPOND_SUCCESS) && result.get("convertedValue") != null) {
+ weight = (BigDecimal) result.get("convertedValue");
+ } else {
+ Debug.logError("Unsupported conversion from [" + weightUomId + "] to [" + desiredUomId + "]",module);
+ return null;
+ }
+ }
+ return weight;
+ }
+ }
+
+
/**
* Generic service to find product by id.
Added: ofbiz/trunk/applications/product/src/org/ofbiz/shipment/shipment/ShipmentWorker.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/src/org/ofbiz/shipment/shipment/ShipmentWorker.java?rev=800198&view=auto
==============================================================================
--- ofbiz/trunk/applications/product/src/org/ofbiz/shipment/shipment/ShipmentWorker.java (added)
+++ ofbiz/trunk/applications/product/src/org/ofbiz/shipment/shipment/ShipmentWorker.java Mon Aug 3 04:57:57 2009
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.ofbiz.shipment.shipment;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import javolution.util.FastMap;
+
+import org.ofbiz.base.util.Debug;
+import org.ofbiz.base.util.UtilHttp;
+import org.ofbiz.base.util.UtilMisc;
+import org.ofbiz.base.util.UtilValidate;
+import org.ofbiz.entity.GenericDelegator;
+import org.ofbiz.entity.GenericEntityException;
+import org.ofbiz.entity.GenericValue;
+import org.ofbiz.entity.util.EntityUtil;
+import org.ofbiz.product.product.ProductWorker;
+import org.ofbiz.service.GenericServiceException;
+import org.ofbiz.service.LocalDispatcher;
+import org.ofbiz.service.ModelService;
+
+/**
+ * ShipmentWorker - Worker methods for Shipment and related entities
+ */
+public class ShipmentWorker {
+
+ public static final String module = ShipmentWorker.class.getName();
+
+ /*
+ * Returns the value of a given ShipmentPackageContent record. Calculated by working out the total value (from the OrderItems) of all ItemIssuances
+ * for the ShipmentItem then dividing that by the total quantity issued for the same to get an average item value then multiplying that by the package
+ * content quantity.
+ * Note: No rounding of the calculation is performed so you will need to round it to the accuracy that you require
+ */
+ public static BigDecimal getShipmentPackageContentValue(GenericValue shipmentPackageContent) {
+ BigDecimal quantity = shipmentPackageContent.getBigDecimal("quantity");
+
+ BigDecimal value = new BigDecimal("0");
+
+ // lookup the issuance to find the order
+ List<GenericValue> issuances = null;
+ try {
+ GenericValue shipmentItem = shipmentPackageContent.getRelatedOne("ShipmentItem");
+ issuances = shipmentItem.getRelated("ItemIssuance");
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ }
+
+ BigDecimal totalIssued = BigDecimal.ZERO;
+ BigDecimal totalValue = BigDecimal.ZERO;
+ if (UtilValidate.isNotEmpty(issuances)) {
+ for (GenericValue issuance : issuances) {
+ // we only need one
+ BigDecimal issuanceQuantity = issuance.getBigDecimal("quantity");
+ BigDecimal issuanceCancelQuantity = issuance.getBigDecimal("cancelQuantity");
+ if (issuanceCancelQuantity != null) {
+ issuanceQuantity = issuanceQuantity.subtract(issuanceCancelQuantity);
+ }
+ // get the order item
+ GenericValue orderItem = null;
+ try {
+ orderItem = issuance.getRelatedOne("OrderItem");
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ }
+
+ if (orderItem != null) {
+ // get the value per unit - (base price * amount)
+ BigDecimal selectedAmount = orderItem.getBigDecimal("selectedAmount");
+ if (selectedAmount == null || selectedAmount.compareTo(BigDecimal.ZERO) <= 0) {
+ selectedAmount = BigDecimal.ONE;
+ }
+
+ BigDecimal unitPrice = orderItem.getBigDecimal("unitPrice");
+ BigDecimal itemValue = unitPrice.multiply(selectedAmount);
+
+ // total value for package (per unit * quantity)
+ totalIssued = totalIssued.add(issuanceQuantity);
+ totalValue = totalValue.add(itemValue.multiply(issuanceQuantity));
+ }
+ }
+ }
+ // take the average value of the issuances and multiply it by the shipment package content quantity
+ value = totalValue.divide(totalIssued, 10, BigDecimal.ROUND_HALF_EVEN).multiply(quantity);
+ return value;
+ }
+}
+
Propchange: ofbiz/trunk/applications/product/src/org/ofbiz/shipment/shipment/ShipmentWorker.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: ofbiz/trunk/applications/product/src/org/ofbiz/shipment/shipment/ShipmentWorker.java
------------------------------------------------------------------------------
svn:keywords = "Date Rev Author URL Id"
Propchange: ofbiz/trunk/applications/product/src/org/ofbiz/shipment/shipment/ShipmentWorker.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: ofbiz/trunk/applications/product/src/org/ofbiz/shipment/thirdparty/usps/UspsServices.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/src/org/ofbiz/shipment/thirdparty/usps/UspsServices.java?rev=800198&r1=800197&r2=800198&view=diff
==============================================================================
--- ofbiz/trunk/applications/product/src/org/ofbiz/shipment/thirdparty/usps/UspsServices.java (original)
+++ ofbiz/trunk/applications/product/src/org/ofbiz/shipment/thirdparty/usps/UspsServices.java Mon Aug 3 04:57:57 2009
@@ -26,6 +26,7 @@
import java.math.BigDecimal;
import java.math.MathContext;
import java.text.DecimalFormat;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
@@ -44,21 +45,26 @@
import org.ofbiz.base.util.GeneralException;
import org.ofbiz.base.util.HttpClient;
import org.ofbiz.base.util.HttpClientException;
+import org.ofbiz.base.util.StringUtil;
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilProperties;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.base.util.UtilXml;
+import org.ofbiz.common.uom.UomWorker;
import org.ofbiz.entity.GenericDelegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
+import org.ofbiz.entity.util.EntityUtil;
import org.ofbiz.party.contact.ContactMechWorker;
+import org.ofbiz.product.product.ProductWorker;
import org.ofbiz.product.store.ProductStoreWorker;
import org.ofbiz.service.DispatchContext;
import org.ofbiz.service.GenericServiceException;
import org.ofbiz.service.LocalDispatcher;
import org.ofbiz.service.ModelService;
import org.ofbiz.service.ServiceUtil;
+import org.ofbiz.shipment.shipment.ShipmentWorker;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
@@ -73,6 +79,15 @@
public static final MathContext generalRounding = new MathContext(10);
+ private static List<String> domesticCountries = FastList.newInstance();
+ // Countries treated as domestic for rate enquiries
+ static {
+ domesticCountries.add("USA");
+ domesticCountries.add("ASM");
+ domesticCountries.add("GU");
+ domesticCountries = Collections.unmodifiableList(domesticCountries);
+ }
+
public static Map<String, Object> uspsRateInquire(DispatchContext dctx, Map<String, ? extends Object> context) {
GenericDelegator delegator = dctx.getDelegator();
@@ -112,6 +127,9 @@
try {
GenericValue shipToAddress = delegator.findByPrimaryKey("PostalAddress", UtilMisc.toMap("contactMechId", shippingContactMechId));
if (shipToAddress != null) {
+ if (!domesticCountries.contains(shipToAddress.getString("countryGeoId"))) {
+ return ServiceUtil.returnFailure("uspsRateInquire is only valid for US destinations.");
+ }
destinationZip = shipToAddress.getString("postalCode");
}
} catch (GenericEntityException e) {
@@ -231,6 +249,25 @@
return result;
}
+ /*
+ * USPS International Service Codes
+ * 1 - Express Mail International
+ * 2 - Priority Mail International
+ * 4 - Global Express Guaranteed (Document and Non-document)
+ * 5 - Global Express Guaranteed Document used
+ * 6 - Global Express Guaranteed Non-Document Rectangular shape
+ * 7 - Global Express Guaranteed Non-Document Non-Rectangular
+ * 8 - Priority Mail Flat Rate Envelope
+ * 9 - Priority Mail Flat Rate Box
+ * 10 - Express Mail International Flat Rate Envelope
+ * 11 - Priority Mail Large Flat Rate Box
+ * 12 - Global Express Guaranteed Envelope
+ * 13 - First Class Mail International Letters
+ * 14 - First Class Mail International Flats
+ * 15 - First Class Mail International Parcels
+ * 16 - Priority Mail Small Flat Rate Box
+ * 21 - PostCards
+ */
public static Map<String, Object> uspsInternationalRateInquire(DispatchContext dctx, Map<String, ? extends Object> context) {
GenericDelegator delegator = dctx.getDelegator();
@@ -247,8 +284,8 @@
if (UtilValidate.isNotEmpty(shippingContactMechId)) {
try {
GenericValue shipToAddress = delegator.findByPrimaryKey("PostalAddress", UtilMisc.toMap("contactMechId", shippingContactMechId));
- if ("USA".equals(shipToAddress.get("countryGeoId"))) {
- return ServiceUtil.returnError("The USPS International Rate Calculation service is not applicable to US destinations, use uspsRateInquire");
+ if (domesticCountries.contains(shipToAddress.get("countryGeoId"))) {
+ return ServiceUtil.returnError("The USPS International Rate Calculation service is not applicable to US destinations (including Possesions), use uspsRateInquire");
}
if (shipToAddress != null && UtilValidate.isNotEmpty(shipToAddress.getString("countryGeoId"))) {
GenericValue countryGeo = shipToAddress.getRelatedOne("CountryGeo");
@@ -295,36 +332,33 @@
// create the request document
Document requestDocument = createUspsRequestDocument("IntlRateRequest", false);
- UtilXml.addChildElementValue(requestDocument.getDocumentElement(), "Country", destinationCountry, requestDocument);
// TODO: Up to 25 packages can be included per request - handle more than 25
for (ListIterator<Map<String, BigDecimal>> li = packages.listIterator(); li.hasNext();) {
Map<String, BigDecimal> packageMap = li.next();
+ Element packageElement = UtilXml.addChildElement(requestDocument.getDocumentElement(), "Package", requestDocument);
+ packageElement.setAttribute("ID", String.valueOf(li.nextIndex() - 1)); // use zero-based index (see examples)
+
BigDecimal packageWeight = isOnePackage ? shippableWeight : calcPackageWeight(dctx, packageMap, shippableItemInfo, BigDecimal.ZERO);
if (packageWeight.compareTo(BigDecimal.ZERO) == 0) {
continue;
}
-
- Element packageElement = UtilXml.addChildElement(requestDocument.getDocumentElement(), "Package", requestDocument);
- packageElement.setAttribute("ID", String.valueOf(li.nextIndex() - 1)); // use zero-based index (see examples)
-
- UtilXml.addChildElementValue(packageElement, "MailType", "Package", requestDocument);
-
- BigDecimal weightPounds = packageWeight.setScale(0, BigDecimal.ROUND_FLOOR);
+ Integer[] weightPoundsOunces = convertPoundsToPoundsOunces(packageWeight);
// for Parcel post, the weight must be at least 1 lb
- if ("PARCEL".equals(serviceCode.toUpperCase()) && (weightPounds.compareTo(BigDecimal.ONE) < 0)) {
- weightPounds = BigDecimal.ONE;
- packageWeight = BigDecimal.ZERO;
+ if ("PARCEL".equals(serviceCode.toUpperCase()) && (weightPoundsOunces[0] < 1)) {
+ weightPoundsOunces[0] = 1;
+ weightPoundsOunces[1] = 0;
}
- // (packageWeight % 1) * 16 (Rounded up to 1 dp)
- BigDecimal weightOunces = packageWeight.remainder(BigDecimal.ONE).multiply(new BigDecimal("16")).setScale(1, BigDecimal.ROUND_CEILING);
- UtilXml.addChildElementValue(packageElement, "Pounds", weightPounds.toPlainString(), requestDocument);
- UtilXml.addChildElementValue(packageElement, "Ounces", weightOunces.toPlainString(), requestDocument);
+ UtilXml.addChildElementValue(packageElement, "Pounds", weightPoundsOunces[0].toString(), requestDocument);
+ UtilXml.addChildElementValue(packageElement, "Ounces", weightPoundsOunces[1].toString(), requestDocument);
UtilXml.addChildElementValue(packageElement, "Machinable", "False", requestDocument);
+ UtilXml.addChildElementValue(packageElement, "MailType", "Package", requestDocument);
// TODO: Add package value so that an insurance fee can be returned
+
+ UtilXml.addChildElementValue(packageElement, "Country", destinationCountry, requestDocument);
}
// send the request
@@ -1451,6 +1485,209 @@
return ServiceUtil.returnSuccess();
}
+ public static Map<String, Object> uspsPriorityMailInternationalLabel(DispatchContext dctx, Map<String, ? extends Object> context) {
+ GenericDelegator delegator = dctx.getDelegator();
+ LocalDispatcher dispatcher = dctx.getDispatcher();
+
+ GenericValue shipmentRouteSegment = (GenericValue) context.get("shipmentRouteSegment");
+
+ // Start the document
+ Document requestDocument;
+ boolean certify = false;
+ if (!"Y".equalsIgnoreCase(UtilProperties.getPropertyValue("shipment.properties", "shipment.usps.test"))) {
+ requestDocument = createUspsRequestDocument("PriorityMailIntlRequest", false);
+ } else {
+ requestDocument = createUspsRequestDocument("PriorityMailIntlCertifyRequest", false);
+ certify = true;
+ }
+ Element rootElement = requestDocument.getDocumentElement();
+
+ // Retrieve from/to address and package details
+ GenericValue originAddress = null;
+ GenericValue originTelecomNumber = null;
+ GenericValue destinationAddress = null;
+ GenericValue destinationProvince = null;
+ GenericValue destinationCountry = null;
+ GenericValue destinationTelecomNumber = null;
+ List<GenericValue> shipmentPackageRouteSegs = null;
+ try {
+ originAddress = shipmentRouteSegment.getRelatedOne("OriginPostalAddress");
+ originTelecomNumber = shipmentRouteSegment.getRelatedOne("OriginTelecomNumber");
+ destinationAddress = shipmentRouteSegment.getRelatedOne("DestPostalAddress");
+ if (destinationAddress != null) {
+ destinationProvince = destinationAddress.getRelatedOne("StateProvinceGeo");
+ destinationCountry = destinationAddress.getRelatedOne("CountryGeo");
+ }
+ destinationTelecomNumber = shipmentRouteSegment.getRelatedOne("DestTelecomNumber");
+ shipmentPackageRouteSegs = shipmentRouteSegment.getRelated("ShipmentPackageRouteSeg");
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ }
+ if (originAddress == null || originTelecomNumber == null) {
+ ServiceUtil.returnError("Unable to request a USPS Priority Mail International Label: ShipmentRouteSegment is missing origin phone or address details");
+ }
+
+ // Origin Info
+ // USPS wants a separate first name and last, best we can do is split the string on the white space, if that doesn't work then default to putting the attnName in both fields
+ String fromAttnName = originAddress.getString("attnName");
+ String fromFirstName = StringUtils.defaultIfEmpty(StringUtils.substringBefore(fromAttnName, " "), fromAttnName);
+ String fromLastName = StringUtils.defaultIfEmpty(StringUtils.substringAfter(fromAttnName, " "), fromAttnName);
+ UtilXml.addChildElementValue(rootElement, "FromFirstName", fromFirstName, requestDocument);
+ // UtilXml.addChildElementValue(rootElement, "FromMiddleInitial", "", requestDocument);
+ UtilXml.addChildElementValue(rootElement, "FromLastName", fromLastName, requestDocument);
+ UtilXml.addChildElementValue(rootElement, "FromFirm", originAddress.getString("toName"), requestDocument);
+ // The following 2 assignments are not typos - USPS address1 = OFBiz address2, USPS address2 = OFBiz address1
+ UtilXml.addChildElementValue(rootElement, "FromAddress1", originAddress.getString("address2"), requestDocument);
+ UtilXml.addChildElementValue(rootElement, "FromAddress2", originAddress.getString("address1"), requestDocument);
+ UtilXml.addChildElementValue(rootElement, "FromCity", originAddress.getString("city"), requestDocument);
+ UtilXml.addChildElementValue(rootElement, "FromState", originAddress.getString("stateProvinceGeoId"), requestDocument);
+ UtilXml.addChildElementValue(rootElement, "FromZip5", originAddress.getString("postalCode"), requestDocument);
+ // USPS expects a phone number consisting of area code + contact number as a single numeric string
+ String fromPhoneNumber = originTelecomNumber.getString("areaCode") + originTelecomNumber.getString("contactNumber");
+ fromPhoneNumber = StringUtil.removeNonNumeric(fromPhoneNumber);
+ UtilXml.addChildElementValue(rootElement, "FromPhone", fromPhoneNumber, requestDocument);
+
+ // Destination Info
+ UtilXml.addChildElementValue(rootElement, "ToName", destinationAddress.getString("attnName"), requestDocument);
+ UtilXml.addChildElementValue(rootElement, "ToFirm", destinationAddress.getString("toName"), requestDocument);
+ UtilXml.addChildElementValue(rootElement, "ToAddress1", destinationAddress.getString("address1"), requestDocument);
+ UtilXml.addChildElementValue(rootElement, "ToAddress2", destinationAddress.getString("address2"), requestDocument);
+ UtilXml.addChildElementValue(rootElement, "ToCity", destinationAddress.getString("city"), requestDocument);
+ UtilXml.addChildElementValue(rootElement, "ToProvince", destinationProvince.getString("geoName"), requestDocument);
+ // TODO: Test these country names, I think we're going to need to maintain a list of USPS names
+ UtilXml.addChildElementValue(rootElement, "ToCountry", destinationCountry.getString("geoName"), requestDocument);
+ UtilXml.addChildElementValue(rootElement, "ToPostalCode", destinationAddress.getString("postalCode"), requestDocument);
+ // TODO: Figure out how to answer this question accurately
+ UtilXml.addChildElementValue(rootElement, "ToPOBoxFlag", "N", requestDocument);
+ String toPhoneNumber = destinationTelecomNumber.getString("countryCode") + destinationTelecomNumber.getString("areaCode") + destinationTelecomNumber.getString("contactNumber");
+ UtilXml.addChildElementValue(rootElement, "ToPhone", toPhoneNumber, requestDocument);
+ UtilXml.addChildElementValue(rootElement, "NonDeliveryOption", "RETURN", requestDocument);
+
+ for (GenericValue shipmentPackageRouteSeg : shipmentPackageRouteSegs) {
+ Document packageDocument = (Document) requestDocument.cloneNode(true);
+ Element packageRootElement = requestDocument.getDocumentElement();
+ // This is our reference and can be whatever we want. For lack of a better alternative we'll use shipmentId:shipmentPackageSeqId:shipmentRouteSegmentId
+ String fromCustomsReference = shipmentRouteSegment.getString("shipmentId") + ":" + shipmentRouteSegment.getString("shipmentRouteSegmentId");
+ fromCustomsReference = StringUtils.join(
+ UtilMisc.toList(
+ shipmentRouteSegment.get("shipmentId"),
+ shipmentPackageRouteSeg.get("shipmentPackageSeqId"),
+ shipmentRouteSegment.get("shipmentRouteSegementId")
+ ), ':');
+ UtilXml.addChildElementValue(rootElement, "FromCustomsReference", fromCustomsReference, packageDocument);
+ // Determine the container type for this package
+ String container = "VARIABLE";
+ String packageTypeCode = null;
+ GenericValue shipmentPackage = null;
+ List<GenericValue> shipmentPackageContents = null;
+ try {
+ shipmentPackage = shipmentPackageRouteSeg.getRelatedOne("ShipmentPackage");
+ shipmentPackageContents = shipmentPackage.getRelated("ShipmentPackageContent");
+ GenericValue shipmentBoxType = shipmentPackage.getRelatedOne("ShipmentBoxType");
+ if (shipmentBoxType != null) {
+ GenericValue carrierShipmentBoxType = EntityUtil.getFirst(shipmentBoxType.getRelatedByAnd("CarrierShipmentBoxType", UtilMisc.toMap("partyId", "USPS")));
+ if (carrierShipmentBoxType != null) {
+ packageTypeCode = carrierShipmentBoxType.getString("packageTypeCode");
+ // Supported type codes
+ List<String> supportedPackageTypeCodes = UtilMisc.toList(
+ "LGFLATRATEBOX",
+ "SMFLATRATEBOX",
+ "FLATRATEBOX",
+ "MDFLATRATEBOX",
+ "FLATRATEENV");
+ if (supportedPackageTypeCodes.contains(packageTypeCode)) {
+ container = packageTypeCode;
+ }
+ }
+ }
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ }
+ UtilXml.addChildElementValue(rootElement, "Container", container, packageDocument);
+ /* TODO:
+ UtilXml.addChildElementValue(rootElement, "Insured", "", packageDocument);
+ UtilXml.addChildElementValue(rootElement, "InsuredNumber", "", packageDocument);
+ UtilXml.addChildElementValue(rootElement, "InsuredAmount", "", packageDocument);
+ */
+ // According to the docs sending an empty postage tag will cause the postage to be calculated
+ UtilXml.addChildElementValue(rootElement, "Postage", "", packageDocument);
+
+ BigDecimal packageWeight = shipmentPackage.getBigDecimal("weight");
+ String weightUomId = shipmentPackage.getString("weightUomId");
+ BigDecimal packageWeightPounds = UomWorker.convertUom(packageWeight, weightUomId, "WT_lb", dispatcher);
+ Integer[] packagePoundsOunces = convertPoundsToPoundsOunces(packageWeightPounds);
+ UtilXml.addChildElementValue(rootElement, "GrossPounds", packagePoundsOunces[0].toString(), packageDocument);
+ UtilXml.addChildElementValue(rootElement, "GrossOunces", packagePoundsOunces[1].toString(), packageDocument);
+
+ UtilXml.addChildElementValue(rootElement, "ContentType", "MERCHANDISE", packageDocument);
+ UtilXml.addChildElementValue(rootElement, "Agreement", "N", packageDocument);
+ UtilXml.addChildElementValue(rootElement, "ImageType", "PDF", packageDocument);
+ // TODO: Try the different layouts
+ UtilXml.addChildElementValue(rootElement, "ImageType", "ALLINONEFILE", packageDocument);
+ UtilXml.addChildElementValue(rootElement, "CustomerRefNo", fromCustomsReference, packageDocument);
+
+ // Add the shipping contents
+ Element shippingContents = UtilXml.addChildElement(rootElement, "ShippingContents", packageDocument);
+ for (GenericValue shipmentPackageContent : shipmentPackageContents) {
+ Element itemDetail = UtilXml.addChildElement(shippingContents, "ItemDetail", packageDocument);
+ GenericValue product = null;
+ GenericValue originGeo = null;
+ try {
+ GenericValue shipmentItem = shipmentPackageContent.getRelatedOne("ShipmentItem");
+ product = shipmentItem.getRelatedOne("Product");
+ originGeo = product.getRelatedOne("OriginGeo");
+ } catch (GenericEntityException e) {
+ Debug.log(e, module);
+ }
+
+ UtilXml.addChildElementValue(itemDetail, "Description", product.getString("productName"), packageDocument);
+ UtilXml.addChildElementValue(itemDetail, "Quantity", shipmentPackageContent.getBigDecimal("quantity").setScale(0, BigDecimal.ROUND_CEILING).toPlainString(), packageDocument);
+ String packageContentValue = ShipmentWorker.getShipmentPackageContentValue(shipmentPackageContent).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString();
+ UtilXml.addChildElementValue(itemDetail, "Value", packageContentValue, packageDocument);
+ BigDecimal productWeight = ProductWorker.getProductWeight(product, "WT_lbs", delegator, dispatcher);
+ Integer[] productPoundsOunces = convertPoundsToPoundsOunces(productWeight);
+ UtilXml.addChildElementValue(itemDetail, "NetPounds", productPoundsOunces[0].toString(), packageDocument);
+ UtilXml.addChildElementValue(itemDetail, "NetOunces", productPoundsOunces[1].toString(), packageDocument);
+ UtilXml.addChildElementValue(itemDetail, "HSTariffNumber", "", packageDocument);
+ UtilXml.addChildElementValue(itemDetail, "CountryOfOrigin", originGeo.getString("geoName"), packageDocument);
+ }
+
+ // Send the request
+ Document responseDocument = null;
+ String api = certify ? "PriorityMailIntlCertify" : "PriorityMailIntl";
+ try {
+ responseDocument = sendUspsRequest(api, requestDocument);
+ } catch (UspsRequestException e) {
+ Debug.log(e, module);
+ return ServiceUtil.returnError("Error sending request for USPS Priority Mail International service: " +
+ e.getMessage());
+ }
+ Element responseElement = responseDocument.getDocumentElement();
+
+ // TODO: No mention of error returns in the docs
+
+ String labelImageString = UtilXml.childElementValue(responseElement, "LabelImage");
+ if (UtilValidate.isEmpty(labelImageString)) {
+ return ServiceUtil.returnError("Incomplete response from the USPS Priority Mail International service: " +
+ "missing or empty LabelImage element");
+ }
+ shipmentPackageRouteSeg.setBytes("labelImage", Base64.base64Decode(labelImageString.getBytes()));
+ String trackingCode = UtilXml.childElementValue(responseElement, "BarcodeNumber");
+ if (UtilValidate.isEmpty(trackingCode)) {
+ return ServiceUtil.returnError("Incomplete response from the USPS Priority Mail International service: " +
+ "missing or empty BarcodeNumber element");
+ }
+ shipmentPackageRouteSeg.set("trackingCode", trackingCode);
+ try {
+ shipmentPackageRouteSeg.store();
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ }
+
+ }
+ return ServiceUtil.returnSuccess();
+ }
+
private static Document createUspsRequestDocument(String rootElement, boolean passwordRequired) {
Document requestDocument = UtilXml.makeEmptyXmlDocument(rootElement);
@@ -1467,7 +1704,13 @@
}
private static Document sendUspsRequest(String requestType, Document requestDocument) throws UspsRequestException {
- String conUrl = UtilProperties.getPropertyValue("shipment.properties", "shipment.usps.connect.url");
+ String conUrl = null;
+ List<String> labelRequestTypes = UtilMisc.toList("PriorityMailIntl", "PriorityMailIntlCertify");
+ if (labelRequestTypes.contains(requestType)) {
+ conUrl = UtilProperties.getPropertyValue("shipment.properties", "shipment.usps.connect.url.labels");
+ } else {
+ conUrl = UtilProperties.getPropertyValue("shipment.properties", "shipment.usps.connect.url");
+ }
if (UtilValidate.isEmpty(conUrl)) {
throw new UspsRequestException("Connection URL not specified; please check your configuration");
}
@@ -1530,6 +1773,18 @@
return responseDocument;
}
+
+ /*
+ * Converts decimal pounds to pounds and ounces as an Integer array, ounces are rounded up to the nearest whole number
+ */
+ public static Integer[] convertPoundsToPoundsOunces(BigDecimal decimalPounds) {
+ if (decimalPounds == null) return null;
+ Integer[] poundsOunces = new Integer[2];
+ poundsOunces[0] = Integer.valueOf(decimalPounds.setScale(0, BigDecimal.ROUND_FLOOR).toPlainString());
+ // (weight % 1) * 16 rounded up to nearest whole number
+ poundsOunces[1] = Integer.valueOf(decimalPounds.remainder(BigDecimal.ONE).multiply(new BigDecimal("16")).setScale(0, BigDecimal.ROUND_CEILING).toPlainString());
+ return poundsOunces;
+ }
}
class UspsRequestException extends GeneralException {