feat: separate fusion field service and LTC into standalone modules, update core modules
- fusion_claims: separated field service logic, updated controllers/views - fusion_tasks: updated task views and map integration - fusion_authorizer_portal: added page 11 signing, schedule booking, migrations - fusion_shipping: new standalone shipping module (Canada Post, FedEx, DHL, Purolator) - fusion_ltc_management: new standalone LTC management module
This commit is contained in:
637
fusion_shipping/api/ups/request.py
Normal file
637
fusion_shipping/api/ups/request.py
Normal file
@@ -0,0 +1,637 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import base64
|
||||
import binascii
|
||||
import io
|
||||
import PIL.PdfImagePlugin # activate PDF support in PIL
|
||||
from PIL import Image
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from odoo.tools.zeep import Client, Plugin
|
||||
from odoo.tools.zeep.exceptions import Fault
|
||||
from odoo.tools.zeep.wsdl.utils import etree_to_string
|
||||
|
||||
from odoo.tools import _, LazyTranslate
|
||||
from odoo.tools.float_utils import float_repr
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_lt = LazyTranslate(__name__)
|
||||
_logger = logging.getLogger(__name__)
|
||||
# uncomment to enable logging of SOAP requests and responses
|
||||
# logging.getLogger('zeep.transports').setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
UPS_ERROR_MAP = {
|
||||
'110002': _lt("Please provide at least one item to ship."),
|
||||
'110208': _lt("Please set a valid country in the recipient address."),
|
||||
'110308': _lt("Please set a valid country in the warehouse address."),
|
||||
'110548': _lt("A shipment cannot have a KGS/IN or LBS/CM as its unit of measurements. Configure it from the delivery method."),
|
||||
'111057': _lt("This measurement system is not valid for the selected country. Please switch from LBS/IN to KGS/CM (or vice versa). Configure it from the delivery method."),
|
||||
'111091': _lt("The selected service is not possible from your warehouse to the recipient address, please choose another service."),
|
||||
'111100': _lt("The selected service is invalid from the requested warehouse, please choose another service."),
|
||||
'111107': _lt("Please provide a valid zip code in the warehouse address."),
|
||||
'111210': _lt("The selected service is invalid to the recipient address, please choose another service."),
|
||||
'111212': _lt("Please provide a valid package type available for service and selected locations."),
|
||||
'111500': _lt("The selected service is not valid with the selected packaging."),
|
||||
'112111': _lt("Please provide a valid shipper number/Carrier Account."),
|
||||
'113020': _lt("Please provide a valid zip code in the warehouse address."),
|
||||
'113021': _lt("Please provide a valid zip code in the recipient address."),
|
||||
'120031': _lt("Exceeds Total Number of allowed pieces per World Wide Express Shipment."),
|
||||
'120100': _lt("Please provide a valid shipper number/Carrier Account."),
|
||||
'120102': _lt("Please provide a valid street in shipper's address."),
|
||||
'120105': _lt("Please provide a valid city in the shipper's address."),
|
||||
'120106': _lt("Please provide a valid state in the shipper's address."),
|
||||
'120107': _lt("Please provide a valid zip code in the shipper's address."),
|
||||
'120108': _lt("Please provide a valid country in the shipper's address."),
|
||||
'120109': _lt("Please provide a valid shipper phone number."),
|
||||
'120113': _lt("Shipper number must contain alphanumeric characters only."),
|
||||
'120114': _lt("Shipper phone extension cannot exceed the length of 4."),
|
||||
'120115': _lt("Shipper Phone must be at least 10 alphanumeric characters."),
|
||||
'120116': _lt("Shipper phone extension must contain only numbers."),
|
||||
'120122': _lt("Please provide a valid shipper Number/Carrier Account."),
|
||||
'120124': _lt("The requested service is unavailable between the selected locations."),
|
||||
'120202': _lt("Please provide a valid street in the recipient address."),
|
||||
'120205': _lt("Please provide a valid city in the recipient address."),
|
||||
'120206': _lt("Please provide a valid state in the recipient address."),
|
||||
'120207': _lt("Please provide a valid zipcode in the recipient address."),
|
||||
'120208': _lt("Please provide a valid Country in recipient's address."),
|
||||
'120209': _lt("Please provide a valid phone number for the recipient."),
|
||||
'120212': _lt("Recipient PhoneExtension cannot exceed the length of 4."),
|
||||
'120213': _lt("Recipient Phone must be at least 10 alphanumeric characters."),
|
||||
'120214': _lt("Recipient PhoneExtension must contain only numbers."),
|
||||
'120302': _lt("Please provide a valid street in the warehouse address."),
|
||||
'120305': _lt("Please provide a valid City in the warehouse address."),
|
||||
'120306': _lt("Please provide a valid State in the warehouse address."),
|
||||
'120307': _lt("Please provide a valid Zip in the warehouse address."),
|
||||
'120308': _lt("Please provide a valid Country in the warehouse address."),
|
||||
'120309': _lt("Please provide a valid warehouse Phone Number"),
|
||||
'120312': _lt("Warehouse PhoneExtension cannot exceed the length of 4."),
|
||||
'120313': _lt("Warehouse Phone must be at least 10 alphanumeric characters."),
|
||||
'120314': _lt("Warehouse Phone must contain only numbers."),
|
||||
'120412': _lt("Please provide a valid shipper Number/Carrier Account."),
|
||||
'121057': _lt("This measurement system is not valid for the selected country. Please switch from LBS/IN to KGS/CM (or vice versa). Configure it from delivery method"),
|
||||
'121210': _lt("The requested service is unavailable between the selected locations."),
|
||||
'128089': _lt("Access License number is Invalid. Provide a valid number (Length should be 0-35 alphanumeric characters)"),
|
||||
'190001': _lt("Cancel shipment not available at this time , Please try again Later."),
|
||||
'190100': _lt("Provided Tracking Ref. Number is invalid."),
|
||||
'190109': _lt("Provided Tracking Ref. Number is invalid."),
|
||||
'250001': _lt("Access License number is invalid for this provider.Please re-license."),
|
||||
'250002': _lt("Username/Password is invalid for this delivery provider."),
|
||||
'250003': _lt("Access License number is invalid for this delivery provider."),
|
||||
'250004': _lt("Username/Password is invalid for this delivery provider."),
|
||||
'250006': _lt("The maximum number of user access attempts was exceeded. So please try again later"),
|
||||
'250007': _lt("The UserId is currently locked out; please try again in 24 hours."),
|
||||
'250009': _lt("Provided Access License Number not found in the UPS database"),
|
||||
'250038': _lt("Please provide a valid shipper number/Carrier Account."),
|
||||
'250047': _lt("Access License number is revoked contact UPS to get access."),
|
||||
'250052': _lt("Authorization system is currently unavailable , try again later."),
|
||||
'250053': _lt("UPS Server Not Found"),
|
||||
'9120200': _lt("Please provide at least one item to ship")
|
||||
}
|
||||
|
||||
|
||||
class LogPlugin(Plugin):
|
||||
""" Small plugin for zeep that catches out/ingoing XML requests and logs them"""
|
||||
def __init__(self, debug_logger):
|
||||
self.debug_logger = debug_logger
|
||||
|
||||
def egress(self, envelope, http_headers, operation, binding_options):
|
||||
self.debug_logger(etree_to_string(envelope).decode(), 'ups_request')
|
||||
return envelope, http_headers
|
||||
|
||||
def ingress(self, envelope, http_headers, operation):
|
||||
self.debug_logger(etree_to_string(envelope).decode(), 'ups_response')
|
||||
return envelope, http_headers
|
||||
|
||||
|
||||
class FixRequestNamespacePlug(Plugin):
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
|
||||
def marshalled(self, context):
|
||||
context.envelope = context.envelope.prune()
|
||||
|
||||
|
||||
class UPSRequest():
|
||||
def __init__(self, debug_logger, username, password, shipper_number, access_number, prod_environment):
|
||||
self.debug_logger = debug_logger
|
||||
# Product and Testing url
|
||||
self.endurl = "https://onlinetools.ups.com/webservices/"
|
||||
if not prod_environment:
|
||||
self.endurl = "https://wwwcie.ups.com/webservices/"
|
||||
|
||||
# Basic detail require to authenticate
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.shipper_number = shipper_number
|
||||
self.access_number = access_number
|
||||
|
||||
self.rate_wsdl = '../wsdl/RateWS.wsdl'
|
||||
self.ship_wsdl = '../wsdl/Ship.wsdl'
|
||||
self.void_wsdl = '../wsdl/Void.wsdl'
|
||||
self.ns = {'err': "http://www.ups.com/XMLSchema/XOLTWS/Error/v1.1"}
|
||||
|
||||
def _add_security_header(self, client, api):
|
||||
# set the detail which require to authenticate
|
||||
user_token = {'Username': self.username, 'Password': self.password}
|
||||
access_token = {'AccessLicenseNumber': self.access_number}
|
||||
security = client._Client__obj.get_element('ns0:UPSSecurity')(UsernameToken=user_token, ServiceAccessToken=access_token)
|
||||
client._Client__obj.set_default_soapheaders([security])
|
||||
|
||||
def _set_service(self, client, api):
|
||||
service = client.create_service(
|
||||
next(iter(client._Client__obj.wsdl.bindings)),
|
||||
'%s%s' % (self.endurl, api))
|
||||
return service
|
||||
|
||||
def _set_client(self, wsdl, api, root):
|
||||
wsdl_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), wsdl)
|
||||
client = Client(wsdl_path, plugins=[FixRequestNamespacePlug(root), LogPlugin(self.debug_logger)])
|
||||
self.factory_ns2 = client.type_factory('ns2')
|
||||
self.factory_ns3 = client.type_factory('ns3')
|
||||
# ns4 only exists for Ship API - we only use it for the invoice
|
||||
self.factory_ns4 = client.type_factory('ns4') if api == 'Ship' else self.factory_ns3
|
||||
self._add_security_header(client, api)
|
||||
return client
|
||||
|
||||
def _clean_phone_number(self, phone):
|
||||
return re.sub('[^0-9]','', phone)
|
||||
|
||||
def check_required_value(self, shipper, ship_from, ship_to, order=False, picking=False):
|
||||
required_field = {'city': 'City', 'country_id': 'Country', 'phone': 'Phone'}
|
||||
# Check required field for shipper
|
||||
res = [required_field[field] for field in required_field if not shipper[field]]
|
||||
if shipper.country_id.code in ('US', 'CA', 'IE') and not shipper.state_id.code:
|
||||
res.append('State')
|
||||
if not shipper.street and not shipper.street2:
|
||||
res.append('Street')
|
||||
if shipper.country_id.code != 'HK' and not shipper.zip:
|
||||
res.append('ZIP code')
|
||||
if res:
|
||||
return _("The address of your company is missing or wrong.\n(Missing field(s) : %s)", ",".join(res))
|
||||
if len(self._clean_phone_number(shipper.phone)) < 10:
|
||||
return str(UPS_ERROR_MAP.get('120115'))
|
||||
# Check required field for warehouse address
|
||||
res = [required_field[field] for field in required_field if not ship_from[field]]
|
||||
if ship_from.country_id.code in ('US', 'CA', 'IE') and not ship_from.state_id.code:
|
||||
res.append('State')
|
||||
if not ship_from.street and not ship_from.street2:
|
||||
res.append('Street')
|
||||
if ship_from.country_id.code != 'HK' and not ship_from.zip:
|
||||
res.append('ZIP code')
|
||||
if res:
|
||||
return _("The address of your warehouse is missing or wrong.\n(Missing field(s) : %s)", ",".join(res))
|
||||
if len(self._clean_phone_number(ship_from.phone)) < 10:
|
||||
return str(UPS_ERROR_MAP.get('120313'))
|
||||
# Check required field for recipient address
|
||||
res = [required_field[field] for field in required_field if field != 'phone' and not ship_to[field]]
|
||||
if ship_to.country_id.code in ('US', 'CA', 'IE') and not ship_to.state_id.code:
|
||||
res.append('State')
|
||||
# The street isn't required if we compute the rate with a partial delivery address in the
|
||||
# express checkout flow.
|
||||
if not ship_to.street and not ship_to.street2 and not ship_to.env.context.get(
|
||||
'express_checkout_partial_delivery_address', False
|
||||
):
|
||||
res.append('Street')
|
||||
if ship_to.country_id.code != 'HK' and not ship_to.zip:
|
||||
res.append('ZIP code')
|
||||
if len(ship_to.street or '') > 35 or len(ship_to.street2 or '') > 35:
|
||||
return _("UPS address lines can only contain a maximum of 35 characters. You can split the contacts addresses on multiple lines to try to avoid this limitation.")
|
||||
if picking and not order:
|
||||
order = picking.sale_id
|
||||
phone = ship_to.phone
|
||||
if order and not phone:
|
||||
phone = order.partner_id.phone
|
||||
if order:
|
||||
if not order.order_line:
|
||||
return _("Please provide at least one item to ship.")
|
||||
error_lines = order.order_line._get_invalid_delivery_weight_lines()
|
||||
if error_lines:
|
||||
return _("The estimated shipping price cannot be computed because the weight is missing for the following product(s): \n %s", ", ".join(error_lines.product_id.mapped('name')))
|
||||
if picking:
|
||||
if not all(ml.result_package_id or ml.product_id.weight for ml in picking.move_line_ids):
|
||||
return _("The delivery cannot be done because the weight of your product is missing.")
|
||||
packages_without_weight = picking.move_line_ids.mapped('result_package_id').filtered(lambda p: not p.shipping_weight)
|
||||
if packages_without_weight:
|
||||
return _('Packages %s do not have a positive shipping weight.', ', '.join(packages_without_weight.mapped('display_name')))
|
||||
# The phone isn't required if we compute the rate with a partial delivery address in the
|
||||
# express checkout flow.
|
||||
if not phone and not ship_to.env.context.get(
|
||||
'express_checkout_partial_delivery_address', False
|
||||
):
|
||||
res.append('Phone')
|
||||
if res:
|
||||
return _("The recipient address is missing or wrong.\n(Missing field(s) : %s)", ",".join(res))
|
||||
# The phone isn't required if we compute the rate with a partial delivery address in the
|
||||
# express checkout flow.
|
||||
if not ship_to.env.context.get(
|
||||
'express_checkout_partial_delivery_address', False
|
||||
) and len(self._clean_phone_number(phone)) < 10:
|
||||
return str(UPS_ERROR_MAP.get('120213'))
|
||||
return False
|
||||
|
||||
def get_error_message(self, error_code, description):
|
||||
result = {}
|
||||
result['error_message'] = str(UPS_ERROR_MAP.get(error_code))
|
||||
if result['error_message'] == "None":
|
||||
result['error_message'] = description
|
||||
return result
|
||||
|
||||
def save_label(self, image64, label_file_type='GIF'):
|
||||
img_decoded = base64.decodebytes(image64.encode('utf-8'))
|
||||
if label_file_type == 'GIF':
|
||||
# Label format is GIF, so need to rotate and convert as PDF
|
||||
image_string = io.BytesIO(img_decoded)
|
||||
im = Image.open(image_string)
|
||||
label_result = io.BytesIO()
|
||||
im.save(label_result, 'pdf')
|
||||
return label_result.getvalue()
|
||||
else:
|
||||
return img_decoded
|
||||
|
||||
def set_package_detail(self, carrier, client, packages, ship_from, ship_to, cod_info, request_type):
|
||||
Packages = []
|
||||
if request_type == "rating":
|
||||
MeasurementType = self.factory_ns2.CodeDescriptionType
|
||||
elif request_type == "shipping":
|
||||
MeasurementType = self.factory_ns2.ShipUnitOfMeasurementType
|
||||
for p in packages:
|
||||
package = self.factory_ns2.PackageType()
|
||||
if hasattr(package, 'Packaging'):
|
||||
package.Packaging = self.factory_ns2.PackagingType()
|
||||
package.Packaging.Code = p.packaging_type or ''
|
||||
elif hasattr(package, 'PackagingType'):
|
||||
package.PackagingType = self.factory_ns2.CodeDescriptionType()
|
||||
package.PackagingType.Code = p.packaging_type or ''
|
||||
|
||||
package.Dimensions = self.factory_ns2.DimensionsType()
|
||||
package.Dimensions.UnitOfMeasurement = MeasurementType()
|
||||
package.Dimensions.UnitOfMeasurement.Code = carrier.ups_package_dimension_unit
|
||||
package.Dimensions.Length = p.dimension['length']
|
||||
package.Dimensions.Width = p.dimension['width']
|
||||
package.Dimensions.Height = p.dimension['height']
|
||||
|
||||
package.PackageServiceOptions = self.factory_ns2.PackageServiceOptionsType()
|
||||
if cod_info:
|
||||
package.PackageServiceOptions.COD = self.factory_ns2.CODType()
|
||||
package.PackageServiceOptions.COD.CODFundsCode = str(cod_info['funds_code'])
|
||||
package.PackageServiceOptions.COD.CODAmount = self.factory_ns2.CODAmountType() if request_type == 'rating' else self.factory_ns2.CurrencyMonetaryType()
|
||||
package.PackageServiceOptions.COD.CODAmount.MonetaryValue = cod_info['monetary_value']
|
||||
package.PackageServiceOptions.COD.CODAmount.CurrencyCode = cod_info['currency']
|
||||
|
||||
if p.currency_id:
|
||||
package.PackageServiceOptions.DeclaredValue = self.factory_ns2.InsuredValueType() if request_type == 'rating' else self.factory_ns2.PackageDeclaredValueType()
|
||||
package.PackageServiceOptions.DeclaredValue.CurrencyCode = p.currency_id.name
|
||||
package.PackageServiceOptions.DeclaredValue.MonetaryValue = float_repr(p.total_cost * carrier.shipping_insurance / 100, 2)
|
||||
if request_type == "shipping":
|
||||
package.PackageServiceOptions.DeclaredValue.Type = self.factory_ns2.DeclaredValueType()
|
||||
package.PackageServiceOptions.DeclaredValue.Type.Code = '01' # EVS
|
||||
|
||||
package.PackageWeight = self.factory_ns2.PackageWeightType()
|
||||
package.PackageWeight.UnitOfMeasurement = MeasurementType()
|
||||
package.PackageWeight.UnitOfMeasurement.Code = carrier.ups_package_weight_unit
|
||||
package.PackageWeight.Weight = carrier._ups_convert_weight(p.weight, carrier.ups_package_weight_unit)
|
||||
|
||||
# Package and shipment reference text is only allowed for shipments within
|
||||
# the USA and within Puerto Rico. This is a UPS limitation.
|
||||
if (p.name and not ' ' in p.name and ship_from.country_id.code in ('US') and ship_to.country_id.code in ('US')):
|
||||
reference_number = self.factory_ns2.ReferenceNumberType()
|
||||
reference_number.Code = 'PM'
|
||||
reference_number.Value = p.name
|
||||
reference_number.BarCodeIndicator = p.name
|
||||
package.ReferenceNumber = reference_number
|
||||
|
||||
Packages.append(package)
|
||||
return Packages
|
||||
|
||||
def set_invoice(self, shipment_info, commodities, ship_to):
|
||||
|
||||
invoice_products = []
|
||||
for commodity in commodities:
|
||||
uom_type = self.factory_ns4.UnitOfMeasurementType()
|
||||
uom_type.Code = 'PC' if commodity.qty == 1 else 'PCS'
|
||||
|
||||
unit_type = self.factory_ns4.UnitType()
|
||||
unit_type.Number = int(commodity.qty)
|
||||
unit_type.Value = float_repr(commodity.monetary_value, 2)
|
||||
unit_type.UnitOfMeasurement = uom_type
|
||||
|
||||
product = self.factory_ns4.ProductType()
|
||||
# split the name of the product to maximum 3 substrings of length 35
|
||||
name = commodity.product_id.name
|
||||
product.Description = [line for line in [name[35 * i:35 * (i + 1)] for i in range(3)] if line]
|
||||
product.Unit = unit_type
|
||||
product.OriginCountryCode = commodity.country_of_origin
|
||||
product.CommodityCode = commodity.product_id.hs_code or ''
|
||||
|
||||
invoice_products.append(product)
|
||||
|
||||
address_sold_to = self.factory_ns4.AddressType()
|
||||
address_sold_to.AddressLine = [line for line in (ship_to.street, ship_to.street2) if line]
|
||||
address_sold_to.City = ship_to.city or ''
|
||||
address_sold_to.PostalCode = ship_to.zip or ''
|
||||
address_sold_to.CountryCode = ship_to.country_id.code or ''
|
||||
if ship_to.country_id.code in ('US', 'CA', 'IE'):
|
||||
address_sold_to.StateProvinceCode = ship_to.state_id.code or ''
|
||||
|
||||
sold_to = self.factory_ns4.SoldToType()
|
||||
if len(ship_to.commercial_partner_id.name) > 35:
|
||||
raise UserError(_('The name of the customer should be no more than 35 characters.'))
|
||||
sold_to.Name = ship_to.commercial_partner_id.name
|
||||
sold_to.AttentionName = ship_to.name
|
||||
sold_to.Address = address_sold_to
|
||||
|
||||
contact = self.factory_ns4.ContactType()
|
||||
contact.SoldTo = sold_to
|
||||
|
||||
invoice = self.factory_ns4.InternationalFormType()
|
||||
invoice.FormType = '01' # Invoice
|
||||
invoice.Product = invoice_products
|
||||
invoice.CurrencyCode = shipment_info.get('itl_currency_code')
|
||||
invoice.InvoiceDate = shipment_info.get('invoice_date')
|
||||
invoice.ReasonForExport = 'RETURN' if shipment_info.get('is_return', False) else 'SALE'
|
||||
invoice.Contacts = contact
|
||||
return invoice
|
||||
|
||||
def get_shipping_price(self, carrier, shipment_info, packages, shipper, ship_from, ship_to, service_type, saturday_delivery, cod_info):
|
||||
client = self._set_client(self.rate_wsdl, 'Rate', 'RateRequest')
|
||||
service = self._set_service(client, 'Rate')
|
||||
request = self.factory_ns3.RequestType()
|
||||
request.RequestOption = 'Rate'
|
||||
|
||||
classification = self.factory_ns2.CodeDescriptionType()
|
||||
classification.Code = '00' # Get rates for the shipper account
|
||||
classification.Description = 'Get rates for the shipper account'
|
||||
|
||||
request_type = "rating"
|
||||
shipment = self.factory_ns2.ShipmentType()
|
||||
|
||||
for package in self.set_package_detail(carrier, client, packages, ship_from, ship_to, cod_info, request_type):
|
||||
shipment.Package.append(package)
|
||||
|
||||
shipment.Shipper = self.factory_ns2.ShipperType()
|
||||
shipment.Shipper.Name = shipper.name or ''
|
||||
shipment.Shipper.Address = self.factory_ns2.AddressType()
|
||||
shipment.Shipper.Address.AddressLine = [shipper.street or '', shipper.street2 or '']
|
||||
shipment.Shipper.Address.City = shipper.city or ''
|
||||
shipment.Shipper.Address.PostalCode = shipper.zip or ''
|
||||
shipment.Shipper.Address.CountryCode = shipper.country_id.code or ''
|
||||
if shipper.country_id.code in ('US', 'CA', 'IE'):
|
||||
shipment.Shipper.Address.StateProvinceCode = shipper.state_id.code or ''
|
||||
shipment.Shipper.ShipperNumber = self.shipper_number or ''
|
||||
# shipment.Shipper.Phone.Number = shipper.phone or ''
|
||||
|
||||
shipment.ShipFrom = self.factory_ns2.ShipFromType()
|
||||
shipment.ShipFrom.Name = ship_from.name or ''
|
||||
shipment.ShipFrom.Address = self.factory_ns2.AddressType()
|
||||
shipment.ShipFrom.Address.AddressLine = [ship_from.street or '', ship_from.street2 or '']
|
||||
shipment.ShipFrom.Address.City = ship_from.city or ''
|
||||
shipment.ShipFrom.Address.PostalCode = ship_from.zip or ''
|
||||
shipment.ShipFrom.Address.CountryCode = ship_from.country_id.code or ''
|
||||
if ship_from.country_id.code in ('US', 'CA', 'IE'):
|
||||
shipment.ShipFrom.Address.StateProvinceCode = ship_from.state_id.code or ''
|
||||
# shipment.ShipFrom.Phone.Number = ship_from.phone or ''
|
||||
|
||||
shipment.ShipTo = self.factory_ns2.ShipToType()
|
||||
shipment.ShipTo.Name = ship_to.name or ''
|
||||
shipment.ShipTo.Address = self.factory_ns2.AddressType()
|
||||
shipment.ShipTo.Address.AddressLine = [ship_to.street or '', ship_to.street2 or '']
|
||||
shipment.ShipTo.Address.City = ship_to.city or ''
|
||||
shipment.ShipTo.Address.PostalCode = ship_to.zip or ''
|
||||
shipment.ShipTo.Address.CountryCode = ship_to.country_id.code or ''
|
||||
if ship_to.country_id.code in ('US', 'CA', 'IE'):
|
||||
shipment.ShipTo.Address.StateProvinceCode = ship_to.state_id.code or ''
|
||||
# shipment.ShipTo.Phone.Number = ship_to.phone or ''
|
||||
if not ship_to.commercial_partner_id.is_company:
|
||||
shipment.ShipTo.Address.ResidentialAddressIndicator = None
|
||||
|
||||
shipment.Service = self.factory_ns2.CodeDescriptionType()
|
||||
shipment.Service.Code = service_type or ''
|
||||
shipment.Service.Description = 'Service Code'
|
||||
if service_type == "96":
|
||||
shipment.NumOfPieces = int(shipment_info.get('total_qty'))
|
||||
|
||||
if saturday_delivery:
|
||||
shipment.ShipmentServiceOptions = self.factory_ns2.ShipmentServiceOptionsType()
|
||||
shipment.ShipmentServiceOptions.SaturdayDeliveryIndicator = saturday_delivery
|
||||
else:
|
||||
shipment.ShipmentServiceOptions = ''
|
||||
|
||||
shipment.ShipmentRatingOptions = self.factory_ns2.ShipmentRatingOptionsType()
|
||||
shipment.ShipmentRatingOptions.NegotiatedRatesIndicator = 1
|
||||
|
||||
try:
|
||||
# Get rate using for provided detail
|
||||
response = service.ProcessRate(Request=request, CustomerClassification=classification, Shipment=shipment)
|
||||
|
||||
# Check if ProcessRate is not success then return reason for that
|
||||
if response.Response.ResponseStatus.Code != "1":
|
||||
return self.get_error_message(response.Response.ResponseStatus.Code, response.Response.ResponseStatus.Description)
|
||||
|
||||
rate = response.RatedShipment[0]
|
||||
charge = rate.TotalCharges
|
||||
|
||||
# Some users are qualified to receive negotiated rates
|
||||
if 'NegotiatedRateCharges' in rate and rate.NegotiatedRateCharges and rate.NegotiatedRateCharges.TotalCharge.MonetaryValue:
|
||||
charge = rate.NegotiatedRateCharges.TotalCharge
|
||||
|
||||
return {
|
||||
'currency_code': charge.CurrencyCode,
|
||||
'price': charge.MonetaryValue,
|
||||
}
|
||||
|
||||
except Fault as e:
|
||||
code = e.detail.xpath("//err:PrimaryErrorCode/err:Code", namespaces=self.ns)[0].text
|
||||
description = e.detail.xpath("//err:PrimaryErrorCode/err:Description", namespaces=self.ns)[0].text
|
||||
return self.get_error_message(code, description)
|
||||
except IOError as e:
|
||||
return self.get_error_message('0', 'UPS Server Not Found:\n%s' % e)
|
||||
|
||||
def send_shipping(self, carrier, shipment_info, packages, shipper, ship_from, ship_to, service_type, saturday_delivery, duty_payment, cod_info=None, label_file_type='GIF', ups_carrier_account=False):
|
||||
client = self._set_client(self.ship_wsdl, 'Ship', 'ShipmentRequest')
|
||||
request = self.factory_ns3.RequestType()
|
||||
request.RequestOption = 'nonvalidate'
|
||||
|
||||
request_type = "shipping"
|
||||
label = self.factory_ns2.LabelSpecificationType()
|
||||
label.LabelImageFormat = self.factory_ns2.LabelImageFormatType()
|
||||
label.LabelImageFormat.Code = label_file_type
|
||||
label.LabelImageFormat.Description = label_file_type
|
||||
if label_file_type != 'GIF':
|
||||
label.LabelStockSize = self.factory_ns2.LabelStockSizeType()
|
||||
label.LabelStockSize.Height = '6'
|
||||
label.LabelStockSize.Width = '4'
|
||||
|
||||
shipment = self.factory_ns2.ShipmentType()
|
||||
shipment.Description = shipment_info.get('description')
|
||||
|
||||
for package in self.set_package_detail(carrier, client, packages, ship_from, ship_to, cod_info, request_type):
|
||||
shipment.Package.append(package)
|
||||
|
||||
shipment.Shipper = self.factory_ns2.ShipperType()
|
||||
shipment.Shipper.Address = self.factory_ns2.ShipAddressType()
|
||||
shipment.Shipper.AttentionName = (shipper.name or '')[:35]
|
||||
shipment.Shipper.Name = (shipper.parent_id.name or shipper.name or '')[:35]
|
||||
shipment.Shipper.Address.AddressLine = [l for l in [shipper.street or '', shipper.street2 or ''] if l]
|
||||
shipment.Shipper.Address.City = shipper.city or ''
|
||||
shipment.Shipper.Address.PostalCode = shipper.zip or ''
|
||||
shipment.Shipper.Address.CountryCode = shipper.country_id.code or ''
|
||||
if shipper.country_id.code in ('US', 'CA', 'IE'):
|
||||
shipment.Shipper.Address.StateProvinceCode = shipper.state_id.code or ''
|
||||
shipment.Shipper.ShipperNumber = self.shipper_number or ''
|
||||
shipment.Shipper.Phone = self.factory_ns2.ShipPhoneType()
|
||||
shipment.Shipper.Phone.Number = self._clean_phone_number(shipper.phone)
|
||||
shipment.Shipper.EMailAddress = shipper.email or ''
|
||||
|
||||
shipment.ShipFrom = self.factory_ns2.ShipFromType()
|
||||
shipment.ShipFrom.Address = self.factory_ns2.ShipAddressType()
|
||||
shipment.ShipFrom.AttentionName = (ship_from.name or '')[:35]
|
||||
shipment.ShipFrom.Name = (ship_from.parent_id.name or ship_from.name or '')[:35]
|
||||
shipment.ShipFrom.Address.AddressLine = [l for l in [ship_from.street or '', ship_from.street2 or ''] if l]
|
||||
shipment.ShipFrom.Address.City = ship_from.city or ''
|
||||
shipment.ShipFrom.Address.PostalCode = ship_from.zip or ''
|
||||
shipment.ShipFrom.Address.CountryCode = ship_from.country_id.code or ''
|
||||
if ship_from.country_id.code in ('US', 'CA', 'IE'):
|
||||
shipment.ShipFrom.Address.StateProvinceCode = ship_from.state_id.code or ''
|
||||
shipment.ShipFrom.Phone = self.factory_ns2.ShipPhoneType()
|
||||
shipment.ShipFrom.Phone.Number = self._clean_phone_number(ship_from.phone)
|
||||
shipment.ShipFrom.EMailAddress = ship_from.email or ''
|
||||
|
||||
shipment.ShipTo = self.factory_ns2.ShipToType()
|
||||
shipment.ShipTo.Address = self.factory_ns2.ShipToAddressType()
|
||||
shipment.ShipTo.AttentionName = (ship_to.name or '')[:35]
|
||||
shipment.ShipTo.Name = (ship_to.parent_id.name or ship_to.name or '')[:35]
|
||||
shipment.ShipTo.Address.AddressLine = [l for l in [ship_to.street or '', ship_to.street2 or ''] if l]
|
||||
shipment.ShipTo.Address.City = ship_to.city or ''
|
||||
shipment.ShipTo.Address.PostalCode = ship_to.zip or ''
|
||||
shipment.ShipTo.Address.CountryCode = ship_to.country_id.code or ''
|
||||
if ship_to.country_id.code in ('US', 'CA', 'IE'):
|
||||
shipment.ShipTo.Address.StateProvinceCode = ship_to.state_id.code or ''
|
||||
shipment.ShipTo.Phone = self.factory_ns2.ShipPhoneType()
|
||||
shipment.ShipTo.Phone.Number = self._clean_phone_number(shipment_info['phone'])
|
||||
shipment.ShipTo.EMailAddress = ship_to.email or ''
|
||||
if not ship_to.commercial_partner_id.is_company:
|
||||
shipment.ShipTo.Address.ResidentialAddressIndicator = None
|
||||
|
||||
shipment.Service = self.factory_ns2.ServiceType()
|
||||
shipment.Service.Code = service_type or ''
|
||||
shipment.Service.Description = 'Service Code'
|
||||
if service_type == "96":
|
||||
shipment.NumOfPiecesInShipment = int(shipment_info.get('total_qty'))
|
||||
shipment.ShipmentRatingOptions = self.factory_ns2.RateInfoType()
|
||||
shipment.ShipmentRatingOptions.NegotiatedRatesIndicator = 1
|
||||
|
||||
# Shipments from US to CA or PR require extra info
|
||||
if ship_from.country_id.code == 'US' and ship_to.country_id.code in ['CA', 'PR']:
|
||||
shipment.InvoiceLineTotal = self.factory_ns2.CurrencyMonetaryType()
|
||||
shipment.InvoiceLineTotal.CurrencyCode = shipment_info.get('itl_currency_code')
|
||||
shipment.InvoiceLineTotal.MonetaryValue = shipment_info.get('ilt_monetary_value')
|
||||
|
||||
# set the default method for payment using shipper account
|
||||
payment_info = self.factory_ns2.PaymentInfoType()
|
||||
shipcharge = self.factory_ns2.ShipmentChargeType()
|
||||
shipcharge.Type = '01'
|
||||
|
||||
# Bill Recevier 'Bill My Account'
|
||||
if ups_carrier_account:
|
||||
shipcharge.BillReceiver = self.factory_ns2.BillReceiverType()
|
||||
shipcharge.BillReceiver.Address = self.factory_ns2.BillReceiverAddressType()
|
||||
shipcharge.BillReceiver.AccountNumber = ups_carrier_account
|
||||
shipcharge.BillReceiver.Address.PostalCode = ship_to.zip
|
||||
else:
|
||||
shipcharge.BillShipper = self.factory_ns2.BillShipperType()
|
||||
shipcharge.BillShipper.AccountNumber = self.shipper_number or ''
|
||||
|
||||
payment_info.ShipmentCharge = [shipcharge]
|
||||
|
||||
if duty_payment == 'SENDER':
|
||||
duty_charge = self.factory_ns2.ShipmentChargeType()
|
||||
duty_charge.Type = '02'
|
||||
duty_charge.BillShipper = self.factory_ns2.BillShipperType()
|
||||
duty_charge.BillShipper.AccountNumber = self.shipper_number or ''
|
||||
payment_info.ShipmentCharge.append(duty_charge)
|
||||
|
||||
shipment.PaymentInformation = payment_info
|
||||
|
||||
sso = self.factory_ns2.ShipmentServiceOptionsType()
|
||||
if shipment_info.get('require_invoice'):
|
||||
sso.InternationalForms = self.set_invoice(shipment_info, [c for pkg in packages for c in pkg.commodities], ship_to)
|
||||
sso.InternationalForms.TermsOfShipment = shipment_info.get('terms_of_shipment')
|
||||
sso.InternationalForms.PurchaseOrderNumber = shipment_info.get('purchase_order_number')
|
||||
if saturday_delivery:
|
||||
sso.SaturdayDeliveryIndicator = saturday_delivery
|
||||
shipment.ShipmentServiceOptions = sso
|
||||
|
||||
self.shipment = shipment
|
||||
self.label = label
|
||||
self.request = request
|
||||
self.label_file_type = label_file_type
|
||||
|
||||
def return_label(self):
|
||||
return_service = self.factory_ns2.ReturnServiceType()
|
||||
return_service.Code = "9"
|
||||
self.shipment.ReturnService = return_service
|
||||
for p in self.shipment.Package:
|
||||
p.Description = "Return of courtesy"
|
||||
|
||||
|
||||
def process_shipment(self):
|
||||
client = self._set_client(self.ship_wsdl, 'Ship', 'ShipmentRequest')
|
||||
service = self._set_service(client, 'Ship')
|
||||
try:
|
||||
response = service.ProcessShipment(
|
||||
Request=self.request, Shipment=self.shipment,
|
||||
LabelSpecification=self.label)
|
||||
|
||||
# Check if shipment is not success then return reason for that
|
||||
if response.Response.ResponseStatus.Code != "1":
|
||||
return self.get_error_message(response.Response.ResponseStatus.Code, response.Response.ResponseStatus.Description)
|
||||
|
||||
result = {}
|
||||
result['label_binary_data'] = {}
|
||||
for package in response.ShipmentResults.PackageResults:
|
||||
result['label_binary_data'][package.TrackingNumber] = self.save_label(package.ShippingLabel.GraphicImage, label_file_type=self.label_file_type)
|
||||
if response.ShipmentResults.Form:
|
||||
result['invoice_binary_data'] = self.save_label(response.ShipmentResults.Form.Image.GraphicImage, label_file_type='pdf') # only pdf supported currently
|
||||
result['tracking_ref'] = response.ShipmentResults.ShipmentIdentificationNumber
|
||||
result['currency_code'] = response.ShipmentResults.ShipmentCharges.TotalCharges.CurrencyCode
|
||||
|
||||
# Some users are qualified to receive negotiated rates
|
||||
negotiated_rate = 'NegotiatedRateCharges' in response.ShipmentResults and response.ShipmentResults.NegotiatedRateCharges and response.ShipmentResults.NegotiatedRateCharges.TotalCharge.MonetaryValue or None
|
||||
|
||||
result['price'] = negotiated_rate or response.ShipmentResults.ShipmentCharges.TotalCharges.MonetaryValue
|
||||
return result
|
||||
|
||||
except Fault as e:
|
||||
code = e.detail.xpath("//err:PrimaryErrorCode/err:Code", namespaces=self.ns)[0].text
|
||||
description = e.detail.xpath("//err:PrimaryErrorCode/err:Description", namespaces=self.ns)[0].text
|
||||
return self.get_error_message(code, description)
|
||||
except IOError as e:
|
||||
return self.get_error_message('0', 'UPS Server Not Found:\n%s' % e)
|
||||
|
||||
def cancel_shipment(self, tracking_number):
|
||||
client = self._set_client(self.void_wsdl, 'Void', 'VoidShipmentRequest')
|
||||
service = self._set_service(client, 'Void')
|
||||
|
||||
request = self.factory_ns3.RequestType()
|
||||
request.TransactionReference = self.factory_ns3.TransactionReferenceType()
|
||||
request.TransactionReference.CustomerContext = "Cancle shipment"
|
||||
voidshipment = {'ShipmentIdentificationNumber': tracking_number or ''}
|
||||
|
||||
result = {}
|
||||
try:
|
||||
response = service.ProcessVoid(
|
||||
Request=request, VoidShipment=voidshipment
|
||||
)
|
||||
if response.Response.ResponseStatus.Code == "1":
|
||||
return result
|
||||
return self.get_error_message(response.Response.ResponseStatus.Code, response.Response.ResponseStatus.Description)
|
||||
|
||||
except Fault as e:
|
||||
code = e.detail.xpath("//err:PrimaryErrorCode/err:Code", namespaces=self.ns)[0].text
|
||||
description = e.detail.xpath("//err:PrimaryErrorCode/err:Description", namespaces=self.ns)[0].text
|
||||
return self.get_error_message(code, description)
|
||||
except IOError as e:
|
||||
return self.get_error_message('0', 'UPS Server Not Found:\n%s' % e)
|
||||
Reference in New Issue
Block a user