1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/delivery_ups/models/ups_request.py
2024-12-10 09:04:09 +07:00

638 lines
35 KiB
Python

# -*- 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 import _, _lt
from odoo.tools.float_utils import float_repr
from odoo.exceptions import UserError
_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 = '../api/RateWS.wsdl'
self.ship_wsdl = '../api/Ship.wsdl'
self.void_wsdl = '../api/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._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.mobile or ship_to.phone
if order and not phone:
phone = order.partner_id.mobile or 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.filtered(lambda line: not line.product_id.weight and not line.is_delivery and line.product_id.type != 'service' and not line.display_type)
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:
for ml in picking.move_line_ids.filtered(lambda ml: not ml.result_package_id and not ml.product_id.weight):
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._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._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 i, p in enumerate(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)