| /* |
| * 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); |
| } |
| } |