blob: c1cae1216027df966078706d7f38d0f57e7672e5 [file] [log] [blame]
/*
* 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.thirdparty.fedex;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javolution.util.FastList;
import javolution.util.FastMap;
import org.ofbiz.base.util.Base64;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.GeneralException;
import org.ofbiz.base.util.HttpClient;
import org.ofbiz.base.util.HttpClientException;
import org.ofbiz.base.util.UtilDateTime;
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.base.util.template.FreeMarkerWorker;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.entity.condition.EntityCondition;
import org.ofbiz.entity.condition.EntityOperator;
import org.ofbiz.entity.util.EntityQuery;
import org.ofbiz.entity.util.EntityUtil;
import org.ofbiz.entity.util.EntityUtilProperties;
import org.ofbiz.party.party.PartyHelper;
import org.ofbiz.service.DispatchContext;
import org.ofbiz.service.GenericServiceException;
import org.ofbiz.service.LocalDispatcher;
import org.ofbiz.service.ServiceUtil;
import org.ofbiz.shipment.shipment.ShipmentServices;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Fedex Shipment Services
*
* Implementation of Fedex shipment interface using Ship Manager Direct API
*
* TODO: FDXShipDeleteRequest/Reply (on error and via service call)
* TODO: FDXCloseRequest/Reply
* TODO: FDXRateRequest/Reply
* TODO: FDXTrackRequest/Reply
* TODO: International shipments
* TODO: Multi-piece shipments
* TODO: Freight shipments
*/
public class FedexServices {
public final static String module = FedexServices.class.getName();
public final static String shipmentPropertiesFile = "shipment.properties";
public static final String resourceError = "ProductUiLabels";
/**
* Opens a URL to Fedex and makes a request.
*
* @param xmlString XML message to send
* @param delegator the delegator
* @param shipmentGatewayConfigId the shipmentGatewayConfigId
* @param resource resource file name
* @return XML string response from FedEx
* @throws FedexConnectException
*/
public static String sendFedexRequest(String xmlString, Delegator delegator, String shipmentGatewayConfigId,
String resource, Locale locale) throws FedexConnectException {
String url = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "connectUrl", resource, "shipment.fedex.connect.url");
if (UtilValidate.isEmpty(url)) {
throw new FedexConnectException(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexConnectUrlIncomplete", locale));
}
// xmlString should contain the auth document at the beginning
// all documents require an <?xml version="1.0" encoding="UTF-8" ?> header
if (! xmlString.matches("^(?s)<\\?xml\\s+version=\"1\\.0\"\\s+encoding=\"UTF-8\"\\s*\\?>.*")) {
throw new FedexConnectException(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexXmlHeaderMalformed", locale));
}
// prepare the connect string
url = url.trim();
String timeOutStr = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "connectTimeout", resource, "shipment.fedex.connect.timeout", "60");
int timeout = 60;
try {
timeout = Integer.parseInt(timeOutStr);
} catch (NumberFormatException e) {
Debug.logError(e, "Unable to set timeout to " + timeOutStr + " using default " + timeout);
}
if (Debug.verboseOn()) {
Debug.logVerbose("Fedex Connect URL : " + url, module);
Debug.logVerbose("Fedex XML String : " + xmlString, module);
}
HttpClient http = new HttpClient(url);
http.setTimeout(timeout * 1000);
String response = null;
try {
response = http.post(xmlString);
} catch (HttpClientException e) {
Debug.logError(e, "Problem connecting to Fedex server", module);
throw new FedexConnectException(UtilProperties.getMessage(resourceError, "FacilityShipmentFedexConnectUrlProblem",
UtilMisc.toMap("errorString", e.toString()), locale));
}
if (response == null) {
throw new FedexConnectException(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexReceivedNullResponse", locale));
}
if (Debug.verboseOn()) {
Debug.logVerbose("Fedex Response : " + response, module);
}
return response;
}
/*
* Register a Fedex account for shipping by obtaining the meter number
*/
public static Map<String, Object> fedexSubscriptionRequest(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
String shipmentGatewayConfigId = (String) context.get("shipmentGatewayConfigId");
String resource = (String) context.get("configProps");
Locale locale = (Locale) context.get("locale");
List<Object> errorList = FastList.newInstance();
Boolean replaceMeterNumber = (Boolean) context.get("replaceMeterNumber");
if (! replaceMeterNumber.booleanValue()) {
String meterNumber = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessMeterNumber", resource, "shipment.fedex.access.meterNumber");
if (UtilValidate.isNotEmpty(meterNumber)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexMeterNumberAlreadyExists",
UtilMisc.toMap("meterNumber", meterNumber), locale));
}
}
String companyPartyId = (String) context.get("companyPartyId");
String contactPartyName = (String) context.get("contactPartyName");
Map<String, Object> result = FastMap.newInstance();
String accountNumber = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessAccountNbr", resource, "shipment.fedex.access.accountNbr");
if (UtilValidate.isEmpty(accountNumber)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexAccountNumberNotFound", locale));
}
if (UtilValidate.isEmpty(contactPartyName)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexContactNameCannotBeEmpty", locale));
}
String companyName = null;
GenericValue postalAddress = null;
String phoneNumber = null;
String faxNumber = null;
String emailAddress = null;
try {
// Make sure the company exists
GenericValue companyParty = EntityQuery.use(delegator).from("Party").where("partyId", companyPartyId).cache().queryOne();
if (companyParty == null) {
String errorMessage = "Party with partyId " + companyPartyId + " does not exist";
Debug.logError(errorMessage, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexCompanyPartyDoesNotExists",
UtilMisc.toMap("companyPartyId", companyPartyId), locale));
}
// Get the company name (required by Fedex)
companyName = PartyHelper.getPartyName(companyParty);
if (UtilValidate.isEmpty(companyName)) {
String errorMessage = "Party with partyId " + companyPartyId + " has no name";
Debug.logError(errorMessage, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexCompanyPartyHasNoName",
UtilMisc.toMap("companyPartyId", companyPartyId), locale));
}
// Get the contact information for the company
List<GenericValue> partyContactDetails = EntityQuery.use(delegator).from("PartyContactDetailByPurpose")
.where("partyId", companyPartyId)
.filterByDate(UtilDateTime.nowTimestamp(), "fromDate", "thruDate", "purposeFromDate", "purposeThruDate")
.queryList();
// Get the first valid postal address (address1, city, postalCode and countryGeoId are required by Fedex)
List<EntityCondition> postalAddressConditions = FastList.newInstance();
postalAddressConditions.add(EntityCondition.makeCondition("contactMechTypeId", EntityOperator.EQUALS, "POSTAL_ADDRESS"));
postalAddressConditions.add(EntityCondition.makeCondition("address1", EntityOperator.NOT_EQUAL, null));
postalAddressConditions.add(EntityCondition.makeCondition("address1", EntityOperator.NOT_EQUAL, ""));
postalAddressConditions.add(EntityCondition.makeCondition("city", EntityOperator.NOT_EQUAL, null));
postalAddressConditions.add(EntityCondition.makeCondition("city", EntityOperator.NOT_EQUAL, ""));
postalAddressConditions.add(EntityCondition.makeCondition("postalCode", EntityOperator.NOT_EQUAL, null));
postalAddressConditions.add(EntityCondition.makeCondition("postalCode", EntityOperator.NOT_EQUAL, ""));
postalAddressConditions.add(EntityCondition.makeCondition("countryGeoId", EntityOperator.NOT_EQUAL, null));
postalAddressConditions.add(EntityCondition.makeCondition("countryGeoId", EntityOperator.NOT_EQUAL, ""));
List<GenericValue> postalAddresses = EntityUtil.filterByCondition(partyContactDetails, EntityCondition.makeCondition(postalAddressConditions, EntityOperator.AND));
// Fedex requires USA or Canada addresses to have a state/province ID, so filter out the ones without
postalAddressConditions.clear();
postalAddressConditions.add(EntityCondition.makeCondition("countryGeoId", EntityOperator.IN, UtilMisc.toList("CAN", "USA")));
postalAddressConditions.add(EntityCondition.makeCondition("stateProvinceGeoId", EntityOperator.EQUALS, null));
postalAddresses = EntityUtil.filterOutByCondition(postalAddresses, EntityCondition.makeCondition(postalAddressConditions, EntityOperator.AND));
postalAddressConditions.clear();
postalAddressConditions.add(EntityCondition.makeCondition("countryGeoId", EntityOperator.IN, UtilMisc.toList("CAN", "USA")));
postalAddressConditions.add(EntityCondition.makeCondition("stateProvinceGeoId", EntityOperator.EQUALS, ""));
postalAddresses = EntityUtil.filterOutByCondition(postalAddresses, EntityCondition.makeCondition(postalAddressConditions, EntityOperator.AND));
postalAddress = EntityUtil.getFirst(postalAddresses);
if (UtilValidate.isEmpty(postalAddress)) {
String errorMessage = "Party with partyId " + companyPartyId + " does not have a current, fully populated postal address";
Debug.logError(errorMessage, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexCompanyPartyHasNotPostalAddress",
UtilMisc.toMap("companyPartyId", companyPartyId), locale));
}
GenericValue countryGeo = EntityQuery.use(delegator).from("Geo").where("geoId", postalAddress.getString("countryGeoId")).cache().queryOne();
String countryCode = countryGeo.getString("geoCode");
String stateOrProvinceCode = null;
// Only add the StateOrProvinceCode element if the address is in USA or Canada
if (countryCode.equals("CA") || countryCode.equals("US")) {
GenericValue stateProvinceGeo = EntityQuery.use(delegator).from("Geo").where("geoId", postalAddress.getString("stateProvinceGeoId")).cache().queryOne();
stateOrProvinceCode = stateProvinceGeo.getString("geoCode");
}
// Get the first valid primary phone number (required by Fedex)
List<EntityCondition> phoneNumberConditions = FastList.newInstance();
phoneNumberConditions.add(EntityCondition.makeCondition("contactMechTypeId", EntityOperator.EQUALS, "TELECOM_NUMBER"));
phoneNumberConditions.add(EntityCondition.makeCondition("contactMechPurposeTypeId", EntityOperator.EQUALS, "PRIMARY_PHONE"));
phoneNumberConditions.add(EntityCondition.makeCondition("areaCode", EntityOperator.NOT_EQUAL, null));
phoneNumberConditions.add(EntityCondition.makeCondition("areaCode", EntityOperator.NOT_EQUAL, ""));
phoneNumberConditions.add(EntityCondition.makeCondition("contactNumber", EntityOperator.NOT_EQUAL, null));
phoneNumberConditions.add(EntityCondition.makeCondition("contactNumber", EntityOperator.NOT_EQUAL, ""));
List<GenericValue> phoneNumbers = EntityUtil.filterByCondition(partyContactDetails, EntityCondition.makeCondition(phoneNumberConditions, EntityOperator.AND));
GenericValue phoneNumberValue = EntityUtil.getFirst(phoneNumbers);
if (UtilValidate.isEmpty(phoneNumberValue)) {
String errorMessage = "Party with partyId " + companyPartyId + " does not have a current, fully populated primary phone number";
Debug.logError(errorMessage, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexCompanyPartyHasNotPrimaryPhoneNumber",
UtilMisc.toMap("companyPartyId", companyPartyId), locale));
}
phoneNumber = phoneNumberValue.getString("areaCode") + phoneNumberValue.getString("contactNumber");
// Fedex doesn't want the North American country code
if (UtilValidate.isNotEmpty(phoneNumberValue.getString("countryCode")) && !(countryCode.equals("CA") || countryCode.equals("US"))) {
phoneNumber = phoneNumberValue.getString("countryCode") + phoneNumber;
}
phoneNumber = phoneNumber.replaceAll("[^+\\d]", "");
// Get the first valid fax number
List<EntityCondition> faxNumberConditions = FastList.newInstance();
faxNumberConditions.add(EntityCondition.makeCondition("contactMechTypeId", EntityOperator.EQUALS, "TELECOM_NUMBER"));
faxNumberConditions.add(EntityCondition.makeCondition("contactMechPurposeTypeId", EntityOperator.EQUALS, "FAX_NUMBER"));
faxNumberConditions.add(EntityCondition.makeCondition("areaCode", EntityOperator.NOT_EQUAL, null));
faxNumberConditions.add(EntityCondition.makeCondition("areaCode", EntityOperator.NOT_EQUAL, ""));
faxNumberConditions.add(EntityCondition.makeCondition("contactNumber", EntityOperator.NOT_EQUAL, null));
faxNumberConditions.add(EntityCondition.makeCondition("contactNumber", EntityOperator.NOT_EQUAL, ""));
List<GenericValue> faxNumbers = EntityUtil.filterByCondition(partyContactDetails, EntityCondition.makeCondition(faxNumberConditions, EntityOperator.AND));
GenericValue faxNumberValue = EntityUtil.getFirst(faxNumbers);
if (! UtilValidate.isEmpty(faxNumberValue)) {
faxNumber = faxNumberValue.getString("areaCode") + faxNumberValue.getString("contactNumber");
// Fedex doesn't want the North American country code
if (UtilValidate.isNotEmpty(faxNumberValue.getString("countryCode")) && !(countryCode.equals("CA") || countryCode.equals("US"))) {
faxNumber = faxNumberValue.getString("countryCode") + faxNumber;
}
faxNumber = faxNumber.replaceAll("[^+\\d]", "");
}
// Get the first valid email address
List<EntityCondition> emailConditions = FastList.newInstance();
emailConditions.add(EntityCondition.makeCondition("contactMechTypeId", EntityOperator.EQUALS, "EMAIL_ADDRESS"));
emailConditions.add(EntityCondition.makeCondition("infoString", EntityOperator.NOT_EQUAL, null));
emailConditions.add(EntityCondition.makeCondition("infoString", EntityOperator.NOT_EQUAL, ""));
List<GenericValue> emailAddresses = EntityUtil.filterByCondition(partyContactDetails, EntityCondition.makeCondition(emailConditions, EntityOperator.AND));
GenericValue emailAddressValue = EntityUtil.getFirst(emailAddresses);
if (! UtilValidate.isEmpty(emailAddressValue)) {
emailAddress = emailAddressValue.getString("infoString");
}
// Get the location of the Freemarker (XML) template for the FDXSubscriptionRequest
String templateLocation = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "templateSubscription", resource, "shipment.fedex.template.subscription.location");
if (UtilValidate.isEmpty(templateLocation)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexSubscriptionTemplateLocationNotFound",
UtilMisc.toMap("templateLocation", templateLocation), locale));
}
// Populate the Freemarker context
Map<String, Object> subscriptionRequestContext = FastMap.newInstance();
subscriptionRequestContext.put("AccountNumber", accountNumber);
subscriptionRequestContext.put("PersonName", contactPartyName);
subscriptionRequestContext.put("CompanyName", companyName);
subscriptionRequestContext.put("PhoneNumber", phoneNumber);
if (UtilValidate.isNotEmpty(faxNumber)) {
subscriptionRequestContext.put("FaxNumber", faxNumber);
}
if (UtilValidate.isNotEmpty(emailAddress)) {
subscriptionRequestContext.put("EMailAddress", emailAddress);
}
subscriptionRequestContext.put("Line1", postalAddress.getString("address1"));
if (UtilValidate.isNotEmpty(postalAddress.getString("address2"))) {
subscriptionRequestContext.put("Line2", postalAddress.getString("address2"));
}
subscriptionRequestContext.put("City", postalAddress.getString("city"));
if (UtilValidate.isNotEmpty(stateOrProvinceCode)) {
subscriptionRequestContext.put("StateOrProvinceCode", stateOrProvinceCode);
}
subscriptionRequestContext.put("PostalCode", postalAddress.getString("postalCode"));
subscriptionRequestContext.put("CountryCode", countryCode);
StringWriter outWriter = new StringWriter();
try {
FreeMarkerWorker.renderTemplateAtLocation(templateLocation, subscriptionRequestContext, outWriter);
} catch (Exception e) {
String errorMessage = "Cannot send Fedex subscription request: Failed to render Fedex XML Subscription Request Template [" + templateLocation + "].";
Debug.logError(e, errorMessage, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexSubscriptionTemplateError",
UtilMisc.toMap("templateLocation", templateLocation, "errorString", e.getMessage()), locale));
}
String fDXSubscriptionRequestString = outWriter.toString();
// Send the request
String fDXSubscriptionReplyString = null;
try {
fDXSubscriptionReplyString = sendFedexRequest(fDXSubscriptionRequestString, delegator, shipmentGatewayConfigId, resource, locale);
Debug.logInfo("Fedex response for FDXSubscriptionRequest:" + fDXSubscriptionReplyString, module);
} catch (FedexConnectException e) {
String errorMessage = "Error sending Fedex request for FDXSubscriptionRequest: " + e.toString();
Debug.logError(e, errorMessage, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexSubscriptionTemplateSendingError",
UtilMisc.toMap("errorString", e.toString()), locale));
}
Document fDXSubscriptionReplyDocument = null;
try {
fDXSubscriptionReplyDocument = UtilXml.readXmlDocument(fDXSubscriptionReplyString, false);
Debug.logInfo("Fedex response for FDXSubscriptionRequest:" + fDXSubscriptionReplyString, module);
} catch (Exception e) {
String errorMessage = "Error parsing the FDXSubscriptionRequest response: " + e.toString();
Debug.logError(e, errorMessage, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexSubscriptionTemplateParsingError",
UtilMisc.toMap("errorString", e.toString()), locale));
}
Element fedexSubscriptionReplyElement = fDXSubscriptionReplyDocument.getDocumentElement();
handleErrors(fedexSubscriptionReplyElement, errorList, locale);
if (UtilValidate.isNotEmpty(errorList)) {
return ServiceUtil.returnError(errorList);
}
String meterNumber = UtilXml.childElementValue(fedexSubscriptionReplyElement, "MeterNumber");
result.put("meterNumber", meterNumber);
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
return result;
}
/**
*
* Send a FDXShipRequest via the Ship Manager Direct API
*/
public static Map<String, Object> fedexShipRequest(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
Locale locale = (Locale) context.get("locale");
String shipmentId = (String) context.get("shipmentId");
String shipmentRouteSegmentId = (String) context.get("shipmentRouteSegmentId");
Map<String, Object> shipmentGatewayConfig = ShipmentServices.getShipmentGatewayConfigFromShipment(delegator, shipmentId, locale);
String shipmentGatewayConfigId = (String) shipmentGatewayConfig.get("shipmentGatewayConfigId");
String resource = (String) shipmentGatewayConfig.get("configProps");
if (UtilValidate.isEmpty(shipmentGatewayConfigId) && UtilValidate.isEmpty(resource)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexGatewayNotAvailable", locale));
}
// Get the location of the Freemarker (XML) template for the FDXShipRequest
String templateLocation = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "templateShipment", resource, "shipment.fedex.template.ship.location");
if (UtilValidate.isEmpty(templateLocation)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentTemplateLocationNotFound",
UtilMisc.toMap("templateLocation", templateLocation), locale));
}
// Get the Fedex account number
String accountNumber = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessAccountNbr", resource, "shipment.fedex.access.accountNbr");
if (UtilValidate.isEmpty(accountNumber)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexAccountNumberNotFound", locale));
}
// Get the Fedex meter number
String meterNumber = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "accessMeterNumber", resource, "shipment.fedex.access.meterNumber");
if (UtilValidate.isEmpty(meterNumber)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexMeterNumberNotFound",
UtilMisc.toMap("meterNumber", meterNumber), locale));
}
// Get the weight units to be used in the request
String weightUomId = EntityUtilProperties.getPropertyValue(shipmentPropertiesFile, "shipment.default.weight.uom", delegator);
if (UtilValidate.isEmpty(weightUomId)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDefaultWeightUomIdNotFound", locale));
} else if (! ("WT_lb".equals(weightUomId) || "WT_kg".equals(weightUomId))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDefaultWeightUomIdNotValid", locale));
}
// Get the dimension units to be used in the request
String dimensionsUomId = EntityUtilProperties.getPropertyValue(shipmentPropertiesFile, "shipment.default.dimension.uom", delegator);
if (UtilValidate.isEmpty(dimensionsUomId)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDefaultDimensionUomIdNotFound", locale));
} else if (! ("LEN_in".equals(dimensionsUomId) || "LEN_cm".equals(dimensionsUomId))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentDefaultDimensionUomIdNotValid", locale));
}
// Get the label image type to be returned
String labelImageType = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "labelImageType", resource, "shipment.fedex.labelImageType");
if (UtilValidate.isEmpty(labelImageType)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexLabelImageTypeNotFound", locale));
} else if (! ("PDF".equals(labelImageType) || "PNG".equals(labelImageType))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexLabelImageTypeNotValid", locale));
}
// Get the default dropoff type
String dropoffType = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "defaultDropoffType", resource, "shipment.fedex.default.dropoffType");
if (UtilValidate.isEmpty(dropoffType)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexDropoffTypeNotFound", locale));
}
try {
Map<String, Object> shipRequestContext = FastMap.newInstance();
// Get the shipment and the shipmentRouteSegment
GenericValue shipment = EntityQuery.use(delegator).from("Shipment").where("shipmentId", shipmentId).queryOne();
if (UtilValidate.isEmpty(shipment)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"ProductShipmentNotFoundId", locale) + shipmentId);
}
GenericValue shipmentRouteSegment = EntityQuery.use(delegator).from("ShipmentRouteSegment").where("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId).queryOne();
if (UtilValidate.isEmpty(shipmentRouteSegment)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"ProductShipmentRouteSegmentNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
// Determine the Fedex carrier
String carrierPartyId = shipmentRouteSegment.getString("carrierPartyId");
if (! "FEDEX".equals(carrierPartyId)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexNotRouteSegmentCarrier",
UtilMisc.toMap("shipmentRouteSegmentId", shipmentRouteSegmentId, "shipmentId", shipmentId), locale));
}
// Check the shipmentRouteSegment's carrier status
if (UtilValidate.isNotEmpty(shipmentRouteSegment.getString("carrierServiceStatusId")) && !"SHRSCS_NOT_STARTED".equals(shipmentRouteSegment.getString("carrierServiceStatusId"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexRouteSegmentStatusNotStarted",
UtilMisc.toMap("shipmentRouteSegmentId", shipmentRouteSegmentId, "shipmentId", shipmentId, "shipmentRouteSegmentStatus", shipmentRouteSegment.getString("carrierServiceStatusId")), locale));
}
// Translate shipmentMethodTypeId to Fedex service code and carrier code
String shipmentMethodTypeId = shipmentRouteSegment.getString("shipmentMethodTypeId");
GenericValue carrierShipmentMethod = EntityQuery.use(delegator).from("CarrierShipmentMethod").where("shipmentMethodTypeId", shipmentMethodTypeId, "partyId", "FEDEX", "roleTypeId", "CARRIER").queryOne();
if (UtilValidate.isEmpty(carrierShipmentMethod)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexRouteSegmentCarrierShipmentMethodNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId, "carrierPartyId", carrierPartyId, "shipmentMethodTypeId", shipmentMethodTypeId), locale));
}
if (UtilValidate.isEmpty(carrierShipmentMethod.getString("carrierServiceCode"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexNoCarrieServiceCode",
UtilMisc.toMap("shipmentMethodTypeId", shipmentMethodTypeId), locale));
}
String service = carrierShipmentMethod.getString("carrierServiceCode");
// CarrierCode is FDXG only for FEDEXGROUND and GROUNDHOMEDELIVERY services.
boolean isGroundService = service.equals("FEDEXGROUND") || service.equals("GROUNDHOMEDELIVERY");
String carrierCode = isGroundService ? "FDXG" : "FDXE";
// Determine the currency by trying the shipmentRouteSegment, then the Shipment, then the framework's default currency, and finally default to USD
String currencyCode = null;
if (UtilValidate.isNotEmpty(shipmentRouteSegment.getString("currencyUomId"))) {
currencyCode = shipmentRouteSegment.getString("currencyUomId");
} else if (UtilValidate.isNotEmpty(shipmentRouteSegment.getString("currencyUomId"))) {
currencyCode = shipment.getString("currencyUomId");
} else {
currencyCode = EntityUtilProperties.getPropertyValue("general.properties", "currency.uom.id.default", "USD", delegator);
}
// Get and validate origin postal address
GenericValue originPostalAddress = shipmentRouteSegment.getRelatedOne("OriginPostalAddress", false);
if (UtilValidate.isEmpty(originPostalAddress)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentOriginPostalAddressNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
} else if (UtilValidate.isEmpty(originPostalAddress.getString("address1")) ||
UtilValidate.isEmpty(originPostalAddress.getString("city")) ||
UtilValidate.isEmpty(originPostalAddress.getString("postalCode")) ||
UtilValidate.isEmpty(originPostalAddress.getString("countryGeoId"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentOriginPostalAddressNotComplete",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
GenericValue originCountryGeo = originPostalAddress.getRelatedOne("CountryGeo", false);
if (UtilValidate.isEmpty(originCountryGeo)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentOriginCountryGeoNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
String originAddressCountryCode = originCountryGeo.getString("geoCode");
String originAddressStateOrProvinceCode = null;
// Only add the StateOrProvinceCode element if the address is in USA or Canada
if (originAddressCountryCode.equals("CA") || originAddressCountryCode.equals("US")) {
if (UtilValidate.isEmpty(originPostalAddress.getString("stateProvinceGeoId"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentOriginStateProvinceGeoIdRequired",
UtilMisc.toMap("contactMechId", originPostalAddress.getString("contactMechId"),
"shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
GenericValue stateProvinceGeo = EntityQuery.use(delegator).from("Geo").where("geoId", originPostalAddress.getString("stateProvinceGeoId")).cache().queryOne();
originAddressStateOrProvinceCode = stateProvinceGeo.getString("geoCode");
}
// Get and validate origin telecom number
GenericValue originTelecomNumber = shipmentRouteSegment.getRelatedOne("OriginTelecomNumber", false);
if (UtilValidate.isEmpty(originTelecomNumber)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentOriginTelecomNumberNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
String originContactPhoneNumber = originTelecomNumber.getString("areaCode") + originTelecomNumber.getString("contactNumber");
// Fedex doesn't want the North American country code
if (UtilValidate.isNotEmpty(originTelecomNumber.getString("countryCode")) && !(originAddressCountryCode.equals("CA") || originAddressCountryCode.equals("US"))) {
originContactPhoneNumber = originTelecomNumber.getString("countryCode") + originContactPhoneNumber;
}
originContactPhoneNumber = originContactPhoneNumber.replaceAll("[^+\\d]", "");
// Get the origin contact name from the owner of the origin facility
GenericValue partyFrom = null;
GenericValue originFacility = shipment.getRelatedOne("OriginFacility", false);
if (UtilValidate.isEmpty(originFacility)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexOriginFacilityRequired",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
} else {
partyFrom = originFacility.getRelatedOne("OwnerParty", false);
if (UtilValidate.isEmpty(partyFrom)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexOwnerPartyRequired",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId,
"facilityId", originFacility.getString("facilityId")), locale));
}
}
String originContactKey = "PERSON".equals(partyFrom.getString("partyTypeId")) ? "OriginContactPersonName" : "OriginContactCompanyName";
String originContactName = PartyHelper.getPartyName(partyFrom, false);
if (UtilValidate.isEmpty(originContactName)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexPartyFromHasNoName",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
// Get and validate destination postal address
GenericValue destinationPostalAddress = shipmentRouteSegment.getRelatedOne("DestPostalAddress", false);
if (UtilValidate.isEmpty(destinationPostalAddress)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentDestPostalAddressNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
} else if (UtilValidate.isEmpty(destinationPostalAddress.getString("address1")) ||
UtilValidate.isEmpty(destinationPostalAddress.getString("city")) ||
UtilValidate.isEmpty(destinationPostalAddress.getString("postalCode")) ||
UtilValidate.isEmpty(destinationPostalAddress.getString("countryGeoId"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentDestPostalAddressIncomplete",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
GenericValue destinationCountryGeo = destinationPostalAddress.getRelatedOne("CountryGeo", false);
if (UtilValidate.isEmpty(destinationCountryGeo)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentDestCountryGeoNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
String destinationAddressCountryCode = destinationCountryGeo.getString("geoCode");
String destinationAddressStateOrProvinceCode = null;
// Only add the StateOrProvinceCode element if the address is in USA or Canada
if (destinationAddressCountryCode.equals("CA") || destinationAddressCountryCode.equals("US")) {
if (UtilValidate.isEmpty(destinationPostalAddress.getString("stateProvinceGeoId"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentDestStateProvinceGeoIdNotFound",
UtilMisc.toMap("contactMechId", destinationPostalAddress.getString("contactMechId"),
"shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
GenericValue stateProvinceGeo = EntityQuery.use(delegator).from("Geo").where("geoId", destinationPostalAddress.getString("stateProvinceGeoId")).cache().queryOne();
destinationAddressStateOrProvinceCode = stateProvinceGeo.getString("geoCode");
}
// Get and validate destination telecom number
GenericValue destinationTelecomNumber = shipmentRouteSegment.getRelatedOne("DestTelecomNumber", false);
if (UtilValidate.isEmpty(destinationTelecomNumber)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentRouteSegmentDestTelecomNumberNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
String destinationContactPhoneNumber = destinationTelecomNumber.getString("areaCode") + destinationTelecomNumber.getString("contactNumber");
// Fedex doesn't want the North American country code
if (UtilValidate.isNotEmpty(destinationTelecomNumber.getString("countryCode")) && !(destinationAddressCountryCode.equals("CA") || destinationAddressCountryCode.equals("US"))) {
destinationContactPhoneNumber = destinationTelecomNumber.getString("countryCode") + destinationContactPhoneNumber;
}
destinationContactPhoneNumber = destinationContactPhoneNumber.replaceAll("[^+\\d]", "");
// Get the destination contact name
String destinationPartyId = shipment.getString("partyIdTo");
if (UtilValidate.isEmpty(destinationPartyId)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexPartyToRequired",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
GenericValue partyTo = EntityQuery.use(delegator).from("Party").where("partyId", destinationPartyId).queryOne();
String destinationContactKey = "PERSON".equals(partyTo.getString("partyTypeId")) ? "DestinationContactPersonName" : "DestinationContactCompanyName";
String destinationContactName = PartyHelper.getPartyName(partyTo, false);
if (UtilValidate.isEmpty(destinationContactName)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexPartyToHasNoName",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
String homeDeliveryType = null;
Timestamp homeDeliveryDate = null;
if ("GROUNDHOMEDELIVERY".equals(service)) {
// Determine the home-delivery instructions
homeDeliveryType = shipmentRouteSegment.getString("homeDeliveryType");
if (UtilValidate.isNotEmpty(homeDeliveryType)) {
if (! (homeDeliveryType.equals("DATECERTAIN") || homeDeliveryType.equals("EVENING") || homeDeliveryType.equals("APPOINTMENT"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexHomeDeliveryTypeInvalid",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
}
homeDeliveryDate = shipmentRouteSegment.getTimestamp("homeDeliveryDate");
if (UtilValidate.isEmpty(homeDeliveryDate)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexHomeDeliveryDateRequired",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
} else if (homeDeliveryDate.before(UtilDateTime.nowTimestamp())) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexHomeDeliveryDateBeforeCurrentDate",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
}
List<GenericValue> shipmentPackageRouteSegs = shipmentRouteSegment.getRelated("ShipmentPackageRouteSeg", null, UtilMisc.toList("+shipmentPackageSeqId"), false);
if (UtilValidate.isEmpty(shipmentPackageRouteSegs)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "FacilityShipmentPackageRouteSegsNotFound",
UtilMisc.toMap("shipmentId", shipmentId, "shipmentRouteSegmentId", shipmentRouteSegmentId), locale));
}
if (shipmentPackageRouteSegs.size() != 1) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexMultiplePackagesNotSupported", locale));
}
// TODO: Multi-piece shipments, including logic to cancel packages 1-n if FDXShipRequest n+1 fails
// Populate the Freemarker context with the non-package-related information
shipRequestContext.put("AccountNumber", accountNumber);
shipRequestContext.put("MeterNumber", meterNumber);
shipRequestContext.put("CarrierCode", carrierCode);
shipRequestContext.put("ShipDate", UtilDateTime.nowTimestamp());
shipRequestContext.put("ShipTime", UtilDateTime.nowTimestamp());
shipRequestContext.put("DropoffType", dropoffType);
shipRequestContext.put("Service", service);
shipRequestContext.put("WeightUnits", weightUomId.equals("WT_kg") ? "KGS" : "LBS");
shipRequestContext.put("CurrencyCode", currencyCode);
shipRequestContext.put("PayorType", "SENDER");
shipRequestContext.put(originContactKey, originContactName);
shipRequestContext.put("OriginContactPhoneNumber", originContactPhoneNumber);
shipRequestContext.put("OriginAddressLine1", originPostalAddress.getString("address1"));
if (UtilValidate.isNotEmpty(originPostalAddress.getString("address2"))) {
shipRequestContext.put("OriginAddressLine2", originPostalAddress.getString("address2"));
}
shipRequestContext.put("OriginAddressCity", originPostalAddress.getString("city"));
if (UtilValidate.isNotEmpty(originAddressStateOrProvinceCode)) {
shipRequestContext.put("OriginAddressStateOrProvinceCode", originAddressStateOrProvinceCode);
}
shipRequestContext.put("OriginAddressPostalCode", originPostalAddress.getString("postalCode"));
shipRequestContext.put("OriginAddressCountryCode", originAddressCountryCode);
shipRequestContext.put(destinationContactKey, destinationContactName);
shipRequestContext.put("DestinationContactPhoneNumber", destinationContactPhoneNumber);
shipRequestContext.put("DestinationAddressLine1", destinationPostalAddress.getString("address1"));
if (UtilValidate.isNotEmpty(destinationPostalAddress.getString("address2"))) {
shipRequestContext.put("DestinationAddressLine2", destinationPostalAddress.getString("address2"));
}
shipRequestContext.put("DestinationAddressCity", destinationPostalAddress.getString("city"));
if (UtilValidate.isNotEmpty(destinationAddressStateOrProvinceCode)) {
shipRequestContext.put("DestinationAddressStateOrProvinceCode", destinationAddressStateOrProvinceCode);
}
shipRequestContext.put("DestinationAddressPostalCode", destinationPostalAddress.getString("postalCode"));
shipRequestContext.put("DestinationAddressCountryCode", destinationAddressCountryCode);
shipRequestContext.put("LabelType", "2DCOMMON"); // Required type for FDXShipRequest. Not directly in the FTL because it shouldn't be changed.
shipRequestContext.put("LabelImageType", labelImageType);
if (UtilValidate.isNotEmpty(homeDeliveryType)) {
shipRequestContext.put("HomeDeliveryType", homeDeliveryType);
}
if (homeDeliveryDate != null) {
shipRequestContext.put("HomeDeliveryDate", homeDeliveryDate);
}
// Get the weight from the ShipmentRouteSegment first, which overrides all later weight computations
boolean hasBillingWeight = false;
BigDecimal billingWeight = shipmentRouteSegment.getBigDecimal("billingWeight");
String billingWeightUomId = shipmentRouteSegment.getString("billingWeightUomId");
if ((billingWeight != null) && (billingWeight.compareTo(BigDecimal.ZERO) > 0)) {
hasBillingWeight = true;
if (billingWeightUomId == null) {
Debug.logWarning("Shipment Route Segment missing billingWeightUomId in shipmentId " + shipmentId + ", assuming default shipment.fedex.weightUomId of " + weightUomId + " from " + shipmentPropertiesFile, module);
billingWeightUomId = weightUomId;
}
// Convert the weight if necessary
if (! billingWeightUomId.equals(weightUomId)) {
Map<String, Object> results = dispatcher.runSync("convertUom", UtilMisc.<String, Object>toMap("uomId", billingWeightUomId, "uomIdTo", weightUomId, "originalValue", billingWeight));
if (ServiceUtil.isError(results) || (results.get("convertedValue") == null)) {
Debug.logWarning("Unable to convert billing weights for shipmentId " + shipmentId , module);
// Try getting the weight from package instead
hasBillingWeight = false;
} else {
billingWeight = (BigDecimal) results.get("convertedValue");
}
}
}
// Loop through Shipment segments (NOTE: only one supported, loop is here for future refactoring reference)
for (GenericValue shipmentPackageRouteSeg: shipmentPackageRouteSegs) {
GenericValue shipmentPackage = shipmentPackageRouteSeg.getRelatedOne("ShipmentPackage", false);
GenericValue shipmentBoxType = shipmentPackage.getRelatedOne("ShipmentBoxType", false);
// FedEx requires the packaging type
String packaging = null;
if (UtilValidate.isEmpty(shipmentBoxType)) {
packaging = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, "defaultPackagingType", resource, "shipment.fedex.default.packagingType");
if (UtilValidate.isEmpty(packaging)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexPackingTypeNotConfigured",
UtilMisc.toMap("shipmentPackageSeqId", shipmentPackage.getString("shipmentPackageSeqId"),
"shipmentId", shipmentId), locale));
}
Debug.logWarning("Package " + shipmentPackage.getString("shipmentPackageSeqId") + " of shipment " + shipmentId + " has no packaging type set - defaulting to " + packaging, module);
} else {
packaging = shipmentBoxType.getString("shipmentBoxTypeId");
}
// Make sure that the packaging type is valid for FedEx
GenericValue carrierShipmentBoxType = EntityQuery.use(delegator).from("CarrierShipmentBoxType").where("partyId", "FEDEX", "shipmentBoxTypeId", packaging).queryOne();
if (UtilValidate.isEmpty(carrierShipmentBoxType)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexPackingTypeInvalid",
UtilMisc.toMap("shipmentPackageSeqId", shipmentPackage.getString("shipmentPackageSeqId"),
"shipmentId", shipmentId), locale));
} else if (UtilValidate.isEmpty(carrierShipmentBoxType.getString("packagingTypeCode"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexPackingTypeMissing",
UtilMisc.toMap("shipmentPackageSeqId", shipmentPackage.getString("shipmentPackageSeqId"),
"shipmentId", shipmentId), locale));
}
packaging = carrierShipmentBoxType.getString("packagingTypeCode");
// Determine the dimensions of the package
BigDecimal dimensionsLength = null;
BigDecimal dimensionsWidth = null;
BigDecimal dimensionsHeight = null;
if (shipmentBoxType != null) {
dimensionsLength = shipmentBoxType.getBigDecimal("boxLength");
dimensionsWidth = shipmentBoxType.getBigDecimal("boxWidth");
dimensionsHeight = shipmentBoxType.getBigDecimal("boxHeight");
String boxDimensionsUomId = null;
GenericValue boxDimensionsUom = shipmentBoxType.getRelatedOne("DimensionUom", false);
if (! UtilValidate.isEmpty(boxDimensionsUom)) {
boxDimensionsUomId = boxDimensionsUom.getString("uomId");
} else {
Debug.logWarning("Packaging type for package " + shipmentPackage.getString("shipmentPackageSeqId") + " of shipmentRouteSegment " + shipmentRouteSegmentId + " of shipment " + shipmentId + " is missing dimensionUomId, assuming default shipment.default.dimension.uom of " + dimensionsUomId + " from " + shipmentPropertiesFile, module);
boxDimensionsUomId = dimensionsUomId;
}
if (dimensionsLength != null && dimensionsLength.compareTo(BigDecimal.ZERO) > 0) {
if (! boxDimensionsUomId.equals(dimensionsUomId)) {
Map<String, Object> results = dispatcher.runSync("convertUom", UtilMisc.<String, Object>toMap("uomId", boxDimensionsUomId, "uomIdTo", dimensionsUomId, "originalValue", dimensionsLength));
if (ServiceUtil.isError(results) || (results.get("convertedValue") == null)) {
Debug.logWarning("Unable to convert length for package " + shipmentPackage.getString("shipmentPackageSeqId") + " of shipmentRouteSegment " + shipmentRouteSegmentId + " of shipment " + shipmentId , module);
dimensionsLength = null;
} else {
dimensionsLength = (BigDecimal) results.get("convertedValue");
}
}
}
if (dimensionsWidth != null && dimensionsWidth.compareTo(BigDecimal.ZERO) > 0) {
if (! boxDimensionsUomId.equals(dimensionsUomId)) {
Map<String, Object> results = dispatcher.runSync("convertUom", UtilMisc.<String, Object>toMap("uomId", boxDimensionsUomId, "uomIdTo", dimensionsUomId, "originalValue", dimensionsWidth));
if (ServiceUtil.isError(results) || (results.get("convertedValue") == null)) {
Debug.logWarning("Unable to convert width for package " + shipmentPackage.getString("shipmentPackageSeqId") + " of shipmentRouteSegment " + shipmentRouteSegmentId + " of shipment " + shipmentId , module);
dimensionsWidth = null;
} else {
dimensionsWidth = (BigDecimal) results.get("convertedValue");
}
}
}
if (dimensionsHeight != null && dimensionsHeight.compareTo(BigDecimal.ZERO) > 0) {
if (! boxDimensionsUomId.equals(dimensionsUomId)) {
Map<String, Object> results = dispatcher.runSync("convertUom", UtilMisc.<String, Object>toMap("uomId", boxDimensionsUomId, "uomIdTo", dimensionsUomId, "originalValue", dimensionsHeight));
if (ServiceUtil.isError(results) || (results.get("convertedValue") == null)) {
Debug.logWarning("Unable to convert height for package " + shipmentPackage.getString("shipmentPackageSeqId") + " of shipmentRouteSegment " + shipmentRouteSegmentId + " of shipment " + shipmentId , module);
dimensionsHeight = null;
} else {
dimensionsHeight = (BigDecimal) results.get("convertedValue");
}
}
}
}
// Determine the package weight (possibly overriden by route segment billing weight)
BigDecimal packageWeight = null;
if (! hasBillingWeight) {
if (UtilValidate.isNotEmpty(shipmentPackage.getString("weight"))) {
packageWeight = shipmentPackage.getBigDecimal("weight");
} else {
// Use default weight if available
try {
packageWeight = new BigDecimal(EntityUtilProperties.getPropertyValue(shipmentPropertiesFile, "shipment.default.weight.value", delegator));
} catch (NumberFormatException ne) {
Debug.logWarning("Default shippable weight not configured (shipment.default.weight.value), assuming 1.0" + weightUomId , module);
packageWeight = BigDecimal.ONE;
}
}
// Convert weight if necessary
String packageWeightUomId = shipmentPackage.getString("weightUomId");
if (UtilValidate.isEmpty(packageWeightUomId)) {
Debug.logWarning("Shipment Route Segment missing weightUomId in shipmentId " + shipmentId + ", assuming shipment.default.weight.uom of " + weightUomId + " from " + shipmentPropertiesFile, module);
packageWeightUomId = weightUomId;
}
if (! packageWeightUomId.equals(weightUomId)) {
Map<String, Object> results = dispatcher.runSync("convertUom", UtilMisc.<String, Object>toMap("uomId", packageWeightUomId, "uomIdTo", weightUomId, "originalValue", packageWeight));
if (ServiceUtil.isError(results) || (results.get("convertedValue") == null)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexWeightOfPackageCannotBeConverted",
UtilMisc.toMap("shipmentPackageSeqId", shipmentPackage.getString("shipmentPackageSeqId"),
"shipmentRouteSegmentId", shipmentRouteSegmentId, "shipmentId", shipmentId), locale));
} else {
packageWeight = (BigDecimal) results.get("convertedValue");
}
}
}
BigDecimal weight = hasBillingWeight ? billingWeight : packageWeight;
if (weight == null || weight.compareTo(BigDecimal.ZERO) < 0) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexWeightOfPackageNotAvailable",
UtilMisc.toMap("shipmentPackageSeqId", shipmentPackage.getString("shipmentPackageSeqId"),
"shipmentRouteSegmentId", shipmentRouteSegmentId, "shipmentId", shipmentId), locale));
}
// Populate the Freemarker context with package-related information
shipRequestContext.put("CustomerReference", shipmentId + ":" + shipmentRouteSegmentId + ":" + shipmentPackage.getString("shipmentPackageSeqId"));
shipRequestContext.put("DropoffType", dropoffType);
shipRequestContext.put("Packaging", packaging);
if (UtilValidate.isNotEmpty(dimensionsUomId) &&
dimensionsLength != null && dimensionsLength.setScale(0, BigDecimal.ROUND_HALF_UP).compareTo(BigDecimal.ZERO) > 0 &&
dimensionsWidth != null && dimensionsWidth.setScale(0, BigDecimal.ROUND_HALF_UP).compareTo(BigDecimal.ZERO) > 0 &&
dimensionsHeight != null && dimensionsHeight.setScale(0, BigDecimal.ROUND_HALF_UP).compareTo(BigDecimal.ZERO) > 0) {
shipRequestContext.put("DimensionsUnits", dimensionsUomId.equals("LEN_in") ? "IN" : "CM");
shipRequestContext.put("DimensionsLength", dimensionsLength.setScale(0, BigDecimal.ROUND_HALF_UP).toString());
shipRequestContext.put("DimensionsWidth", dimensionsWidth.setScale(0, BigDecimal.ROUND_HALF_UP).toString());
shipRequestContext.put("DimensionsHeight", dimensionsHeight.setScale(0, BigDecimal.ROUND_HALF_UP).toString());
}
shipRequestContext.put("Weight", weight.setScale(1, BigDecimal.ROUND_UP).toString());
}
StringWriter outWriter = new StringWriter();
try {
FreeMarkerWorker.renderTemplateAtLocation(templateLocation, shipRequestContext, outWriter);
} catch (Exception e) {
String errorMessage = "Cannot confirm Fedex shipment: Failed to render Fedex XML Ship Request Template [" + templateLocation + "].";
Debug.logError(e, errorMessage, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentTemplateError",
UtilMisc.toMap("templateLocation", templateLocation, "errorString", e.getMessage()), locale));
}
// Pass the request string to the sending method
String fDXShipRequestString = outWriter.toString();
String fDXShipReplyString = null;
try {
fDXShipReplyString = sendFedexRequest(fDXShipRequestString, delegator, shipmentGatewayConfigId, resource, locale);
if (Debug.verboseOn()) {
Debug.logVerbose(fDXShipReplyString, module);
}
} catch (FedexConnectException e) {
String errorMessage = "Error sending Fedex request for FDXShipRequest: ";
Debug.logError(e, errorMessage, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentTemplateSendingError",
UtilMisc.toMap("errorString", e.toString()), locale));
}
// Pass the reply to the handler method
return handleFedexShipReply(fDXShipReplyString, shipmentRouteSegment, shipmentPackageRouteSegs, locale);
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentTemplateServiceError",
UtilMisc.toMap("errorString", e.toString()), locale));
} catch (GenericServiceException se) {
Debug.logError(se, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentTemplateServiceError",
UtilMisc.toMap("errorString", se.toString()), locale));
}
}
/**
* Extract the tracking number and shipping label from the FDXShipReply XML string
* @param fDXShipReplyString
* @param shipmentRouteSegment
* @param shipmentPackageRouteSegs
* @throws GenericEntityException
*/
public static Map<String, Object> handleFedexShipReply(String fDXShipReplyString, GenericValue shipmentRouteSegment,
List<GenericValue> shipmentPackageRouteSegs, Locale locale) throws GenericEntityException {
List<Object> errorList = FastList.newInstance();
GenericValue shipmentPackageRouteSeg = shipmentPackageRouteSegs.get(0);
Document fdxShipReplyDocument = null;
try {
fdxShipReplyDocument = UtilXml.readXmlDocument(fDXShipReplyString, false);
} catch (Exception e) {
String errorMessage = "Error parsing the FDXShipReply: " + e.toString();
Debug.logError(e, errorMessage, module);
// TODO Cancel the package
}
if (UtilValidate.isEmpty(fdxShipReplyDocument)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentTemplateParsingError", locale));
}
// Tracking number: Tracking/TrackingNumber
Element rootElement = fdxShipReplyDocument.getDocumentElement();
handleErrors(rootElement, errorList, locale);
if (UtilValidate.isNotEmpty(errorList)) {
return ServiceUtil.returnError(errorList);
}
Element trackingElement = UtilXml.firstChildElement(rootElement, "Tracking");
String trackingNumber = UtilXml.childElementValue(trackingElement, "TrackingNumber");
// Label: Labels/OutboundLabel
Element labelElement = UtilXml.firstChildElement(rootElement, "Labels");
String encodedImageString = UtilXml.childElementValue(labelElement, "OutboundLabel");
if (UtilValidate.isEmpty(encodedImageString)) {
Debug.logError("Cannot find FDXShipReply label. FDXShipReply document is: " + fDXShipReplyString, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentTemplateLabelNotFound",
UtilMisc.toMap("shipmentPackageRouteSeg", shipmentPackageRouteSeg,
"fDXShipReplyString", fDXShipReplyString), locale));
}
byte[] labelBytes = Base64.base64Decode(encodedImageString.getBytes());
if (labelBytes != null) {
// Store in db blob
shipmentPackageRouteSeg.setBytes("labelImage", labelBytes);
} else {
Debug.logInfo("Failed to either decode returned FedEx label or no data found in Labels/OutboundLabel.", module);
// TODO: Cancel the package
}
shipmentPackageRouteSeg.set("trackingCode", trackingNumber);
shipmentPackageRouteSeg.set("labelHtml", encodedImageString);
shipmentPackageRouteSeg.store();
shipmentRouteSegment.set("trackingIdNumber", trackingNumber);
shipmentRouteSegment.put("carrierServiceStatusId", "SHRSCS_CONFIRMED");
shipmentRouteSegment.store();
return ServiceUtil.returnSuccess(UtilProperties.getMessage(resourceError,
"FacilityShipmentFedexShipmentConfirmed", locale));
}
public static void handleErrors(Element rootElement, List<Object> errorList, Locale locale) {
Element errorElement = null;
if ("Error".equalsIgnoreCase(rootElement.getNodeName())) {
errorElement = rootElement;
} else {
errorElement = UtilXml.firstChildElement(rootElement, "Error");
}
if (UtilValidate.isNotEmpty(errorElement)) {
Element errorCodeElement = UtilXml.firstChildElement(errorElement, "Code");
Element errorMessageElement = UtilXml.firstChildElement(errorElement, "Message");
if (errorCodeElement != null || errorMessageElement != null) {
String errorCode = UtilXml.childElementValue(errorElement, "Code");
String errorMessage = UtilXml.childElementValue(errorElement, "Message");
if (UtilValidate.isNotEmpty(errorCode) || UtilValidate.isNotEmpty(errorMessage)) {
errorList.add(UtilProperties.getMessage(resourceError, "FacilityShipmentFedexErrorMessage",
UtilMisc.toMap("errorCode", errorCode, "errorMessage", errorMessage), locale));
}
}
}
}
private static String getShipmentGatewayConfigValue(Delegator delegator, String shipmentGatewayConfigId, String shipmentGatewayConfigParameterName,
String resource, String parameterName) {
String returnValue = "";
if (UtilValidate.isNotEmpty(shipmentGatewayConfigId)) {
try {
GenericValue fedex = EntityQuery.use(delegator).from("ShipmentGatewayFedex").where("shipmentGatewayConfigId", shipmentGatewayConfigId).queryOne();
if (UtilValidate.isNotEmpty(fedex)) {
Object fedexField = fedex.get(shipmentGatewayConfigParameterName);
if (fedexField != null) {
returnValue = fedexField.toString().trim();
}
}
} catch (GenericEntityException e) {
Debug.logError(e, module);
}
} else {
String value = EntityUtilProperties.getPropertyValue(resource, parameterName, delegator);
if (value != null) {
returnValue = value.trim();
}
}
return returnValue;
}
private static String getShipmentGatewayConfigValue(Delegator delegator, String shipmentGatewayConfigId, String shipmentGatewayConfigParameterName,
String resource, String parameterName, String defaultValue) {
String returnValue = getShipmentGatewayConfigValue(delegator, shipmentGatewayConfigId, shipmentGatewayConfigParameterName, resource, parameterName);
if (UtilValidate.isEmpty(returnValue)) {
returnValue = defaultValue;
}
return returnValue;
}
}
@SuppressWarnings("serial")
class FedexConnectException extends GeneralException {
FedexConnectException() {
super();
}
FedexConnectException(String msg) {
super(msg);
}
FedexConnectException(Throwable t) {
super(t);
}
FedexConnectException(String msg, Throwable t) {
super(msg, t);
}
}