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:
2026-03-11 16:19:52 +00:00
parent 1f79cdcaaf
commit 431052920e
274 changed files with 52782 additions and 7302 deletions

View File

@@ -0,0 +1,7 @@
# Nexa Systems Inc
## Changelog
### 19.0.1.0.0
- Initial version

View File

@@ -0,0 +1,3 @@
from . import models
from . import wizard
from . import api

View File

@@ -0,0 +1,32 @@
{
"name": "Fusion Shipping",
"version": "19.0.1.0.0",
"category": "Inventory/Delivery",
"summary": "All-in-one shipping integration — Canada Post, UPS, FedEx, DHL Express. "
"Live pricing, label generation, shipment tracking, and multi-package support.",
"license": "OPL-1",
"depends": ["stock_delivery", "sale_management", "mail", "payment"],
"data": [
"security/ir.model.access.csv",
"data/ir_sequence_data.xml",
"data/fusion_shipping_data.xml",
"data/ir_cron_data.xml",
"views/fusion_shipment_views.xml",
"views/delivery_carrier_view.xml",
"views/choose_delivery_carrier_views.xml",
"views/sale_order_views.xml",
"views/stock_picking_views.xml",
"views/res_partner_views.xml",
"views/report_templates.xml",
"views/menus.xml",
"views/res_config_settings_views.xml",
],
"images": ["static/description/icon.png"],
"author": "Nexa Systems Inc",
"website": "https://nexasystems.ca",
"maintainer": "Nexa Systems Inc",
"support": "support@nexasystems.ca",
"installable": True,
"auto_install": False,
"application": True,
}

View File

@@ -0,0 +1,7 @@
from . import canada_post
from . import ups
from . import ups_rest
from . import fedex
from . import fedex_rest
from . import dhl
from . import dhl_rest

View File

@@ -0,0 +1,2 @@
from . import response
from . import utils

View File

@@ -0,0 +1,164 @@
import lxml
import datetime
import logging
from collections import defaultdict
from odoo.addons.fusion_shipping.api.canada_post.utils import get_dom_tree, python_2_unicode_compatible
import json
_logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class ResponseDataObject():
def __init__(self, mydict, datetime_nodes=[]):
self._load_dict(mydict, list(datetime_nodes))
def __repr__(self):
return str(self)
def __str__(self):
return "%s" % self.__dict__
def has_key(self, name):
try:
getattr(self, name)
return True
except AttributeError:
return False
def get(self, name, default=None):
try:
return getattr(self, name)
except AttributeError:
return default
def _setattr(self, name, value, datetime_nodes):
if name.lower() in datetime_nodes:
try:
ts = "%s %s" % (value.partition('T')[0], value.partition('T')[2].partition('.')[0])
value = datetime.datetime.strptime(ts, '%Y-%m-%d %H:%M:%S')
except ValueError:
pass
setattr(self, name, value)
def _load_dict(self, mydict, datetime_nodes):
for a in list(mydict.items()):
if isinstance(a[1], dict):
o = ResponseDataObject(a[1], datetime_nodes)
setattr(self, a[0], o)
elif isinstance(a[1], list):
objs = []
for i in a[1]:
if i is None or isinstance(i, str) or isinstance(i, str):
objs.append(i)
else:
objs.append(ResponseDataObject(i, datetime_nodes))
setattr(self, a[0], objs)
else:
self._setattr(a[0], a[1], datetime_nodes)
class Response():
def __init__(self, obj, verb=None, parse_response=True):
self._obj = obj
if parse_response:
try:
self._dom = self._parse_xml(obj.content)
self._dict = self._etree_to_dict(self._dom)
if verb and 'Envelope' in list(self._dict.keys()):
elem = self._dom.find('Body').find('%sResponse' % verb)
if elem is not None:
self._dom = elem
self._dict = self._dict['Envelope']['Body'].get('%sResponse' % verb, self._dict)
elif verb:
elem = self._dom.find('%sResponse' % verb)
if elem is not None:
self._dom = elem
self._dict = self._dict.get('%sResponse' % verb, self._dict)
self.reply = ResponseDataObject(self._dict,[])
except lxml.etree.XMLSyntaxError as e:
_logger.debug('Response parse failed: %s' % e)
self.reply = ResponseDataObject({}, [])
else:
self.reply = ResponseDataObject({}, [])
def _get_node_path(self, t):
i = t
path = []
path.insert(0, i.tag)
while 1:
try:
path.insert(0, i.getparent().tag)
i = i.getparent()
except AttributeError:
break
return '.'.join(path)
@staticmethod
def _pullval(v):
if len(v) == 1:
return v[0]
else:
return v
def _etree_to_dict(self, t):
if type(t) == lxml.etree._Comment:
return {}
# remove xmlns from nodes
t.tag = self._get_node_tag(t)
d = {t.tag: {} if t.attrib else None}
children = list(t)
if children:
dd = defaultdict(list)
for dc in map(self._etree_to_dict, children):
for k, v in list(dc.items()):
dd[k].append(v)
d = {t.tag: dict((k, self._pullval(v)) for k, v in list(dd.items()))}
parent_path = self._get_node_path(t)
for k in list(d[t.tag].keys()):
path = "%s.%s" % (parent_path, k)
if t.attrib:
d[t.tag].update(('_' + k, v) for k, v in list(t.attrib.items()))
if t.text:
text = t.text.strip()
if children or t.attrib:
if text:
d[t.tag]['value'] = text
else:
d[t.tag] = text
return d
def __getattr__(self, name):
return getattr(self._obj, name)
def _parse_xml(self, xml):
return get_dom_tree(xml)
def _get_node_tag(self, node):
return node.tag.replace('{' + node.nsmap.get(node.prefix, '') + '}', '')
def dom(self, lxml=True):
if not lxml:
pass
return self._dom
def dict(self):
return self._dict
def json(self):
return json.dumps(self.dict())

View File

@@ -0,0 +1,215 @@
import sys
from lxml import etree as ET
def parse_yaml(yaml_file):
"""
This is simple approach to parsing a yaml config that is only
intended for this SDK as this only supports a very minimal subset
of yaml options.
"""
with open(yaml_file) as f:
data = {None: {}}
current_key = None
for line in f.readlines():
# ignore comments
if line.startswith('#'):
continue
# parse the header
elif line[0].isalnum():
key = line.strip().replace(':', '')
current_key = key
data[current_key] = {}
# parse the key: value line
elif line[0].isspace():
values = line.strip().split(':')
if len(values) == 2:
cval = values[1].strip()
if cval == '0':
cval = False
elif cval == '1':
cval = True
data[current_key][values[0].strip()] = cval
return data
def python_2_unicode_compatible(klass):
"""
A decorator that defines __unicode__ and __str__ methods under Python 2.
Under Python 3 it does nothing.
To support Python 2 and 3 with a single code base, define a __str__ method
returning text and apply this decorator to the class.
"""
if sys.version_info[0] < 3:
if '__str__' not in klass.__dict__:
raise ValueError("@python_2_unicode_compatible cannot be applied "
"to %s because it doesn't define __str__()." %
klass.__name__)
klass.__unicode__ = klass.__str__
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
return klass
def get_dom_tree(xml):
tree = ET.fromstring(xml)
return tree.getroottree().getroot()
def attribute_check(root):
attrs = []
value = None
if isinstance(root, dict):
if '#text' in root:
value = root['#text']
if '@attrs' in root:
for ak, av in sorted(root.pop('@attrs').items()):
attrs.append(str('{0}="{1}"').format(ak, smart_encode(av)))
return attrs, value
def smart_encode(value):
try:
if sys.version_info[0] < 3:
return str(value).encode('utf-8')
else:
return value
except UnicodeDecodeError:
return value
def to_xml(root):
return dict2xml(root)
def dict2xml(root):
xml = str('')
if root is None:
return xml
if isinstance(root, dict):
for key in sorted(root.keys()):
if isinstance(root[key], dict):
attrs, value = attribute_check(root[key])
if not value:
value = dict2xml(root[key])
elif isinstance(value, dict):
value = dict2xml(value)
attrs_sp = str('')
if len(attrs) > 0:
attrs_sp = str(' ')
xml = str('{xml}<{tag}{attrs_sp}{attrs}>{value}</{tag}>') \
.format(**{'tag': key, 'xml': str(xml), 'attrs': str(' ').join(attrs),
'value': smart_encode(value), 'attrs_sp': attrs_sp})
elif isinstance(root[key], list):
for item in root[key]:
attrs, value = attribute_check(item)
if not value:
value = dict2xml(item)
elif isinstance(value, dict):
value = dict2xml(value)
attrs_sp = ''
if len(attrs) > 0:
attrs_sp = ' '
xml = str('{xml}<{tag}{attrs_sp}{attrs}>{value}</{tag}>') \
.format(**{'xml': str(xml), 'tag': key, 'attrs': ' '.join(attrs), 'value': smart_encode(value),
'attrs_sp': attrs_sp})
else:
value = root[key]
xml = str('{xml}<{tag}>{value}</{tag}>') \
.format(**{'xml': str(xml), 'tag': key, 'value': smart_encode(value)})
elif isinstance(root, str) or isinstance(root, int) \
or isinstance(root, str) or isinstance(root, int) \
or isinstance(root, float):
xml = str('{0}{1}').format(str(xml), root)
else:
raise Exception('Unable to serialize node of type %s (%s)' % \
(type(root), root))
return xml
def getValue(response_dict, *args, **kwargs):
args_a = [w for w in args]
first = args_a[0]
args_a.remove(first)
h = kwargs.get('mydict', {})
if h:
h = h.get(first, {})
else:
h = response_dict.get(first, {})
if len(args) == 1:
try:
return h.get('value', None)
except:
return h
last = args_a.pop()
for a in args_a:
h = h.get(a, {})
h = h.get(last, {})
try:
return h.get('value', None)
except:
return h
def getNodeText(node):
"Returns the node's text string."
rc = []
if hasattr(node, 'childNodes'):
for cn in node.childNodes:
if cn.nodeType == cn.TEXT_NODE:
rc.append(cn.data)
elif cn.nodeType == cn.CDATA_SECTION_NODE:
rc.append(cn.data)
return ''.join(rc)
def perftest_dict2xml():
sample_dict = {
'searchFilter': {'categoryId': {'#text': 222, '@attrs': {'site': 'US'}}},
'paginationInput': {
'pageNumber': '1',
'pageSize': '25'
},
'itemFilter': [
{'name': 'Condition',
'value': 'Used'},
{'name': 'LocatedIn',
'value': 'GB'},
],
'sortOrder': 'StartTimeNewest'
}
xml = dict2xml(sample_dict)
if __name__ == '__main__':
import timeit
import doctest
failure_count, test_count = doctest.testmod()
sys.exit(failure_count)

View File

@@ -0,0 +1 @@
from . import request

View File

@@ -0,0 +1,343 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from lxml.etree import fromstring
from datetime import datetime, date, timedelta
from lxml import etree
from odoo.tools.zeep import Client, Plugin
from odoo.tools.zeep.wsdl.utils import etree_to_string
from odoo import _
from odoo import release
from odoo.exceptions import UserError
from odoo.tools import float_repr, float_round
from odoo.tools.misc import file_path
class DHLProvider():
def __init__(self, debug_logger, request_type='ship', prod_environment=False):
self.debug_logger = debug_logger
if not prod_environment:
self.url = 'https://xmlpitest-ea.dhl.com/XMLShippingServlet?isUTF8Support=true'
else:
self.url = 'https://xmlpi-ea.dhl.com/XMLShippingServlet?isUTF8Support=true'
if request_type == "ship":
self.client = self._set_client('ship-10.0.wsdl', 'Ship')
self.factory = self.client.type_factory('ns1')
elif request_type =="rate":
self.client = self._set_client('rate.wsdl', 'Rate')
self.factory = self.client.type_factory('ns1')
self.factory_dct_request = self.client.type_factory('ns2')
self.factory_dct_response = self.client.type_factory('ns3')
def _set_client(self, wsdl_filename, api):
wsdl_path = file_path(f'fusion_shipping/api/dhl/wsdl/{wsdl_filename}')
client = Client(wsdl_path)
return client
def _set_request(self, site_id, password):
request = self.factory.Request()
service_header = self.factory.ServiceHeader()
service_header.MessageTime = datetime.now()
service_header.MessageReference = 'ref:' + datetime.now().isoformat() #CHANGEME
service_header.SiteID = site_id
service_header.Password = password
request.ServiceHeader = service_header
metadata = self.factory.MetaData()
metadata.SoftwareName = release.product_name
metadata.SoftwareVersion = release.series
request.MetaData = metadata
return request
def _set_region_code(self, region_code):
return region_code
def _set_requested_pickup_time(self, requested_pickup):
if requested_pickup:
return "Y"
else:
return "N"
def _set_billing(self, shipper_account, payment_type, duty_payment_type, is_dutiable):
billing = self.factory.Billing()
billing.ShipperAccountNumber = shipper_account
billing.ShippingPaymentType = payment_type
if is_dutiable:
billing.DutyPaymentType = duty_payment_type
return billing
def _set_consignee(self, partner_id):
consignee = self.factory.Consignee()
consignee.CompanyName = partner_id.commercial_company_name or partner_id.name
consignee.AddressLine1 = partner_id.street or partner_id.street2
consignee.AddressLine2 = partner_id.street and partner_id.street2 or None
consignee.City = partner_id.city
if partner_id.state_id:
consignee.Division = partner_id.state_id.name
consignee.DivisionCode = partner_id.state_id.code
consignee.PostalCode = partner_id.zip
consignee.CountryCode = partner_id.country_id.code
consignee.CountryName = partner_id.country_id.name
contact = self.factory.Contact()
contact.PersonName = partner_id.name
contact.PhoneNumber = partner_id.phone
if partner_id.email:
contact.Email = partner_id.email
consignee.Contact = contact
return consignee
def _set_dct_to(self, partner_id):
to = self.factory_dct_request.DCTTo()
country_code = partner_id.country_id.code
zip_code = partner_id.zip or ''
if country_code == 'ES' and (zip_code.startswith('35') or zip_code.startswith('38')):
country_code = 'IC'
to.CountryCode = country_code
to.Postalcode = zip_code
to.City = partner_id.city
return to
def _set_shipper(self, account_number, company_partner_id, warehouse_partner_id):
shipper = self.factory.Shipper()
shipper.ShipperID = account_number
shipper.CompanyName = company_partner_id.name
shipper.AddressLine1 = warehouse_partner_id.street or warehouse_partner_id.street2
shipper.AddressLine2 = warehouse_partner_id.street and warehouse_partner_id.street2 or None
shipper.City = warehouse_partner_id.city
if warehouse_partner_id.state_id:
shipper.Division = warehouse_partner_id.state_id.name
shipper.DivisionCode = warehouse_partner_id.state_id.code
shipper.PostalCode = warehouse_partner_id.zip
shipper.CountryCode = warehouse_partner_id.country_id.code
shipper.CountryName = warehouse_partner_id.country_id.name
contact = self.factory.Contact()
contact.PersonName = warehouse_partner_id.name
contact.PhoneNumber = warehouse_partner_id.phone
if warehouse_partner_id.email:
contact.Email = warehouse_partner_id.email
shipper.Contact = contact
return shipper
def _set_dct_from(self, warehouse_partner_id):
dct_from = self.factory_dct_request.DCTFrom()
dct_from.CountryCode = warehouse_partner_id.country_id.code
dct_from.Postalcode = warehouse_partner_id.zip
dct_from.City = warehouse_partner_id.city
return dct_from
def _set_dutiable(self, total_value, currency_name, incoterm):
dutiable = self.factory.Dutiable()
dutiable.DeclaredValue = float_repr(total_value, 2)
dutiable.DeclaredCurrency = currency_name
if not incoterm:
raise UserError(_("Please define an incoterm in the associated sale order or set a default incoterm for the company in the accounting's settings."))
dutiable.TermsOfTrade = incoterm.code
return dutiable
def _set_dct_dutiable(self, total_value, currency_name):
dct_dutiable = self.factory_dct_request.DCTDutiable()
dct_dutiable.DeclaredCurrency = currency_name
dct_dutiable.DeclaredValue = total_value
return dct_dutiable
def _set_dct_bkg_details(self, carrier, packages):
bkg_details = self.factory_dct_request.BkgDetailsType()
bkg_details.PaymentCountryCode = packages[0].company_id.partner_id.country_id.code
bkg_details.Date = date.today()
bkg_details.ReadyTime = timedelta(hours=1,minutes=2)
bkg_details.DimensionUnit = "CM" if carrier.dhl_package_dimension_unit == "C" else "IN"
bkg_details.WeightUnit = "KG" if carrier.dhl_package_weight_unit == "K" else "LB"
bkg_details.InsuredValue = float_repr(sum(pkg.total_cost for pkg in packages) * carrier.shipping_insurance / 100, precision_digits=3)
bkg_details.InsuredCurrency = packages[0].currency_id.name
pieces = []
for sequence, package in enumerate(packages):
piece = self.factory_dct_request.PieceType()
piece.PieceID = sequence
piece.PackageTypeCode = package.packaging_type
piece.Height = package.dimension['height']
piece.Depth = package.dimension['length']
piece.Width = package.dimension['width']
piece.Weight = carrier._dhl_convert_weight(package.weight, carrier.dhl_package_weight_unit)
pieces.append(piece)
bkg_details.Pieces = {'Piece': pieces}
bkg_details.PaymentAccountNumber = carrier.sudo().dhl_account_number
if carrier.dhl_dutiable:
bkg_details.IsDutiable = "Y"
else:
bkg_details.IsDutiable = "N"
bkg_details.NetworkTypeCode = "AL"
return bkg_details
def _set_shipment_details(self, picking):
shipment_details = self.factory.ShipmentDetails()
pieces = []
packages = picking.carrier_id._get_packages_from_picking(picking, picking.carrier_id.dhl_default_package_type_id)
for sequence, package in enumerate(packages):
piece = self.factory.Piece()
piece.PieceID = sequence
piece.Height = package.dimension['height']
piece.Depth = package.dimension['length']
piece.Width = package.dimension['width']
piece.Weight = picking.carrier_id._dhl_convert_weight(package.weight, picking.carrier_id.dhl_package_weight_unit)
piece.PieceContents = package.name
pieces.append(piece)
shipment_details.Pieces = self.factory.Pieces(pieces)
shipment_details.WeightUnit = picking.carrier_id.dhl_package_weight_unit
shipment_details.GlobalProductCode = picking.carrier_id.dhl_product_code
shipment_details.LocalProductCode = picking.carrier_id.dhl_product_code
shipment_details.Date = date.today()
shipment_details.Contents = "MY DESCRIPTION"
shipment_details.DimensionUnit = picking.carrier_id.dhl_package_dimension_unit
shipment_details.InsuredAmount = float_repr(sum(pkg.total_cost for pkg in packages) * picking.carrier_id.shipping_insurance / 100, precision_digits=2)
if picking.carrier_id.dhl_dutiable:
shipment_details.IsDutiable = "Y"
sale = picking.reference_ids.sale_ids
currency = sale and sale[0].currency_id or picking.company_id.currency_id
shipment_details.CurrencyCode = currency.name
return shipment_details
def _set_label_image_format(self, label_image_format):
return label_image_format
def _set_label(self, label):
label_obj = self.factory.Label()
label_obj.LabelTemplate = label
return label_obj
def _set_return(self):
return_service = self.factory.SpecialService()
return_service.SpecialServiceType = "PV"
return return_service
def _set_insurance(self, shipment_details):
insurance_service = self.factory.SpecialService()
insurance_service.SpecialServiceType = "II"
insurance_service.ChargeValue = shipment_details.InsuredAmount
insurance_service.CurrencyCode = shipment_details.CurrencyCode
return insurance_service
def _process_shipment(self, shipment_request):
ShipmentRequest = self.client._Client__obj.get_element('ns0:ShipmentRequest')
document = etree.Element('root')
ShipmentRequest.render(document, shipment_request)
request_to_send = etree_to_string(list(document)[0])
headers = {'Content-Type': 'text/xml'}
response = self.client._Client__obj.transport.post(self.url, request_to_send, headers=headers)
if self.debug_logger:
self.debug_logger(request_to_send, 'dhl_shipment_request')
self.debug_logger(response.content, 'dhl_shipment_response')
response_element_xml = fromstring(response.content)
Response = self.client._Client__obj.get_element(response_element_xml.tag)
response_zeep = Response.type.parse_xmlelement(response_element_xml)
dict_response = {'tracking_number': 0.0,
'price': 0.0,
'currency': False}
# This condition handle both 'ShipmentValidateErrorResponse' and
# 'ErrorResponse', we could handle them differently if needed as
# the 'ShipmentValidateErrorResponse' is something you cannot do,
# and 'ErrorResponse' are bad values given in the request.
if hasattr(response_zeep.Response, 'Status'):
condition = response_zeep.Response.Status.Condition[0]
error_msg = "%s: %s" % (condition.ConditionCode, condition.ConditionData)
raise UserError(error_msg)
return response_zeep
def _process_rating(self, rating_request):
DCTRequest = self.client._Client__obj.get_element('ns0:DCTRequest')
document = etree.Element('root')
DCTRequest.render(document, rating_request)
request_to_send = etree_to_string(list(document)[0])
headers = {'Content-Type': 'text/xml'}
response = self.client._Client__obj.transport.post(self.url, request_to_send, headers=headers)
if self.debug_logger:
self.debug_logger(request_to_send, 'dhl_rating_request')
self.debug_logger(response.content, 'dhl_rating_response')
response_element_xml = fromstring(response.content)
dict_response = {'tracking_number': 0.0,
'price': 0.0,
'currency': False}
# This condition handle both 'ShipmentValidateErrorResponse' and
# 'ErrorResponse', we could handle them differently if needed as
# the 'ShipmentValidateErrorResponse' is something you cannot do,
# and 'ErrorResponse' are bad values given in the request.
if response_element_xml.find('GetQuoteResponse') is not None:
return response_element_xml
else:
condition = response_element_xml.find('Response/Status/Condition')
error_msg = "%s: %s" % (condition.find('ConditionCode').text, condition.find('ConditionData').text)
raise UserError(error_msg)
def check_required_value(self, carrier, recipient, shipper, order=False, picking=False):
carrier = carrier.sudo()
recipient_required_field = ['city', 'zip', 'country_id']
if not carrier.dhl_SiteID:
return _("DHL Site ID is missing, please modify your delivery method settings.")
if not carrier.dhl_password:
return _("DHL password is missing, please modify your delivery method settings.")
if not carrier.dhl_account_number:
return _("DHL account number is missing, please modify your delivery method settings.")
# The street isn't required if we compute the rate with a partial delivery address in the
# express checkout flow.
if not recipient.street and not recipient.street2 and not recipient.env.context.get(
'express_checkout_partial_delivery_address', False
):
recipient_required_field.append('street')
res = [field for field in recipient_required_field if not recipient[field]]
if res:
return _("The address of the customer is missing or wrong (Missing field(s) :\n %s)", ", ".join(res).replace("_id", ""))
shipper_required_field = ['city', 'zip', 'phone', 'country_id']
if not shipper.street and not shipper.street2:
shipper_required_field.append('street')
res = [field for field in shipper_required_field if not shipper[field]]
if res:
return _("The address of your company warehouse is missing or wrong (Missing field(s) :\n %s)", ", ".join(res).replace("_id", ""))
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')))
return False
def _set_export_declaration(self, carrier, picking, is_return=False):
export_lines = []
move_lines = picking.move_line_ids.filtered(lambda line: line.product_id.type == 'consu')
currency_id = picking.sale_id and picking.sale_id.currency_id or picking.company_id.currency_id
for sequence, line in enumerate(move_lines, start=1):
if line.move_id.sale_line_id:
unit_quantity = line.product_uom_id._compute_quantity(line.quantity, line.move_id.sale_line_id.product_uom_id)
else:
unit_quantity = line.product_uom_id._compute_quantity(line.quantity, line.product_id.uom_id)
rounded_qty = max(1, float_round(unit_quantity, precision_digits=0, rounding_method='HALF-UP'))
item = self.factory.ExportLineItem()
item.LineNumber = sequence
item.Quantity = int(rounded_qty)
item.QuantityUnit = 'PCS' # Pieces - very generic
if len(line.product_id.name) > 75:
raise UserError(_("DHL doesn't support products with name greater than 75 characters."))
item.Description = line.product_id.name
item.Value = float_repr(line.sale_price / rounded_qty, currency_id.decimal_places)
item.Weight = item.GrossWeight = {
"Weight": carrier._dhl_convert_weight(line.product_id.weight, carrier.dhl_package_weight_unit),
"WeightUnit": carrier.dhl_package_weight_unit,
}
item.ManufactureCountryCode = line.product_id.country_of_origin.code or line.picking_id.picking_type_id.warehouse_id.partner_id.country_id.code
if line.product_id.hs_code:
item.ImportCommodityCode = line.product_id.hs_code
item.CommodityCode = line.product_id.hs_code
export_lines.append(item)
export_declaration = self.factory.ExportDeclaration()
export_declaration.InvoiceDate = datetime.today()
export_declaration.InvoiceNumber = carrier.env['ir.sequence'].sudo().next_by_code('delivery_dhl.commercial_invoice')
if is_return:
export_declaration.ExportReason = 'RETURN'
export_declaration.ExportLineItem = export_lines
if picking.sale_id.client_order_ref:
export_declaration.ReceiverReference = picking.sale_id.client_order_ref
return export_declaration

View File

@@ -0,0 +1,92 @@
<?xml version="1.0"?>
<xsd:schema targetNamespace="http://www.dhl.com" xmlns:dhl="http://www.dhl.com/datatypes" xmlns:dct="http://www.dhl.com/DCTResponsedatatypes" xmlns="http://www.dhl.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified">
<xsd:import namespace="http://www.dhl.com/datatypes_global" schemaLocation="datatypes_global_v62.xsd" />
<xsd:import namespace="http://www.dhl.com/DCTResponsedatatypes" schemaLocation="DCTResponsedatatypes_global.xsd" />
<xsd:element name="DCTResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="GetQuoteResponse">
<xsd:annotation>
<xsd:documentation>Root element of Quote request
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Response">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>Generic response header</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="ServiceHeader" type="ServiceHeader" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="BkgDetails" minOccurs="0" type="dct:BkgDetailsType" maxOccurs="1" />
<xsd:element name="Srvs" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Srv" type="dct:SrvType" minOccurs="0" maxOccurs="unbounded">
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="Note" minOccurs="0" type="dct:NoteType" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="GetCapabilityResponse">
<xsd:annotation>
<xsd:documentation>Root element of Capability request
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Response">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation>Generic response header</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="ServiceHeader" type="ServiceHeader" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="BkgDetails" minOccurs="0" type="dct:BkgDetailsType" maxOccurs="1" />
<xsd:element name="Srvs" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Srv" type="dct:SrvType" minOccurs="0" maxOccurs="unbounded">
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="Note" minOccurs="0" type="dct:NoteType" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="ServiceHeader">
<xsd:annotation>
<xsd:documentation>Standard routing header</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="MessageTime" type="xsd:dateTime" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Time this message is sent</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="MessageReference" type="xsd:string" minOccurs="0">
<xsd:annotation>
<xsd:documentation>A string, peferably number, to uniquely identify individual messages. Minimum length must be 28 and maximum length is 32</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="SiteID" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0"?>
<xsd:schema targetNamespace="http://www.dhl.com" xmlns:dhl="http://www.dhl.com/datatypes_global" xmlns="http://www.dhl.com" xmlns:dct="http://www.dhl.com/DCTRequestdatatypes" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified">
<xsd:import namespace="http://www.dhl.com/datatypes_global" schemaLocation="datatypes_global_v62.xsd" />
<xsd:import namespace="http://www.dhl.com/DCTRequestdatatypes" schemaLocation="DCTRequestdatatypes_global.xsd" />
<xsd:element name="DCTRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="GetQuote">
<xsd:annotation>
<xsd:documentation>Root element of Quote request
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Request" type="dhl:Request" />
<xsd:element name="From" type="dct:DCTFrom" minOccurs="1" />
<xsd:element name="BkgDetails" minOccurs="1" type="dct:BkgDetailsType" />
<xsd:element name="To" minOccurs="1" type="dct:DCTTo" />
<xsd:element name="Dutiable" minOccurs="0" type="dct:DCTDutiable" />
<xsd:element name="GenReq" minOccurs="0" type="dct:GenReq" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="GetCapability">
<xsd:annotation>
<xsd:documentation>Root element of Capability request
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Request" type="dhl:Request" />
<xsd:element name="From" type="dct:DCTFrom" minOccurs="1" />
<xsd:element name="BkgDetails" minOccurs="1" type="dct:BkgDetailsType" />
<xsd:element name="To" minOccurs="1" type="dct:DCTTo" />
<xsd:element name="Dutiable" minOccurs="0" type="dct:DCTDutiable" />
<xsd:element name="GenReq" minOccurs="0" type="dct:GenReq" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="schemaVersion" type="xsd:decimal" use="required" fixed="2.0" />
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -0,0 +1,449 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema targetNamespace="http://www.dhl.com/DCTRequestdatatypes" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.dhl.com/DCTRequestdatatypes" elementFormDefault="unqualified"
attributeFormDefault="unqualified">
<xsd:element name="DCTRequestDataTypes">
<xsd:annotation>
<xsd:documentation>Comment describing your root element</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:complexType name="DCTFrom">
<xsd:sequence>
<xsd:element name="CountryCode" minOccurs="1">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="2" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Postalcode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="12" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="City" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="45" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Suburb" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="45" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BkgDetailsType">
<xsd:sequence>
<xsd:element name="PaymentCountryCode" minOccurs="1">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="2" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Date" minOccurs="1">
<xsd:simpleType>
<xsd:restriction base="xsd:date" />
</xsd:simpleType>
</xsd:element>
<xsd:element name="ReadyTime">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>Time in hours and minutes (HH:MM)</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:duration" />
</xsd:simpleType>
</xsd:element>
<xsd:element name="ReadyTimeGMTOffset" minOccurs="0">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>
Time in hours and minutes (HH:MM)
</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:minLength value="0"></xsd:minLength>
<xsd:maxLength value="6"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="DimensionUnit" minOccurs="1">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>
Dimension Unit I (inches);Centimeters (CM)
</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="IN" />
<xsd:enumeration value="CM" />
<xsd:maxLength value="3"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="WeightUnit" minOccurs="1">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>
Kilogram (KG),Pounds (LB)
</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="KG" />
<xsd:enumeration value="LB" />
<xsd:maxLength value="3"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="NumberOfPieces" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:positiveInteger">
<xsd:minInclusive value="1" />
<xsd:maxInclusive value="999" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ShipmentWeight" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="15" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Volume" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="15" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="MaxPieceWeight" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="15" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="MaxPieceHeight" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="10" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="MaxPieceDepth" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="10" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="MaxPieceWidth" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="10" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Pieces" minOccurs="0">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Piece" type="PieceType" maxOccurs="999" minOccurs="1">
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="PaymentAccountNumber" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="12"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="IsDutiable" minOccurs="0">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>
Y - Dutiable/Non-Doc,
N - Non-dutiable/Doc
</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
<xsd:enumeration value="Y" />
<xsd:enumeration value="N" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="NetworkTypeCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="2" />
<xsd:enumeration value="DD" />
<xsd:enumeration value="TD" />
<xsd:enumeration value="AL" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="QtdShp" minOccurs="0" maxOccurs="unbounded" type="QtdShpType" />
<xsd:element name="InsuredValue" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="InsuredCurrency" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3"></xsd:length>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="PaymentType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Payment type - by method of payment ( DHL account)</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="D" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="AcctPickupCloseTime" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Account's Pickup Close Time to be retrieved from GCDB or specified by customer</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:dateTime"/>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DCTTo">
<xsd:sequence>
<xsd:element name="CountryCode" minOccurs="1">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="2" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Postalcode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="12" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="City" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="45" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Suburb" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="45" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DCTDutiable">
<xsd:sequence>
<xsd:element name="DeclaredCurrency" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="DeclaredValue" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:float">
<xsd:minInclusive value="0.000" />
<xsd:maxInclusive value="999999999999999999.999" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PieceType">
<xsd:sequence>
<xsd:element name="PieceID" minOccurs="1">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:pattern value="[0-9]+"></xsd:pattern>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="PackageTypeCode" default="BOX" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
<xsd:enumeration value="FLY" />
<xsd:enumeration value="COY" />
<xsd:enumeration value="NCY" />
<xsd:enumeration value="PAL" />
<xsd:enumeration value="DBL" />
<xsd:enumeration value="BOX" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Height" minOccurs="0">
<xsd:annotation>
<xsd:documentation>required if width and depth are specified</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="10" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Depth" minOccurs="0">
<xsd:annotation>
<xsd:documentation>required if width and height are specified</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="10" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Width" minOccurs="0">
<xsd:annotation>
<xsd:documentation>required if height and depth are specified</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="10" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Weight" minOccurs="1">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="15" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="QtdShpType">
<xsd:sequence>
<xsd:element name="GlobalProductCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:pattern value="[A-Z0-9]+"></xsd:pattern>
<xsd:minLength value="0"></xsd:minLength>
<xsd:maxLength value="6"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="LocalProductCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:minLength value="0"></xsd:minLength>
<xsd:maxLength value="6"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="QtdShpExChrg" minOccurs="0" maxOccurs="unbounded" type="QtdShpExChrgType">
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="QtdShpExChrgType">
<xsd:sequence>
<xsd:element name="SpecialServiceType" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="6"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="LocalSpecialServiceType" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="3"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="GenReq">
<xsd:sequence>
<xsd:element name="OSINFO" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
<xsd:enumeration value="Y" />
<xsd:enumeration value="N" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="NXTPU" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
<xsd:enumeration value="Y" />
<xsd:enumeration value="N" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="FCNTWTYCD" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="2" />
<xsd:enumeration value="DD" />
<xsd:enumeration value="TD" />
<xsd:enumeration value="AL" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="CUSTAGRIND" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
<xsd:enumeration value="Y" />
<xsd:enumeration value="N" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="VLDTRT_DD" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
<xsd:enumeration value="Y" />
<xsd:enumeration value="N" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,876 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema targetNamespace="http://www.dhl.com/DCTResponsedatatypes" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.dhl.com/DCTResponsedatatypes" elementFormDefault="unqualified"
attributeFormDefault="unqualified">
<xsd:element name="DCTResponseDataTypes">
<xsd:annotation>
<xsd:documentation>Comment describing your root element
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:complexType name="OrgnSvcAreaType">
<xsd:sequence>
<xsd:element name="FacilityCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ServiceAreaCode" minOccurs="1">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DestSvcAreaType">
<xsd:sequence>
<xsd:element name="FacilityCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ServiceAreaCode" minOccurs="1">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BkgDetailsType">
<xsd:sequence>
<xsd:element name="QtdShp" minOccurs="0" maxOccurs="unbounded" type="QtdShpType" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="SrvCombType">
<xsd:sequence>
<xsd:element name="GlobalServiceName" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="45" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="GlobalServiceCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="6" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="LocalServiceCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="LocalServiceTypeName" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="45" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ChargeCodeType" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
<xsd:enumeration value="FEE" />
<xsd:enumeration value="SCH" />
<xsd:enumeration value="XCH" />
<xsd:enumeration value="NRI" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="SOfferedCustAgreement" minOccurs="1">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="SrvComb" minOccurs="0">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Prod" minOccurs="0" type="ProdType" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ProdType">
<xsd:sequence>
<xsd:element name="VldSrvComb" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="SpecialServiceType" type="xsd:string" minOccurs="0" maxOccurs="1">
</xsd:element>
<!-- xsd:element name="VldMrkSrvComb" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType> <xsd:sequence> <xsd:element name="LocalChargeCode" type="xsd:string" minOccurs="0" maxOccurs="1"> </xsd:element>
</xsd:sequence> </xsd:complexType> </xsd:element -->
<xsd:element name="LocalServiceType" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
</xsd:element>
<xsd:element name="CombRSrv" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="RestrictedSpecialServiceType" minOccurs="0" type="xsd:string" maxOccurs="1">
</xsd:element>
<xsd:element name="RestrictedLocalServiceType" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="TotalDiscount" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="NoteType">
<xsd:sequence>
<xsd:element name="ActionStatus" type="xsd:string" minOccurs="0" maxOccurs="1"/>
<xsd:element name="Condition" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="ConditionCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:minLength value="0"></xsd:minLength>
<xsd:maxLength value="10"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ConditionData" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="QtdShpExChrgType">
<xsd:sequence>
<xsd:element name="SpecialServiceType" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="6"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="LocalServiceType" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="3"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="GlobalServiceName" type="xsd:string" minOccurs="0" maxOccurs="1">
</xsd:element>
<xsd:element name="LocalServiceTypeName" type="xsd:string" minOccurs="0" maxOccurs="1">
</xsd:element>
<xsd:element name="SOfferedCustAgreement" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="ChargeCodeType" type="xsd:string" minOccurs="0"></xsd:element>
<xsd:element name="InsPrmRateInPercentage" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="8" />
<xsd:fractionDigits value="4" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="CurrencyCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ChargeValue" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ChargeTaxAmount" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ChargeTaxRate" minOccurs="0" maxOccurs="unbounded">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="6" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<!-- Added for Brazil DCT change -->
<xsd:element name="ChargeTaxAmountDet" minOccurs="0" type="ChargeTaxAmountDetType" maxOccurs="unbounded" />
<xsd:element name="QtdSExtrChrgInAdCur" minOccurs="0" type="QtdSExtrChrgInAdCurType" maxOccurs="unbounded" />
<!-- Added for Brazil DCT change -->
</xsd:sequence>
</xsd:complexType>
<!-- Start:Added for Brazil DCT changes -->
<xsd:complexType name="WeightChargeTaxDetType">
<xsd:sequence>
<xsd:element name="TaxTypeRate" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="8" />
<xsd:fractionDigits value="6" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="TaxTypeCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="6"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="WeightChargeTax" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="BaseAmt" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ChargeTaxAmountDetType">
<xsd:sequence>
<xsd:element name="TaxTypeRate" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="8" />
<xsd:fractionDigits value="6" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="TaxTypeCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="6"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="TaxAmount" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="BaseAmount" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="QtdSInAdCurType">
<xsd:sequence>
<xsd:element name="CustomsValue" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ExchangeRate" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="6" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="CurrencyCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="CurrencyRoleTypeCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="5" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="WeightCharge" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="TotalAmount" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="TotalTaxAmount" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="WeightChargeTax" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="WeightChargeTaxDet" minOccurs="0" type="WeightChargeTaxDetType" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="QtdSExtrChrgInAdCurType">
<xsd:sequence>
<xsd:element name="ChargeValue" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ChargeExchangeRate" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="6" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ChargeTaxAmount" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ChargeTaxRate" minOccurs="0" maxOccurs="unbounded">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="6" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="CurrencyCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="CurrencyRoleTypeCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="5" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ChargeTaxAmountDet" minOccurs="0" type="ChargeTaxAmountDetType" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<!-- End:Added for Brazil DCT changes -->
<xsd:complexType name="QtdShpType">
<xsd:sequence>
<xsd:element name="OriginServiceArea" minOccurs="1" maxOccurs="1" type="OrgnSvcAreaType" />
<xsd:element name="DestinationServiceArea" minOccurs="1" maxOccurs="1" type="DestSvcAreaType" />
<xsd:element name="GlobalProductCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="6"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="LocalProductCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="3"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ProductShortName" type="xsd:string" minOccurs="0" maxOccurs="1">
</xsd:element>
<xsd:element name="LocalProductName" type="xsd:string" minOccurs="0" maxOccurs="1">
</xsd:element>
<xsd:element name="NetworkTypeCode" type="xsd:string" minOccurs="0" maxOccurs="1">
</xsd:element>
<xsd:element name="POfferedCustAgreement" type="xsd:string" minOccurs="0" maxOccurs="1">
</xsd:element>
<xsd:element name="TransInd" type="xsd:string" minOccurs="0" maxOccurs="1"></xsd:element>
<xsd:element name="PickupDate" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:date" />
</xsd:simpleType>
</xsd:element>
<xsd:element name="PickupCutoffTime" type="xsd:string" minOccurs="0" maxOccurs="1"></xsd:element>
<xsd:element name="BookingTime" minOccurs="0">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>
Booking Time
</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:duration" />
</xsd:simpleType>
</xsd:element>
<xsd:element name="CurrencyCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ExchangeRate" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="6" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="WeightCharge" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="WeightChargeTax" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="weightChargeTaxRate" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="6" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="TotalTransitDays" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:int"></xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="PickupPostalLocAddDays" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:int"></xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="DeliveryPostalLocAddDays" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:int"></xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="PickupNonDHLCourierCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="DeliveryNonDHLCourierCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="DeliveryDate" minOccurs="0" maxOccurs="unbounded" type="DeliveryDate" />
<xsd:element name="DeliveryTime" minOccurs="0">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>
Delivery Time
</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:duration" />
</xsd:simpleType>
</xsd:element>
<xsd:element name="DeliveryTimeGMTOffset" minOccurs="0">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>
Delivery Time GMT Offset
</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="6" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="DimensionalWeight" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="15" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="WeightUnit" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="3"></xsd:maxLength>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="PickupDayOfWeekNum" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="DestinationDayOfWeekNum" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="QuotedWeight" type="QuotedWeight" minOccurs="0" />
<xsd:element name="QuotedWeightUOM" type="QuotedWeightUOM" minOccurs="0" />
<xsd:element name="QtdShpExChrg" minOccurs="0" type="QtdShpExChrgType" maxOccurs="unbounded" />
<xsd:element name="PricingDate" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:date" />
</xsd:simpleType>
</xsd:element>
<xsd:element name="ShippingCharge" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="TotalTaxAmount" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="TotalDiscount" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<!-- Added for Brazil DCT changes -->
<xsd:element name="QtdSInAdCur" minOccurs="0" type="QtdSInAdCurType" maxOccurs="unbounded" />
<xsd:element name="WeightChargeTaxDet" minOccurs="0" type="WeightChargeTaxDetType" maxOccurs="unbounded" />
<!-- End : Added for Brazil DCT changes -->
<xsd:element name="PickupWindowEarliestTime" minOccurs="1">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>Pickup window start time</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string" />
</xsd:simpleType>
</xsd:element>
<xsd:element name="PickupWindowLatestTime" minOccurs="1">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>Pickup window end time</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string" />
</xsd:simpleType>
</xsd:element>
<xsd:element name="BookingCutoffOffset" minOccurs="1">
<xsd:simpleType>
<xsd:annotation>
<xsd:documentation>Booking cut off time offset</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string" />
</xsd:simpleType>
</xsd:element>
<xsd:element name="PickupLeadTime" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="PickupCloseTime" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="WeightChargeDisc" minOccurs="0" type="WeightChargeDisc" maxOccurs="unbounded" />
<xsd:element name="QtdShpExChrgDisc" minOccurs="0" type="QtdShpExChrgDisc" maxOccurs="unbounded" />
<!-- <xsd:element name="QtdShpExChrg" minOccurs="0" type="QtdShpExChrg" /> -->
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="QuotedWeight">
<xsd:annotation>
<xsd:documentation>Weight of piece or shipment</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:decimal">
<xsd:fractionDigits value="3" />
<xsd:minInclusive value="0.000" />
<xsd:maxInclusive value="999999.999" />
<xsd:totalDigits value="10" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="QuotedWeightUOM">
<xsd:annotation>
<xsd:documentation>WeightUOM</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:minLength value="1" />
<xsd:maxLength value="3" />
<xsd:enumeration value="KG" />
<xsd:enumeration value="Lbs" />
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="MrkSrvType">
<xsd:sequence>
<xsd:choice>
<xsd:element name="LocalProductCode" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="LocalServiceType" type="xsd:string" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:choice>
<xsd:element name="ProductShortName" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="GlobalServiceName" type="xsd:string" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:choice>
<xsd:element name="LocalProductName" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="LocalServiceTypeName" type="xsd:string" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:choice>
<!-- Added for Brazil DCT change -->
<xsd:element name="ProductDesc" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="ServiceDesc" type="xsd:string" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<!-- Added for Brazil DCT change -->
<xsd:element name="NetworkTypeCode" type="xsd:string" minOccurs="0" maxOccurs="1">
</xsd:element>
<xsd:choice>
<xsd:element name="POfferedCustAgreement" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="SOfferedCustAgreement" type="xsd:string" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:element name="TransInd" type="xsd:string" minOccurs="0" maxOccurs="1">
</xsd:element>
<xsd:element name="ChargeCodeType" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
</xsd:element>
<!--Start: Added for Brazil DCT change -->
<xsd:element name="MrkSrvInd" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<!--End: Added for Brazil DCT change -->
<xsd:element name="LocalProductCtryCd" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="2" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="LocalProductDesc" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="GlobalProductDesc" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="GlobalServiceType" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="BillingServiceIndicator" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="LocalServiceName" type="xsd:string" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ProdNtwrkType">
<xsd:sequence>
<xsd:element name="NetworkTypeCode" minOccurs="1">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="2" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="SrvType">
<xsd:sequence>
<xsd:element name="GlobalProductCode" type="xsd:string" minOccurs="1" maxOccurs="1">
</xsd:element>
<xsd:element name="MrkSrv" type="MrkSrvType" minOccurs="0" maxOccurs="unbounded">
</xsd:element>
<xsd:element name="SBTP" type="SBTPType" minOccurs="0" maxOccurs="1"></xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="SBTPType">
<xsd:sequence>
<xsd:element name="Prod" type="ProdType" minOccurs="0" maxOccurs="1"></xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DeliveryDate">
<xsd:sequence>
<xsd:element name="DeliveryType" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="DlvyDateTime" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="DeliveryDateTimeOffset" type="xsd:string" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="WeightChargeDisc">
<xsd:sequence>
<xsd:element name="DiscAmt" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="BaseAmount" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="CurrencyCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="DiscType" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="DiscPercentage" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="QtdShpExChrgDisc">
<xsd:sequence>
<xsd:element name="DiscAmt" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="5" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="BaseAmt" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="CurrencyCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="CurrencyRoleTypeCode" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:length value="5" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="DiscPercentage" minOccurs="0">
<xsd:simpleType>
<xsd:restriction base="xsd:decimal">
<xsd:totalDigits value="18" />
<xsd:fractionDigits value="3" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema targetNamespace="http://www.dhl.com"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.dhl.com"
xmlns:dhl="http://www.dhl.com/datatypes_global" elementFormDefault="unqualified">
<xsd:import namespace="http://www.dhl.com/datatypes_global"
schemaLocation="datatypes_global_v62.xsd" />
<xsd:element name="ErrorResponse">
<xsd:annotation>
<xsd:documentation>Generic error response root element
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Response" type="dhl:ErrorResponse" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -0,0 +1,9 @@
<definitions name="Ship" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:ns="http://www.dhl.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" targetNamespace="http://www.dhl.com">
<types>
<xsd:schema>
<xsd:import namespace="http://www.dhl.com" schemaLocation="DCT-req_global-2.0.xsd"/>
<xsd:import namespace="http://www.dhl.com" schemaLocation="DCT-Response_global-2.0.xsd"/>
<xsd:import namespace="http://www.dhl.com" schemaLocation="err-res.xsd"/>
</xsd:schema>
</types>
</definitions>

View File

@@ -0,0 +1,10 @@
<definitions name="Ship" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:ns="http://www.dhl.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" targetNamespace="http://www.dhl.com">
<types>
<xsd:schema>
<xsd:import namespace="http://www.dhl.com" schemaLocation="ship-val-global-req-10.0.xsd"/>
<xsd:import namespace="http://www.dhl.com" schemaLocation="ship-val-global-res-10.0.xsd"/>
<xsd:import namespace="http://www.dhl.com" schemaLocation="ship-val-err-res-10.0.xsd"/>
<xsd:import namespace="http://www.dhl.com" schemaLocation="err-res.xsd"/>
</xsd:schema>
</types>
</definitions>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema targetNamespace="http://www.dhl.com"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.dhl.com"
xmlns:dhl="http://www.dhl.com/datatypes" elementFormDefault="unqualified">
<xsd:import namespace="http://www.dhl.com/datatypes"
schemaLocation="datatypes.xsd" />
<xsd:element name="ShipmentValidateErrorResponse">
<xsd:annotation>
<xsd:documentation>Shipment Validation error response root element
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Response" type="dhl:ErrorResponse" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -0,0 +1,212 @@
<?xml version="1.0"?>
<xsd:schema xmlns:dhl="http://www.dhl.com/datatypes_global" xmlns="http://www.dhl.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.dhl.com" elementFormDefault="unqualified">
<xsd:import namespace="http://www.dhl.com/datatypes_global" schemaLocation="datatypes_global_v10.xsd"/>
<xsd:element name="ShipmentRequest">
<xsd:annotation>
<xsd:documentation>Root element of Shipment Validation Global request</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Request" type="dhl:Request">
<xsd:annotation>
<xsd:documentation>The element contains the header information for the message. It is present in both the request and response XML message. The request element contains a complex data type Service Header</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="RegionCode" type="dhl:RegionCode" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The RegionCode element indicates the shipment to be route to the specific region eCom backend. It is a optional field. The valid values are AP, EU and AM</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="RequestedPickupTime" type="dhl:YesNo" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The RequestedPickupTime element indicates whether a pickup time is requested or not. It is a mandatory field. The valid vaues are Y (Yes) and N (No). If the value equals to Y, eCom Backend will return ReadyByTime and CallInTime, which query based on Postcode or City name. This logic only applicable for AM region</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="LanguageCode" type="dhl:LanguageCode" default="en">
<xsd:annotation>
<xsd:documentation>LanguageCode element contains the ISO language code used by the requestor. This element should be declared once in the Shipment validation request message</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="LatinResponseInd" type="dhl:YesNo" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The LatinResponseInd element contains the indication from user whether the response xml will be transliterated to Latin characters. Currently, it is only workable for Cyrillic configured countries</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Billing" type="dhl:Billing">
<xsd:annotation>
<xsd:documentation>The Billing element contains the billing information of the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Consignee" type="dhl:Consignee">
<xsd:annotation>
<xsd:documentation>Consignee element contains the details of the Consignee (Receiver of the shipment). This element should be declared once in the shipment validation request message</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Commodity" type="dhl:Commodity" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>The commodity element identifies the commodity or commodities being shipped. This element should be declared once in the shipment validation request message</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Dutiable" type="dhl:Dutiable" minOccurs="0">
<xsd:annotation>
<xsd:documentation>For non-domestic shipments, the Dutiable element provides information which defines the types of duties to be levied</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="UseDHLInvoice" type="dhl:YesNo" minOccurs="0" default="N">
<xsd:annotation>
<xsd:documentation>UseDHLInvoice element is an optional field. By providing UseDHLInvoice field value of Y, Shipment Validation service will generate DHL Custom Invoice image. With value of N, this means customer will use their own custom invoice for shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DHLInvoiceLanguageCode" type="dhl:InvLanguageCode" minOccurs="0" default="en">
<xsd:annotation>
<xsd:documentation>DHLInvoiceLanguageCode element is an optional field. This field indicates the DHL custom invoice language code. Default language code is en (English)</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DHLInvoiceType" type="dhl:InvoiceType" minOccurs="0" default="CMI">
<xsd:annotation>
<xsd:documentation>DHLInvoiceType element is an optional field. This field indicates the DHL Custom Invoice type. CMI for Commercial Invoice and PFI for Proforma Invoice</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ExportDeclaration" type="dhl:ExportDeclaration" minOccurs="0">
<xsd:annotation>
<xsd:documentation>For non-domestic shipments, the ExportDeclaration element provides information which is used to aid in the export of a shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Reference" type="dhl:Reference" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>This element identifies the reference information. It is an optional field in the shipment validation request. Only the first reference will be taken currently</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ShipmentDetails" type="dhl:ShipmentDetails">
<xsd:annotation>
<xsd:documentation>The ShipmentDetails element contains the details of the shipment. It must be declared once in the request message</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Shipper" type="dhl:Shipper">
<xsd:annotation>
<xsd:documentation>Shipper element contains the details of the Shipper. This element should be declared once in the Shipment validation Request message</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="SpecialService" type="dhl:SpecialService" minOccurs="0" maxOccurs="10">
<xsd:annotation>
<xsd:documentation>The SpecialService Element provides various special services for the shipment. It is an optional field. Please refer to the Reference Data and use appropriate Service codes enabled for the Country/Region</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Notification" type="dhl:Notification" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The Notification element contains the notification address and customized message to the designated recipient address for the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Place" type="dhl:Place" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The place element contains the address from the shipment has to be picked. This element should be declared once in the request message</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="EProcShip" type="dhl:YesNo" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The EProcShip element indicates whether shipment has to generate waybill or not. It is an optional field. Its value is either Y (Yes For not generating the waybill and not manifest the shipment) or N (No To generate the waybill)</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Airwaybill" type="dhl:AWBNumber" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The Airwaybill element indicates the waybill number of the Paperless Trade (PLT) shipment that will be used for Image Upload service. Note: This is only applicable for Image Upload service</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DocImages" type="dhl:DocImages" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The DocImages element indicates Paperless Trade (PLT) shipment related Commercial Invoice or other supporting document images</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="LabelImageFormat" type="dhl:LabelImageFormat" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The LabelImageFormat element indicates to receive the GLSs generated Transport label and Archive document image in Shipment Validation response XML for the required output format that supported by GLS</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="RequestArchiveDoc" type="dhl:YesNo" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The RequestArchiveDoc element indicates to receive the GLSs generated Archive document image in Shipment Validation response XML for the required output format that supported by GLS. With value of Y, Archive document image will be returned to Shipment Validation response XML if LabelImageFormat element is defined</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="NumberOfArchiveDoc" type="dhl:NumberOfArchiveDoc" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The NumberOfArchiveDoc element indicates the number of archive doc that user wants to generate. With value of 1, one archive document image will be returned to Shipment Validation response XML. With value of 2, two archive document image will be returned</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="RequestQRCode" type="dhl:YesNo" default="N" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The RequestQRCode element indicates for labelless shipment that user wants to generate the QR code. With value of 'Y', it will return the QR code in Shipment Validation response XML. With value of 'N', it will not return QR code in Shipment Validation response XML.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="RequestTransportLabel" type="dhl:YesNo" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The RequestTransportLabel element indicates whether user wants to generate the Transport Label. With value of 'Y', it will return the Transport Label in Shipment Validation response XML. With value of 'N', it will not return Transport Label in Shipment Validation response XML.The RequestTransportLabel element is an optional value to request for Transport Label. This element can be set as N only when requesting for a QRCode in the Shipment Validation response if RequestQRCode = Y ).</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Label" type="dhl:Label" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The Label element defines the required label template, customers logo image plus image format, and DPI resolution</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ODDLinkReq" type="dhl:YesNo" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The ODDLinkReq element indicates to receive the URL link for On Demand Delivery (ODD) page for the specified Waybill number, Shipper Account Number, Origin Service Area Code and Destination Service Area Code in Shipment Validation response XML. With value of Y, URL link for On Demand Delivery (ODD) page will be returned to Shipment Validation response XML if ODDLinkReq element is defined</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DGs" type="dhl:DGs" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The DGs element defines the details of the Dangerous Goods items that included in shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="GetPriceEstimate" type="dhl:YesNo" minOccurs="0" default="Y">
<xsd:annotation>
<xsd:documentation>The GetPriceEstimate element is the option for retrieving the product capability or/and estimatd tariff for the given origin, destination and shipment details. Default option is Yes (Y)</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="SinglePieceImage" type="dhl:YesNo" default="N" minOccurs="0">
<xsd:annotation>
<xsd:documentation>SinglePieceImage element is the option is generate the Transport Label and Waybill Document in single piece output.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ShipmentIdentificationNumber" type="dhl:AWBNumber" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The shipment identification number does not need to be transmitted in the request as the operation will assign a new number and return it in the response. Only used when UseOwnShipmentdentificationNumber set to Y and this feature enabled within customer profile.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="UseOwnShipmentIdentificationNumber" default="N" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Customer able to provide SID if set to Y. The default is N.
Y or 1 = allows you to define your own AWB in the tag above N or 0 = Auto-allocates the AWB from DHL Express</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="1"/>
<xsd:enumeration value="Y"/>
<xsd:enumeration value="N"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="Importer" type="dhl:Importer" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Party that makes (or on whose behalf an agent or broker makes) the import declaration, and who is liable for the payment of duties (if any) on the imported goods. Normally, this party is named either as the consignee in the shipping documents and/or as the buyer in the exporter's invoice.></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Exporter" type="dhl:Exporter" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The party who is responsible for the legality of the shipment under applicable export control laws, which includes determining the proper export classification and authorization needed to ship the Items.The Exporter is usually the party who controls the transaction, acts as declarant in its own name and provides the corresponding instructions for the export, regardless of who files the export declaration. The Exporter may or may not be the Shipper.></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Seller" type="dhl:Seller" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The party that makes, offers or contracts to make a sale to an actual or potential buyer. Also called vendor.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Payer" type="dhl:Payer" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The party responsible for the full or partial payment of associated charges/cost. There can be many payers related to the different elements linked to a Shipment (e.g Services, fiscal charges).</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="schemaVersion" type="xsd:decimal" use="required" fixed="10.0"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -0,0 +1,341 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.dhl.com" xmlns:dhl="http://www.dhl.com/datatypes_global" targetNamespace="http://www.dhl.com" elementFormDefault="unqualified">
<xsd:import namespace="http://www.dhl.com/datatypes_global" schemaLocation="datatypes_global_v10.xsd"/>
<xsd:element name="ShipmentResponse">
<xsd:annotation>
<xsd:documentation>Shipment Validation Global response root element</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Response" type="dhl:Response">
<xsd:annotation>
<xsd:documentation>The element contains the header information for the message. It is present in both the request and response XML message. The response element contains a complex datatype ServiceHeader</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="RegionCode" type="dhl:RegionCode">
<xsd:annotation>
<xsd:documentation>The RegionCode element indicates the shipment transaction originated from which region</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Note" type="dhl:Note">
<xsd:annotation>
<xsd:documentation>The Note element is a complex element which consists of two child elements “ActionNote” and “Condition” element. The Note element is returned by the backend service while processing the Shipment validation request. The element is a mandatory element</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="AirwayBillNumber" type="dhl:AWBNumber">
<xsd:annotation>
<xsd:documentation>The AirwayBillNumber element contains the DHL defines 10-digit waybill number. It is a mandatory field in the shipment validation response. If shipment validation request is successful, this is the actual waybill number assigned to the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DHLInvoiceLanguageCode" type="dhl:InvLanguageCode" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The DHLInvoiceLanguageCode identifies the Invoice Language code. It is a optional field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DHLInvoiceType" type="dhl:InvoiceType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The DHLInvoiceType element identifies the type of invoice. It is a optional field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="BillingCode" type="dhl:BillCode" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The BillingCode element identifies how the shipment is billed. It is a optional field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="CurrencyCode" type="dhl:CurrencyCode">
<xsd:annotation>
<xsd:documentation>The CurrencyCode element identifies the shipment is billed in which currency</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="CourierMessage" type="dhl:CourierMsg" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The CourierMessage element contains the courier message. It is an optional field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DestinationServiceArea" type="dhl:DestinationServiceArea">
<xsd:annotation>
<xsd:documentation>The DestinationServiceArea element contains the information of the shipments destination along with the facility code and the inbound sort code information</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="OriginServiceArea" type="dhl:OriginServiceArea">
<xsd:annotation>
<xsd:documentation>The OriginServiceArea element contains the information of the shipments origin along with the outbound sort code info</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="PackageCharge" type="dhl:Money" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The PackageCharge element contains the package charge of the shipment. It is an optional field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Rated" type="dhl:YesNo">
<xsd:annotation>
<xsd:documentation>The Rated element indicates whether shipment is rated or not. It is a mandatory field. Its value is either Y (Yes) or N (No). “N” indicates that no rates could be retrieved for the given search criteria. Value will be “N” for third party or receiver payment type. “N” value when shipper is payer indicates that rates could not be obtained because of system configuration or availability reasons</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ShippingCharge" type="dhl:Money" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The ShipmentCharge element contains the shipment charge for the shipment. It is an optional field. This field will be filled if rated is Y</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ShipmentCharges" type="dhl:ShipmentCharges" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The shipmentcharges element consist of shipment details shipment charges. This element is optional</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="WeightUnit">
<xsd:annotation>
<xsd:documentation>The WeightUnit element contains the unit by which the shipment weight is measured. It is a mandatory field</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:minLength value="0"/>
<xsd:maxLength value="1"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="ChargeableWeight" type="dhl:Weight" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The ChargeableWeight element contains the weight that is used to calculate shipment charge. This is an optional field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DimensionalWeight" type="dhl:Weight" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The DimensionalWeight element contains the dimensional weight of the shipment. It is an optional field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ReadyByTime" type="dhl:TimeHM" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The ReadyByTime element indicates the ready by time of the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="PickupCharge" type="dhl:Money" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The PickupCharge element indicates pick up charges of the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="CallInTime" type="dhl:TimeHM" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The CallInTime indicates the Callin time of the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DaysAdvanceNotice" type="dhl:AdvanceDaysNotice" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The DaysAdvanceNotice element indicates the advance days notice required for the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ConversionRate" type="dhl:Money" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The ConversionRate indicates the conversion rate of the shipment if any</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="CountryCode" type="dhl:CountryCode">
<xsd:annotation>
<xsd:documentation>Its value should be valid DHL Country/Region code. This element must be declared once in the response element. Please refer to the Reference Data (DHL Country/Region) for Country/Region codes</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Barcodes">
<xsd:annotation>
<xsd:documentation>The Barcodes element contains the Barcode images for construction of Waybill and DHL Routing barcode. It is a mandatory field</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="AWBBarCode" type="dhl:BarCode">
<xsd:annotation>
<xsd:documentation>Air Waybill Barcode</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="OriginDestnBarcode" type="dhl:BarCode">
<xsd:annotation>
<xsd:documentation>Origin Destination Barcode</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ClientIDBarCode" type="dhl:BarCode" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Client ID Barcode</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="BarCode2D" type="dhl:BarCode" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>Barcode 2D</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DHLRoutingBarCode" type="dhl:BarCode" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>DHL Routing Barcode</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="Piece" type="xsd:positiveInteger" minOccurs="0"> <!-- Manually added minOccurs="0" - GOA -->
<xsd:annotation>
<xsd:documentation>No of pieces contained in shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Contents" type="xsd:string">
<xsd:annotation>
<xsd:documentation>The contents element contains the shipment content description. It is a mandatory field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Reference" type="dhl:Reference" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>This element identifies the reference information. It is an optional field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Consignee" type="dhl:Consignee">
<xsd:annotation>
<xsd:documentation>Consignee element contains the details of the Consignee (Receiver). This element should be declared once in the shipment validation response message</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Shipper" type="dhl:Shipper">
<xsd:annotation>
<xsd:documentation>Shipper element contains the details of the Shipper. This element should be declared once in the Book Shipment validation Response message</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="AccountNumber" type="dhl:AccountNumber" minOccurs="0">
<xsd:annotation>
<xsd:documentation>This element contains the DHL account number. It is a mandatory field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="CustomerID" type="xsd:string">
<xsd:annotation>
<xsd:documentation>This element contains the Customer ID</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ShipmentDate" type="dhl:Date">
<xsd:annotation>
<xsd:documentation>This element contains the date of the shipment. It is a mandatory field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="GlobalProductCode" type="dhl:GlobalProductCode">
<xsd:annotation>
<xsd:documentation>The GlobalProductCode element contains the DHL Global product code for the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="CustData" type="dhl:CustData" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The CustData element consists of customer data that required to be printed on shipment level in GLS transport label CI template</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="SpecialService" type="dhl:SpecialService" minOccurs="0" maxOccurs="5">
<xsd:annotation>
<xsd:documentation>The SpecialService Element provides various special services for shipment. It is an optional field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Billing" type="dhl:Billing" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The Billing element contains the billing information of the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Dutiable" type="dhl:Dutiable" minOccurs="0"> <!-- Manually added minOccurs="0" - GOA -->
<xsd:annotation>
<xsd:documentation>For non-domestic shipments, The Dutiable element provides informations which defines the types of duties to be levied</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ExportDeclaration" type="dhl:ExportDeclaration" minOccurs="0">
<xsd:annotation>
<xsd:documentation>For non -domestic shipments, the ExportDeclaration element provides information which is used for export declarartion documents.It is a mandatory field</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="NewShipper" type="dhl:YesNo" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The NewShipper element indicates whether shipper is new or not. The valid values are Y (Yes) and N (No)</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DHLRoutingCode" type="dhl:DHLRoutingCode" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>The DHL ISO Routing Barcode in plain text format. It contains destination Country/Region, destination zip code, transport products and associated product features</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DHLRoutingDataId" type="dhl:DHLRoutingCode" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>The DHL Routing barcode contains the data Identifier.The available values are 2L, 403</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ProductContentCode" type="xsd:string" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>The Product content code indicates the DHLs internal code of the product</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ProductShortName" type="xsd:string" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>The product short name indicates the type of the product name which the underlying transport service has been sold to the cusomter</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="InternalServiceCode" type="dhl:InternalServiceCode" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>The internal service code contains the DHLs product handling relevant services or features</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DeliveryDateCode" type="xsd:string" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>The delivery date code contains the date of delivery of the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DeliveryTimeCode" type="xsd:string" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>The delivery time code contains the time of delivery of the shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Pieces" type="dhl:ShipValResponsePieces" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>The Pieces element contains the License Plate information and barcode along with the piece details</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="PLTStatus" type="dhl:PLTStatus" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The PLTStatus element indicates the shipper accounts PLT subscription status. The valid values are A, D and S.
For PLTStatus field value of 'A' Active, it signifies it is a valid PLT registered customer and allowed to proceed for PLT shipment process.
For PLTStatus field value of 'D' De-registered, XML-PI Shipment Validation service will stop the shipment processing and returned an error to the XML-PI client.
For PLTStatus field value of 'S' Suspended, XML-PI Shipment Validation service will stop the shipment processing and return an error message to the XML-PI client.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="QtdSInAdCur" type="dhl:QtdSInAdCur" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>The QtdSInAdCur element contains the multiple currency billing details returned for different billing role of shipment charges if available in DCT tariff. Note: For PLT shipment, it will be interface with DCT getQuote interface for tariff. For EU regular shipment, if it is switched to interface with DCT getQuote is configurable in XML Services application backend</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="LabelImage" type="dhl:LabelImage" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>The LabelImage element contains the GLSs generated Global and Archive label image output if it is required by user via LabelImageFormat element in request XML</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="ODDURLLink" type="xsd:string" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The ODDURLLink element contains the URL for the On Demand Delivery (ODD) page. This element is optional</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="DGs" type="dhl:DGs" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The DGs element defines the details of the Dangerous Goods items that included in shipment</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Label" type="dhl:Label" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The Label element defines the required label template used and its customers barcode type, barcode code and barcode text if available in clients request</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Importer" type="dhl:Importer" minOccurs="0">
<xsd:annotation>
<xsd:documentation>Party that makes (or on whose behalf an agent or broker makes) the import declaration, and who is liable for the payment of duties (if any) on the imported goods. Normally, this party is named either as the consignee in the shipping documents and/or as the buyer in the exporter's invoice.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Exporter" type="dhl:Exporter" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The party who is responsible for the legality of the shipment under applicable export control laws, which includes determining the proper export classification and authorization needed to ship the Items.The Exporter is usually the party who controls the transaction, acts as declarant in its own name and provides the corresponding instructions for the export, regardless of who files the export declaration. The Exporter may or may not be the Shipper.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Seller" type="dhl:Seller" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The party that makes, offers or contracts to make a sale to an actual or potential buyer. Also called vendor.</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="Payer" type="dhl:Payer" minOccurs="0">
<xsd:annotation>
<xsd:documentation>The party responsible for the full or partial payment of associated charges/cost. There can be many payers related to the different elements linked to a Shipment (e.g Services, fiscal charges).</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -0,0 +1 @@
from . import request

View File

@@ -0,0 +1,240 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import requests
from datetime import datetime
from json.decoder import JSONDecodeError
from requests.exceptions import RequestException
from odoo import _
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_round, json_float_round
TEST_BASE_URL = 'https://express.api.dhl.com/mydhlapi/test/'
PROD_BASE_URL = 'https://express.api.dhl.com/mydhlapi/'
class DHLProvider:
def __init__(self, carrier):
super_carrier = carrier.sudo()
self.logger = super_carrier.log_xml
self.base_url = PROD_BASE_URL if super_carrier.prod_environment else TEST_BASE_URL
if not super_carrier.dhl_api_key:
raise ValidationError(_("DHL API key is missing, please modify your delivery method settings."))
if not super_carrier.dhl_api_secret:
raise ValidationError(_("DHL API secret is missing, please modify your delivery method settings."))
self.session = requests.Session()
self.session.auth = (super_carrier.dhl_api_key, super_carrier.dhl_api_secret)
def _send_request(self, url, method='GET', data=None, json=None):
url = f'{self.base_url}{url}'
self.logger(f'{url}\n{method}\n{data}\n{json}', f'dhl request {url}')
try:
res = self.session.request(method=method, url=url, data=data, json=json, timeout=15)
self.logger(f'{res.status_code} {res.text}', f'dhl response {url}')
except RequestException as err:
self.logger(str(err), f'dhl response {url}')
raise ValidationError(_('Something went wrong, please try again later!!')) from None
return res
def _process_errors(self, res_body):
err_msgs = [' '.join([res_body.get('title', ''), res_body.get('detail', '')])]
if res_body.get('additionalDetails'):
for detail in res_body['additionalDetails']:
err_msgs.append(detail)
return '\n'.join(err_msgs)
def _check_required_value(self, carrier, recipient, shipper, order=False, picking=False):
carrier = carrier.sudo()
recipient_required_field = ['city', 'zip', 'phone', 'country_id']
if not carrier.dhl_account_number:
return _("DHL account number is missing, please modify your delivery method settings.")
# The street isn't required if we compute the rate with a partial delivery address in the
# express checkout flow.
if not recipient.street and not recipient.street2 and not recipient.env.context.get(
'express_checkout_partial_delivery_address', False
):
recipient_required_field.append('street')
res = [field for field in recipient_required_field if not recipient[field]]
if res:
return _("The address of the customer is missing or wrong (Missing field(s) :\n %s)", ", ".join(res).replace("_id", ""))
shipper_required_field = ['city', 'zip', 'phone', 'country_id']
if not shipper.street2:
shipper_required_field.append('street')
res = [field for field in shipper_required_field if not shipper[field]]
if res:
return _("The address of your company warehouse is missing or wrong (Missing field(s) :\n %s)", ", ".join(res).replace("_id", ""))
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')))
return ''
def _get_from_vals(self, warehouse_partner_id):
return {
'countryCode': warehouse_partner_id.country_id.code,
'postalCode': warehouse_partner_id.zip,
'cityName': warehouse_partner_id.city,
}
def _get_to_vals(self, partner_id):
return {
'countryCode': partner_id.country_id.code,
'postalCode': partner_id.zip,
'cityName': partner_id.city,
}
def _get_package_vals(self, carrier, packages):
return [{
'weight': carrier._dhl_rest_convert_weight(p['weight']),
'dimensions': {
'length': p['dimension']['length'],
'width': p['dimension']['width'],
'height': p['dimension']['height'],
}
} for p in packages]
def _get_dutiable_vals(self, total_value, currency_name):
return [{
'typeCode': 'declaredValue',
'value': total_value,
'currency': currency_name,
}]
def _get_rates(self, rating_request):
url = 'rates'
res = self._send_request(url, method='POST', json=rating_request)
try:
res_body = res.json()
except JSONDecodeError as err:
self.logger(str(err), f'dhl response decoding error {url}')
raise ValidationError(_('Could not decode the response from DHL.')) from None
if not res.ok:
raise ValidationError(self._process_errors(res_body)) from None
return res_body
def _get_billing_vals(self, shipper_account, payment_type):
return [{
'typeCode': payment_type,
'number': shipper_account,
}]
def _get_consignee_vals(self, partner_id):
consignee_dict = {
'postalAddress': {
'postalCode': partner_id.zip,
'cityName': partner_id.city,
'countryCode': partner_id.country_id.code,
'addressLine1': partner_id.street or partner_id.street2,
},
'contactInformation': {
'phone': partner_id.phone,
'companyName': partner_id.commercial_company_name or partner_id.name,
'fullName': partner_id.name,
}
}
if partner_id.email:
consignee_dict['contactInformation']['email'] = partner_id.email
if partner_id.street2:
consignee_dict['postalAddress']['addressLine2'] = partner_id.street2
if partner_id.state_id:
consignee_dict['postalAddress']['provinceName'] = partner_id.state_id.name
consignee_dict['postalAddress']['provinceCode'] = partner_id.state_id.code
return consignee_dict
def _get_shipper_vals(self, company_partner_id, warehouse_partner_id):
shipper_dict = {
'postalAddress': {
'postalCode': warehouse_partner_id.zip,
'cityName': warehouse_partner_id.city,
'countryCode': warehouse_partner_id.country_id.code,
'addressLine1': warehouse_partner_id.street or warehouse_partner_id.street2,
},
'contactInformation': {
'phone': company_partner_id.phone,
'companyName': company_partner_id.commercial_company_name or company_partner_id.name,
'fullName': company_partner_id.name,
},
}
if company_partner_id.email:
shipper_dict['contactInformation']['email'] = company_partner_id.email
if warehouse_partner_id.street2:
shipper_dict['postalAddress']['addressLine2'] = warehouse_partner_id.street2
if warehouse_partner_id.state_id:
shipper_dict['postalAddress']['provinceName'] = warehouse_partner_id.state_id.name
shipper_dict['postalAddress']['provinceCode'] = warehouse_partner_id.state_id.code
return shipper_dict
def _get_export_declaration_vals(self, carrier, picking, is_return=False):
export_declaration = {}
export_lines = []
move_lines = picking.move_line_ids.filtered(lambda line: line.product_id.type in ['product', 'consu'])
for sequence, line in enumerate(move_lines, start=1):
if line.move_id.sale_line_id:
unit_quantity = line.product_uom_id._compute_quantity(line.quantity, line.move_id.sale_line_id.product_uom_id)
else:
unit_quantity = line.product_uom_id._compute_quantity(line.quantity, line.product_id.uom_id)
rounded_qty = max(1, float_round(unit_quantity, precision_digits=0, rounding_method='HALF-UP'))
item = {
'number': sequence,
'description': line.product_id.name,
'price': json_float_round(line.sale_price / rounded_qty, 3),
'quantity': {
'value': int(rounded_qty),
'unitOfMeasurement': 'PCS'
},
'weight': {
'netValue': carrier._dhl_rest_convert_weight(line.product_id.weight),
'grossValue': carrier._dhl_rest_convert_weight(line.product_id.weight),
},
'manufacturerCountry': line.picking_id.picking_type_id.warehouse_id.partner_id.country_id.code
}
if line.product_id.hs_code:
item['commodityCodes'] = [{'typeCode': 'inbound', 'value': line.product_id.hs_code}]
export_lines.append(item)
export_declaration['lineItems'] = export_lines
export_declaration['invoice'] = {
'number': carrier.env['ir.sequence'].sudo().next_by_code('delivery_dhl_rest.commercial_invoice'),
'date': datetime.today().strftime('%Y-%m-%d'),
}
if is_return:
export_declaration['exportReasonType'] = 'return'
if picking.sale_id.client_order_ref:
export_declaration['recipientReference'] = picking.sale_id.client_order_ref
return export_declaration
def _get_shipment_vals(self, picking):
packages = picking.carrier_id._dhl_rest_get_picking_packages(picking)
return [{
'weight': picking.carrier_id._dhl_rest_convert_weight(package['weight']),
'dimensions': {
'length': package.get('dimension', {}).get('length', 0),
'width': package.get('dimension', {}).get('width', 0),
'height': package.get('dimension', {}).get('height', 0),
},
'description': package.get('name', '')
} for package in packages]
def _get_insurance_vals(self, insurance_percentage, total_value, currency_name):
return {
'serviceCode': 'II',
'value': float_round(total_value * insurance_percentage / 100, precision_digits=3),
'currency': currency_name,
}
def _send_shipment(self, shipment_request):
url = 'shipments'
res = self._send_request(url, method='POST', json=shipment_request)
try:
res_body = res.json()
except JSONDecodeError as err:
self.logger(str(err), f'dhl response decoding error {url}')
raise ValidationError(_('Could not decode the response from DHL.')) from None
if not res.ok:
raise ValidationError(self._process_errors(res_body)) from None
return res_body

View File

@@ -0,0 +1 @@
from . import request

View File

@@ -0,0 +1,502 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import binascii
import logging
import re
from datetime import datetime, date
from os.path import join as opj
from odoo.tools.zeep import Client, Plugin, Settings
from odoo.tools.zeep.exceptions import Fault
from odoo.tools.zeep.wsdl.utils import etree_to_string
from odoo.tools import remove_accents, float_repr
from odoo.tools.misc import file_path
_logger = logging.getLogger(__name__)
# uncomment to enable logging of Zeep requests and responses
# logging.getLogger('zeep.transports').setLevel(logging.DEBUG)
STATECODE_REQUIRED_COUNTRIES = ['US', 'CA', 'PR ', 'IN']
# Why using standardized ISO codes? It's way more fun to use made up codes...
# https://www.fedex.com/us/developer/WebHelp/ws/2014/dvg/WS_DVG_WebHelp/Appendix_F_Currency_Codes.htm
FEDEX_CURR_MATCH = {
u'UYU': u'UYP',
u'XCD': u'ECD',
u'MXN': u'NMP',
u'KYD': u'CID',
u'CHF': u'SFR',
u'GBP': u'UKL',
u'IDR': u'RPA',
u'DOP': u'RDD',
u'JPY': u'JYE',
u'KRW': u'WON',
u'SGD': u'SID',
u'CLP': u'CHP',
u'JMD': u'JAD',
u'KWD': u'KUD',
u'AED': u'DHS',
u'TWD': u'NTD',
u'ARS': u'ARN',
u'LVL': u'EURO',
}
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(), 'fedex_request')
return envelope, http_headers
def ingress(self, envelope, http_headers, operation):
self.debug_logger(etree_to_string(envelope).decode(), 'fedex_response')
return envelope, http_headers
def marshalled(self, context):
context.envelope = context.envelope.prune()
class FedexRequest():
""" Low-level object intended to interface Odoo recordsets with FedEx,
through appropriate SOAP requests """
def __init__(self, debug_logger, request_type="shipping", prod_environment=False, ):
self.debug_logger = debug_logger
self.hasCommodities = False
wsdl_folder = 'prod' if prod_environment else 'test'
if request_type == "shipping":
wsdl_path = opj('fusion_shipping', 'api', 'fedex', 'wsdl', wsdl_folder, 'ShipService_v28.wsdl')
self.start_shipping_transaction(wsdl_path)
elif request_type == "rating":
wsdl_path = opj('fusion_shipping', 'api', 'fedex', 'wsdl', wsdl_folder, 'RateService_v31.wsdl')
self.start_rating_transaction(wsdl_path)
# Authentification stuff
def web_authentication_detail(self, key, password):
WebAuthenticationCredential = self.factory.WebAuthenticationCredential()
WebAuthenticationCredential.Key = key
WebAuthenticationCredential.Password = password
self.WebAuthenticationDetail = self.factory.WebAuthenticationDetail()
self.WebAuthenticationDetail.UserCredential = WebAuthenticationCredential
def transaction_detail(self, transaction_id):
self.TransactionDetail = self.factory.TransactionDetail()
self.TransactionDetail.CustomerTransactionId = transaction_id
def client_detail(self, account_number, meter_number):
self.ClientDetail = self.factory.ClientDetail()
self.ClientDetail.AccountNumber = account_number
self.ClientDetail.MeterNumber = meter_number
# Common stuff
def set_shipper(self, company_partner, warehouse_partner):
Contact = self.factory.Contact()
Contact.PersonName = remove_accents(company_partner.name) if not company_partner.is_company else ''
Contact.CompanyName = remove_accents(company_partner.commercial_company_name) or ''
Contact.PhoneNumber = warehouse_partner.phone or ''
Contact.EMailAddress = warehouse_partner.email or ''
# TODO fedex documentation asks for TIN number, but it seems to work without
Address = self.factory.Address()
Address.StreetLines = [remove_accents(warehouse_partner.street) or '',remove_accents(warehouse_partner.street2) or '']
Address.City = remove_accents(warehouse_partner.city) or ''
if warehouse_partner.country_id.code in STATECODE_REQUIRED_COUNTRIES:
Address.StateOrProvinceCode = warehouse_partner.state_id.code or ''
else:
Address.StateOrProvinceCode = ''
Address.PostalCode = warehouse_partner.zip or ''
Address.CountryCode = warehouse_partner.country_id.code or ''
self.RequestedShipment.Shipper = self.factory.Party()
self.RequestedShipment.Shipper.Contact = Contact
self.RequestedShipment.Shipper.Address = Address
def set_recipient(self, recipient_partner):
Contact = self.factory.Contact()
if recipient_partner.is_company:
Contact.PersonName = ''
Contact.CompanyName = remove_accents(recipient_partner.name)
else:
Contact.PersonName = remove_accents(recipient_partner.name)
Contact.CompanyName = remove_accents(recipient_partner.commercial_company_name) or ''
Contact.PhoneNumber = recipient_partner.phone or ''
Contact.EMailAddress = recipient_partner.email or ''
Address = self.factory.Address()
Address.StreetLines = [remove_accents(recipient_partner.street) or '', remove_accents(recipient_partner.street2) or '']
Address.City = remove_accents(recipient_partner.city) or ''
if recipient_partner.country_id.code in STATECODE_REQUIRED_COUNTRIES:
Address.StateOrProvinceCode = recipient_partner.state_id.code or ''
else:
Address.StateOrProvinceCode = ''
Address.PostalCode = recipient_partner.zip or ''
Address.CountryCode = recipient_partner.country_id.code or ''
self.RequestedShipment.Recipient = self.factory.Party()
self.RequestedShipment.Recipient.Contact = Contact
self.RequestedShipment.Recipient.Address = Address
def shipment_request(self, dropoff_type, service_type, packaging_type, overall_weight_unit, saturday_delivery):
self.RequestedShipment = self.factory.RequestedShipment()
self.RequestedShipment.SpecialServicesRequested = self.factory.ShipmentSpecialServicesRequested()
self.RequestedShipment.ShipTimestamp = datetime.now()
self.RequestedShipment.DropoffType = dropoff_type
self.RequestedShipment.ServiceType = service_type
self.RequestedShipment.PackagingType = packaging_type
# Resuest estimation of duties and taxes for international shipping
if service_type in ['INTERNATIONAL_ECONOMY', 'INTERNATIONAL_PRIORITY']:
self.RequestedShipment.EdtRequestType = 'ALL'
else:
self.RequestedShipment.EdtRequestType = 'NONE'
self.RequestedShipment.PackageCount = 0
self.RequestedShipment.TotalWeight = self.factory.Weight()
self.RequestedShipment.TotalWeight.Units = overall_weight_unit
self.RequestedShipment.TotalWeight.Value = 0
self.listCommodities = []
if saturday_delivery:
timestamp_day = self.RequestedShipment.ShipTimestamp.strftime("%A")
if (service_type == 'FEDEX_2_DAY' and timestamp_day == 'Thursday') or (service_type in ['PRIORITY_OVERNIGHT', 'FIRST_OVERNIGHT', 'INTERNATIONAL_PRIORITY'] and timestamp_day == 'Friday'):
self.RequestedShipment.SpecialServicesRequested.SpecialServiceTypes.append('SATURDAY_DELIVERY')
def set_currency(self, currency):
# set perferred currency as GBP instead of UKL
currency = 'GBP' if currency == 'UKL' else currency
self.RequestedShipment.PreferredCurrency = currency
# ask Fedex to include our preferred currency in the response
self.RequestedShipment.RateRequestTypes = 'PREFERRED'
def set_master_package(self, weight, package_count, master_tracking_id=False):
self.RequestedShipment.TotalWeight.Value = weight
self.RequestedShipment.PackageCount = package_count
if master_tracking_id:
self.RequestedShipment.MasterTrackingId = self.factory.TrackingId()
self.RequestedShipment.MasterTrackingId.TrackingIdType = 'FEDEX'
self.RequestedShipment.MasterTrackingId.TrackingNumber = master_tracking_id
# weight_value, package_code=False, package_height=0, package_width=0, package_length=0,
def add_package(self, carrier, delivery_package, fdx_company_currency, sequence_number=False, mode='shipping', po_number=False, dept_number=False, reference=False):
package = self.factory.RequestedPackageLineItem()
package_weight = self.factory.Weight()
package_weight.Value = carrier._fedex_convert_weight(delivery_package.weight, carrier.fedex_weight_unit)
package_weight.Units = self.RequestedShipment.TotalWeight.Units
package.PhysicalPackaging = 'BOX'
if delivery_package.packaging_type == 'YOUR_PACKAGING':
package.Dimensions = self.factory.Dimensions()
package.Dimensions.Height = int(delivery_package.dimension['height'])
package.Dimensions.Width = int(delivery_package.dimension['width'])
package.Dimensions.Length = int(delivery_package.dimension['length'])
# TODO in master, add unit in product packaging and perform unit conversion
package.Dimensions.Units = "IN" if self.RequestedShipment.TotalWeight.Units == 'LB' else 'CM'
if po_number:
po_reference = self.factory.CustomerReference()
po_reference.CustomerReferenceType = 'P_O_NUMBER'
po_reference.Value = po_number
package.CustomerReferences.append(po_reference)
if dept_number:
dept_reference = self.factory.CustomerReference()
dept_reference.CustomerReferenceType = 'DEPARTMENT_NUMBER'
dept_reference.Value = dept_number
package.CustomerReferences.append(dept_reference)
if reference:
customer_reference = self.factory.CustomerReference()
customer_reference.CustomerReferenceType = 'CUSTOMER_REFERENCE'
customer_reference.Value = reference
package.CustomerReferences.append(customer_reference)
if carrier.shipping_insurance:
package.InsuredValue = self.factory.Money()
insured_value = delivery_package.total_cost * carrier.shipping_insurance / 100
pkg_order = delivery_package.order_id or delivery_package.picking_id.sale_id
# Get the currency from the sale order if it exists, so that it matches that of customs_value
if pkg_order:
package.InsuredValue.Currency = _convert_curr_iso_fdx(pkg_order.currency_id.name)
package.InsuredValue.Amount = float_repr(delivery_package.company_id.currency_id._convert(insured_value, pkg_order.currency_id, pkg_order.company_id, date.today()), 2)
else:
package.InsuredValue.Currency = fdx_company_currency
package.InsuredValue.Amount = float_repr(insured_value, 2)
package.Weight = package_weight
if mode == 'rating':
package.GroupPackageCount = 1
if sequence_number:
package.SequenceNumber = sequence_number
if mode == 'rating':
self.RequestedShipment.RequestedPackageLineItems.append(package)
else:
self.RequestedShipment.RequestedPackageLineItems = package
# Rating stuff
def start_rating_transaction(self, wsdl_path):
settings = Settings(strict=False)
self.client = Client(file_path(wsdl_path), plugins=[LogPlugin(self.debug_logger)], settings=settings)
self.factory = self.client.type_factory('ns0')
self.VersionId = self.factory.VersionId()
self.VersionId.ServiceId = 'crs'
self.VersionId.Major = '31'
self.VersionId.Intermediate = '0'
self.VersionId.Minor = '0'
def rate(self, request):
formatted_response = {'price': {}}
try:
self.response = self.client.service.getRates(WebAuthenticationDetail=request['WebAuthenticationDetail'],
ClientDetail=request['ClientDetail'],
TransactionDetail=request['TransactionDetail'],
Version=request['VersionId'],
RequestedShipment=request['RequestedShipment'])
if (self.response.HighestSeverity != 'ERROR' and self.response.HighestSeverity != 'FAILURE'):
if not getattr(self.response, "RateReplyDetails", False):
raise Exception("No rating found")
for rating in self.response.RateReplyDetails[0].RatedShipmentDetails:
formatted_response['price'][rating.ShipmentRateDetail.TotalNetFedExCharge.Currency] = float(rating.ShipmentRateDetail.TotalNetFedExCharge.Amount)
if len(self.response.RateReplyDetails[0].RatedShipmentDetails) == 1:
if 'CurrencyExchangeRate' in self.response.RateReplyDetails[0].RatedShipmentDetails[0].ShipmentRateDetail and self.response.RateReplyDetails[0].RatedShipmentDetails[0].ShipmentRateDetail['CurrencyExchangeRate']:
formatted_response['price'][self.response.RateReplyDetails[0].RatedShipmentDetails[0].ShipmentRateDetail.CurrencyExchangeRate.FromCurrency] = float(self.response.RateReplyDetails[0].RatedShipmentDetails[0].ShipmentRateDetail.TotalNetFedExCharge.Amount) / float(self.response.RateReplyDetails[0].RatedShipmentDetails[0].ShipmentRateDetail.CurrencyExchangeRate.Rate)
else:
errors_message = '\n'.join([("%s: %s" % (n.Code, n.Message)) for n in self.response.Notifications if (n.Severity == 'ERROR' or n.Severity == 'FAILURE')])
formatted_response['errors_message'] = errors_message
if any([n.Severity == 'WARNING' for n in self.response.Notifications]):
warnings_message = '\n'.join([("%s: %s" % (n.Code, n.Message)) for n in self.response.Notifications if n.Severity == 'WARNING'])
formatted_response['warnings_message'] = warnings_message
except Fault as fault:
formatted_response['errors_message'] = fault
except IOError:
formatted_response['errors_message'] = "Fedex Server Not Found"
except Exception as e:
formatted_response['errors_message'] = e.args[0]
return formatted_response
# Shipping stuff
def start_shipping_transaction(self, wsdl_path):
self.client = Client(file_path(wsdl_path), plugins=[LogPlugin(self.debug_logger)])
self.factory = self.client.type_factory("ns0")
self.VersionId = self.factory.VersionId()
self.VersionId.ServiceId = 'ship'
self.VersionId.Major = '28'
self.VersionId.Intermediate = '0'
self.VersionId.Minor = '0'
def shipment_label(self, label_format_type, image_type, label_stock_type, label_printing_orientation, label_order):
LabelSpecification = self.factory.LabelSpecification()
LabelSpecification.LabelFormatType = label_format_type
LabelSpecification.ImageType = image_type
LabelSpecification.LabelStockType = label_stock_type
LabelSpecification.LabelPrintingOrientation = label_printing_orientation
LabelSpecification.LabelOrder = label_order
self.RequestedShipment.LabelSpecification = LabelSpecification
def commercial_invoice(self, document_stock_type, send_etd=False):
shipping_document = self.factory.ShippingDocumentSpecification()
shipping_document.ShippingDocumentTypes = "COMMERCIAL_INVOICE"
commercial_invoice_detail = self.factory.CommercialInvoiceDetail()
commercial_invoice_detail.Format = self.factory.ShippingDocumentFormat()
commercial_invoice_detail.Format.ImageType = "PDF"
commercial_invoice_detail.Format.StockType = document_stock_type
shipping_document.CommercialInvoiceDetail = commercial_invoice_detail
self.RequestedShipment.ShippingDocumentSpecification = shipping_document
if send_etd:
self.RequestedShipment.SpecialServicesRequested.SpecialServiceTypes.append('ELECTRONIC_TRADE_DOCUMENTS')
etd_details = self.factory.EtdDetail()
etd_details.RequestedDocumentCopies.append('COMMERCIAL_INVOICE')
self.RequestedShipment.SpecialServicesRequested.EtdDetail = etd_details
def shipping_charges_payment(self, shipping_charges_payment_account):
self.RequestedShipment.ShippingChargesPayment = self.factory.Payment()
self.RequestedShipment.ShippingChargesPayment.PaymentType = 'SENDER'
Payor = self.factory.Payor()
Payor.ResponsibleParty = self.factory.Party()
Payor.ResponsibleParty.AccountNumber = shipping_charges_payment_account
self.RequestedShipment.ShippingChargesPayment.Payor = Payor
def duties_payment(self, sender_party, responsible_account_number, payment_type):
self.RequestedShipment.CustomsClearanceDetail.DutiesPayment = self.factory.Payment()
self.RequestedShipment.CustomsClearanceDetail.DutiesPayment.PaymentType = payment_type
if payment_type == 'SENDER':
Payor = self.factory.Payor()
Payor.ResponsibleParty = self.factory.Party()
Payor.ResponsibleParty.Address = self.factory.Address()
Payor.ResponsibleParty.Address.CountryCode = sender_party.country_id.code
Payor.ResponsibleParty.AccountNumber = responsible_account_number
self.RequestedShipment.CustomsClearanceDetail.DutiesPayment.Payor = Payor
def customs_value(self, customs_value_currency, customs_value_amount, document_content):
self.RequestedShipment.CustomsClearanceDetail = self.factory.CustomsClearanceDetail()
if self.hasCommodities:
self.RequestedShipment.CustomsClearanceDetail.Commodities = self.listCommodities
self.RequestedShipment.CustomsClearanceDetail.CustomsValue = self.factory.Money()
self.RequestedShipment.CustomsClearanceDetail.CustomsValue.Currency = customs_value_currency
self.RequestedShipment.CustomsClearanceDetail.CustomsValue.Amount = float_repr(customs_value_amount, 2)
if self.RequestedShipment.Shipper.Address.CountryCode == "IN" and self.RequestedShipment.Recipient.Address.CountryCode == "IN":
if not self.RequestedShipment.CustomsClearanceDetail.CommercialInvoice:
self.RequestedShipment.CustomsClearanceDetail.CommercialInvoice = self.factory.CommercialInvoice()
else:
del self.RequestedShipment.CustomsClearanceDetail.CommercialInvoice.TaxesOrMiscellaneousChargeType
self.RequestedShipment.CustomsClearanceDetail.CommercialInvoice.Purpose = 'SOLD'
# Old keys not requested anymore but still in WSDL; not removing them causes crash
del self.RequestedShipment.CustomsClearanceDetail['ClearanceBrokerage']
del self.RequestedShipment.CustomsClearanceDetail['FreightOnValue']
self.RequestedShipment.CustomsClearanceDetail.DocumentContent = document_content
def commodities(self, carrier, delivery_commodity, commodity_currency):
self.hasCommodities = True
commodity = self.factory.Commodity()
commodity.UnitPrice = self.factory.Money()
commodity.UnitPrice.Currency = commodity_currency
commodity.UnitPrice.Amount = delivery_commodity.monetary_value
commodity.NumberOfPieces = '1'
commodity.CountryOfManufacture = delivery_commodity.country_of_origin
commodity_weight = self.factory.Weight()
commodity_weight.Value = carrier._fedex_convert_weight(delivery_commodity.product_id.weight * delivery_commodity.qty, carrier.fedex_weight_unit)
commodity_weight.Units = carrier.fedex_weight_unit
commodity.Weight = commodity_weight
commodity.Description = re.sub(r'[\[\]<>;={}"|]', '', delivery_commodity.product_id.name)
commodity.Quantity = delivery_commodity.qty
commodity.QuantityUnits = 'EA'
customs_value = self.factory.Money()
customs_value.Currency = commodity_currency
customs_value.Amount = delivery_commodity.monetary_value * delivery_commodity.qty
commodity.CustomsValue = customs_value
commodity.HarmonizedCode = delivery_commodity.product_id.hs_code.replace(".", "") if delivery_commodity.product_id.hs_code else ''
self.listCommodities.append(commodity)
def return_label(self, tracking_number, origin_date):
return_details = self.factory.ReturnShipmentDetail()
return_details.ReturnType = "PRINT_RETURN_LABEL"
if tracking_number and origin_date:
return_association = self.factory.ReturnAssociationDetail()
return_association.TrackingNumber = tracking_number
return_association.ShipDate = origin_date
return_details.ReturnAssociation = return_association
self.RequestedShipment.SpecialServicesRequested.SpecialServiceTypes.append("RETURN_SHIPMENT")
self.RequestedShipment.SpecialServicesRequested.ReturnShipmentDetail = return_details
if self.hasCommodities:
bla = self.factory.CustomsOptionDetail()
bla.Type = "FAULTY_ITEM"
self.RequestedShipment.CustomsClearanceDetail.CustomsOptions = bla
def process_shipment(self, request):
formatted_response = {'tracking_number': 0.0,
'price': {},
'master_tracking_id': None,
'date': None}
try:
self.response = self.client.service.processShipment(WebAuthenticationDetail=request['WebAuthenticationDetail'],
ClientDetail=request['ClientDetail'],
TransactionDetail=request['TransactionDetail'],
Version=request['VersionId'],
RequestedShipment=request['RequestedShipment'])
if (self.response.HighestSeverity != 'ERROR' and self.response.HighestSeverity != 'FAILURE'):
formatted_response['tracking_number'] = self.response.CompletedShipmentDetail.CompletedPackageDetails[0].TrackingIds[0].TrackingNumber
if 'CommitDate' in self.response.CompletedShipmentDetail.OperationalDetail:
formatted_response['date'] = self.response.CompletedShipmentDetail.OperationalDetail.CommitDate
else:
formatted_response['date'] = date.today()
if 'ShipmentRating' in self.response.CompletedShipmentDetail and self.response.CompletedShipmentDetail.ShipmentRating:
for rating in self.response.CompletedShipmentDetail.ShipmentRating.ShipmentRateDetails:
formatted_response['price'][rating.TotalNetFedExCharge.Currency] = float(rating.TotalNetFedExCharge.Amount)
if 'CurrencyExchangeRate' in rating and rating.CurrencyExchangeRate:
formatted_response['price'][rating.CurrencyExchangeRate.FromCurrency] = float(rating.TotalNetFedExCharge.Amount / rating.CurrencyExchangeRate.Rate)
else:
formatted_response['price']['USD'] = 0.0
if 'MasterTrackingId' in self.response.CompletedShipmentDetail:
formatted_response['master_tracking_id'] = self.response.CompletedShipmentDetail.MasterTrackingId.TrackingNumber
else:
errors_message = '\n'.join([("%s: %s" % (n.Code, n.Message)) for n in self.response.Notifications if (n.Severity == 'ERROR' or n.Severity == 'FAILURE')])
formatted_response['errors_message'] = errors_message
if any([n.Severity == 'WARNING' for n in self.response.Notifications]):
warnings_message = '\n'.join([("%s: %s" % (n.Code, n.Message)) for n in self.response.Notifications if n.Severity == 'WARNING'])
formatted_response['warnings_message'] = warnings_message
except Fault as fault:
formatted_response['errors_message'] = fault
except IOError:
formatted_response['errors_message'] = "Fedex Server Not Found"
return formatted_response
def _get_labels(self, file_type):
labels = [self.get_label()]
if file_type.upper() in ['PNG'] and self.response.CompletedShipmentDetail.CompletedPackageDetails[0].PackageDocuments:
for auxiliary in self.response.CompletedShipmentDetail.CompletedPackageDetails[0].PackageDocuments[0].Parts:
labels.append(auxiliary.Image)
return labels
def get_label(self):
return self.response.CompletedShipmentDetail.CompletedPackageDetails[0].Label.Parts[0].Image
def get_document(self):
if self.response.CompletedShipmentDetail.ShipmentDocuments:
return self.response.CompletedShipmentDetail.ShipmentDocuments[0].Parts[0].Image
else:
return False
# Deletion stuff
def set_deletion_details(self, tracking_number):
self.TrackingId = self.factory.TrackingId()
self.TrackingId.TrackingIdType = 'FEDEX'
self.TrackingId.TrackingNumber = tracking_number
self.DeletionControl = self.factory.DeletionControlType('DELETE_ALL_PACKAGES')
def delete_shipment(self, request):
formatted_response = {'delete_success': False}
try:
# Here, we send the Order 66
self.response = self.client.service.deleteShipment(WebAuthenticationDetail=request['WebAuthenticationDetail'],
ClientDetail=request['ClientDetail'],
TransactionDetail=request['TransactionDetail'],
Version=request['VersionId'],
TrackingId=request['TrackingId'],
DeletionControl=request['DeletionControl'])
if (self.response.HighestSeverity != 'ERROR' and self.response.HighestSeverity != 'FAILURE'):
formatted_response['delete_success'] = True
else:
errors_message = '\n'.join([("%s: %s" % (n.Code, n.Message)) for n in self.response.Notifications if (n.Severity == 'ERROR' or n.Severity == 'FAILURE')])
formatted_response['errors_message'] = errors_message
if any([n.Severity == 'WARNING' for n in self.response.Notifications]):
warnings_message = '\n'.join([("%s: %s" % (n.Code, n.Message)) for n in self.response.Notifications if n.Severity == 'WARNING'])
formatted_response['warnings_message'] = warnings_message
except Fault as fault:
formatted_response['errors_message'] = fault
except IOError:
formatted_response['errors_message'] = "Fedex Server Not Found"
return formatted_response
def _convert_curr_fdx_iso(code):
curr_match = {v: k for k, v in FEDEX_CURR_MATCH.items()}
return curr_match.get(code, code)
def _convert_curr_iso_fdx(code):
return FEDEX_CURR_MATCH.get(code, code)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
from . import request

View File

@@ -0,0 +1,656 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
from json import JSONDecodeError
import requests
from requests import RequestException
from odoo import _
from odoo.exceptions import ValidationError, UserError
from odoo.tools import float_repr
TEST_BASE_URL = "https://apis-sandbox.fedex.com"
PROD_BASE_URL = "https://apis.fedex.com"
# Why using standardized ISO codes? It's way more fun to use made up codes...
# https://developer.fedex.com/api/en-us/guides/api-reference.html#currencycodes
FEDEX_CURR_MATCH = {
'XCD': 'ECD',
'MXN': 'NMP',
'KYD': 'CID',
'CHF': 'SFR',
'DOP': 'RDD',
'JPY': 'JYE',
'KRW': 'WON',
'SGD': 'SID',
'CLP': 'CHP',
'JMD': 'JAD',
'KWD': 'KUD',
'AED': 'DHS',
'TWD': 'NTD',
'ARS': 'ARN',
'VES': 'VEF',
# 'LVL': 'EUR',
# 'UYU': 'UYP',
'GBP': 'UKL',
# 'IDR': 'RPA',
}
FEDEX_MX_STATE_MATCH = {
'AGU': 'AG',
'BCN': 'BC',
'BCS': 'BS',
'CAM': 'CM',
'CHH': 'CH',
'CHP': 'CS',
'CMX': 'DF',
'COA': 'CO',
'COL': 'CL',
'DUR': 'DG',
'GRO': 'GR',
'GUA': 'GT',
'HID': 'HG',
'JAL': 'JA',
'MEX': 'EM',
'MIC': 'MI',
'MOR': 'MO',
'NAY': 'NA',
'NLE': 'NL',
'OAX': 'OA',
'PUE': 'PU',
'QUE': 'QE',
'ROO': 'QR',
'SIN': 'SI',
'SLP': 'SL',
'SON': 'SO',
'TAB': 'TB',
'TAM': 'TM',
'TLA': 'TL',
'VER': 'VE',
'YUC': 'YU',
'ZAC': 'ZA'
}
FEDEX_AE_STATE_MATCH = {
'AZ': 'AB',
'AJ': 'AJ',
'DU': 'DU',
'FU': 'FU',
'RK': 'RA',
'SH': 'SH',
'UQ': 'UM',
}
FEDEX_STOCK_TYPE_MATCH = {
'PAPER_4X6.75': 'PAPER_4X675',
'PAPER_7X4.75': 'PAPER_7X475',
'PAPER_8.5X11_BOTTOM_HALF_LABEL': 'PAPER_85X11_BOTTOM_HALF_LABEL',
'PAPER_8.5X11_TOP_HALF_LABEL': 'PAPER_85X11_TOP_HALF_LABEL',
'STOCK_4X6.75': 'STOCK_4X675',
'STOCK_4X6.75_LEADING_DOC_TAB': 'STOCK_4X675_LEADING_DOC_TAB',
'STOCK_4X6.75_TRAILING_DOC_TAB': 'STOCK_4X675_TRAILING_DOC_TAB',
}
class FedexRequest:
def __init__(self, carrier):
super_carrier = carrier.sudo()
self.base_url = PROD_BASE_URL if super_carrier.prod_environment else TEST_BASE_URL
self.access_token = super_carrier.fedex_rest_access_token
self.client_id = super_carrier.fedex_rest_developer_key
self.client_secret = super_carrier.fedex_rest_developer_password
self.account_number = super_carrier.fedex_rest_account_number
self.weight_units = super_carrier.fedex_rest_weight_unit
self.vat_override = super_carrier.fedex_rest_override_shipper_vat
self.email_notifications = super_carrier.fedex_rest_email_notifications
self.documentation_type = super_carrier.fedex_rest_documentation_type
self.insurance = super_carrier.shipping_insurance
self.check_residential = super_carrier.fedex_rest_residential_address
self.dropoff_type = super_carrier.fedex_rest_droppoff_type
self.service_type = super_carrier.fedex_rest_service_type
self.label_stock = _convert_stock_type(super_carrier.fedex_rest_label_stock_type)
self.label_file = super_carrier.fedex_rest_label_file_type
self.duty_payment = super_carrier.fedex_rest_duty_payment
self.make_return = super_carrier.return_label_on_delivery
self.debug_logger = super_carrier.log_xml
self.carrier = super_carrier
self.session = requests.Session()
def _send_fedex_request(self, url, data, method='POST'):
new_token = False
if not self.access_token:
self.access_token = self._get_new_access_token()
self.carrier.fedex_rest_access_token = self.access_token
new_token = True
def _request_call():
try:
response = self.session.request(method, self.base_url + url, json=data, headers={
'Content-Type': "application/json",
'Authorization': "Bearer " + self.access_token
}, timeout=15
)
self.debug_logger("%s %s\n%s\n\n%s" % (
response.request.method,
response.request.url,
'\n'.join([f'{k}: {v}' for k, v in response.request.headers.items()]),
response.request.body.decode('utf-8')
), 'fedex_rest_request')
self.debug_logger("%s %s\n%s\n\n%s" % (
response.status_code,
response.reason,
'\n'.join([f'{k}: {v}' for k, v in response.headers.items()]),
response.text
), 'fedex_rest_response')
except RequestException:
raise ValidationError(_('Something went wrong, please try again later!!')) from None
return response
res = _request_call()
if res.status_code == 401 and not new_token:
self.access_token = self._get_new_access_token()
self.carrier.fedex_rest_access_token = self.access_token
res = _request_call()
try:
response_data = res.json()
except JSONDecodeError:
raise ValidationError(_('Could not decode response')) from None
if not res.ok:
raise ValidationError(self._process_errors(response_data))
if 'output' not in response_data:
raise ValidationError(_('Could not decode response'))
return response_data['output']
def _process_errors(self, res_body):
err_msgs = []
for err in res_body.get('errors', []):
err_msgs.append(f"{err['message']} ({err['code']})")
return ','.join(err_msgs)
def _process_alerts(self, response):
messages = []
alerts = response.get('alerts', [])
if 'rateReplyDetails' in response:
alerts += response['rateReplyDetails'][0].get('customerMessages', [])
for alert in alerts:
messages.append(f"{alert['message']} ({alert['code']})")
return '\n'.join(messages)
def _get_new_access_token(self):
if not self.client_id or not self.client_secret:
raise ValidationError(_('You must setup a client ID and secret on the carrier first'))
try:
response = self.session.post(
self.base_url + "/oauth/token",
f"grant_type=client_credentials&client_id={self.client_id}&client_secret={self.client_secret}",
headers={'Content-Type': "application/x-www-form-urlencoded"},
timeout=15
)
response_data = response.json()
except RequestException:
raise ValidationError(_('Something went wrong, please try again later!!')) from None
except JSONDecodeError:
raise ValidationError(_('Could not decode response')) from None
if not response.ok:
raise ValidationError(self._process_errors(response_data))
if 'access_token' not in response_data:
raise ValidationError(_('Could not decode response'))
return response_data['access_token']
def _parse_state_code(self, state_code, country_code):
if country_code == 'CH':
# For Switzerland, keep the part before the hyphen
return state_code.split('-')[0]
else:
# For other countries, keep the part after the hyphen
split_code = state_code.split('-')
if split_code[0] == country_code and len(split_code) > 1:
return split_code[1]
else:
return state_code
def _get_location_from_partner(self, partner, check_residential=False):
res = {'countryCode': partner.country_id.code}
if partner.city:
res['city'] = partner.city
if partner.zip:
res['postalCode'] = partner.zip
if partner.state_id:
state_code = self._parse_state_code(partner.state_id.code, partner.country_id.code)
# need to adhere to two character length state code
if partner.country_id.code == 'MX':
state_code = FEDEX_MX_STATE_MATCH[state_code]
if partner.country_id.code == 'AE':
state_code = FEDEX_AE_STATE_MATCH.get(state_code, state_code)
if partner.country_id.code == 'IN' and partner.state_id.code == 'UK':
state_code = 'UT'
if len(state_code) <= 2:
res['stateOrProvinceCode'] = state_code
if check_residential:
setting = self.check_residential
if setting == 'always' or (setting == 'check' and self._check_residential_address({**res, 'streetLines': [partner.street, partner.street2]})):
res['residential'] = True
return res
def _check_residential_address(self, address):
if not address['streetLines'][1]:
del address['streetLines'][1]
result = self._send_fedex_request('/address/v1/addresses/resolve', {
'addressesToValidate': [{'address': address}]
})
return result['resolvedAddresses'][0]['classification'] != 'BUSINESS' # We assume residential until proven otherwise
def _get_address_from_partner(self, partner, check_residential=False):
res = self._get_location_from_partner(partner, check_residential)
res['streetLines'] = [partner.street]
if partner.street2:
res['streetLines'].append(partner.street2)
return res
def _get_contact_from_partner(self, partner, company_partner=False):
res = {'phoneNumber': partner.phone}
if company_partner and not res['phoneNumber']:
# Fallback to phone on the company if none on the WH
res['phoneNumber'] = company_partner.phone
if company_partner:
# Always put the name of the company, if the partner is a WH
res['companyName'] = partner.name[:35]
res['personName'] = partner.name[:70]
elif partner.is_company:
res['companyName'] = partner.name[:35]
res['personName'] = partner.name[:70]
else:
res['personName'] = partner.name[:70]
if partner.parent_id:
res['companyName'] = partner.parent_id.name[:35]
elif partner.company_name:
res['companyName'] = partner.company_name[:35]
if partner.email:
res['emailAddress'] = partner.email
elif company_partner and company_partner.email:
res['emailAddress'] = company_partner.email
return res
def _get_package_info(self, package):
res = {
'weight': {
'units': self.weight_units,
'value': self.carrier._fedex_rest_convert_weight(package.weight)
},
}
if int(package.dimension['length']) or int(package.dimension['width']) or int(package.dimension['height']):
# FedEx will raise a warning when mixing imperial and metric units (MIXED.MEASURING.UNITS.INCLUDED).
# So we force the dimension unit based on the selected weight unit on the delivery method.
res['dimensions'] = {
'units': 'IN' if self.weight_units == 'LB' else 'CM',
'length': int(package.dimension['length']),
'width': int(package.dimension['width']),
'height': int(package.dimension['height']),
}
if self.insurance:
res['declaredValue'] = {
'amount': float_repr(package.total_cost * self.insurance / 100, 2),
'currency': _convert_curr_iso_fdx(package.currency_id.name),
}
return res
def _get_detailed_package_info(self, package, customPackaging, order_no=False):
res = self._get_package_info(package)
if customPackaging:
res['subPackagingType'] = 'PACKAGE'
description = ', '.join([c.product_id.name for c in package.commodities])
res['itemDescription'] = description[:50]
res['itemDescriptionForClearance'] = description
if order_no:
res['customerReferences'] = [{
'customerReferenceType': 'P_O_NUMBER',
'value': order_no
}]
return res
def _get_commodities_info(self, commodity, currency):
res = {
'description': commodity.product_id.name[:450],
'customsValue': ({'amount': commodity.monetary_value * commodity.qty, 'currency': currency}),
'unitPrice': ({'amount': commodity.monetary_value, 'currency': currency}),
'countryOfManufacture': commodity.country_of_origin,
'weight': {
'units': self.weight_units,
'value': self.carrier._fedex_rest_convert_weight(commodity.product_id.weight),
},
'quantity': commodity.qty,
'quantityUnits': commodity.product_id.uom_id.fedex_code,
'numberOfPieces': 1,
}
if commodity.product_id.hs_code:
res['harmonizedCode'] = commodity.product_id.hs_code
return res
def _get_tins_from_partner(self, partner, custom_vat=False):
res = []
if custom_vat:
res.append({
'number': self.vat_override,
'tinType': 'BUSINESS_UNION'
})
if partner.vat and partner.is_company:
res.append({'number': partner.vat, 'tinType': 'BUSINESS_NATIONAL'})
elif partner.parent_id and partner.parent_id.vat and partner.parent_id.is_company:
res.append({'number': partner.parent_id.vat, 'tinType': 'BUSINESS_NATIONAL'})
return res
def _get_shipping_price(self, ship_from, ship_to, packages, currency):
fedex_currency = _convert_curr_iso_fdx(currency)
request_data = {
'accountNumber': {'value': self.account_number},
'requestedShipment': {
'rateRequestType': ['PREFERRED'],
'preferredCurrency': fedex_currency,
'pickupType': self.dropoff_type,
'serviceType': self.service_type,
'packagingType': packages[0].packaging_type,
'shipper': {'address': self._get_location_from_partner(ship_from)},
'recipient': {'address': self._get_location_from_partner(ship_to, True)},
'requestedPackageLineItems': [self._get_package_info(p) for p in packages],
'customsClearanceDetail': {
'commercialInvoice': {'shipmentPurpose': 'SOLD'},
'commodities': [self._get_commodities_info(c, fedex_currency) for pkg in packages for c in pkg.commodities],
'freightOnValue': 'CARRIER_RISK' if self.insurance == 100 else 'OWN_RISK',
'dutiesPayment': {'paymentType': 'SENDER'} # Only allowed value...
}
}
}
self._add_extra_data_to_request(request_data, 'rate')
res = self._send_fedex_request("/rate/v1/rates/quotes", request_data)
try:
rate = next(filter(lambda d: d['currency'] == fedex_currency, res['rateReplyDetails'][0]['ratedShipmentDetails']), {})
if rate.get('totalNetChargeWithDutiesAndTaxes', 0):
price = rate['totalNetChargeWithDutiesAndTaxes']
else:
price = rate['totalNetCharge']
except KeyError:
raise ValidationError(_('Could not decode response')) from None
return {
'price': price,
'alert_message': self._process_alerts(res),
}
def _ship_package(self, ship_from_wh, ship_from_company, ship_to, sold_to, packages, currency, order_no, customer_ref, picking_no, incoterms, freight_charge):
fedex_currency = _convert_curr_iso_fdx(currency)
package_type = packages[0].packaging_type
request_data = {
'accountNumber': {'value': self.account_number},
'labelResponseOptions': 'LABEL',
'requestedShipment': {
'rateRequestType': ['PREFERRED'],
'preferredCurrency': fedex_currency,
'pickupType': self.dropoff_type,
'serviceType': self.service_type,
'packagingType': package_type,
'shippingChargesPayment': {'paymentType': 'SENDER'},
'labelSpecification': {'labelStockType': self.label_stock, 'imageType': self.label_file},
'shipper': {
'address': self._get_address_from_partner(ship_from_wh),
'contact': self._get_contact_from_partner(ship_from_wh, ship_from_company),
'tins': self._get_tins_from_partner(ship_from_company, self.vat_override),
},
'recipients': [{
'address': self._get_address_from_partner(ship_to, True),
'contact': self._get_contact_from_partner(ship_to),
'tins': self._get_tins_from_partner(ship_to),
}],
'requestedPackageLineItems': [self._get_detailed_package_info(p, package_type == 'YOUR_PACKAGING', order_no) for p in packages],
'customsClearanceDetail': {
'dutiesPayment': {'paymentType': self.duty_payment},
'commodities': [self._get_commodities_info(c, fedex_currency) for pkg in packages for c in pkg.commodities],
'commercialInvoice': {
'shipmentPurpose': 'SOLD',
'originatorName': ship_from_company.name,
'comments': ['', picking_no], # First one is special instructions
},
}
}
}
if freight_charge:
request_data['requestedShipment']['customsClearanceDetail']['commercialInvoice']['freightCharge'] = {
'amount': freight_charge,
'currency': fedex_currency,
}
if incoterms:
request_data['requestedShipment']['customsClearanceDetail']['commercialInvoice']['termsOfSale'] = incoterms
if customer_ref:
request_data['requestedShipment']['customsClearanceDetail']['commercialInvoice']['customerReferences'] = [{
'customerReferenceType': 'CUSTOMER_REFERENCE',
'value': customer_ref,
}]
if request_data['requestedShipment']['shipper']['address']['countryCode'] == 'IN' and request_data['requestedShipment']['recipients'][0]['address']['countryCode'] == 'IN':
request_data['requestedShipment']['customsClearanceDetail']['freightOnValue'] = 'CARRIER_RISK' if self.insurance == 100 else 'OWN_RISK'
if sold_to and sold_to != ship_to:
request_data['requestedShipment']['soldTo'] = {
'address': self._get_address_from_partner(sold_to),
'contact': self._get_contact_from_partner(sold_to),
'tins': self._get_tins_from_partner(sold_to),
}
if ship_to.vat or ship_to.parent_id.vat:
request_data['requestedShipment']['customsClearanceDetail']['recipientCustomsId'] = {
'type': 'COMPANY',
'value': ship_to.vat or ship_to.parent_id.vat,
}
if self.email_notifications and ship_to.email:
request_data['requestedShipment']['emailNotificationDetail'] = {
'aggregationType': 'PER_PACKAGE',
'emailNotificationRecipients': [{
'emailNotificationRecipientType': 'RECIPIENT',
'emailAddress': ship_to.email,
'name': ship_to.name,
'notificationFormatType': 'HTML',
'notificationType': 'EMAIL',
'notificationEventType': ['ON_DELIVERY', 'ON_EXCEPTION', 'ON_SHIPMENT', 'ON_TENDER', 'ON_ESTIMATED_DELIVERY']
}]
}
if self.documentation_type != 'none':
request_data['requestedShipment']['shippingDocumentSpecification'] = {
'shippingDocumentTypes': ['COMMERCIAL_INVOICE'],
'commercialInvoiceDetail': {
'documentFormat': {'stockType': 'PAPER_LETTER', 'docType': 'PDF'}
}
}
if self.documentation_type == 'etd':
request_data['requestedShipment']['shipmentSpecialServices'] = {
"specialServiceTypes": [
"ELECTRONIC_TRADE_DOCUMENTS"
],
"etdDetail": {
"requestedDocumentTypes": [
"COMMERCIAL_INVOICE"
]
}
}
if self.make_return:
request_data['requestedShipment']['customsClearanceDetail']['customsOption'] = {'type': 'COURTESY_RETURN_LABEL'}
self._add_extra_data_to_request(request_data, 'ship')
res = self._send_fedex_request("/ship/v1/shipments", request_data)
try:
shipment = res['transactionShipments'][0]
details = shipment['completedShipmentDetail']
pieces = shipment['pieceResponses']
# Sometimes the shipment might be created but no pricing calculated, we just set to 0.
price = self._decode_pricing(details['shipmentRating']) if 'shipmentRating' in details else 0.0
except KeyError:
raise ValidationError(_('Could not decode response')) from None
return {
'service_info': f"{details.get('carrierCode', '')} > {details.get('serviceDescription', {}).get('description', '')} > {details.get('packagingDescription', '')}",
'tracking_numbers': ','.join([
t.get('trackingNumber', '')
for pkg in details.get('completedPackageDetails', [])
for t in pkg.get('trackingIds', [])
]),
'labels': [
(
p.get('trackingNumber', ''),
next(filter(lambda d: d.get('contentType', '') == 'LABEL', p.get('packageDocuments', {})), {}).get('encodedLabel')
)
for p in pieces
],
'price': price,
'documents': ', '.join([
f"{d.get('minimumCopiesRequired')}x {d.get('type', '')}"
for d in details.get('documentRequirements', {}).get('generationDetails', {})
if d.get('minimumCopiesRequired', 0)
]),
'alert_message': self._process_alerts(shipment),
'invoice': next(filter(
lambda d: d.get('contentType', '') == 'COMMERCIAL_INVOICE',
shipment.get('shipmentDocuments', {})
), {}).get('encodedLabel', ''),
'date': shipment.get('shipDatestamp', ''),
}
def _return_package(self, ship_from, ship_to_company, ship_to_wh, packages, currency, tracking, date):
fedex_currency = _convert_curr_iso_fdx(currency)
package_type = packages[0].packaging_type
request_data = {
'accountNumber': {'value': self.account_number},
'labelResponseOptions': 'LABEL',
'requestedShipment': {
'rateRequestType': ['PREFERRED'],
'preferredCurrency': fedex_currency,
'pickupType': self.dropoff_type,
'serviceType': self.service_type,
'packagingType': package_type,
'shippingChargesPayment': {'paymentType': 'SENDER'},
'shipmentSpecialServices': {
'specialServiceTypes': ['RETURN_SHIPMENT'],
'returnShipmentDetail': {
'returnType': 'PRINT_RETURN_LABEL',
'returnAssociationDetail': {'trackingNumber': tracking, 'shipDatestamp': date},
}
},
'labelSpecification': {'labelStockType': self.label_stock, 'imageType': self.label_file},
'shipper': {
'address': self._get_address_from_partner(ship_from),
'contact': self._get_contact_from_partner(ship_from),
'tins': self._get_tins_from_partner(ship_from),
},
'recipients': [{
'address': self._get_address_from_partner(ship_to_wh, True),
'contact': self._get_contact_from_partner(ship_to_wh, ship_to_company),
'tins': self._get_tins_from_partner(ship_to_company, self.vat_override),
}],
'requestedPackageLineItems': [self._get_detailed_package_info(p, package_type == 'YOUR_PACKAGING') for p in packages],
'customsClearanceDetail': {
'dutiesPayment': {'paymentType': 'SENDER'}, # Only allowed value for returns
'commodities': [self._get_commodities_info(c, fedex_currency) for pkg in packages for c in pkg.commodities],
'customsOption': {'type': 'REJECTED'},
}
}
}
if request_data['requestedShipment']['shipper']['address']['countryCode'] == 'IN' and request_data['requestedShipment']['recipients'][0]['address']['countryCode'] == 'IN':
request_data['requestedShipment']['customsClearanceDetail']['freightOnValue'] = 'CARRIER_RISK' if self.insurance == 100 else 'OWN_RISK'
if self.vat_override or ship_to_company.vat:
request_data['requestedShipment']['customsClearanceDetail']['recipientCustomsId'] = {
'type': 'COMPANY',
'value': self.vat_override or ship_to_company.vat,
}
self._add_extra_data_to_request(request_data, 'return')
res = self._send_fedex_request("/ship/v1/shipments", request_data)
try:
shipment = res['transactionShipments'][0]
details = shipment['completedShipmentDetail']
pieces = shipment['pieceResponses']
except KeyError:
raise ValidationError(_('Could not decode response')) from None
return {
'tracking_numbers': ','.join([
t.get('trackingNumber', '')
for pkg in details.get('completedPackageDetails', [])
for t in pkg.get('trackingIds', [])
]),
'labels': [
(
p.get('trackingNumber', ''),
next(filter(lambda d: d.get('contentType', '') == 'LABEL', p.get('packageDocuments', {})), {}).get('encodedLabel')
)
for p in pieces
],
'documents': ', '.join([
f"{d.get('minimumCopiesRequired')}x {d.get('type', '')}"
for d in details.get('documentRequirements', {}).get('generationDetails', {})
if d.get('minimumCopiesRequired', 0)
]),
'alert_message': self._process_alerts(shipment),
}
def _decode_pricing(self, rating_result):
actual = next(filter(lambda d: d['rateType'] == rating_result['actualRateType'], rating_result['shipmentRateDetails']), {})
if actual.get('totalNetChargeWithDutiesAndTaxes', False):
return actual['totalNetChargeWithDutiesAndTaxes']
return actual['totalNetCharge']
def cancel_shipment(self, tracking_nr):
res = self._send_fedex_request('/ship/v1/shipments/cancel', {
'accountNumber': {'value': self.account_number},
'deletionControl': 'DELETE_ALL_PACKAGES', # Cancel the entire shipment, not only the individual package.
'trackingNumber': tracking_nr,
}, 'PUT')
if not res.get('cancelledShipment', False):
return {
'delete_success': False,
'errors_message': res.get('message', 'Cancel shipment failed. Reason unknown.'),
}
return {
'delete_success': True,
'alert_message': self._process_alerts(res),
}
def _add_extra_data_to_request(self, request, request_type):
"""Adds the extra data to the request.
When there are multiple items in a list, they will all be affected by
the change.
"""
extra_data_input = {
'rate': self.carrier.fedex_rest_extra_data_rate_request,
'ship': self.carrier.fedex_rest_extra_data_ship_request,
'return': self.carrier.fedex_rest_extra_data_return_request,
}.get(request_type) or ''
try:
extra_data = json.loads('{' + extra_data_input + '}')
except SyntaxError:
raise UserError(_('Invalid syntax for FedEx extra data.')) from None
def extra_data_to_request(request, extra_data):
"""recursive function that adds extra data to the current request."""
for key, new_value in extra_data.items():
request[key] = current_value = request.get(key)
if isinstance(current_value, list):
for item in current_value:
extra_data_to_request(item, new_value)
elif isinstance(new_value, dict) and isinstance(current_value, dict):
extra_data_to_request(current_value, new_value)
else:
request[key] = new_value
extra_data_to_request(request, extra_data)
def _convert_curr_fdx_iso(code):
curr_match = {v: k for k, v in FEDEX_CURR_MATCH.items()}
return curr_match.get(code, code)
def _convert_curr_iso_fdx(code):
return FEDEX_CURR_MATCH.get(code, code)
def _convert_stock_type(stock_type):
return FEDEX_STOCK_TYPE_MATCH.get(stock_type, stock_type)

View File

@@ -0,0 +1 @@
from . import request

View 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)

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema targetNamespace="http://www.ups.com/XMLSchema/XOLTWS/Error/v1.1" xmlns:error="http://www.ups.com/XMLSchema/XOLTWS/Error/v1.1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" version="201707">
<xsd:element name="Errors">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="ErrorDetail" type="error:ErrorDetailType" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="ErrorDetailType">
<xsd:sequence>
<xsd:element name="Severity" type="xsd:string"/>
<xsd:element name="PrimaryErrorCode" type="error:CodeType"/>
<xsd:element name="MinimumRetrySeconds" type="xsd:string" minOccurs="0"/>
<xsd:element name="Location" type="error:LocationType" minOccurs="0"/>
<xsd:element name="SubErrorCode" type="error:CodeType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="AdditionalInformation" type="error:AdditionalInfoType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="ElementLevelInformation" type="error:ElementLevelInformationType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ElementLevelInformationType">
<xsd:sequence>
<xsd:element name="Level" type="xsd:string"/>
<xsd:element name="ElementIdentifier" type="error:ElementIdentifierType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ElementIdentifierType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Value" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CodeType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string"/>
<xsd:element name="Digest" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AdditionalInfoType">
<xsd:sequence>
<xsd:element name="Type" type="xsd:string"/>
<xsd:element name="Value" type="error:AdditionalCodeDescType" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AdditionalCodeDescType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LocationType">
<xsd:sequence>
<xsd:element name="LocationElementName" type="xsd:string" minOccurs="0"/>
<xsd:element name="XPathOfElement" type="xsd:string" minOccurs="0"/>
<xsd:element name="OriginalValue" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,318 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema targetNamespace="http://www.ups.com/XMLSchema/XOLTWS/IF/v1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ups="http://www.ups.com/XMLSchema" xmlns:IF="http://www.ups.com/XMLSchema/XOLTWS/IF/v1.0" elementFormDefault="qualified">
<xsd:complexType name="InternationalFormType">
<xsd:sequence>
<xsd:element name="FormType" type="xsd:string" maxOccurs="6"/>
<xsd:element name="UserCreatedForm" type="IF:UserCreatedFormType" minOccurs="0"/>
<xsd:element name="CN22Form" type="IF:CN22FormType" minOccurs="0"/>
<xsd:element name="UPSPremiumCareForm" type="IF:UPSPremiumCareFormType" minOccurs="0"/>
<xsd:element name="AdditionalDocumentIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="FormGroupIdName" type="xsd:string" minOccurs="0"/>
<xsd:element name="SEDFilingOption" type="xsd:string" minOccurs="0"/>
<xsd:element name="EEIFilingOption" type="IF:EEIFilingOptionType" minOccurs="0"/>
<xsd:element name="Contacts" type="IF:ContactType" minOccurs="0"/>
<xsd:element name="Product" type="IF:ProductType" maxOccurs="50"/>
<xsd:element name="InvoiceNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="InvoiceDate" type="xsd:string" minOccurs="0"/>
<xsd:element name="PurchaseOrderNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="TermsOfShipment" type="xsd:string" minOccurs="0"/>
<xsd:element name="ReasonForExport" type="xsd:string" minOccurs="0"/>
<xsd:element name="Comments" type="xsd:string" minOccurs="0"/>
<xsd:element name="DeclarationStatement" type="xsd:string" minOccurs="0"/>
<xsd:element name="Discount" type="IF:IFChargesType" minOccurs="0"/>
<xsd:element name="FreightCharges" type="IF:IFChargesType" minOccurs="0"/>
<xsd:element name="InsuranceCharges" type="IF:IFChargesType" minOccurs="0"/>
<xsd:element name="OtherCharges" type="IF:OtherChargesType" minOccurs="0"/>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="BlanketPeriod" type="IF:BlanketPeriodType" minOccurs="0"/>
<xsd:element name="ExportDate" type="xsd:string" minOccurs="0"/>
<xsd:element name="ExportingCarrier" type="xsd:string" minOccurs="0"/>
<xsd:element name="CarrierID" type="xsd:string" minOccurs="0"/>
<xsd:element name="InBondCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="EntryNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="PointOfOrigin" type="xsd:string" minOccurs="0"/>
<xsd:element name="PointOfOriginType" type="xsd:string" minOccurs="0"/>
<xsd:element name="ModeOfTransport" type="xsd:string" minOccurs="0"/>
<xsd:element name="PortOfExport" type="xsd:string" minOccurs="0"/>
<xsd:element name="PortOfUnloading" type="xsd:string" minOccurs="0"/>
<xsd:element name="LoadingPier" type="xsd:string" minOccurs="0"/>
<xsd:element name="PartiesToTransaction" type="xsd:string" minOccurs="0"/>
<xsd:element name="RoutedExportTransactionIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="ContainerizedIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="License" type="IF:LicenseType" minOccurs="0"/>
<xsd:element name="ECCNNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="OverridePaperlessIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="ShipperMemo" type="xsd:string" minOccurs="0"/>
<xsd:element name="MultiCurrencyInvoiceLineTotal" type="xsd:string" minOccurs="0"/>
<xsd:element name="HazardousMaterialsIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="UPSPremiumCareFormType">
<xsd:sequence>
<xsd:element name="ShipmentDate" type="xsd:string"/>
<xsd:element name="PageSize" type="xsd:string"/>
<xsd:element name="PrintType" type="xsd:string"/>
<xsd:element name="NumOfCopies" type="xsd:string"/>
<xsd:element name="LanguageForUPSPremiumCare" type="IF:LanguageForUPSPremiumCareType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LanguageForUPSPremiumCareType">
<xsd:sequence>
<xsd:element name="Language" type="xsd:string" maxOccurs="2"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="UserCreatedFormType">
<xsd:sequence>
<xsd:element name="DocumentID" type="xsd:string" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CN22FormType">
<xsd:sequence>
<xsd:element name="LabelSize" type="xsd:string" minOccurs="0"/>
<xsd:element name="PrintsPerPage" type="xsd:string" minOccurs="0"/>
<xsd:element name="LabelPrintType" type="xsd:string" minOccurs="0"/>
<xsd:element name="CN22Type" type="xsd:string" minOccurs="0"/>
<xsd:element name="CN22OtherDescription" type="xsd:string" minOccurs="0"/>
<xsd:element name="FoldHereText" type="xsd:string" minOccurs="0"/>
<xsd:element name="CN22Content" type="IF:CN22ContentType" minOccurs="0" maxOccurs="3"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CN22ContentType">
<xsd:sequence>
<xsd:element name="CN22ContentQuantity" type="xsd:string" minOccurs="0"/>
<xsd:element name="CN22ContentDescription" type="xsd:string" minOccurs="0"/>
<xsd:element name="CN22ContentWeight" type="IF:ProductWeightType" minOccurs="0"/>
<xsd:element name="CN22ContentTotalValue" type="xsd:string" minOccurs="0"/>
<xsd:element name="CN22ContentCurrencyCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="CN22ContentCountryOfOrigin" type="xsd:string" minOccurs="0"/>
<xsd:element name="CN22ContentTariffNumber" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ContactType">
<xsd:sequence>
<xsd:element name="ForwardAgent" type="IF:ForwardAgentType" minOccurs="0"/>
<xsd:element name="UltimateConsignee" type="IF:UltimateConsigneeType" minOccurs="0"/>
<xsd:element name="IntermediateConsignee" type="IF:IntermediateConsigneeType" minOccurs="0"/>
<xsd:element name="Producer" type="IF:ProducerType" minOccurs="0"/>
<xsd:element name="SoldTo" type="IF:SoldToType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ForwardAgentType">
<xsd:sequence>
<xsd:element name="CompanyName" type="xsd:string"/>
<xsd:element name="TaxIdentificationNumber" type="xsd:string"/>
<xsd:element name="Address" type="IF:AddressType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AddressType">
<xsd:sequence>
<xsd:element name="AddressLine" type="xsd:string" maxOccurs="3"/>
<xsd:element name="City" type="xsd:string"/>
<xsd:element name="StateProvinceCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="Town" type="xsd:string" minOccurs="0"/>
<xsd:element name="PostalCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="CountryCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="UltimateConsigneeType">
<xsd:sequence>
<xsd:element name="CompanyName" type="xsd:string"/>
<xsd:element name="Address" type="IF:AddressType"/>
<xsd:element name="UltimateConsigneeType" type="IF:UltimateConsigneeTypeType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="IntermediateConsigneeType">
<xsd:sequence>
<xsd:element name="CompanyName" type="xsd:string"/>
<xsd:element name="Address" type="IF:AddressType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ProducerType">
<xsd:sequence>
<xsd:element name="Option" type="xsd:string" minOccurs="0"/>
<xsd:element name="CompanyName" type="xsd:string" minOccurs="0"/>
<xsd:element name="TaxIdentificationNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="Address" type="IF:AddressType" minOccurs="0"/>
<xsd:element name="AttentionName" type="xsd:string" minOccurs="0"/>
<xsd:element name="Phone" type="IF:PhoneType" minOccurs="0"/>
<xsd:element name="EMailAddress" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ProductType">
<xsd:sequence>
<xsd:element name="Description" type="xsd:string" maxOccurs="3"/>
<xsd:element name="Unit" type="IF:UnitType" minOccurs="0"/>
<xsd:element name="CommodityCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="PartNumber" type="xsd:string" minOccurs="0" ups:usage="notused"/>
<xsd:element name="OriginCountryCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="JointProductionIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="NetCostCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="NetCostDateRange" type="IF:NetCostDateType" minOccurs="0"/>
<xsd:element name="PreferenceCriteria" type="xsd:string" minOccurs="0"/>
<xsd:element name="ProducerInfo" type="xsd:string" minOccurs="0"/>
<xsd:element name="MarksAndNumbers" type="xsd:string" minOccurs="0"/>
<xsd:element name="NumberOfPackagesPerCommodity" type="xsd:string" minOccurs="0"/>
<xsd:element name="ProductWeight" type="IF:ProductWeightType" minOccurs="0"/>
<xsd:element name="VehicleID" type="xsd:string" minOccurs="0"/>
<xsd:element name="ScheduleB" type="IF:ScheduleBType" minOccurs="0"/>
<xsd:element name="ExportType" type="xsd:string" minOccurs="0"/>
<xsd:element name="SEDTotalValue" type="xsd:string" minOccurs="0"/>
<xsd:element name="ExcludeFromForm" type="IF:ExcludeFromFormType" minOccurs="0"/>
<xsd:element name="ProductCurrencyCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackingListInfo" type="IF:PackingListInfoType" minOccurs="0"/>
<xsd:element name="EEIInformation" type="IF:EEIInformationType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ExcludeFromFormType">
<xsd:sequence>
<xsd:element name="FormType" type="xsd:string" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="UnitType">
<xsd:sequence>
<xsd:element name="Number" type="xsd:string"/>
<xsd:element name="UnitOfMeasurement" type="IF:UnitOfMeasurementType"/>
<xsd:element name="Value" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackingListInfoType">
<xsd:sequence>
<xsd:element name="PackageAssociated" type="IF:PackageAssociatedType" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageAssociatedType">
<xsd:sequence>
<xsd:element name="PackageNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="ProductAmount" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="UnitOfMeasurementType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="NetCostDateType">
<xsd:sequence>
<xsd:element name="BeginDate" type="xsd:string"/>
<xsd:element name="EndDate" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ProductWeightType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="IF:UnitOfMeasurementType"/>
<xsd:element name="Weight" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ScheduleBType">
<xsd:sequence>
<xsd:element name="Number" type="xsd:string"/>
<xsd:element name="Quantity" type="xsd:string" minOccurs="0" maxOccurs="2"/>
<xsd:element name="UnitOfMeasurement" type="IF:UnitOfMeasurementType" maxOccurs="2"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="IFChargesType">
<xsd:sequence>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="OtherChargesType">
<xsd:sequence>
<xsd:element name="MonetaryValue" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BlanketPeriodType">
<xsd:sequence>
<xsd:element name="BeginDate" type="xsd:string"/>
<xsd:element name="EndDate" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LicenseType">
<xsd:sequence>
<xsd:element name="Number" type="xsd:string" minOccurs="0"/>
<xsd:element name="Date" type="xsd:string" minOccurs="0"/>
<xsd:element name="ExceptionCode" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="SoldToType">
<xsd:sequence>
<xsd:element name="Name" type="xsd:string"/>
<xsd:element name="AttentionName" type="xsd:string"/>
<xsd:element name="TaxIdentificationNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="Phone" type="IF:PhoneType" minOccurs="0"/>
<xsd:element name="Option" type="xsd:string" minOccurs="0"/>
<xsd:element name="Address" type="IF:AddressType"/>
<xsd:element name="EMailAddress" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PhoneType">
<xsd:sequence>
<xsd:element name="Number" type="xsd:string"/>
<xsd:element name="Extension" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DDTCInformationType">
<xsd:sequence>
<xsd:element name="ITARExemptionNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="USMLCategoryCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="EligiblePartyIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="RegistrationNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="Quantity" type="xsd:string" minOccurs="0"/>
<xsd:element name="UnitOfMeasurement" type="IF:UnitOfMeasurementType"/>
<xsd:element name="SignificantMilitaryEquipmentIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="ACMNumber" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="EEILicenseType">
<xsd:sequence>
<xsd:element name="Number" type="xsd:string" minOccurs="0"/>
<xsd:element name="Code" type="xsd:string" minOccurs="0"/>
<xsd:element name="LicenseLineValue" type="xsd:string" minOccurs="0"/>
<xsd:element name="ECCNNumber" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="EEIFilingOptionType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string" minOccurs="0"/>
<xsd:element name="EMailAddress" type="xsd:string" minOccurs="0"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
<xsd:element name="UPSFiled" type="IF:UPSFiledType" minOccurs="0"/>
<xsd:element name="ShipperFiled" type="IF:ShipperFiledType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="UPSFiledType">
<xsd:sequence>
<xsd:element name="POA" type="IF:POAType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipperFiledType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string" minOccurs="0"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
<xsd:element name="PreDepartureITNNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="ExemptionLegend" type="xsd:string" minOccurs="0"/>
<xsd:element name="EEIShipmentReferenceNumber" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="EEIInformationType">
<xsd:sequence>
<xsd:element name="ExportInformation" type="xsd:string" minOccurs="0"/>
<xsd:element name="License" type="IF:EEILicenseType" minOccurs="0"/>
<xsd:element name="DDTCInformation" type="IF:DDTCInformationType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="POAType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string" minOccurs="0"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="UltimateConsigneeTypeType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string" minOccurs="0"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- UPS Rate Service WSDL Release Date Dec 29, 2007 -->
<!-- Copyright 2007-2008 United Parcel Service of America, Inc. All rights reserved. -->
<wsdl:definitions name="RateWS" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:error="http://www.ups.com/XMLSchema/XOLTWS/Error/v1.1" xmlns:upss="http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0" xmlns:rate="http://www.ups.com/XMLSchema/XOLTWS/Rate/v1.1" xmlns:tns="http://www.ups.com/WSDL/XOLTWS/Rate/v1.1" targetNamespace="http://www.ups.com/WSDL/XOLTWS/Rate/v1.1">
<wsdl:types>
<xsd:schema>
<!-- This schema defines the UPS Security header used for authorization purposes -->
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0" schemaLocation="UPSSecurity.xsd"/>
<!-- This schema defines the error detail data types returned within SOAPFaults to provide more specific information pertaining to the problem. -->
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/Error/v1.1" schemaLocation="Error1.1.xsd"/>
<!-- This schema defines the Rate service data types -->
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/Rate/v1.1" schemaLocation="RateWebServiceSchema.xsd"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="RateRequestMessage">
<wsdl:part name="Body" element="rate:RateRequest"/>
<wsdl:part name="UPSSecurity" element="upss:UPSSecurity"/>
</wsdl:message>
<wsdl:message name="RateResponseMessage">
<wsdl:part name="Body" element="rate:RateResponse"/>
</wsdl:message>
<wsdl:message name="RateErrorMessage">
<wsdl:part name="RateError" element="error:Errors"/>
</wsdl:message>
<wsdl:portType name="RatePortType">
<wsdl:operation name="ProcessRate">
<wsdl:input name="RateRequest" message="tns:RateRequestMessage"/>
<wsdl:output name="RateResponse" message="tns:RateResponseMessage"/>
<wsdl:fault name="RateError" message="tns:RateErrorMessage"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="RateBinding" type="tns:RatePortType">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="ProcessRate">
<soap:operation soapAction="http://onlinetools.ups.com/webservices/RateBinding/v1.1" style="document"/>
<wsdl:input name="RateRequest">
<soap:body parts="Body" use="literal"/>
<soap:header message="tns:RateRequestMessage" part="UPSSecurity" use="literal">
<soap:headerfault message="tns:RateErrorMessage" part="RateError" use="literal"/>
</soap:header>
</wsdl:input>
<wsdl:output name="RateResponse">
<soap:body parts="Body" use="literal"/>
</wsdl:output>
<wsdl:fault name="RateError">
<soap:fault name="RateError" use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="RateService">
<wsdl:port name="RatePort" binding="tns:RateBinding">
<!-- Production URL -->
<!-- <soap:address location="https://onlinetools.ups.com/webservices/Rate"/> -->
<!-- CIE (Customer Integration Environment) URL -->
<soap:address location="https://wwwcie.ups.com/webservices/Rate"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

View File

@@ -0,0 +1,619 @@
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:common="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0" xmlns:rate="http://www.ups.com/XMLSchema/XOLTWS/Rate/v1.1" xmlns:ups="http://www.ups.com/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.ups.com/XMLSchema/XOLTWS/Rate/v1.1" version="201701">
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0" schemaLocation="common.xsd"/>
<xsd:element name="RateRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="common:Request"/>
<xsd:element name="PickupType" type="rate:CodeDescriptionType" minOccurs="0"/>
<xsd:element minOccurs="0" name="CustomerClassification" type="rate:CodeDescriptionType"/>
<xsd:element name="Shipment" type="rate:ShipmentType"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="RateResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="common:Response"/>
<xsd:element maxOccurs="unbounded" name="RatedShipment" type="rate:RatedShipmentType"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="BillingWeightType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="rate:CodeDescriptionType"/>
<xsd:element name="Weight" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="RatedPackageType">
<xsd:sequence>
<xsd:element minOccurs="0" name="TransportationCharges" type="rate:ChargesType"/>
<xsd:element name="BaseServiceCharge" type="rate:ChargesType" minOccurs="0"/>
<xsd:element minOccurs="0" name="ServiceOptionsCharges" type="rate:ChargesType"/>
<xsd:element minOccurs="0" name="TotalCharges" type="rate:ChargesType"/>
<xsd:element minOccurs="0" name="Weight" type="xsd:string"/>
<xsd:element minOccurs="0" name="BillingWeight" type="rate:BillingWeightType"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="Accessorial" type="rate:AccessorialType"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="ItemizedCharges" type="rate:ChargesType"/>
<xsd:element minOccurs="0" name="NegotiatedCharges" type="rate:NegotiatedChargesType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AccessorialType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element minOccurs="0" name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="NegotiatedChargesType">
<xsd:sequence>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="ItemizedCharges" type="rate:ChargesType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="RatedShipmentType">
<xsd:sequence>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="Disclaimer" type="rate:DisclaimerType"/>
<xsd:element name="Service" type="rate:CodeDescriptionType"/>
<xsd:element minOccurs="0" name="RateChart" type="xsd:string"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="RatedShipmentAlert" type="rate:RatedShipmentInfoType"/>
<xsd:element minOccurs="0" name="BillableWeightCalculationMethod" type="xsd:string"/>
<xsd:element minOccurs="0" name="RatingMethod" type="xsd:string"/>
<xsd:element name="BillingWeight" type="rate:BillingWeightType"/>
<xsd:element name="TransportationCharges" type="rate:ChargesType"/>
<xsd:element name="BaseServiceCharge" type="rate:ChargesType" minOccurs="0"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="ItemizedCharges" type="rate:ChargesType"/>
<xsd:element minOccurs="0" name="FRSShipmentData" type="rate:FRSShipmentType"/>
<xsd:element name="ServiceOptionsCharges" type="rate:ChargesType"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="TaxCharges" type="rate:TaxChargeType"/>
<xsd:element name="TotalCharges" type="rate:ChargesType"/>
<xsd:element minOccurs="0" name="TotalChargesWithTaxes" type="rate:ChargesType"/>
<xsd:element minOccurs="0" name="NegotiatedRateCharges" type="rate:TotalChargeType"/>
<xsd:element minOccurs="0" name="GuaranteedDelivery" type="rate:GuaranteedDeliveryType"/>
<xsd:element maxOccurs="unbounded" name="RatedPackage" type="rate:RatedPackageType"/>
<xsd:element name="TimeInTransit" type="rate:TimeInTransitResponseType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TimeInTransitResponseType">
<xsd:sequence>
<xsd:element name="PickupDate" type="xsd:string"/>
<xsd:element name="DocumentsOnlyIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackageBillType" type="xsd:string" minOccurs="0"/>
<xsd:element name="ServiceSummary" type="rate:ServiceSummaryType" maxOccurs="unbounded"/>
<xsd:element name="AutoDutyCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="Disclaimer" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ServiceSummaryType">
<xsd:sequence>
<xsd:element name="Service" type="rate:CodeDescriptionType"/>
<xsd:element name="GuaranteedIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="Disclaimer" type="xsd:string" minOccurs="0"/>
<xsd:element name="EstimatedArrival" type="rate:EstimatedArrivalType"/>
<xsd:element name="SaturdayDelivery" type="xsd:string" minOccurs="0"/>
<xsd:element name="SaturdayDeliveryDisclaimer" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="EstimatedArrivalType">
<xsd:sequence>
<xsd:element name="Arrival" type="rate:PickupType"/>
<xsd:element name="BusinessDaysInTransit" type="xsd:string"/>
<xsd:element name="Pickup" type="rate:PickupType"/>
<xsd:element name="DayOfWeek" type="xsd:string" minOccurs="0"/>
<xsd:element name="CustomerCenterCutoff" type="xsd:string" minOccurs="0"/>
<xsd:element name="DelayCount" type="xsd:string" minOccurs="0"/>
<xsd:element name="HolidayCount" type="xsd:string" minOccurs="0"/>
<xsd:element name="RestDays" type="xsd:string" minOccurs="0"/>
<xsd:element name="TotalTransitDays" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DisclaimerType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TaxChargeType">
<xsd:sequence>
<xsd:element name="Type" type="xsd:string"/>
<xsd:element minOccurs="0" name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TotalChargeType">
<xsd:sequence>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="ItemizedCharges" type="rate:ChargesType"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="TaxCharges" type="rate:TaxChargeType"/>
<xsd:element name="TotalCharge" type="rate:ChargesType"/>
<xsd:element minOccurs="0" name="TotalChargesWithTaxes" type="rate:ChargesType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="RatedShipmentInfoType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ChargesType">
<xsd:sequence>
<xsd:element minOccurs="0" name="Code" type="xsd:string"/>
<xsd:element minOccurs="0" name="Description" type="xsd:string"/>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
<xsd:element minOccurs="0" name="SubType" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TransportationChargesType">
<xsd:sequence>
<xsd:element name="GrossCharge" type="rate:ChargesType"/>
<xsd:element name="DiscountAmount" type="rate:ChargesType"/>
<xsd:element name="DiscountPercentage" type="xsd:string"/>
<xsd:element name="NetCharge" type="rate:ChargesType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FRSShipmentType">
<xsd:sequence>
<xsd:element name="TransportationCharges" type="rate:TransportationChargesType"/>
<xsd:element minOccurs="0" name="FreightDensityRate" type="rate:FreightDensityRateType"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="HandlingUnits" type="rate:HandlingUnitsResponseType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FreightDensityRateType">
<xsd:sequence>
<xsd:element name="Density" type="xsd:string"/>
<xsd:element name="TotalCubicFeet" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HandlingUnitsResponseType">
<xsd:sequence>
<xsd:element name="Quantity" type="xsd:string"/>
<xsd:element name="Type" type="rate:CodeDescriptionType"/>
<xsd:element name="Dimensions" type="rate:HandlingUnitsDimensionsType"/>
<xsd:element minOccurs="0" name="AdjustedHeight" type="rate:AdjustedHeightType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AddressType">
<xsd:sequence>
<xsd:element maxOccurs="3" minOccurs="0" name="AddressLine" type="xsd:string"/>
<xsd:element minOccurs="0" name="City" type="xsd:string"/>
<xsd:element minOccurs="0" name="StateProvinceCode" type="xsd:string"/>
<xsd:element minOccurs="0" name="PostalCode" type="xsd:string"/>
<xsd:element name="CountryCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipToAddressType">
<xsd:complexContent>
<xsd:extension base="rate:AddressType">
<xsd:sequence>
<xsd:element minOccurs="0" name="ResidentialAddressIndicator" type="xsd:string"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="ShipAddressType">
<xsd:complexContent>
<xsd:extension base="rate:AddressType">
<xsd:sequence>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="CODType">
<xsd:sequence>
<xsd:element name="CODFundsCode" type="xsd:string"/>
<xsd:element name="CODAmount" type="rate:CODAmountType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CODAmountType">
<xsd:sequence>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DeliveryConfirmationType">
<xsd:sequence>
<xsd:element name="DCISType" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DimensionsType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="rate:CodeDescriptionType"/>
<xsd:element minOccurs="0" name="Length" type="xsd:string"/>
<xsd:element minOccurs="0" name="Width" type="xsd:string"/>
<xsd:element minOccurs="0" name="Height" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="InsuredValueType">
<xsd:sequence>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageType">
<xsd:sequence>
<xsd:element minOccurs="0" name="PackagingType" type="rate:CodeDescriptionType"/>
<xsd:element minOccurs="0" name="Dimensions" type="rate:DimensionsType"/>
<xsd:element name="DimWeight" type="rate:PackageWeightType" minOccurs="0"/>
<xsd:element minOccurs="0" name="PackageWeight" type="rate:PackageWeightType"/>
<xsd:element minOccurs="0" name="Commodity" type="rate:CommodityType"/>
<xsd:element minOccurs="0" name="LargePackageIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="PackageServiceOptions" type="rate:PackageServiceOptionsType"/>
<xsd:element minOccurs="0" name="AdditionalHandlingIndicator" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CommodityType">
<xsd:sequence>
<xsd:element name="FreightClass" type="xsd:string"/>
<xsd:element minOccurs="0" name="NMFC" type="rate:NMFCCommodityType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="NMFCCommodityType">
<xsd:sequence>
<xsd:element name="PrimeCode" type="xsd:string"/>
<xsd:element minOccurs="0" name="SubCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageServiceOptionsType">
<xsd:sequence>
<xsd:element minOccurs="0" name="DeliveryConfirmation" type="rate:DeliveryConfirmationType"/>
<xsd:element minOccurs="0" name="AccessPointCOD" type="rate:PackageServiceOptionsAccessPointCODType"/>
<xsd:element minOccurs="0" name="COD" type="rate:CODType"/>
<xsd:element minOccurs="0" name="DeclaredValue" type="rate:InsuredValueType"/>
<xsd:element minOccurs="0" name="ShipperDeclaredValue" type="rate:ShipperDeclaredValueType"/>
<xsd:element minOccurs="0" name="ProactiveIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="Insurance" type="rate:InsuranceType"/>
<xsd:element minOccurs="0" name="VerbalConfirmationIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="UPSPremiumCareIndicator" type="xsd:string"/>
<xsd:element name="HazMat" type="rate:HazMatType" minOccurs="0"/>
<xsd:element minOccurs="0" name="DryIce" type="rate:DryIceType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HazMatType">
<xsd:sequence>
<xsd:element name="PackageIdentifier" type="xsd:string" minOccurs="0"/>
<xsd:element name="QValue" type="xsd:string" minOccurs="0"/>
<xsd:element name="OverPackedIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="AllPackedInOneIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="HazMatChemicalRecord" type="rate:HazMatChemicalRecordType" maxOccurs="3"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HazMatChemicalRecordType">
<xsd:sequence>
<xsd:element name="ChemicalRecordIdentifier" type="xsd:string" minOccurs="0"/>
<xsd:element name="ClassDivisionNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="IDNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="TransportationMode" type="xsd:string"/>
<xsd:element name="RegulationSet" type="xsd:string"/>
<xsd:element name="EmergencyPhone" type="xsd:string" minOccurs="0"/>
<xsd:element name="EmergencyContact" type="xsd:string" minOccurs="0"/>
<xsd:element name="ReportableQuantity" type="xsd:string" minOccurs="0"/>
<xsd:element name="SubRiskClass" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackagingGroupType" type="xsd:string" minOccurs="0"/>
<xsd:element name="Quantity" type="xsd:string" minOccurs="0"/>
<xsd:element name="UOM" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackagingInstructionCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="ProperShippingName" type="xsd:string" minOccurs="0"/>
<xsd:element name="TechnicalName" type="xsd:string" minOccurs="0"/>
<xsd:element name="AdditionalDescription" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackagingType" type="xsd:string" minOccurs="0"/>
<xsd:element name="HazardLabelRequired" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackagingTypeQuantity" type="xsd:string" minOccurs="0"/>
<xsd:element name="CommodityRegulatedLevelCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="TransportCategory" type="xsd:string" minOccurs="0"/>
<xsd:element name="TunnelRestrictionCode" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageServiceOptionsAccessPointCODType">
<xsd:sequence>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DryIceType">
<xsd:sequence>
<xsd:element name="RegulationSet" type="xsd:string"/>
<xsd:element name="DryIceWeight" type="rate:DryIceWeightType"/>
<xsd:element minOccurs="0" name="MedicalUseIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="AuditRequired" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DryIceWeightType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="rate:CodeDescriptionType"/>
<xsd:element name="Weight" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipperDeclaredValueType">
<xsd:sequence>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="InsuranceType">
<xsd:sequence>
<xsd:element minOccurs="0" name="BasicFlexibleParcelIndicator" type="rate:InsuranceValueType"/>
<xsd:element minOccurs="0" name="ExtendedFlexibleParcelIndicator" type="rate:InsuranceValueType"/>
<xsd:element minOccurs="0" name="TimeInTransitFlexibleParcelIndicator" type="rate:InsuranceValueType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="InsuranceValueType">
<xsd:sequence>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageWeightType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="rate:CodeDescriptionType"/>
<xsd:element name="Weight" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="UOMCodeDescriptionType">
<xsd:sequence>
<xsd:element minOccurs="0" name="Code" type="xsd:string"/>
<xsd:element minOccurs="0" name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CodeDescriptionType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element minOccurs="0" name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentRatingOptionsType">
<xsd:sequence>
<xsd:element minOccurs="0" name="NegotiatedRatesIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="FRSShipmentIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="RateChartIndicator" type="xsd:string"/>
<xsd:element name="UserLevelDiscountIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipFromType">
<xsd:sequence>
<xsd:element minOccurs="0" name="Name" type="xsd:string"/>
<xsd:element name="Address" type="rate:ShipAddressType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipToType">
<xsd:sequence>
<xsd:element minOccurs="0" name="Name" type="xsd:string"/>
<xsd:element name="Address" type="rate:ShipToAddressType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentType">
<xsd:sequence>
<xsd:element name="OriginRecordTransactionTimestamp" type="xsd:string" minOccurs="0"/>
<xsd:element name="Shipper" type="rate:ShipperType"/>
<xsd:element name="ShipTo" type="rate:ShipToType"/>
<xsd:element minOccurs="0" name="ShipFrom" type="rate:ShipFromType"/>
<xsd:element minOccurs="0" name="AlternateDeliveryAddress" type="rate:AlternateDeliveryAddressType"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" name="ShipmentIndicationType" type="rate:IndicationType"/>
<xsd:element name="PaymentDetails" type="rate:PaymentDetailsType" minOccurs="0"/>
<xsd:element minOccurs="0" name="FRSPaymentInformation" type="rate:FRSPaymentInfoType"/>
<xsd:element minOccurs="0" name="FreightShipmentInformation" type="rate:FreightShipmentInformationType"/>
<xsd:element name="GoodsNotInFreeCirculationIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element minOccurs="0" name="Service" type="rate:CodeDescriptionType"/>
<xsd:element minOccurs="0" name="NumOfPieces" type="xsd:string"/>
<xsd:element name="ShipmentTotalWeight" type="rate:ShipmentWeightType" minOccurs="0"/>
<xsd:element minOccurs="0" name="DocumentsOnlyIndicator" type="xsd:string"/>
<xsd:element maxOccurs="unbounded" name="Package" type="rate:PackageType"/>
<xsd:element minOccurs="0" name="ShipmentServiceOptions" type="rate:ShipmentServiceOptionsType"/>
<xsd:element minOccurs="0" name="ShipmentRatingOptions" type="rate:ShipmentRatingOptionsType"/>
<xsd:element minOccurs="0" name="InvoiceLineTotal" type="rate:InvoiceLineTotalType"/>
<xsd:element minOccurs="0" name="RatingMethodRequestedIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="TaxInformationIndicator" type="xsd:string"/>
<xsd:element name="PromotionalDiscountInformation" type="rate:PromotionalDiscountInformationType" minOccurs="0"/>
<xsd:element name="DeliveryTimeInformation" type="rate:TimeInTransitRequestType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TimeInTransitRequestType">
<xsd:sequence>
<xsd:element name="PackageBillType" type="xsd:string" minOccurs="0"/>
<xsd:element name="Pickup" type="rate:PickupType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PickupType">
<xsd:sequence>
<xsd:element name="Date" type="xsd:string"/>
<xsd:element name="Time" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PromotionalDiscountInformationType">
<xsd:sequence>
<xsd:element name="PromoCode" type="xsd:string"/>
<xsd:element name="PromoAliasCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentWeightType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="rate:CodeDescriptionType"/>
<xsd:element name="Weight" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PaymentDetailsType">
<xsd:sequence>
<xsd:element name="ShipmentCharge" type="rate:ShipmentChargeType" minOccurs="0" maxOccurs="2"/>
<xsd:element name="SplitDutyVATIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentChargeType">
<xsd:sequence>
<xsd:element name="Type" type="xsd:string"/>
<xsd:element name="BillShipper" type="rate:BillShipperChargeType" minOccurs="0"/>
<xsd:element name="BillReceiver" type="rate:BillReceiverChargeType" minOccurs="0"/>
<xsd:element name="BillThirdParty" type="rate:BillThirdPartyChargeType" minOccurs="0"/>
<xsd:element name="ConsigneeBilledIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BillShipperChargeType">
<xsd:sequence>
<xsd:element name="AccountNumber" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BillReceiverChargeType">
<xsd:sequence>
<xsd:element name="AccountNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="Address" type="rate:BillReceiverAddressType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BillThirdPartyChargeType">
<xsd:sequence>
<xsd:element name="AccountNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="Address" type="rate:AddressType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BillReceiverAddressType">
<xsd:sequence>
<xsd:element name="PostalCode" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AlternateDeliveryAddressType">
<xsd:sequence>
<xsd:element minOccurs="0" name="Name" type="xsd:string"/>
<xsd:element name="Address" type="rate:ADRType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ADRType">
<xsd:sequence>
<xsd:element maxOccurs="3" minOccurs="0" name="AddressLine" type="xsd:string"/>
<xsd:element minOccurs="0" name="City" type="xsd:string"/>
<xsd:element minOccurs="0" name="StateProvinceCode" type="xsd:string"/>
<xsd:element minOccurs="0" name="PostalCode" type="xsd:string"/>
<xsd:element name="CountryCode" type="xsd:string"/>
<xsd:element minOccurs="0" name="ResidentialAddressIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="POBoxIndicator" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="IndicationType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element minOccurs="0" name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentServiceOptionsType">
<xsd:sequence>
<xsd:element minOccurs="0" name="SaturdayPickupIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="SaturdayDeliveryIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="AccessPointCOD" type="rate:ShipmentServiceOptionsAccessPointCODType"/>
<xsd:element minOccurs="0" name="DeliverToAddresseeOnlyIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="DirectDeliveryOnlyIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="COD" type="rate:CODType"/>
<xsd:element minOccurs="0" name="DeliveryConfirmation" type="rate:DeliveryConfirmationType"/>
<xsd:element minOccurs="0" name="ReturnOfDocumentIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="UPScarbonneutralIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="CertificateOfOriginIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="PickupOptions" type="rate:PickupOptionsType"/>
<xsd:element minOccurs="0" name="DeliveryOptions" type="rate:DeliveryOptionsType"/>
<xsd:element minOccurs="0" name="RestrictedArticles" type="rate:RestrictedArticlesType"/>
<xsd:element minOccurs="0" name="ShipperExportDeclarationIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="CommercialInvoiceRemovalIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="ImportControl" type="rate:ImportControlType"/>
<xsd:element minOccurs="0" name="ReturnService" type="rate:ReturnServiceType"/>
<xsd:element minOccurs="0" name="SDLShipmentIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="EPRAIndicator" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentServiceOptionsAccessPointCODType">
<xsd:sequence>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ReturnServiceType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element minOccurs="0" name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ImportControlType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element minOccurs="0" name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="RestrictedArticlesType">
<xsd:sequence>
<xsd:element minOccurs="0" name="AlcoholicBeveragesIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="DiagnosticSpecimensIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="PerishablesIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="PlantsIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="SeedsIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="SpecialExceptionsIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="TobaccoIndicator" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PickupOptionsType">
<xsd:sequence>
<xsd:element minOccurs="0" name="LiftGateAtPickupIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="HoldForPickupIndicator" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DeliveryOptionsType">
<xsd:sequence>
<xsd:element minOccurs="0" name="LiftGateAtDeliveryIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="DropOffAtUPSFacilityIndicator" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipperType">
<xsd:sequence>
<xsd:element minOccurs="0" name="Name" type="xsd:string"/>
<xsd:element minOccurs="0" name="ShipperNumber" type="xsd:string"/>
<xsd:element name="Address" type="rate:AddressType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="GuaranteedDeliveryType">
<xsd:sequence>
<xsd:element minOccurs="0" name="BusinessDaysInTransit" type="xsd:string"/>
<xsd:element minOccurs="0" name="DeliveryByTime" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FRSPaymentInfoType">
<xsd:sequence>
<xsd:element name="Type" type="rate:CodeDescriptionType"/>
<xsd:element minOccurs="0" name="AccountNumber" type="xsd:string"/>
<xsd:element minOccurs="0" name="Address" type="rate:PayerAddressType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FreightShipmentInformationType">
<xsd:sequence>
<xsd:element minOccurs="0" name="FreightDensityInfo" type="rate:FreightDensityInfoType"/>
<xsd:element minOccurs="0" name="DensityEligibleIndicator" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PayerAddressType">
<xsd:sequence>
<xsd:element minOccurs="0" name="PostalCode" type="xsd:string"/>
<xsd:element name="CountryCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FreightDensityInfoType">
<xsd:sequence>
<xsd:element minOccurs="0" name="AdjustedHeightIndicator" type="xsd:string"/>
<xsd:element minOccurs="0" name="AdjustedHeight" type="rate:AdjustedHeightType"/>
<xsd:element maxOccurs="unbounded" name="HandlingUnits" type="rate:HandlingUnitsType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AdjustedHeightType">
<xsd:sequence>
<xsd:element name="Value" type="xsd:string"/>
<xsd:element name="UnitOfMeasurement" type="rate:CodeDescriptionType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HandlingUnitsType">
<xsd:sequence>
<xsd:element name="Quantity" type="xsd:string"/>
<xsd:element name="Type" type="rate:CodeDescriptionType"/>
<xsd:element name="Dimensions" type="rate:HandlingUnitsDimensionsType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HandlingUnitsDimensionsType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="rate:CodeDescriptionType"/>
<xsd:element name="Length" type="xsd:string"/>
<xsd:element name="Width" type="xsd:string"/>
<xsd:element name="Height" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="InvoiceLineTotalType">
<xsd:sequence>
<xsd:element minOccurs="0" name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- UPS Ship Service WSDL Release Date Dec 29, 2007 -->
<!-- Copyright 2007-2008 United Parcel Service of America, Inc. All rights reserved. -->
<wsdl:definitions name="Ship" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:error="http://www.ups.com/XMLSchema/XOLTWS/Error/v1.1" xmlns:upss="http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0" xmlns:ship="http://www.ups.com/XMLSchema/XOLTWS/Ship/v1.0" xmlns:tns="http://www.ups.com/WSDL/XOLTWS/Ship/v1.0" targetNamespace="http://www.ups.com/WSDL/XOLTWS/Ship/v1.0">
<wsdl:types>
<xsd:schema>
<!-- This schema defines the UPS Security header used for authorization purposes -->
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0" schemaLocation="UPSSecurity.xsd"/>
<!-- This schema defines the error detail data types returned within SOAPFaults to provide more specific information pertaining to the problem. -->
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/Error/v1.1" schemaLocation="Error1.1.xsd"/>
<!-- This schema defines the Ship service data types -->
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/Ship/v1.0" schemaLocation="ShipWebServiceSchema.xsd"/>
</xsd:schema>
</wsdl:types>
<!-- Ship request/response Message Calls -->
<wsdl:message name="ShipmentRequestMessage">
<wsdl:part name="Body" element="ship:ShipmentRequest"/>
<wsdl:part name="UPSSecurity" element="upss:UPSSecurity"/>
</wsdl:message>
<wsdl:message name="ShipmentResponseMessage">
<wsdl:part name="Body" element="ship:ShipmentResponse"/>
</wsdl:message>
<wsdl:message name="ShipmentErrorMessage">
<wsdl:part name="ShipmentError" element="error:Errors"/>
</wsdl:message>
<wsdl:message name="ShipConfirmRequestMessage">
<wsdl:part name="Body" element="ship:ShipConfirmRequest"/>
<wsdl:part name="UPSSecurity" element="upss:UPSSecurity"/>
</wsdl:message>
<wsdl:message name="ShipConfirmResponseMessage">
<wsdl:part name="Body" element="ship:ShipConfirmResponse"/>
</wsdl:message>
<wsdl:message name="ShipConfirmErrorMessage">
<wsdl:part name="ShipConfirmError" element="error:Errors"/>
</wsdl:message>
<wsdl:message name="ShipAcceptRequestMessage">
<wsdl:part name="Body" element="ship:ShipAcceptRequest"/>
<wsdl:part name="UPSSecurity" element="upss:UPSSecurity"/>
</wsdl:message>
<wsdl:message name="ShipAcceptResponseMessage">
<wsdl:part name="Body" element="ship:ShipAcceptResponse"/>
</wsdl:message>
<wsdl:message name="ShipAcceptErrorMessage">
<wsdl:part name="ShipAcceptError" element="error:Errors"/>
</wsdl:message>
<!-- -->
<!-- Ship Web Service port declaration -->
<wsdl:portType name="ShipPortType">
<wsdl:operation name="ProcessShipment">
<wsdl:input name="ShipmentRequest" message="tns:ShipmentRequestMessage"/>
<wsdl:output name="ShipmentResponse" message="tns:ShipmentResponseMessage"/>
<wsdl:fault name="ShipmentError" message="tns:ShipmentErrorMessage"/>
</wsdl:operation>
<wsdl:operation name="ProcessShipConfirm">
<wsdl:input name="ShipConfirmRequest" message="tns:ShipConfirmRequestMessage"/>
<wsdl:output name="ShipConfirmResponse" message="tns:ShipConfirmResponseMessage"/>
<wsdl:fault name="ShipConfirmError" message="tns:ShipConfirmErrorMessage"/>
</wsdl:operation>
<wsdl:operation name="ProcessShipAccept">
<wsdl:input name="ShipAcceptRequest" message="tns:ShipAcceptRequestMessage"/>
<wsdl:output name="ShipAcceptResponse" message="tns:ShipAcceptResponseMessage"/>
<wsdl:fault name="ShipAcceptError" message="tns:ShipAcceptErrorMessage"/>
</wsdl:operation>
</wsdl:portType>
<!-- Ship Web Service binding -->
<wsdl:binding name="ShipBinding" type="tns:ShipPortType">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="ProcessShipment">
<soap:operation soapAction="http://onlinetools.ups.com/webservices/ShipBinding/v1.0" style="document"/>
<wsdl:input name="ShipmentRequest">
<soap:body parts="Body" use="literal"/>
<soap:header message="tns:ShipmentRequestMessage" part="UPSSecurity" use="literal">
<soap:headerfault message="tns:ShipmentErrorMessage" part="ShipmentError" use="literal"/>
</soap:header>
</wsdl:input>
<wsdl:output name="ShipmentResponse">
<soap:body parts="Body" use="literal"/>
</wsdl:output>
<wsdl:fault name="ShipmentError">
<soap:fault name="ShipmentError" use="literal"/>
</wsdl:fault>
</wsdl:operation>
<wsdl:operation name="ProcessShipConfirm">
<soap:operation soapAction="http://onlinetools.ups.com/webservices/ShipBinding/v1.0" style="document"/>
<wsdl:input name="ShipConfirmRequest">
<soap:body parts="Body" use="literal"/>
<soap:header message="tns:ShipConfirmRequestMessage" part="UPSSecurity" use="literal">
<soap:headerfault message="tns:ShipConfirmErrorMessage" part="ShipConfirmError" use="literal"/>
</soap:header>
</wsdl:input>
<wsdl:output name="ShipConfirmResponse">
<soap:body parts="Body" use="literal"/>
</wsdl:output>
<wsdl:fault name="ShipConfirmError">
<soap:fault name="ShipConfirmError" use="literal"/>
</wsdl:fault>
</wsdl:operation>
<wsdl:operation name="ProcessShipAccept">
<soap:operation soapAction="http://onlinetools.ups.com/webservices/ShipBinding/v1.0" style="document"/>
<wsdl:input name="ShipAcceptRequest">
<soap:body parts="Body" use="literal"/>
<soap:header message="tns:ShipAcceptRequestMessage" part="UPSSecurity" use="literal">
<soap:headerfault message="tns:ShipAcceptErrorMessage" part="ShipAcceptError" use="literal"/>
</soap:header>
</wsdl:input>
<wsdl:output name="ShipAcceptResponse">
<soap:body parts="Body" use="literal"/>
</wsdl:output>
<wsdl:fault name="ShipAcceptError">
<soap:fault name="ShipAcceptError" use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
<!-- Ship Web Service-->
<wsdl:service name="ShipService">
<wsdl:port name="ShipPort" binding="tns:ShipBinding">
<!-- Production URL -->
<!-- <soap:address location="https://onlinetools.ups.com/webservices/Ship"/> -->
<!-- CIE (Customer Integration Environment) URL -->
<soap:address location="https://wwwcie.ups.com/webservices/Ship"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

View File

@@ -0,0 +1,865 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema targetNamespace="http://www.ups.com/XMLSchema/XOLTWS/Ship/v1.0" xmlns:ups="http://www.ups.com/XMLSchema" xmlns:ship="http://www.ups.com/XMLSchema/XOLTWS/Ship/v1.0" xmlns:ifs="http://www.ups.com/XMLSchema/XOLTWS/IF/v1.0" xmlns:common="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" version="201707">
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0" schemaLocation="common.xsd"/>
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/IF/v1.0" schemaLocation="IFWS.xsd"/>
<xsd:element name="ShipmentRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="common:Request"/>
<xsd:element name="Shipment" type="ship:ShipmentType"/>
<xsd:element name="LabelSpecification" type="ship:LabelSpecificationType" minOccurs="0"/>
<xsd:element name="ReceiptSpecification" type="ship:ReceiptSpecificationType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="ShipConfirmRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="common:Request"/>
<xsd:element name="Shipment" type="ship:ShipmentType"/>
<xsd:element name="LabelSpecification" type="ship:LabelSpecificationType" minOccurs="0"/>
<xsd:element name="ReceiptSpecification" type="ship:ReceiptSpecificationType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="ShipAcceptRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="common:Request"/>
<xsd:element name="ShipmentDigest" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="ShipmentResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="common:Response"/>
<xsd:element name="ShipmentResults" type="ship:ShipmentResultsType"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="ShipConfirmResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="common:Response"/>
<xsd:element name="ShipmentResults" type="ship:ShipmentResultsType"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="ShipAcceptResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="common:Response"/>
<xsd:element name="ShipmentResults" type="ship:ShipmentResultsType"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="ShipmentType">
<xsd:sequence>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
<xsd:element name="ReturnService" type="ship:ReturnServiceType" minOccurs="0"/>
<xsd:element name="DocumentsOnlyIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="Shipper" type="ship:ShipperType"/>
<xsd:element name="ShipTo" type="ship:ShipToType"/>
<xsd:element name="AlternateDeliveryAddress" type="ship:AlternateDeliveryAddressType" minOccurs="0"/>
<xsd:element name="ShipFrom" type="ship:ShipFromType" minOccurs="0"/>
<xsd:element name="PaymentInformation" type="ship:PaymentInfoType" minOccurs="0"/>
<xsd:element name="FRSPaymentInformation" type="ship:FRSPaymentInfoType" minOccurs="0"/>
<xsd:element name="FreightShipmentInformation" type="ship:FreightShipmentInformationType" minOccurs="0"/>
<xsd:element name="GoodsNotInFreeCirculationIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="ShipmentRatingOptions" type="ship:RateInfoType" minOccurs="0"/>
<xsd:element name="MovementReferenceNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="ReferenceNumber" type="ship:ReferenceNumberType" minOccurs="0" maxOccurs="2"/>
<xsd:element name="Service" type="ship:ServiceType"/>
<xsd:element name="InvoiceLineTotal" type="ship:CurrencyMonetaryType" minOccurs="0"/>
<xsd:element name="NumOfPiecesInShipment" type="xsd:string" minOccurs="0"/>
<xsd:element name="USPSEndorsement" type="xsd:string" minOccurs="0"/>
<xsd:element name="MILabelCN22Indicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="SubClassification" type="xsd:string" minOccurs="0"/>
<xsd:element name="CostCenter" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackageID" type="xsd:string" minOccurs="0"/>
<xsd:element name="IrregularIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="ShipmentIndicationType" type="ship:IndicationType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="MIDualReturnShipmentKey" type="xsd:string" minOccurs="0"/>
<xsd:element name="MIDualReturnShipmentIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="RatingMethodRequestedIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="TaxInformationIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="PromotionalDiscountInformation" type="ship:PromotionalDiscountInformationType" minOccurs="0"/>
<xsd:element name="ShipmentServiceOptions" minOccurs="0">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="ship:ShipmentServiceOptionsType"/>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="Package" type="ship:PackageType" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PromotionalDiscountInformationType">
<xsd:sequence>
<xsd:element name="PromoCode" type="xsd:string"/>
<xsd:element name="PromoAliasCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ReturnServiceType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipperType">
<xsd:complexContent>
<xsd:extension base="ship:CompanyInfoType">
<xsd:sequence>
<xsd:element name="ShipperNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="FaxNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="EMailAddress" type="xsd:string" minOccurs="0"/>
<xsd:element name="Address" type="ship:ShipAddressType"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="CompanyInfoType">
<xsd:sequence>
<xsd:element name="Name" type="xsd:string"/>
<xsd:element name="AttentionName" type="xsd:string" minOccurs="0"/>
<xsd:element name="CompanyDisplayableName" type="xsd:string" minOccurs="0"/>
<xsd:element name="TaxIdentificationNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="TaxIDType" type="ship:TaxIDCodeDescType" minOccurs="0"/>
<xsd:element name="Phone" type="ship:ShipPhoneType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipPhoneType">
<xsd:sequence>
<xsd:element name="Number" type="xsd:string"/>
<xsd:element name="Extension" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipAddressType">
<xsd:sequence>
<xsd:element name="AddressLine" type="xsd:string" maxOccurs="3"/>
<xsd:element name="City" type="xsd:string"/>
<xsd:element name="StateProvinceCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="PostalCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="CountryCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipToType">
<xsd:complexContent>
<xsd:extension base="ship:CompanyInfoType">
<xsd:sequence>
<xsd:element name="FaxNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="EMailAddress" type="xsd:string" minOccurs="0"/>
<xsd:element name="Address" type="ship:ShipToAddressType"/>
<xsd:element name="LocationID" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="ShipToAddressType">
<xsd:complexContent>
<xsd:extension base="ship:ShipAddressType">
<xsd:sequence>
<xsd:element name="ResidentialAddressIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="ShipFromType">
<xsd:complexContent>
<xsd:extension base="ship:CompanyInfoType">
<xsd:sequence>
<xsd:element name="FaxNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="Address" type="ship:ShipAddressType"/>
<xsd:element name="EMailAddress" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="PrepaidType">
<xsd:sequence>
<xsd:element name="BillShipper" type="ship:BillShipperType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BillShipperType">
<xsd:sequence>
<xsd:element name="AccountNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="CreditCard" type="ship:CreditCardType" minOccurs="0"/>
<xsd:element name="AlternatePaymentMethod" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CreditCardType">
<xsd:sequence>
<xsd:element name="Type" type="xsd:string"/>
<xsd:element name="Number" type="xsd:string"/>
<xsd:element name="ExpirationDate" type="xsd:string"/>
<xsd:element name="SecurityCode" type="xsd:string"/>
<xsd:element name="Address" type="ship:CreditCardAddressType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CreditCardAddressType">
<xsd:sequence>
<xsd:element name="AddressLine" type="xsd:string" maxOccurs="3"/>
<xsd:element name="City" type="xsd:string"/>
<xsd:element name="StateProvinceCode" type="xsd:string"/>
<xsd:element name="PostalCode" type="xsd:string"/>
<xsd:element name="CountryCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BillThirdPartyChargeType">
<xsd:sequence>
<xsd:element name="AccountNumber" type="xsd:string"/>
<xsd:element name="Address" type="ship:AccountAddressType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AccountAddressType">
<xsd:sequence>
<xsd:element name="PostalCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="CountryCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FreightCollectType">
<xsd:sequence>
<xsd:element name="BillReceiver" type="ship:BillReceiverType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BillReceiverType">
<xsd:sequence>
<xsd:element name="AccountNumber" type="xsd:string"/>
<xsd:element name="Address" type="ship:BillReceiverAddressType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BillReceiverAddressType">
<xsd:sequence>
<xsd:element name="PostalCode" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PaymentInfoType">
<xsd:sequence>
<xsd:element name="ShipmentCharge" type="ship:ShipmentChargeType" maxOccurs="2"/>
<xsd:element name="SplitDutyVATIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentChargeType">
<xsd:sequence>
<xsd:element name="Type" type="xsd:string"/>
<xsd:element name="BillShipper" type="ship:BillShipperType" minOccurs="0"/>
<xsd:element name="BillReceiver" type="ship:BillReceiverType" minOccurs="0"/>
<xsd:element name="BillThirdParty" type="ship:BillThirdPartyChargeType" minOccurs="0"/>
<xsd:element name="ConsigneeBilledIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FRSPaymentInfoType">
<xsd:sequence>
<xsd:element name="Type" type="ship:PaymentType"/>
<xsd:element name="AccountNumber" type="xsd:string"/>
<xsd:element name="Address" type="ship:AccountAddressType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PaymentType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="RateInfoType">
<xsd:sequence>
<xsd:element name="NegotiatedRatesIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="FRSShipmentIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="RateChartIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="TPFCNegotiatedRatesIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="UserLevelDiscountIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ReferenceNumberType">
<xsd:sequence>
<xsd:element name="BarCodeIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="Code" type="xsd:string" minOccurs="0"/>
<xsd:element name="Value" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ServiceType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CurrencyMonetaryType">
<xsd:sequence>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentServiceOptionsType">
<xsd:sequence>
<xsd:element name="SaturdayDeliveryIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="SaturdayPickupIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="COD" type="ship:CODType" minOccurs="0"/>
<xsd:element name="AccessPointCOD" type="ship:ShipmentServiceOptionsAccessPointCODType" minOccurs="0"/>
<xsd:element name="DeliverToAddresseeOnlyIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="DirectDeliveryOnlyIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="Notification" type="ship:NotificationType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="LabelDelivery" type="ship:LabelDeliveryType" minOccurs="0"/>
<xsd:element name="InternationalForms" type="ifs:InternationalFormType" minOccurs="0"/>
<xsd:element name="DeliveryConfirmation" type="ship:DeliveryConfirmationType" minOccurs="0"/>
<xsd:element name="ReturnOfDocumentIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="ImportControlIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="LabelMethod" type="ship:LabelMethodType" minOccurs="0"/>
<xsd:element name="CommercialInvoiceRemovalIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="UPScarbonneutralIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="PreAlertNotification" type="ship:PreAlertNotificationType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="ExchangeForwardIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="HoldForPickupIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="DropoffAtUPSFacilityIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="LiftGateForPickUpIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="LiftGateForDeliveryIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="SDLShipmentIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="EPRAReleaseCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="RestrictedArticles" type="ship:RestrictedArticlesType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="RestrictedArticlesType">
<xsd:sequence>
<xsd:element name="DiagnosticSpecimensIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PreAlertNotificationType">
<xsd:sequence>
<xsd:element name="EMailMessage" type="ship:PreAlertEMailMessageType" minOccurs="0"/>
<xsd:element name="VoiceMessage" type="ship:PreAlertVoiceMessageType" minOccurs="0"/>
<xsd:element name="TextMessage" type="ship:PreAlertTextMessageType" minOccurs="0"/>
<xsd:element name="Locale" type="ship:LocaleType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PreAlertEMailMessageType">
<xsd:sequence>
<xsd:element name="EMailAddress" type="xsd:string"/>
<xsd:element name="UndeliverableEMailAddress" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LocaleType">
<xsd:sequence>
<xsd:element name="Language" type="xsd:string"/>
<xsd:element name="Dialect" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PreAlertVoiceMessageType">
<xsd:sequence>
<xsd:element name="PhoneNumber" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PreAlertTextMessageType">
<xsd:sequence>
<xsd:element name="PhoneNumber" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ContactInfoType">
<xsd:sequence>
<xsd:element name="Name" type="xsd:string" minOccurs="0"/>
<xsd:element name="Phone" type="ship:ShipPhoneType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CODType">
<xsd:sequence>
<xsd:element name="CODFundsCode" type="xsd:string"/>
<xsd:element name="CODAmount" type="ship:CurrencyMonetaryType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentServiceOptionsAccessPointCODType">
<xsd:sequence>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="NotificationType">
<xsd:sequence>
<xsd:element name="NotificationCode" type="xsd:string"/>
<xsd:element name="EMail" type="ship:EmailDetailsType"/>
<xsd:element name="VoiceMessage" type="ship:ShipmentServiceOptionsNotificationVoiceMessageType" minOccurs="0"/>
<xsd:element name="TextMessage" type="ship:ShipmentServiceOptionsNotificationTextMessageType" minOccurs="0"/>
<xsd:element name="Locale" type="ship:LocaleType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LabelDeliveryType">
<xsd:sequence>
<xsd:element name="EMail" type="ship:EmailDetailsType" minOccurs="0"/>
<xsd:element name="LabelLinksIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="EmailDetailsType">
<xsd:sequence>
<xsd:element name="EMailAddress" type="xsd:string" maxOccurs="unbounded"/>
<xsd:element name="UndeliverableEMailAddress" type="xsd:string" minOccurs="0"/>
<xsd:element name="FromEMailAddress" type="xsd:string" minOccurs="0"/>
<xsd:element name="FromName" type="xsd:string" minOccurs="0"/>
<xsd:element name="Memo" type="xsd:string" minOccurs="0"/>
<xsd:element name="Subject" type="xsd:string" minOccurs="0"/>
<xsd:element name="SubjectCode" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageType">
<xsd:sequence>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
<xsd:element name="Packaging" type="ship:PackagingType" minOccurs="0"/>
<xsd:element name="Dimensions" type="ship:DimensionsType" minOccurs="0"/>
<xsd:element name="DimWeight" type="ship:PackageWeightType" minOccurs="0"/>
<xsd:element name="PackageWeight" type="ship:PackageWeightType" minOccurs="0"/>
<xsd:element name="LargePackageIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="ReferenceNumber" type="ship:ReferenceNumberType" minOccurs="0" maxOccurs="2"/>
<xsd:element name="AdditionalHandlingIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackageServiceOptions" type="ship:PackageServiceOptionsType" minOccurs="0"/>
<xsd:element name="Commodity" type="ship:CommodityType" minOccurs="0"/>
<xsd:element name="HazMatPackageInformation" type="ship:HazMatPackageInformationType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackagingType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DimensionsType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="ship:ShipUnitOfMeasurementType"/>
<xsd:element name="Length" type="xsd:string"/>
<xsd:element name="Width" type="xsd:string"/>
<xsd:element name="Height" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipUnitOfMeasurementType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageWeightType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="ship:ShipUnitOfMeasurementType"/>
<xsd:element name="Weight" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageServiceOptionsType">
<xsd:sequence>
<xsd:element name="DeliveryConfirmation" type="ship:DeliveryConfirmationType" minOccurs="0"/>
<xsd:element name="DeclaredValue" type="ship:PackageDeclaredValueType" minOccurs="0"/>
<xsd:element name="COD" type="ship:PSOCODType" minOccurs="0"/>
<xsd:element name="AccessPointCOD" type="ship:PackageServiceOptionsAccessPointCODType" minOccurs="0"/>
<xsd:element name="VerbalConfirmation" type="ship:VerbalConfirmationType" minOccurs="0"/>
<xsd:element name="ShipperReleaseIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="Notification" type="ship:PSONotificationType" minOccurs="0"/>
<xsd:element name="HazMat" type="ship:HazMatType" minOccurs="0" maxOccurs="3"/>
<xsd:element name="DryIce" type="ship:DryIceType" minOccurs="0"/>
<xsd:element name="UPSPremiumCareIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="ProactiveIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackageIdentifier" type="xsd:string" minOccurs="0"/>
<xsd:element name="ClinicalTrialsID" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageDeclaredValueType">
<xsd:sequence>
<xsd:element name="Type" type="ship:DeclaredValueType" minOccurs="0"/>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DeclaredValueType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DeliveryConfirmationType">
<xsd:sequence>
<xsd:element name="DCISType" type="xsd:string"/>
<xsd:element name="DCISNumber" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LabelMethodType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="VerbalConfirmationType">
<xsd:sequence>
<xsd:element name="ContactInfo" type="ship:ContactInfoType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PSOCODType">
<xsd:sequence>
<xsd:element name="CODFundsCode" type="xsd:string"/>
<xsd:element name="CODAmount" type="ship:CurrencyMonetaryType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageServiceOptionsAccessPointCODType">
<xsd:sequence>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PSONotificationType">
<xsd:sequence>
<xsd:element name="NotificationCode" type="xsd:string"/>
<xsd:element name="EMail" type="ship:EmailDetailsType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LabelSpecificationType">
<xsd:sequence>
<xsd:element name="LabelImageFormat" type="ship:LabelImageFormatType"/>
<xsd:element name="HTTPUserAgent" type="xsd:string" minOccurs="0"/>
<xsd:element name="LabelStockSize" type="ship:LabelStockSizeType" minOccurs="0"/>
<xsd:element name="Instruction" type="ship:InstructionCodeDescriptionType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="CharacterSet" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="InstructionCodeDescriptionType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LabelImageFormatType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LabelStockSizeType">
<xsd:sequence>
<xsd:element name="Height" type="xsd:string"/>
<xsd:element name="Width" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CommodityType">
<xsd:sequence>
<xsd:element name="FreightClass" type="xsd:string"/>
<xsd:element name="NMFC" type="ship:NMFCType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="NMFCType">
<xsd:sequence>
<xsd:element name="PrimeCode" type="xsd:string"/>
<xsd:element name="SubCode" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentResultsType">
<xsd:sequence>
<xsd:element name="Disclaimer" type="ship:DisclaimerType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="ShipmentCharges" type="ship:ShipmentChargesType" minOccurs="0"/>
<xsd:element name="NegotiatedRateCharges" type="ship:NegotiatedRateChargesType" minOccurs="0"/>
<xsd:element name="FRSShipmentData" type="ship:FRSShipmentDataType" minOccurs="0"/>
<xsd:element name="RatingMethod" type="xsd:string" minOccurs="0"/>
<xsd:element name="BillableWeightCalculationMethod" type="xsd:string" minOccurs="0"/>
<xsd:element name="BillingWeight" type="ship:BillingWeightType"/>
<xsd:element name="ShipmentIdentificationNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="MIDualReturnShipmentKey" type="xsd:string" minOccurs="0"/>
<xsd:element name="ShipmentDigest" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackageResults" type="ship:PackageResultsType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="ControlLogReceipt" type="ship:ImageType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="Form" type="ship:FormType" minOccurs="0"/>
<xsd:element name="CODTurnInPage" type="ship:SCReportType" minOccurs="0"/>
<xsd:element name="HighValueReport" type="ship:HighValueReportType" minOccurs="0"/>
<xsd:element name="LabelURL" type="xsd:string" minOccurs="0"/>
<xsd:element name="LocalLanguageLabelURL" type="xsd:string" minOccurs="0"/>
<xsd:element name="ReceiptURL" type="xsd:string" minOccurs="0"/>
<xsd:element name="LocalLanguageReceiptURL" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DisclaimerType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentChargesType">
<xsd:sequence>
<xsd:element name="RateChart" type="xsd:string" minOccurs="0"/>
<xsd:element name="BaseServiceCharge" type="ship:ShipChargeType" minOccurs="0"/>
<xsd:element name="TransportationCharges" type="ship:ShipChargeType"/>
<xsd:element name="ItemizedCharges" type="ship:ShipChargeType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="ServiceOptionsCharges" type="ship:ShipChargeType"/>
<xsd:element name="TaxCharges" type="ship:TaxChargeType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="TotalCharges" type="ship:ShipChargeType"/>
<xsd:element name="TotalChargesWithTaxes" type="ship:ShipChargeType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="NegotiatedRateChargesType">
<xsd:sequence>
<xsd:element name="ItemizedCharges" type="ship:ShipChargeType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="TaxCharges" type="ship:TaxChargeType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="TotalCharge" type="ship:ShipChargeType" minOccurs="0"/>
<xsd:element name="TotalChargesWithTaxes" type="ship:ShipChargeType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipChargeType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string" minOccurs="0"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
<xsd:element name="CurrencyCode" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string"/>
<xsd:element name="SubType" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TaxChargeType">
<xsd:sequence>
<xsd:element name="Type" type="xsd:string"/>
<xsd:element name="MonetaryValue" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FRSShipmentDataType">
<xsd:sequence>
<xsd:element name="TransportationCharges" type="ship:TransportationChargeType"/>
<xsd:element name="FreightDensityRate" type="ship:FreightDensityRateType" minOccurs="0"/>
<xsd:element name="HandlingUnits" type="ship:HandlingUnitsResponseType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TransportationChargeType">
<xsd:sequence>
<xsd:element name="GrossCharge" type="ship:ShipChargeType"/>
<xsd:element name="DiscountAmount" type="ship:ShipChargeType"/>
<xsd:element name="DiscountPercentage" type="xsd:string"/>
<xsd:element name="NetCharge" type="ship:ShipChargeType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BillingWeightType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="ship:BillingUnitOfMeasurementType"/>
<xsd:element name="Weight" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="BillingUnitOfMeasurementType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="PackageResultsType">
<xsd:sequence>
<xsd:element name="TrackingNumber" type="xsd:string"/>
<xsd:element name="BaseServiceCharge" type="ship:ShipChargeType" minOccurs="0"/>
<xsd:element name="ServiceOptionsCharges" type="ship:ShipChargeType" minOccurs="0"/>
<xsd:element name="ShippingLabel" type="ship:LabelType" minOccurs="0"/>
<xsd:element name="ShippingReceipt" type="ship:ReceiptType" minOccurs="0"/>
<xsd:element name="USPSPICNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="CN22Number" type="xsd:string" minOccurs="0"/>
<xsd:element name="Accessorial" type="ship:AccessorialType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="Form" type="ship:FormType" minOccurs="0"/>
<xsd:element name="ItemizedCharges" type="ship:ShipChargeType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="NegotiatedCharges" type="ship:NegotiatedChargesType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AccessorialType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LabelType">
<xsd:complexContent>
<xsd:extension base="ship:ImageType">
<xsd:sequence>
<xsd:element name="InternationalSignatureGraphicImage" type="xsd:string" minOccurs="0"/>
<xsd:element name="HTMLImage" type="xsd:string" minOccurs="0"/>
<xsd:element name="PDF417" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="ReceiptType">
<xsd:complexContent>
<xsd:extension base="ship:ImageType"/>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="ImageType">
<xsd:sequence>
<xsd:element name="ImageFormat" type="ship:ImageFormatType"/>
<xsd:element name="GraphicImage" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FormType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string"/>
<xsd:element name="Image" type="ship:FormImageType" minOccurs="0"/>
<xsd:element name="FormGroupId" type="xsd:string" minOccurs="0"/>
<xsd:element name="FormGroupIdName" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FormImageType">
<xsd:sequence>
<xsd:element name="ImageFormat" type="ship:ImageFormatType" minOccurs="0"/>
<xsd:element name="GraphicImage" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ImageFormatType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="SCReportType">
<xsd:sequence>
<xsd:element name="Image" type="ship:ImageType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HighValueReportType">
<xsd:sequence>
<xsd:element name="Image" type="ship:ImageType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HazMatPackageInformationType">
<xsd:sequence>
<xsd:element name="AllPackedInOneIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="OverPackedIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="QValue" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HazMatType">
<xsd:sequence>
<xsd:element name="PackagingTypeQuantity" type="xsd:string" minOccurs="0"/>
<xsd:element name="RecordIdentifier1" type="xsd:string" minOccurs="0"/>
<xsd:element name="RecordIdentifier2" type="xsd:string" minOccurs="0"/>
<xsd:element name="RecordIdentifier3" type="xsd:string" minOccurs="0"/>
<xsd:element name="SubRiskClass" type="xsd:string" minOccurs="0"/>
<xsd:element name="aDRItemNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="aDRPackingGroupLetter" type="xsd:string" minOccurs="0"/>
<xsd:element name="TechnicalName" type="xsd:string" minOccurs="0"/>
<xsd:element name="HazardLabelRequired" type="xsd:string" minOccurs="0"/>
<xsd:element name="ClassDivisionNumber" type="xsd:string"/>
<xsd:element name="ReferenceNumber" type="xsd:string" minOccurs="0"/>
<xsd:element name="Quantity" type="xsd:string"/>
<xsd:element name="UOM" type="xsd:string"/>
<xsd:element name="PackagingType" type="xsd:string"/>
<xsd:element name="IDNumber" type="xsd:string"/>
<xsd:element name="ProperShippingName" type="xsd:string"/>
<xsd:element name="AdditionalDescription" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackagingGroupType" type="xsd:string" minOccurs="0"/>
<xsd:element name="PackagingInstructionCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="EmergencyPhone" type="xsd:string" minOccurs="0"/>
<xsd:element name="EmergencyContact" type="xsd:string" minOccurs="0"/>
<xsd:element name="ReportableQuantity" type="xsd:string" minOccurs="0"/>
<xsd:element name="RegulationSet" type="xsd:string"/>
<xsd:element name="TransportationMode" type="xsd:string"/>
<xsd:element name="CommodityRegulatedLevelCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="TransportCategory" type="xsd:string" minOccurs="0"/>
<xsd:element name="TunnelRestrictionCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="ChemicalRecordIdentifier" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DryIceType">
<xsd:sequence>
<xsd:element name="RegulationSet" type="xsd:string"/>
<xsd:element name="DryIceWeight" type="ship:DryIceWeightType"/>
<xsd:element name="MedicalUseIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DryIceWeightType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="ship:ShipUnitOfMeasurementType"/>
<xsd:element name="Weight" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ReceiptSpecificationType">
<xsd:sequence>
<xsd:element name="ImageFormat" type="ship:ReceiptImageFormatType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ReceiptImageFormatType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TaxIDCodeDescType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="IndicationType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AlternateDeliveryAddressType">
<xsd:sequence>
<xsd:element name="Name" type="xsd:string" minOccurs="0"/>
<xsd:element name="AttentionName" type="xsd:string" minOccurs="0"/>
<xsd:element name="UPSAccessPointID" type="xsd:string" minOccurs="0"/>
<xsd:element name="Address" type="ship:ADLAddressType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentServiceOptionsNotificationVoiceMessageType">
<xsd:sequence>
<xsd:element name="PhoneNumber" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ShipmentServiceOptionsNotificationTextMessageType">
<xsd:sequence>
<xsd:element name="PhoneNumber" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ADLAddressType">
<xsd:sequence>
<xsd:element name="AddressLine" type="xsd:string" maxOccurs="3"/>
<xsd:element name="City" type="xsd:string"/>
<xsd:element name="StateProvinceCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="PostalCode" type="xsd:string" minOccurs="0"/>
<xsd:element name="CountryCode" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FreightShipmentInformationType">
<xsd:sequence>
<xsd:element name="FreightDensityInfo" type="ship:FreightDensityInfoType" minOccurs="0"/>
<xsd:element name="DensityEligibleIndicator" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HandlingUnitsType">
<xsd:sequence>
<xsd:element name="Quantity" type="xsd:string"/>
<xsd:element name="Type" type="ship:ShipUnitOfMeasurementType"/>
<xsd:element name="Dimensions" type="ship:HandlingUnitsDimensionsType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HandlingUnitsResponseType">
<xsd:sequence>
<xsd:element name="Quantity" type="xsd:string"/>
<xsd:element name="Type" type="ship:ShipUnitOfMeasurementType"/>
<xsd:element name="Dimensions" type="ship:HandlingUnitsDimensionsType"/>
<xsd:element name="AdjustedHeight" type="ship:AdjustedHeightType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="HandlingUnitsDimensionsType">
<xsd:sequence>
<xsd:element name="UnitOfMeasurement" type="ship:ShipUnitOfMeasurementType"/>
<xsd:element name="Length" type="xsd:string"/>
<xsd:element name="Width" type="xsd:string"/>
<xsd:element name="Height" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FreightDensityRateType">
<xsd:sequence>
<xsd:element name="Density" type="xsd:string" minOccurs="0"/>
<xsd:element name="TotalCubicFeet" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="FreightDensityInfoType">
<xsd:sequence>
<xsd:element name="AdjustedHeightIndicator" type="xsd:string" minOccurs="0"/>
<xsd:element name="AdjustedHeight" type="ship:AdjustedHeightType" minOccurs="0"/>
<xsd:element name="HandlingUnits" type="ship:HandlingUnitsType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AdjustedHeightType">
<xsd:sequence>
<xsd:element name="Value" type="xsd:string"/>
<xsd:element name="UnitOfMeasurement" type="ship:ShipUnitOfMeasurementType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="NegotiatedChargesType">
<xsd:sequence>
<xsd:element name="ItemizedCharges" type="ship:ShipChargeType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,23 @@
<xsd:schema targetNamespace="http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0" xmlns:upss="http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xsd:element name="UPSSecurity">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="UsernameToken">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Username" type="xsd:string"/>
<xsd:element name="Password" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="ServiceAccessToken">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="AccessLicenseNumber" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- UPS Void Shipment Service WSDL Release Date Mar 11, 2008 -->
<!-- Copyright 2007-2008 United Parcel Service of America, Inc. All rights reserved. -->
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:error="http://www.ups.com/XMLSchema/XOLTWS/Error/v1.1" xmlns:upss="http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0" xmlns:common="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0" xmlns:void="http://www.ups.com/XMLSchema/XOLTWS/Void/v1.1" xmlns:tns="http://www.ups.com/WSDL/XOLTWS/Void/v1.1" targetNamespace="http://www.ups.com/WSDL/XOLTWS/Void/v1.1">
<wsdl:types>
<xsd:schema>
<!-- This schema defines the UPS Security header used for authorization purposes -->
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0" schemaLocation="UPSSecurity.xsd"/>
<!-- This schema defines the error detail data types returned within SOAPFaults to provide more specific information pertaining to the problem. -->
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/Error/v1.1" schemaLocation="Error1.1.xsd"/>
<!-- This schema defines the Void Shipment service data types -->
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/Void/v1.1" schemaLocation="VoidWebServiceSchema.xsd"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="VoidRequestMessage">
<wsdl:part name="Body" element="void:VoidShipmentRequest"/>
<wsdl:part name="UPSSecurity" element="upss:UPSSecurity"/>
</wsdl:message>
<wsdl:message name="VoidResponseMessage">
<wsdl:part name="Body" element="void:VoidShipmentResponse"/>
</wsdl:message>
<wsdl:message name="VoidErrorMessage">
<wsdl:part name="VoidError" element="error:Errors"/>
</wsdl:message>
<wsdl:portType name="VoidPortType">
<wsdl:operation name="ProcessVoid">
<wsdl:input name="VoidShipmentRequest" message="tns:VoidRequestMessage"/>
<wsdl:output name="VoidShipmentResponse" message="tns:VoidResponseMessage"/>
<wsdl:fault name="VoidError" message="tns:VoidErrorMessage"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="VoidBinding" type="tns:VoidPortType">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="ProcessVoid">
<soap:operation soapAction="http://onlinetools.ups.com/webservices/VoidBinding/v1.1" style="document"/>
<wsdl:input name="VoidShipmentRequest">
<soap:body parts="Body" use="literal"/>
<soap:header message="tns:VoidRequestMessage" part="UPSSecurity" use="literal">
<soap:headerfault message="tns:VoidErrorMessage" part="VoidError" use="literal"/>
</soap:header>
</wsdl:input>
<wsdl:output name="VoidShipmentResponse">
<soap:body parts="Body" use="literal"/>
</wsdl:output>
<wsdl:fault name="VoidError">
<soap:fault name="VoidError" use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="VoidService">
<wsdl:port name="VoidPort" binding="tns:VoidBinding">
<!-- Production URL -->
<!-- <soap:address location="https://onlinetools.ups.com/webservices/Void"/> -->
<!-- CIE (Customer Integration Environment) URL -->
<soap:address location="https://wwwcie.ups.com/webservices/Void"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

View File

@@ -0,0 +1,45 @@
<xsd:schema targetNamespace="http://www.ups.com/XMLSchema/XOLTWS/Void/v1.1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:void="http://www.ups.com/XMLSchema/XOLTWS/Void/v1.1" xmlns:common="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0" elementFormDefault="qualified" version="201601">
<xsd:import namespace="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0" schemaLocation="common.xsd"/>
<xsd:element name="VoidShipmentRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="common:Request"/>
<xsd:element name="VoidShipment">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="ShipmentIdentificationNumber" type="xsd:string"/>
<xsd:element name="TrackingNumber" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="VoidShipmentResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="common:Response"/>
<xsd:element name="SummaryResult">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Status" type="void:CodeDescriptionType"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="PackageLevelResult" type="void:PackageLevelResult" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="PackageLevelResult">
<xsd:sequence>
<xsd:element name="TrackingNumber" type="xsd:string"/>
<xsd:element name="Status" type="void:CodeDescriptionType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CodeDescriptionType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema targetNamespace="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ups="http://www.ups.com/XMLSchema" xmlns:common="http://www.ups.com/XMLSchema/XOLTWS/Common/v1.0" elementFormDefault="qualified" version="201707">
<xsd:element name="Request" type="common:RequestType"/>
<xsd:element name="Response" type="common:ResponseType"/>
<xsd:element name="ClientInformation" type="common:ClientInformationType"/>
<xsd:complexType name="ClientInformationType">
<xsd:sequence>
<xsd:element name="Property" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="Key" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="RequestType">
<xsd:sequence>
<xsd:element name="RequestOption" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="SubVersion" type="xsd:string" minOccurs="0"/>
<xsd:element name="TransactionReference" type="common:TransactionReferenceType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TransactionReferenceType">
<xsd:sequence>
<xsd:element name="CustomerContext" type="xsd:string" minOccurs="0"/>
<xsd:element name="TransactionIdentifier" type="xsd:string" minOccurs="0" ups:usage="notused"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ResponseType">
<xsd:sequence>
<xsd:element name="ResponseStatus" type="common:CodeDescriptionType"/>
<xsd:element name="Alert" type="common:CodeDescriptionType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="AlertDetail" type="common:DetailType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="TransactionReference" type="common:TransactionReferenceType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CodeDescriptionType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="DetailType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Description" type="xsd:string"/>
<xsd:element name="ElementLevelInformation" type="common:ElementLevelInformationType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ElementLevelInformationType">
<xsd:sequence>
<xsd:element name="Level" type="xsd:string"/>
<xsd:element name="ElementIdentifier" type="common:ElementIdentifierType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ElementIdentifierType">
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/>
<xsd:element name="Value" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

View File

@@ -0,0 +1 @@
from . import request

View File

@@ -0,0 +1,488 @@
import requests
import re
import base64
import io
# activate PDF support in PIL
import PIL.PdfImagePlugin # pylint: disable=W0611
from PIL import Image
from json.decoder import JSONDecodeError
from requests.exceptions import RequestException
from odoo import _
from odoo.exceptions import ValidationError
from odoo.tools import float_repr
from odoo.tools.urls import urljoin as url_join
TEST_BASE_URL = "https://wwwcie.ups.com"
PROD_BASE_URL = "https://onlinetools.ups.com"
API_VERSION = "v1"
TOKEN_TYPE = "Bearer"
class UPSRequest:
def __init__(self, carrier):
super_carrier = carrier.sudo()
self.logger = carrier.log_xml
self.base_url = PROD_BASE_URL if carrier.prod_environment else TEST_BASE_URL
self.access_token = super_carrier.ups_access_token
self.client_id = super_carrier.ups_client_id
self.client_secret = super_carrier.ups_client_secret
self.shipper_number = super_carrier.ups_shipper_number
self.carrier = carrier
self.session = requests.Session()
def _send_request(self, url, method='GET', data=None, json=None, headers=None, auth=None):
url = url_join(self.base_url, url)
def _request_call(req_headers):
if not req_headers and self.access_token:
req_headers = {
"Authorization": '%s %s' % (TOKEN_TYPE, self.access_token)
}
self.logger(f'{url}\n{method}\n{req_headers}\n{data}\n{json}', f'ups request {url}')
try:
res = self.session.request(method=method, url=url, json=json, data=data, headers=req_headers, auth=auth, timeout=15)
self.logger(f'{res.status_code} {res.text}', f'ups response {url}')
except RequestException as err:
self.logger(str(err), f'ups response {url}')
raise ValidationError(_('Something went wrong, please try again later!!'))
return res
res = _request_call(headers)
if res.status_code == 401 and auth is None:
self.access_token = self._get_new_access_token()
self.carrier.sudo().ups_access_token = self.access_token
res = _request_call(None)
return res
def _process_errors(self, res_body):
err_msgs = []
response = res_body.get('response')
if response:
for err in response.get('errors', []):
err_msgs.append(err['message'])
return ','.join(err_msgs)
def _process_alerts(self, response):
alerts = response.get('Alert', [])
if isinstance(alerts, list):
messages = [alert['Description'] for alert in alerts]
return '\n'.join(messages)
return alerts['Description']
def _get_new_access_token(self):
if not self.client_id or not self.client_secret:
raise ValidationError(_('You must setup a client ID and secret on the carrier first'))
url = '/security/v1/oauth/token'
headers = {
'x-merchant-id': self.client_id
}
data = {
"grant_type": "client_credentials"
}
res = self._send_request(url, 'POST', data=data, headers=headers, auth=(self.client_id, self.client_secret))
try:
res_body = res.json()
except JSONDecodeError as err:
self.logger(str(err), f'ups response decode error {url}')
raise ValidationError(_('Could not decode response'))
if not res.ok:
raise ValidationError(self._process_errors(res_body))
return res_body.get('access_token')
def _clean_phone_number(self, phone):
return re.sub('[^0-9]', '', phone)
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 _check_required_value(self, order=False, picking=False, is_return=False):
is_express_checkout_partial_delivery_address = False
if order:
is_express_checkout_partial_delivery_address = order.env.context.get('express_checkout_partial_delivery_address')
shipper = order.company_id.partner_id
ship_from = order.warehouse_id.partner_id
ship_to = order.partner_shipping_id
elif picking and is_return:
ship_from = shipper = picking.partner_id
ship_to = picking.picking_type_id.warehouse_id.partner_id
else:
shipper = picking.company_id.partner_id
ship_from = picking.picking_type_id.warehouse_id.partner_id
ship_to = picking.partner_id
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 _("Shipper Phone must be at least 10 alphanumeric characters.")
# 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 _("Warehouse Phone must be at least 10 alphanumeric characters."),
# 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')
if not ship_to.street and not ship_to.street2 and not is_express_checkout_partial_delivery_address:
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.")
if error_lines := order.order_line._get_invalid_delivery_weight_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 %s is missing.", ml.product_id.display_name)
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')))
if not phone and not is_express_checkout_partial_delivery_address:
res.append('Phone')
if res:
return _("The recipient address is missing or wrong.\n(Missing field(s) : %s)", ",".join(res))
if phone and len(self._clean_phone_number(phone)) < 10:
return _("Recipient Phone must be at least 10 alphanumeric characters."),
return False
def _set_package_details(self, packages, carrier, ship_from, ship_to, cod_info, ship=False, is_return=False):
# Package Type key in ship request and rate request are different
package_type_key = 'Packaging' if ship else 'PackagingType'
res_packages = []
for p in packages:
# merchandise description: product names (len: [1-35]), strip non-alphanumeric chars
desc = ','.join(['%s' % re.sub(r'[\W_]+', ' ', c.product_id.name) for c in p.commodities])
if is_return:
desc = 'return ' + desc
if len(desc) > 35:
desc = desc[:32] + '...'
package = {
package_type_key: {
'Code': p.packaging_type or '00',
},
'Description': desc or 'UPS shipment',
'PackageWeight': {
'UnitOfMeasurement': {
'Code': carrier.ups_package_weight_unit,
},
'Weight': str(carrier._ups_convert_weight(p.weight, carrier.ups_package_weight_unit)),
},
'Dimensions': {
'UnitOfMeasurement': {
'Code': carrier.ups_package_dimension_unit or '',
},
'Length': str(p.dimension['length']) or '',
'Width': str(p.dimension['width']) or '',
'Height': str(p.dimension['height']) or '',
}
}
package_service_options = {}
if cod_info:
package_service_options['COD'] = {
'CODFundsCode': cod_info['funds_code'],
'CODAmount': {
'MonetaryValue': str(cod_info['monetary_value']),
'CurrencyCode': cod_info['currency'],
}
}
if p.currency_id:
package_service_options['DeclaredValue'] = {
'CurrencyCode': p.currency_id.name,
'MonetaryValue': float_repr(p.total_cost * carrier.shipping_insurance / 100, 2),
}
if package_service_options:
package['PackageServiceOptions'] = package_service_options
# 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')):
package.update({
'ReferenceNumber': {
'Code': 'PM',
'Value': p.name,
'BarCodeIndicator': p.name,
}
})
res_packages.append(package)
return res_packages
def _get_ship_data_from_partner(self, partner, shipper_no=None):
return {
'AttentionName': (partner.name or '')[:35],
'Name': (partner.parent_id.name or partner.name or '')[:35],
'EMailAddress': partner.email or '',
'ShipperNumber': shipper_no or '',
'Phone': {
'Number': (partner.phone or '').replace(' ', ''),
},
'Address': {
'AddressLine': [partner.street or '', partner.street2 or ''],
'City': partner.city or '',
'PostalCode': partner.zip or '',
'CountryCode': partner.country_id.code or '',
'StateProvinceCode': partner.state_id.code or '',
},
}
def _get_shipping_price(self, shipper, ship_from, ship_to, total_qty, packages, carrier, cod_info=None):
service_type = carrier.ups_default_service_type
saturday_delivery = carrier.ups_saturday_delivery
url = f'/api/rating/{API_VERSION}/Rate'
shipment_service_options = {}
if saturday_delivery:
shipment_service_options['SaturdayDeliveryIndicator'] = saturday_delivery
if carrier.ups_require_signature:
shipment_service_options['DeliveryConfirmation'] = {'DCISType': '1'}
data = {
'RateRequest': {
'Request': {
'RequestOption': 'Rate',
},
'Shipment': {
'Package': self._set_package_details(packages, carrier, ship_from, ship_to, cod_info),
'Shipper': self._get_ship_data_from_partner(shipper, self.shipper_number),
'ShipFrom': self._get_ship_data_from_partner(ship_from),
'ShipTo': self._get_ship_data_from_partner(ship_to),
'Service': {
'Code': service_type,
},
'NumOfPieces': str(int(total_qty)) if service_type == '96' else None,
'ShipmentServiceOptions': shipment_service_options if shipment_service_options else None,
'ShipmentRatingOptions': {
'NegotiatedRatesIndicator': "1",
}
}
},
}
res = self._send_request(url, method='POST', json=data)
if not res.ok:
return {'error_message': self._process_errors(res.json())}
res = res.json()
rate = res['RateResponse']['RatedShipment']
charge = rate['TotalCharges']
# Some users are qualified to receive negotiated rates
if 'NegotiatedRateCharges' in rate and rate['NegotiatedRateCharges']['TotalCharge']['MonetaryValue']:
charge = rate['NegotiatedRateCharges']['TotalCharge']
return {
'currency_code': charge['CurrencyCode'],
'price': charge['MonetaryValue'],
'alert_message': self._process_alerts(res['RateResponse']['Response']),
}
def _set_invoice(self, shipment_info, commodities, ship_to, is_return):
invoice_products = []
for commodity in commodities:
# 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],
'Unit': {
'Number': str(int(commodity.qty)),
'UnitOfMeasurement': {
'Code': 'PC' if commodity.qty == 1 else 'PCS',
},
'Value': float_repr(commodity.monetary_value, 2)
},
'OriginCountryCode': commodity.country_of_origin,
'CommodityCode': commodity.product_id.hs_code or '',
}
invoice_products.append(product)
if len(ship_to.commercial_partner_id.name) > 35:
raise ValidationError(_('The name of the customer should be no more than 35 characters.'))
contacts = {
'SoldTo': {
'Name': ship_to.commercial_partner_id.name,
'AttentionName': ship_to.name,
'Address': {
'AddressLine': [line for line in (ship_to.street, ship_to.street2) if line],
'City': ship_to.city,
'PostalCode': ship_to.zip,
'CountryCode': ship_to.country_id.code,
'StateProvinceCode': ship_to.state_id.code or '' if ship_to.country_id.code in ('US', 'CA', 'IE') else None
}
}
}
return {
'FormType': '01',
'Product': invoice_products,
'CurrencyCode': shipment_info.get('itl_currency_code'),
'InvoiceDate': shipment_info.get('invoice_date'),
'ReasonForExport': 'RETURN' if is_return else 'SALE',
'Contacts': contacts,
}
def _send_shipping(self, shipment_info, packages, carrier, shipper, ship_from, ship_to, service_type, duty_payment,
saturday_delivery=False, cod_info=None, label_file_type='GIF', ups_carrier_account=False, is_return=False):
url = f'/api/shipments/{API_VERSION}/ship'
# Payment Info
shipment_charge = {
'Type': '01',
}
payment_info = [shipment_charge]
if ups_carrier_account:
shipment_charge['BillReceiver'] = {
'AccountNumber': ups_carrier_account,
'Address': {
'PostalCode': ship_to.zip,
}
}
else:
shipment_charge['BillShipper'] = {
'AccountNumber': self.shipper_number,
}
if duty_payment == 'SENDER':
payment_info.append({
'Type': '02',
'BillShipper': {'AccountNumber': self.shipper_number},
})
shipment_service_options = {}
if shipment_info.get('require_invoice'):
shipment_service_options['InternationalForms'] = self._set_invoice(shipment_info, [c for pkg in packages for c in pkg.commodities],
ship_to, is_return)
shipment_service_options['InternationalForms']['PurchaseOrderNumber'] = shipment_info.get('purchase_order_number')
shipment_service_options['InternationalForms']['TermsOfShipment'] = shipment_info.get('terms_of_shipment')
if saturday_delivery:
shipment_service_options['SaturdayDeliveryIndicator'] = saturday_delivery
if carrier.ups_require_signature:
shipment_service_options['DeliveryConfirmation'] = {'DCISType': '1'}
request = {
'ShipmentRequest': {
'Request': {
'RequestOption': 'nonvalidate',
},
'LabelSpecification': {
'LabelImageFormat': {
'Code': label_file_type,
},
'LabelStockSize': {'Height': '6', 'Width': '4'} if label_file_type != 'GIF' else None,
},
'Shipment': {
'Description': shipment_info.get('description'),
'ReturnService': {'Code': '9'}if is_return else None,
'Package': self._set_package_details(packages, carrier, ship_from, ship_to, cod_info, ship=True, is_return=is_return),
'Shipper': self._get_ship_data_from_partner(shipper, self.shipper_number),
'ShipFrom': self._get_ship_data_from_partner(ship_from),
'ShipTo': self._get_ship_data_from_partner(ship_to),
'Service': {
'Code': service_type,
},
'NumOfPiecesInShipment': int(shipment_info.get('total_qty')) if service_type == '96' else None,
'ShipmentServiceOptions': shipment_service_options if shipment_service_options else None,
'ShipmentRatingOptions': {
'NegotiatedRatesIndicator': '1',
},
'PaymentInformation': {
'ShipmentCharge': payment_info,
},
},
},
}
# Include ReferenceNumber only if the shipment is not US/US or PR/PR
if not (
(ship_from.country_id.code == 'US' and ship_to.country_id.code == 'US') or
(ship_from.country_id.code == 'PR' and ship_to.country_id.code == 'PR')
):
request['ShipmentRequest']['Shipment']['ReferenceNumber'] = {
'Value': shipment_info.get('reference_number')
}
# 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']:
request['ShipmentRequest']['Shipment']['InvoiceLineTotal'] = {
'CurrencyCode': shipment_info.get('itl_currency_code'),
'MonetaryValue': shipment_info.get('ilt_monetary_value'),
}
res = self._send_request(url, 'POST', json=request)
if res.status_code == 401:
raise ValidationError(_("Invalid Authentication Information: Please check your credentials and configuration within UPS's system."))
try:
res_body = res.json()
except JSONDecodeError as err:
self.logger(str(err), f'ups response decode error {url}')
raise ValidationError(_('Could not decode response'))
if not res.ok:
raise ValidationError(self._process_errors(res.json()))
result = {}
shipment_result = res_body['ShipmentResponse']['ShipmentResults']
packs = shipment_result.get('PackageResults', [])
# get package labels
if not isinstance(packs, list):
packs = [packs]
result['tracking_ref'] = shipment_result['ShipmentIdentificationNumber']
labels_binary = [(pack['TrackingNumber'], self._save_label(pack['ShippingLabel']['GraphicImage'], label_file_type=label_file_type)) for pack in packs]
result['label_binary_data'] = labels_binary
# save international form if in response
international_form = shipment_result.get('Form', False)
if international_form:
result['invoice_binary_data'] = self._save_label(international_form['Image']['GraphicImage'], label_file_type='pdf')
# Some users are qualified to receive negotiated rates
if shipment_result.get('NegotiatedRateCharges'):
charge = shipment_result['NegotiatedRateCharges']['TotalCharge']
else:
charge = shipment_result['ShipmentCharges']['TotalCharges']
result['currency_code'] = charge['CurrencyCode']
result['price'] = charge['MonetaryValue']
return result
def _cancel_shipping(self, shipping_id):
url = f'/api/shipments/{API_VERSION}/void/cancel/{shipping_id}'
res = self._send_request(url, 'DELETE')
if res.status_code == 401:
raise ValidationError(_("Invalid Authentication Information: Please check your credentials and configuration within UPS's system."))
try:
res_body = res.json()
except JSONDecodeError as err:
self.logger(str(err), f'ups response decode error {url}')
raise ValidationError(_('Could not decode response'))
if not res.ok:
raise ValidationError(self._process_errors(res.json()))
return res_body['VoidShipmentResponse']['SummaryResult']['Status']

View File

@@ -0,0 +1,560 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- ================================================================== -->
<!-- Package Types (noupdate="0" so they refresh on module upgrade) -->
<!-- ================================================================== -->
<data noupdate="0">
<!-- ── Standard Canada Post Box Sizes (inches / lbs) ── -->
<record id="fusion_cp_pkg_26x20x20" model="stock.package.type">
<field name="name">CP Box 26x20x20 in, 35 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">26</field>
<field name="width">20</field>
<field name="height">20</field>
<field name="max_weight">35</field>
</record>
<record id="fusion_cp_pkg_26x26x9" model="stock.package.type">
<field name="name">CP Box 26x26x9 in, 15 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">26</field>
<field name="width">26</field>
<field name="height">9</field>
<field name="max_weight">15</field>
</record>
<record id="fusion_cp_pkg_32x28x10" model="stock.package.type">
<field name="name">CP Box 32x28x10 in, 12 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">32</field>
<field name="width">28</field>
<field name="height">10</field>
<field name="max_weight">12</field>
</record>
<record id="fusion_cp_pkg_34x28x10" model="stock.package.type">
<field name="name">CP Box 34x28x10 in, 10 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">34</field>
<field name="width">28</field>
<field name="height">10</field>
<field name="max_weight">10</field>
</record>
<record id="fusion_cp_pkg_9x25x27" model="stock.package.type">
<field name="name">CP Box 9x25x27 in, 16 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">9</field>
<field name="width">25</field>
<field name="height">27</field>
<field name="max_weight">16</field>
</record>
<record id="fusion_cp_pkg_9x25x29" model="stock.package.type">
<field name="name">CP Box 9x25x29 in, 17 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">9</field>
<field name="width">25</field>
<field name="height">29</field>
<field name="max_weight">17</field>
</record>
<record id="fusion_cp_pkg_10x10x6" model="stock.package.type">
<field name="name">CP Box 10x10x6 in, 35 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">10</field>
<field name="width">10</field>
<field name="height">6</field>
<field name="max_weight">35</field>
</record>
<record id="fusion_cp_pkg_25x15x30" model="stock.package.type">
<field name="name">CP Box 25x15x30 in, 25 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">25</field>
<field name="width">15</field>
<field name="height">30</field>
<field name="max_weight">25</field>
</record>
<record id="fusion_cp_pkg_10x10x10" model="stock.package.type">
<field name="name">CP Box 10x10x10 in, 2 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">10</field>
<field name="width">10</field>
<field name="height">10</field>
<field name="max_weight">2</field>
</record>
<record id="fusion_cp_pkg_28x15x30" model="stock.package.type">
<field name="name">CP Box 28x15x30 in, 40 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">28</field>
<field name="width">15</field>
<field name="height">30</field>
<field name="max_weight">40</field>
</record>
<record id="fusion_cp_pkg_28x10x30" model="stock.package.type">
<field name="name">CP Box 28x10x30 in, 16 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">28</field>
<field name="width">10</field>
<field name="height">30</field>
<field name="max_weight">16</field>
</record>
<record id="fusion_cp_pkg_22x10x6_cm" model="stock.package.type">
<field name="name">CP Box 22x10x6 cm, 10 kg</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">22</field>
<field name="width">10</field>
<field name="height">6</field>
<field name="max_weight">10</field>
</record>
<record id="fusion_cp_pkg_22x13x6" model="stock.package.type">
<field name="name">CP Box 22x13x6 in, 10 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">22</field>
<field name="width">13</field>
<field name="height">6</field>
<field name="max_weight">10</field>
</record>
<record id="fusion_cp_pkg_12x10x5" model="stock.package.type">
<field name="name">CP Box 12x10x5 in, 2 lb</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">12</field>
<field name="width">10</field>
<field name="height">5</field>
<field name="max_weight">2</field>
</record>
<!-- Old default box - replaced with a standard size -->
<record id="fusion_cp_packaging_BOX" model="stock.package.type">
<field name="name">CP Box 10x10x10 in (Legacy)</field>
<field name="package_carrier_type">fusion_canada_post</field>
<field name="packaging_length">10</field>
<field name="width">10</field>
<field name="height">10</field>
<field name="max_weight">2</field>
</record>
<!-- ── UPS REST Package Types ── -->
<record id="fusion_ups_rest_packaging_01" model="stock.package.type">
<field name="name">UPS Letter</field>
<field name="shipper_package_code">01</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">0.5</field>
</record>
<record id="fusion_ups_rest_packaging_02" model="stock.package.type">
<field name="name">UPS Package/customer supplied</field>
<field name="shipper_package_code">02</field>
<field name="height">10</field>
<field name="width">10</field>
<field name="packaging_length">10</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_rest_packaging_03" model="stock.package.type">
<field name="name">UPS Tube</field>
<field name="shipper_package_code">03</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_rest_packaging_04" model="stock.package.type">
<field name="name">UPS Pak</field>
<field name="shipper_package_code">04</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_rest_packaging_21" model="stock.package.type">
<field name="name">UPS Express Box</field>
<field name="shipper_package_code">21</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_rest_packaging_24" model="stock.package.type">
<field name="name">UPS 25KG Box</field>
<field name="shipper_package_code">24</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">25</field>
</record>
<record id="fusion_ups_rest_packaging_25" model="stock.package.type">
<field name="name">UPS 10KG Box</field>
<field name="shipper_package_code">25</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">10</field>
</record>
<record id="fusion_ups_rest_packaging_30" model="stock.package.type">
<field name="name">UPS Pallet</field>
<field name="shipper_package_code">30</field>
<field name="height">10</field>
<field name="width">10</field>
<field name="packaging_length">10</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">1000</field>
</record>
<record id="fusion_ups_rest_packaging_2a" model="stock.package.type">
<field name="name">UPS Small Express Box</field>
<field name="shipper_package_code">2a</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_rest_packaging_2b" model="stock.package.type">
<field name="name">UPS Medium Express Box</field>
<field name="shipper_package_code">2b</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_rest_packaging_2c" model="stock.package.type">
<field name="name">UPS Large Express Box</field>
<field name="shipper_package_code">2c</field>
<field name="package_carrier_type">fusion_ups_rest</field>
<field name="max_weight">68</field>
</record>
<!-- ── UPS SOAP (Legacy) Package Types ── -->
<record id="fusion_ups_packaging_01" model="stock.package.type">
<field name="name">UPS Letter</field>
<field name="shipper_package_code">01</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">0.5</field>
</record>
<record id="fusion_ups_packaging_02" model="stock.package.type">
<field name="name">UPS Package/customer supplied</field>
<field name="shipper_package_code">02</field>
<field name="height">52</field>
<field name="width">52</field>
<field name="packaging_length">52</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_packaging_03" model="stock.package.type">
<field name="name">UPS Tube</field>
<field name="shipper_package_code">03</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_packaging_04" model="stock.package.type">
<field name="name">UPS Pak</field>
<field name="shipper_package_code">04</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_packaging_21" model="stock.package.type">
<field name="name">UPS Express Box</field>
<field name="shipper_package_code">21</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_packaging_24" model="stock.package.type">
<field name="name">UPS 25KG Box</field>
<field name="shipper_package_code">24</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">25</field>
</record>
<record id="fusion_ups_packaging_25" model="stock.package.type">
<field name="name">UPS 10KG Box</field>
<field name="shipper_package_code">25</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">10</field>
</record>
<record id="fusion_ups_packaging_30" model="stock.package.type">
<field name="name">UPS Pallet</field>
<field name="shipper_package_code">30</field>
<field name="height">1920</field>
<field name="width">2133</field>
<field name="packaging_length">3048</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">1000</field>
</record>
<record id="fusion_ups_packaging_2a" model="stock.package.type">
<field name="name">UPS Small Express Box</field>
<field name="shipper_package_code">2a</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_packaging_2b" model="stock.package.type">
<field name="name">UPS Medium Express Box</field>
<field name="shipper_package_code">2b</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">68</field>
</record>
<record id="fusion_ups_packaging_2c" model="stock.package.type">
<field name="name">UPS Flat Pack</field>
<field name="shipper_package_code">2c</field>
<field name="package_carrier_type">fusion_ups</field>
<field name="max_weight">68</field>
</record>
<!-- ── FedEx REST Package Types ── -->
<record id="fusion_fedex_rest_packaging_FEDEX_BOX" model="stock.package.type">
<field name="name">FedEx Box</field>
<field name="shipper_package_code">FEDEX_BOX</field>
<field name="package_carrier_type">fusion_fedex_rest</field>
<field name="max_weight">9.07</field>
</record>
<record id="fusion_fedex_rest_packaging_FEDEX_10KG_BOX" model="stock.package.type">
<field name="name">FedEx 10kg Box</field>
<field name="shipper_package_code">FEDEX_10KG_BOX</field>
<field name="package_carrier_type">fusion_fedex_rest</field>
<field name="max_weight">10</field>
</record>
<record id="fusion_fedex_rest_packaging_FEDEX_25KG_BOX" model="stock.package.type">
<field name="name">FedEx 25kg Box</field>
<field name="shipper_package_code">FEDEX_25KG_BOX</field>
<field name="package_carrier_type">fusion_fedex_rest</field>
<field name="max_weight">25</field>
</record>
<record id="fusion_fedex_rest_packaging_FEDEX_ENVELOPE" model="stock.package.type">
<field name="name">FedEx Letters</field>
<field name="shipper_package_code">FEDEX_ENVELOPE</field>
<field name="package_carrier_type">fusion_fedex_rest</field>
<field name="max_weight">0.5</field>
</record>
<record id="fusion_fedex_rest_packaging_FEDEX_PAK" model="stock.package.type">
<field name="name">FedEx Pak</field>
<field name="shipper_package_code">FEDEX_PAK</field>
<field name="package_carrier_type">fusion_fedex_rest</field>
<field name="max_weight">9.07</field>
</record>
<record id="fusion_fedex_rest_packaging_FEDEX_TUBE" model="stock.package.type">
<field name="name">FedEx Tube</field>
<field name="shipper_package_code">FEDEX_TUBE</field>
<field name="package_carrier_type">fusion_fedex_rest</field>
<field name="max_weight">9.07</field>
</record>
<record id="fusion_fedex_rest_packaging_YOUR_PACKAGING" model="stock.package.type">
<field name="name">FedEx Your Packaging</field>
<field name="shipper_package_code">YOUR_PACKAGING</field>
<field name="package_carrier_type">fusion_fedex_rest</field>
</record>
<!-- ── FedEx SOAP (Legacy) Package Types ── -->
<record id="fusion_fedex_packaging_FEDEX_BOX" model="stock.package.type">
<field name="name">FedEx Box</field>
<field name="shipper_package_code">FEDEX_BOX</field>
<field name="package_carrier_type">fusion_fedex</field>
<field name="max_weight">9.07</field>
</record>
<record id="fusion_fedex_packaging_FEDEX_10KG_BOX" model="stock.package.type">
<field name="name">FedEx 10kg Box</field>
<field name="shipper_package_code">FEDEX_10KG_BOX</field>
<field name="package_carrier_type">fusion_fedex</field>
<field name="max_weight">10</field>
</record>
<record id="fusion_fedex_packaging_FEDEX_25KG_BOX" model="stock.package.type">
<field name="name">FedEx 25kg Box</field>
<field name="shipper_package_code">FEDEX_25KG_BOX</field>
<field name="package_carrier_type">fusion_fedex</field>
<field name="max_weight">25</field>
</record>
<record id="fusion_fedex_packaging_FEDEX_ENVELOPE" model="stock.package.type">
<field name="name">FedEx Letters</field>
<field name="shipper_package_code">FEDEX_ENVELOPE</field>
<field name="package_carrier_type">fusion_fedex</field>
<field name="max_weight">0.5</field>
</record>
<record id="fusion_fedex_packaging_FEDEX_PAK" model="stock.package.type">
<field name="name">FedEx Pak</field>
<field name="shipper_package_code">FEDEX_PAK</field>
<field name="package_carrier_type">fusion_fedex</field>
<field name="max_weight">0</field>
</record>
<record id="fusion_fedex_packaging_FEDEX_TUBE" model="stock.package.type">
<field name="name">FedEx Tube</field>
<field name="shipper_package_code">FEDEX_TUBE</field>
<field name="package_carrier_type">fusion_fedex</field>
<field name="max_weight">9.07</field>
</record>
<record id="fusion_fedex_packaging_YOUR_PACKAGING" model="stock.package.type">
<field name="name">FedEx Your Packaging</field>
<field name="shipper_package_code">YOUR_PACKAGING</field>
<field name="package_carrier_type">fusion_fedex</field>
</record>
<!-- ── DHL REST Package Types ── -->
<record id="fusion_dhl_rest_packaging_FLY" model="stock.package.type">
<field name="name">DHL Flyer/Smalls</field>
<field name="shipper_package_code">FLY</field>
<field name="package_carrier_type">fusion_dhl_rest</field>
<field name="max_weight">0.23</field>
</record>
<record id="fusion_dhl_rest_packaging_COY" model="stock.package.type">
<field name="name">DHL Parcels/Conveyables</field>
<field name="shipper_package_code">COY</field>
<field name="package_carrier_type">fusion_dhl_rest</field>
<field name="max_weight">31.5</field>
</record>
<record id="fusion_dhl_rest_packaging_NCY" model="stock.package.type">
<field name="name">DHL Non-conveyables</field>
<field name="shipper_package_code">NCY</field>
<field name="package_carrier_type">fusion_dhl_rest</field>
</record>
<record id="fusion_dhl_rest_packaging_PAL" model="stock.package.type">
<field name="name">DHL Pallets</field>
<field name="shipper_package_code">PAL</field>
<field name="height">160</field>
<field name="width">120</field>
<field name="packaging_length">120</field>
<field name="package_carrier_type">fusion_dhl_rest</field>
<field name="max_weight">1000</field>
</record>
<record id="fusion_dhl_rest_packaging_DBL" model="stock.package.type">
<field name="name">DHL Double Pallets</field>
<field name="shipper_package_code">DBL</field>
<field name="package_carrier_type">fusion_dhl_rest</field>
<field name="max_weight">1000</field>
</record>
<record id="fusion_dhl_rest_packaging_BOX" model="stock.package.type">
<field name="name">DHL Box</field>
<field name="shipper_package_code">BOX</field>
<field name="height">40</field>
<field name="width">44</field>
<field name="packaging_length">54</field>
<field name="package_carrier_type">fusion_dhl_rest</field>
<field name="max_weight">70</field>
</record>
<!-- ── DHL SOAP (Legacy) Package Types ── -->
<record id="fusion_dhl_packaging_FLY" model="stock.package.type">
<field name="name">DHL Flyer/Smalls</field>
<field name="shipper_package_code">FLY</field>
<field name="package_carrier_type">fusion_dhl</field>
<field name="max_weight">0.23</field>
</record>
<record id="fusion_dhl_packaging_COY" model="stock.package.type">
<field name="name">DHL Parcels/Conveyables</field>
<field name="shipper_package_code">COY</field>
<field name="package_carrier_type">fusion_dhl</field>
<field name="max_weight">31.5</field>
</record>
<record id="fusion_dhl_packaging_NCY" model="stock.package.type">
<field name="name">DHL Non-conveyables</field>
<field name="shipper_package_code">NCY</field>
<field name="package_carrier_type">fusion_dhl</field>
</record>
<record id="fusion_dhl_packaging_PAL" model="stock.package.type">
<field name="name">DHL Pallets</field>
<field name="shipper_package_code">PAL</field>
<field name="height">1600</field>
<field name="width">1200</field>
<field name="packaging_length">1200</field>
<field name="package_carrier_type">fusion_dhl</field>
<field name="max_weight">1000</field>
</record>
<record id="fusion_dhl_packaging_DBL" model="stock.package.type">
<field name="name">DHL Double Pallets</field>
<field name="shipper_package_code">DBL</field>
<field name="package_carrier_type">fusion_dhl</field>
<field name="max_weight">1000</field>
</record>
<record id="fusion_dhl_packaging_BOX" model="stock.package.type">
<field name="name">DHL Box</field>
<field name="shipper_package_code">BOX</field>
<field name="height">40</field>
<field name="width">44</field>
<field name="packaging_length">54</field>
<field name="package_carrier_type">fusion_dhl</field>
<field name="max_weight">70</field>
</record>
</data>
<!-- ================================================================== -->
<!-- Sequences & Products (noupdate="1") -->
<!-- ================================================================== -->
<data noupdate="1">
<!-- ── DHL Commercial Invoice Sequence ── -->
<record id="dhl_commercial_invoice_seq" model="ir.sequence">
<field name="name">DHL Commercial Invoice</field>
<field name="code">delivery_dhl_rest.commercial_invoice</field>
<field name="prefix">CI</field>
<field name="padding">5</field>
</record>
<!-- ── Canada Post Delivery Product ── -->
<record id="product_product_delivery_fusion_canada_post" model="product.product">
<field name="name">SHIPPING BY CANADA POST</field>
<field name="default_code">CPSHIPPING</field>
<field name="type">service</field>
<field name="categ_id" ref="delivery.product_category_deliveries"/>
<field name="sale_ok" eval="False"/>
<field name="purchase_ok" eval="False"/>
<field name="list_price">0.0</field>
</record>
<!-- ── Default Canada Post Carrier ── -->
<record id="delivery_carrier_fusion_canada_post" model="delivery.carrier">
<field name="name">Canada Post</field>
<field
name="product_id"
ref="fusion_shipping.product_product_delivery_fusion_canada_post"
/>
<field name="delivery_type">fusion_canada_post</field>
<field name="fusion_cp_type">commercial</field>
<field name="fusion_cp_dimension_unit">in</field>
<field name="option_code">SO</field>
<field name="service_type">DOM.RP</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="ir_cron_fusion_tracking_refresh" model="ir.cron">
<field name="name">Fusion Shipping: Refresh Tracking</field>
<field name="model_id" ref="model_fusion_shipment"/>
<field name="state">code</field>
<field name="code">model._cron_refresh_tracking()</field>
<field name="interval_number">8</field>
<field name="interval_type">hours</field>
<field name="active">True</field>
</record>
</odoo>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="seq_fusion_shipment" model="ir.sequence">
<field name="name">Fusion Shipment</field>
<field name="code">fusion.shipment</field>
<field name="prefix">FS-</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,11 @@
from . import delivery_carrier
from . import product_packaging
from . import res_company
from . import res_partner
from . import payment_provider
from . import uom_uom
from . import fusion_shipment
from . import fusion_tracking_event
from . import fusion_order_package
from . import sale_order
from . import stock_picking

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
from odoo import models, fields
class FusionOrderPackage(models.Model):
"""Stores per-package dimensions and service info on a sale order.
Created when the user confirms the "Add Shipping" wizard after
defining one or more packages with dimensions.
Used by ``send_shipping`` to apply the correct dimensions
to each physical package on the delivery order.
"""
_name = 'fusion.order.package'
_description = 'Order Package'
_order = 'sequence, id'
sale_order_id = fields.Many2one(
'sale.order',
string='Sale Order',
ondelete='cascade',
required=True,
index=True,
)
sequence = fields.Integer(default=10)
package_type_id = fields.Many2one(
'stock.package.type',
string='Box Type',
)
package_length = fields.Float(string='Length')
package_width = fields.Float(string='Width')
package_height = fields.Float(string='Height')
weight = fields.Float(string='Weight')
service_code = fields.Char(string='Service Code')
service_name = fields.Char(string='Service')
price = fields.Float(string='Shipping Cost', digits='Product Price')
expected_delivery = fields.Char(string='Expected Delivery')
currency_id = fields.Many2one(
'res.currency',
related='sale_order_id.currency_id',
)

View File

@@ -0,0 +1,747 @@
import base64
import logging
from datetime import datetime as dt_mod
from lxml import etree
from requests import request
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.addons.fusion_shipping.api.canada_post.response import Response
_logger = logging.getLogger(__name__)
class FusionShipment(models.Model):
_name = 'fusion.shipment'
_description = 'Fusion Shipment'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'shipment_date desc, id desc'
_rec_name = 'name'
name = fields.Char(
string='Reference',
required=True,
readonly=True,
default=lambda self: _('New'),
copy=False,
)
carrier_type = fields.Selection(
[
('canada_post', 'Canada Post'),
('ups', 'UPS'),
('ups_rest', 'UPS (REST)'),
('fedex', 'FedEx'),
('fedex_rest', 'FedEx (REST)'),
('dhl', 'DHL Express'),
('dhl_rest', 'DHL Express (REST)'),
],
string='Carrier Type',
readonly=True,
tracking=True,
help='The shipping carrier used for this shipment.',
)
tracking_number = fields.Char(
string='Tracking Number',
index=True,
readonly=True,
copy=False,
tracking=True,
)
shipment_id = fields.Char(
string='CP Shipment ID',
readonly=True,
copy=False,
help='Canada Post internal shipment identifier',
)
sale_order_id = fields.Many2one(
'sale.order',
string='Sale Order',
index=True,
ondelete='set null',
tracking=True,
)
picking_id = fields.Many2one(
'stock.picking',
string='Transfer',
index=True,
ondelete='set null',
tracking=True,
)
carrier_id = fields.Many2one(
'delivery.carrier',
string='Carrier',
ondelete='set null',
)
shipment_date = fields.Datetime(
string='Shipment Date',
default=fields.Datetime.now,
tracking=True,
)
status = fields.Selection(
[
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('shipped', 'Shipped'),
('delivered', 'Delivered'),
('returned', 'Returned'),
('cancelled', 'Cancelled'),
],
string='Status',
default='confirmed',
tracking=True,
)
# Label attachments (Many2one for storage)
label_attachment_id = fields.Many2one(
'ir.attachment',
string='Printable Label (4x6)',
ondelete='set null',
help='The thermal printer label (4x6 format)',
)
full_label_attachment_id = fields.Many2one(
'ir.attachment',
string='Full Label (8.5x11)',
ondelete='set null',
help='Full page label with instructions and receipt',
)
receipt_attachment_id = fields.Many2one(
'ir.attachment',
string='Receipt',
ondelete='set null',
)
commercial_invoice_attachment_id = fields.Many2one(
'ir.attachment',
string='Commercial Invoice',
ondelete='set null',
)
# Return label
return_tracking_number = fields.Char(
string='Return Tracking Number',
readonly=True,
copy=False,
)
return_label_attachment_id = fields.Many2one(
'ir.attachment',
string='Return Label',
ondelete='set null',
)
# Shipment details
shipping_cost = fields.Monetary(
string='Shipping Cost',
currency_field='currency_id',
readonly=True,
)
service_type = fields.Char(
string='Service Type',
readonly=True,
)
weight = fields.Float(
string='Weight',
digits='Stock Weight',
readonly=True,
)
package_name = fields.Char(
string='Package',
readonly=True,
help='Name of the package this shipment covers',
)
# Address fields (computed)
sender_name = fields.Char(
string='Sender',
compute='_compute_sender_fields',
store=True,
)
sender_address = fields.Char(
string='Sender Address',
compute='_compute_sender_fields',
store=True,
)
recipient_name = fields.Char(
string='Recipient',
compute='_compute_recipient_fields',
store=True,
)
recipient_address = fields.Char(
string='Recipient Address',
compute='_compute_recipient_fields',
store=True,
)
company_id = fields.Many2one(
'res.company',
string='Company',
default=lambda self: self.env.company,
)
currency_id = fields.Many2one(
'res.currency',
related='company_id.currency_id',
store=True,
)
# Tracking history
tracking_event_ids = fields.One2many(
'fusion.tracking.event',
'shipment_id',
string='Tracking Events',
)
tracking_event_count = fields.Integer(
string='Tracking Events',
compute='_compute_tracking_event_count',
)
last_tracking_update = fields.Datetime(
string='Last Tracking Update',
readonly=True,
)
delivery_date = fields.Datetime(
string='Delivery Date',
readonly=True,
)
def _compute_tracking_event_count(self):
for rec in self:
rec.tracking_event_count = len(rec.tracking_event_ids)
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('name', _('New')) == _('New'):
vals['name'] = self.env['ir.sequence'].next_by_code(
'fusion.shipment'
) or _('New')
return super().create(vals_list)
@api.depends('picking_id', 'picking_id.picking_type_id.warehouse_id.partner_id')
def _compute_sender_fields(self):
for rec in self:
partner = (
rec.picking_id.picking_type_id.warehouse_id.partner_id
if rec.picking_id
else False
)
if partner:
rec.sender_name = partner.name or ''
parts = filter(None, [
partner.street,
partner.city,
partner.state_id.name if partner.state_id else '',
partner.zip,
])
rec.sender_address = ', '.join(parts)
else:
rec.sender_name = ''
rec.sender_address = ''
@api.depends('picking_id', 'picking_id.partner_id')
def _compute_recipient_fields(self):
for rec in self:
partner = rec.picking_id.partner_id if rec.picking_id else False
if partner:
rec.recipient_name = partner.name or ''
parts = filter(None, [
partner.street,
partner.city,
partner.state_id.name if partner.state_id else '',
partner.zip,
])
rec.recipient_address = ', '.join(parts)
else:
rec.recipient_name = ''
rec.recipient_address = ''
def action_open_sale_order(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': 'Sale Order',
'res_model': 'sale.order',
'res_id': self.sale_order_id.id,
'view_mode': 'form',
'target': 'current',
}
def action_open_picking(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': 'Transfer',
'res_model': 'stock.picking',
'res_id': self.picking_id.id,
'view_mode': 'form',
'target': 'current',
}
def _action_open_attachment(self, attachment):
"""Open an attachment PDF in the browser viewer (new tab)."""
self.ensure_one()
if not attachment:
return False
return {
'type': 'ir.actions.act_url',
'url': '/web/content/%s?download=false' % attachment.id,
'target': 'new',
}
def action_view_label(self):
return self._action_open_attachment(self.label_attachment_id)
def action_view_full_label(self):
return self._action_open_attachment(self.full_label_attachment_id)
def action_view_receipt(self):
return self._action_open_attachment(self.receipt_attachment_id)
def action_view_commercial_invoice(self):
return self._action_open_attachment(self.commercial_invoice_attachment_id)
# ── Tracking ──────────────────────────────────────────────
def action_refresh_tracking(self):
"""Fetch latest tracking events from Canada Post VIS API."""
self.ensure_one()
if not self.tracking_number:
raise ValidationError(
_("No tracking number available for this shipment."))
carrier = self.carrier_id
if not carrier:
raise ValidationError(
_("No carrier linked to this shipment."))
# VIS tracking uses /vis/ path, not /rs/
if carrier.prod_environment:
base = "https://soa-gw.canadapost.ca"
else:
base = "https://ct.soa-gw.canadapost.ca"
url = "%s/vis/track/pin/%s/detail" % (base, self.tracking_number)
headers = {
'Accept': 'application/vnd.cpc.track-v2+xml',
'Accept-language': 'en-CA',
}
try:
resp = request(
method='GET', url=url, headers=headers,
auth=(carrier.username, carrier.password))
_logger.info("Tracking API %s%s", url, resp.status_code)
if resp.status_code == 404:
# "No Pin History" — normal for new/sandbox shipments
self.last_tracking_update = fields.Datetime.now()
self.message_post(
body=_("No tracking history available yet for %s.") %
self.tracking_number)
return
if resp.status_code != 200:
raise ValidationError(
_("Canada Post tracking error: %s %s\n%s") % (
resp.status_code, resp.reason,
resp.text[:500] if resp.text else ''))
api_resp = Response(resp)
result = api_resp.dict()
except ValidationError:
raise
except Exception as e:
raise ValidationError(
_("Failed to fetch tracking: %s") % str(e))
self._process_tracking_events(result)
def _process_tracking_events(self, result):
"""Parse CP tracking detail response and store events."""
detail = result.get('tracking-detail', result)
sig_events = detail.get('significant-events', {})
occurrences = sig_events.get('occurrence', [])
# Single event returns dict, multiple returns list
if isinstance(occurrences, dict):
occurrences = [occurrences]
# Replace existing events
self.tracking_event_ids.unlink()
vals_list = []
for occ in occurrences:
event_date_str = occ.get('event-date', '')
event_time_str = occ.get('event-time', '')
# Build combined datetime for sorting
event_datetime = False
if event_date_str:
try:
if event_time_str:
event_datetime = dt_mod.strptime(
'%s %s' % (event_date_str, event_time_str),
'%Y-%m-%d %H:%M:%S')
else:
event_datetime = dt_mod.strptime(
event_date_str, '%Y-%m-%d')
except (ValueError, TypeError):
pass
vals_list.append({
'shipment_id': self.id,
'event_date': event_date_str or False,
'event_time': event_time_str or '',
'event_datetime': event_datetime,
'event_description': occ.get('event-description', ''),
'event_type': occ.get('event-type', ''),
'event_site': occ.get('event-site', ''),
'event_province': occ.get('event-province', ''),
'signatory_name': occ.get('signatory-name', ''),
})
if vals_list:
self.env['fusion.tracking.event'].create(vals_list)
self.last_tracking_update = fields.Datetime.now()
self._update_status_from_tracking(detail)
self.message_post(
body=_("Tracking refreshed: %d events loaded.") % len(vals_list))
def _update_status_from_tracking(self, detail):
"""Auto-update shipment status based on tracking data."""
if self.status == 'cancelled':
return
delivered_date = detail.get('actual-delivery-date', '')
if delivered_date:
if self._has_return_events():
self.status = 'returned'
else:
self.status = 'delivered'
try:
self.delivery_date = dt_mod.strptime(
delivered_date, '%Y-%m-%d')
except (ValueError, TypeError):
self.delivery_date = fields.Datetime.now()
elif self._has_return_events():
self.status = 'returned'
elif self.status == 'confirmed' and self.tracking_event_ids:
self.status = 'shipped'
def _has_return_events(self):
"""Check if any tracking events indicate a return/RTS."""
RETURN_TYPES = {'RTS', 'RETURN', 'RTS_LABEL_PROC'}
for event in self.tracking_event_ids:
if event.event_type in RETURN_TYPES:
return True
desc = (event.event_description or '').lower()
if 'return to sender' in desc or 'item returned' in desc:
return True
return False
# ── Void & Reissue ─────────────────────────────────────
def action_void_shipment(self):
"""Void this shipment via Canada Post API (DELETE endpoint)."""
self.ensure_one()
if self.status == 'cancelled':
raise ValidationError(
_("This shipment is already cancelled."))
if not self.shipment_id:
raise ValidationError(
_("No CP shipment ID — cannot void."))
carrier = self.carrier_id
if not carrier:
raise ValidationError(
_("No carrier linked to this shipment."))
if carrier.prod_environment:
base = "https://soa-gw.canadapost.ca"
else:
base = "https://ct.soa-gw.canadapost.ca"
customer = carrier.customer_number
if carrier.fusion_cp_type == 'commercial':
url = "%s/rs/%s/%s/shipment/%s" % (
base, customer, customer, self.shipment_id)
accept_header = 'application/vnd.cpc.shipment-v8+xml'
else:
url = "%s/rs/%s/ncshipment/%s" % (
base, customer, self.shipment_id)
accept_header = 'application/vnd.cpc.ncshipment-v4+xml'
try:
resp = request(
method='DELETE', url=url,
headers={
'Accept': accept_header,
'Accept-language': 'en-CA',
},
auth=(carrier.username, carrier.password))
_logger.info(
"Void Shipment %s%s", url, resp.status_code)
if resp.status_code == 204:
self.status = 'cancelled'
self.message_post(
body=_("Shipment voided successfully via "
"Canada Post API."))
else:
# Parse CP error XML for a clean message
error_msg = self._parse_cp_error_response(resp)
except ValidationError:
raise
except Exception as e:
raise ValidationError(
_("Failed to void shipment: %s") % str(e))
def _parse_cp_error_response(self, resp):
"""Parse a Canada Post error XML response and raise a clean
ValidationError with the human-readable description.
If the response cannot be parsed, falls back to the raw text.
"""
description = ''
try:
root = etree.fromstring(resp.content)
# Strip namespace for easy tag matching
ns = {'cp': root.nsmap.get(None, '')}
if ns['cp']:
msgs = root.findall('.//cp:message', ns)
else:
msgs = root.findall('.//message')
parts = []
for msg in msgs:
desc_el = msg.find(
'cp:description', ns) if ns['cp'] else msg.find(
'description')
if desc_el is not None and desc_el.text:
parts.append(desc_el.text.strip())
if parts:
description = '\n'.join(parts)
except Exception:
description = ''
if not description:
description = (resp.text[:500]
if resp.text else 'Unknown error')
raise ValidationError(
_("Canada Post: %s") % description)
def action_reissue_shipment(self):
"""Void current shipment and create a new one."""
self.ensure_one()
if self.status != 'cancelled':
self.action_void_shipment()
if not self.picking_id:
raise ValidationError(
_("No transfer linked — cannot reissue."))
picking = self.picking_id
carrier = picking.carrier_id or self.carrier_id
if not carrier:
raise ValidationError(
_("No carrier found for reissue."))
result = carrier.send_shipping(picking)
if result:
picking.carrier_tracking_ref = result[0].get(
'tracking_number', '')
self.message_post(
body=_("Shipment reissued. See new shipment record."))
def action_track_on_carrier(self):
"""Open the carrier's tracking website in a new tab."""
self.ensure_one()
if not self.tracking_number:
raise ValidationError(
_("No tracking number available for this shipment."))
# Default tracking URLs per carrier type
DEFAULT_TRACKING = {
'canada_post': 'https://www.canadapost.ca/trackweb/en#/resultList?searchFor=',
'ups': 'https://www.ups.com/track?tracknum=',
'ups_rest': 'https://www.ups.com/track?tracknum=',
'fedex': 'https://www.fedex.com/wtrk/track/?trknbr=',
'fedex_rest': 'https://www.fedex.com/wtrk/track/?trknbr=',
'dhl': 'https://www.dhl.com/en/express/tracking.html?AWB=',
'dhl_rest': 'https://www.dhl.com/en/express/tracking.html?AWB=',
}
base_link = ''
if self.carrier_id and self.carrier_id.tracking_link:
base_link = self.carrier_id.tracking_link
elif self.carrier_type:
base_link = DEFAULT_TRACKING.get(self.carrier_type, '')
if not base_link:
base_link = 'https://www.google.com/search?q='
return {
'type': 'ir.actions.act_url',
'url': '%s%s' % (base_link, self.tracking_number),
'target': 'new',
}
# ── Cron ─────────────────────────────────────────────────
@api.model
def _cron_refresh_tracking(self):
"""Auto-refresh tracking for all active (non-terminal) shipments."""
shipments = self.search([
('status', 'in', ('confirmed', 'shipped')),
('tracking_number', '!=', False),
])
_logger.info(
"Cron: refreshing tracking for %d shipments", len(shipments))
for shipment in shipments:
try:
shipment.action_refresh_tracking()
self.env.cr.commit()
except Exception as e:
self.env.cr.rollback()
_logger.warning(
"Cron: tracking refresh failed for %s: %s",
shipment.name, str(e))
# ── Return Labels ────────────────────────────────────────
def action_view_return_label(self):
return self._action_open_attachment(self.return_label_attachment_id)
def action_create_return_label(self):
"""Create a prepaid return label via Canada Post Authorized
Returns API. Bill-on-scan: charged only when the customer
uses the label at a post office.
"""
self.ensure_one()
if self.return_tracking_number:
raise ValidationError(
_("A return label has already been created "
"for this shipment."))
if not self.carrier_id:
raise ValidationError(
_("No carrier linked to this shipment."))
if not self.picking_id:
raise ValidationError(
_("No transfer linked to this shipment."))
carrier = self.carrier_id
customer = carrier.customer_number
warehouse_partner = (
self.picking_id.picking_type_id.warehouse_id.partner_id)
if not warehouse_partner:
raise ValidationError(
_("No warehouse address found. Please configure "
"a contact on the warehouse."))
if carrier.prod_environment:
base = "https://soa-gw.canadapost.ca"
else:
base = "https://ct.soa-gw.canadapost.ca"
url = "%s/rs/%s/%s/authorizedreturn" % (base, customer, customer)
xml = self._build_return_label_xml(carrier, warehouse_partner)
headers = {
'Content-Type': 'application/vnd.cpc.authreturn-v2+xml',
'Accept': 'application/vnd.cpc.authreturn-v2+xml',
'Accept-language': 'en-CA',
}
try:
resp = request(
method='POST', url=url, data=xml,
headers=headers,
auth=(carrier.username, carrier.password))
_logger.info(
"Return Label API %s%s", url, resp.status_code)
if resp.status_code not in (200, 201):
self._parse_cp_error_response(resp)
self._process_return_label_response(resp, carrier)
except ValidationError:
raise
except Exception as e:
raise ValidationError(
_("Failed to create return label: %s") % str(e))
def _build_return_label_xml(self, carrier, return_to_partner):
"""Build Authorized Return XML.
return_to_partner = warehouse address (where the return goes).
"""
ns = "http://www.canadapost.ca/ws/authreturn-v2"
root = etree.Element("authorized-return", xmlns=ns)
etree.SubElement(root, "service-code").text = "DOM.EP"
# Receiver — warehouse where the item is returned to
receiver = etree.SubElement(root, "receiver")
rec_name = etree.SubElement(receiver, "name")
rec_name.text = (return_to_partner.name or '')[:44]
rec_company = etree.SubElement(receiver, "company")
rec_company.text = (
return_to_partner.commercial_company_name
or return_to_partner.name or '')[:44]
rec_addr = etree.SubElement(receiver, "domestic-address")
etree.SubElement(rec_addr, "address-line-1").text = (
return_to_partner.street or '')[:44]
if return_to_partner.street2:
etree.SubElement(rec_addr, "address-line-2").text = (
return_to_partner.street2)[:44]
etree.SubElement(rec_addr, "city").text = (
return_to_partner.city or '')[:40]
etree.SubElement(rec_addr, "province").text = (
return_to_partner.state_id.code or '')
etree.SubElement(rec_addr, "postal-code").text = (
(return_to_partner.zip or '').replace(' ', ''))
# Parcel characteristics
parcel = etree.SubElement(root, "parcel-characteristics")
etree.SubElement(parcel, "weight").text = str(
max(self.weight or 0.5, 0.1))
# Print preferences
prefs = etree.SubElement(root, "print-preferences")
etree.SubElement(prefs, "output-format").text = (
carrier.fusion_cp_output_format or '8.5x11')
return etree.tostring(
root, xml_declaration=True, encoding='UTF-8')
def _process_return_label_response(self, resp, carrier):
"""Parse return label response, download label PDF,
store on shipment.
"""
api_resp = Response(resp)
result = api_resp.dict()
auth_return = result.get('authorized-return', result)
tracking_pin = auth_return.get('tracking-pin', '')
self.return_tracking_number = tracking_pin
# Get label artifact URL from links
links = auth_return.get('links', {})
link_list = links.get('link', [])
if isinstance(link_list, dict):
link_list = [link_list]
label_url = ''
for link in link_list:
if link.get('@rel') == 'labelDetails':
label_url = link.get('@href', '')
break
if label_url:
label_resp = request(
method='GET', url=label_url,
headers={
'Accept': 'application/pdf',
'Accept-language': 'en-CA',
},
auth=(carrier.username, carrier.password))
if label_resp.status_code == 200:
attachment = self.env['ir.attachment'].create({
'name': 'Return-Label-%s.pdf' % tracking_pin,
'type': 'binary',
'datas': base64.b64encode(
label_resp.content).decode(),
'res_model': self._name,
'res_id': self.id,
'mimetype': 'application/pdf',
})
self.return_label_attachment_id = attachment
self.message_post(
body=_("Return label created. Tracking: %s") % tracking_pin,
attachment_ids=(
self.return_label_attachment_id.ids
if self.return_label_attachment_id else []))

View File

@@ -0,0 +1,44 @@
from odoo import models, fields
class FusionTrackingEvent(models.Model):
_name = 'fusion.tracking.event'
_description = 'Shipping Tracking Event'
_order = 'event_datetime desc, id desc'
_rec_name = 'event_description'
shipment_id = fields.Many2one(
'fusion.shipment',
string='Shipment',
required=True,
ondelete='cascade',
index=True,
)
event_date = fields.Date(
string='Event Date',
)
event_time = fields.Char(
string='Event Time',
help='Time from Canada Post (HH:MM:SS)',
)
event_datetime = fields.Datetime(
string='Date/Time',
help='Combined date and time for sorting',
)
event_description = fields.Char(
string='Description',
)
event_type = fields.Char(
string='Event Type',
help='Canada Post event type code',
)
event_site = fields.Char(
string='Location',
)
event_province = fields.Char(
string='Province',
)
signatory_name = fields.Char(
string='Signatory',
help='Name of person who signed for delivery',
)

View File

@@ -0,0 +1,39 @@
from odoo import _, api, models
from odoo.addons.payment import utils as payment_utils
class PaymentProvider(models.Model):
_inherit = 'payment.provider'
@api.model
def _get_compatible_providers(self, *args, sale_order_id=None, report=None, **kwargs):
""" Override of payment to allow only COD providers if allow_cash_on_delivery is enabled for
selected UPS delivery method.
:param int sale_order_id: The sales order to be paid, if any, as a `sale.order` id.
:param dict report: The availability report.
:return: The compatible providers.
:rtype: payment.provider
"""
compatible_providers = super()._get_compatible_providers(
*args, sale_order_id=sale_order_id, report=report, **kwargs
)
sale_order = self.env['sale.order'].browse(sale_order_id).exists()
if (
sale_order.carrier_id.delivery_type in ('fusion_ups', 'fusion_ups_rest')
and sale_order.carrier_id.allow_cash_on_delivery
):
unfiltered_providers = compatible_providers
compatible_providers = compatible_providers.filtered(
lambda p: p.custom_mode == 'cash_on_delivery'
)
payment_utils.add_to_report(
report,
unfiltered_providers - compatible_providers,
available=False,
reason=_("UPS provider is configured to use only Collect on Delivery."),
)
return compatible_providers

View File

@@ -0,0 +1,15 @@
from odoo import fields, models
class PackageType(models.Model):
_inherit = "stock.package.type"
package_carrier_type = fields.Selection(selection_add=[
('fusion_canada_post', 'Canada Post'),
('fusion_ups', 'UPS'),
('fusion_ups_rest', 'UPS (REST)'),
('fusion_fedex', 'FedEx'),
('fusion_fedex_rest', 'FedEx (REST)'),
('fusion_dhl', 'DHL Express'),
('fusion_dhl_rest', 'DHL Express (REST)'),
])

View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class ResCompany(models.Model):
_inherit = "res.company"
def _default_uom_setting(self):
weight_uom_id = self.env.ref('uom.product_uom_kgm', raise_if_not_found=False)
if not weight_uom_id:
uom_categ_id = self.env.ref('uom.product_uom_categ_kgm').id
weight_uom_id = self.env['uom.uom'].search([('category_id', '=', uom_categ_id), ('factor', '=', 1)], limit=1)
return weight_uom_id
weight_unit_of_measurement_id = fields.Many2one('uom.uom',string="Weight Unit Of Measurement",help="Unit of measure for item weight",default=_default_uom_setting)

View File

@@ -0,0 +1,11 @@
from odoo import fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
property_ups_carrier_account = fields.Char(
string="UPS Account Number",
company_dependent=True,
help="UPS carrier account number for bill-my-account shipping.",
)

View File

@@ -0,0 +1,70 @@
from odoo import models, fields
class SaleOrder(models.Model):
_inherit = 'sale.order'
fusion_service_code = fields.Char(
string='Service Code',
copy=False,
help='Carrier service code selected during shipping method selection',
)
# Per-package dimensions & service info (replaces single-dim fields).
fusion_package_ids = fields.One2many(
'fusion.order.package',
'sale_order_id',
string='Packages',
copy=False,
)
# Legacy single-package dimension fields (kept for backward
# compatibility with existing orders -- new orders populate the
# One2many above and fill these from the first package).
fusion_package_length = fields.Float(
string='Package Length', copy=False)
fusion_package_width = fields.Float(
string='Package Width', copy=False)
fusion_package_height = fields.Float(
string='Package Height', copy=False)
fusion_shipment_ids = fields.One2many(
'fusion.shipment',
'sale_order_id',
string='Shipments',
)
fusion_shipment_count = fields.Integer(
string='Shipments',
compute='_compute_fusion_shipment_count',
)
partner_ups_carrier_account = fields.Char(
related='partner_id.property_ups_carrier_account',
string="UPS Carrier Account",
readonly=False,
)
ups_bill_my_account = fields.Boolean(
related='carrier_id.ups_bill_my_account',
string="UPS Bill My Account",
)
def _compute_fusion_shipment_count(self):
for order in self:
order.fusion_shipment_count = len(
order.fusion_shipment_ids)
def action_view_fusion_shipments(self):
self.ensure_one()
shipments = self.env['fusion.shipment'].search(
[('sale_order_id', '=', self.id)]
)
action = {
'type': 'ir.actions.act_window',
'name': 'Shipments',
'res_model': 'fusion.shipment',
'view_mode': 'list,form',
'domain': [('sale_order_id', '=', self.id)],
}
if len(shipments) == 1:
action['view_mode'] = 'form'
action['res_id'] = shipments.id
return action

View File

@@ -0,0 +1,34 @@
from odoo import models, fields
class StockPicking(models.Model):
_inherit = 'stock.picking'
fusion_shipment_count = fields.Integer(
string='Shipments',
compute='_compute_fusion_shipment_count',
)
def _compute_fusion_shipment_count(self):
Shipment = self.env['fusion.shipment']
for picking in self:
picking.fusion_shipment_count = Shipment.search_count(
[('picking_id', '=', picking.id)]
)
def action_view_fusion_shipments(self):
self.ensure_one()
shipments = self.env['fusion.shipment'].search(
[('picking_id', '=', self.id)]
)
action = {
'type': 'ir.actions.act_window',
'name': 'Shipments',
'res_model': 'fusion.shipment',
'view_mode': 'list,form',
'domain': [('picking_id', '=', self.id)],
}
if len(shipments) == 1:
action['view_mode'] = 'form'
action['res_id'] = shipments.id
return action

View File

@@ -0,0 +1,7 @@
from odoo import models, fields
class UoM(models.Model):
_inherit = 'uom.uom'
fedex_code = fields.Char(string='Fedex Code', help="UoM Code sent to FedEx")

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,12 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fusion_shipment_user,fusion.shipment.user,model_fusion_shipment,base.group_user,1,0,0,0
access_fusion_shipment_stock_user,fusion.shipment.stock.user,model_fusion_shipment,stock.group_stock_user,1,1,1,0
access_fusion_shipment_manager,fusion.shipment.manager,model_fusion_shipment,stock.group_stock_manager,1,1,1,1
access_fusion_tracking_event_user,fusion.tracking.event.user,model_fusion_tracking_event,base.group_user,1,0,0,0
access_fusion_tracking_event_stock_user,fusion.tracking.event.stock.user,model_fusion_tracking_event,stock.group_stock_user,1,1,1,0
access_fusion_tracking_event_manager,fusion.tracking.event.manager,model_fusion_tracking_event,stock.group_stock_manager,1,1,1,1
access_choose_delivery_fusion_rate_user,choose.delivery.fusion.rate.user,model_choose_delivery_fusion_rate,base.group_user,1,1,1,1
access_choose_delivery_fusion_package_user,choose.delivery.fusion.package.user,model_choose_delivery_fusion_package,base.group_user,1,1,1,1
access_fusion_order_package_user,fusion.order.package.user,model_fusion_order_package,base.group_user,1,0,0,0
access_fusion_order_package_stock_user,fusion.order.package.stock.user,model_fusion_order_package,stock.group_stock_user,1,1,1,0
access_fusion_order_package_manager,fusion.order.package.manager,model_fusion_order_package,stock.group_stock_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fusion_shipment_user fusion.shipment.user model_fusion_shipment base.group_user 1 0 0 0
3 access_fusion_shipment_stock_user fusion.shipment.stock.user model_fusion_shipment stock.group_stock_user 1 1 1 0
4 access_fusion_shipment_manager fusion.shipment.manager model_fusion_shipment stock.group_stock_manager 1 1 1 1
5 access_fusion_tracking_event_user fusion.tracking.event.user model_fusion_tracking_event base.group_user 1 0 0 0
6 access_fusion_tracking_event_stock_user fusion.tracking.event.stock.user model_fusion_tracking_event stock.group_stock_user 1 1 1 0
7 access_fusion_tracking_event_manager fusion.tracking.event.manager model_fusion_tracking_event stock.group_stock_manager 1 1 1 1
8 access_choose_delivery_fusion_rate_user choose.delivery.fusion.rate.user model_choose_delivery_fusion_rate base.group_user 1 1 1 1
9 access_choose_delivery_fusion_package_user choose.delivery.fusion.package.user model_choose_delivery_fusion_package base.group_user 1 1 1 1
10 access_fusion_order_package_user fusion.order.package.user model_fusion_order_package base.group_user 1 0 0 0
11 access_fusion_order_package_stock_user fusion.order.package.stock.user model_fusion_order_package stock.group_stock_user 1 1 1 0
12 access_fusion_order_package_manager fusion.order.package.manager model_fusion_order_package stock.group_stock_manager 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 KiB

View File

@@ -0,0 +1,95 @@
html {
padding: 50px;
font-family: sans-serif;
}
html h1 {
text-align: center;
}
html h2 {
text-align: center;
color: grey;
}
#exampleSlider {
position: relative;
}
@media (max-width: 767px) {
#exampleSlider {
border-color: transparent;
}
}
#exampleSlider .MS-content {
margin: 15px 5%;
overflow: hidden;
white-space: nowrap;
border: 1px solid red;
}
@media (max-width: 767px) {
#exampleSlider .MS-content {
margin: 0;
}
}
#exampleSlider .MS-content .item {
display: inline-block;
height: 30%;
overflow: hidden;
position: relative;
vertical-align: top;
border: 1px solid green;
border-right: none;
width: 33.33%;
}
@media (max-width: 1200px) {
#exampleSlider .MS-content .item {
width: 25%;
}
}
@media (max-width: 992px) {
#exampleSlider .MS-content .item {
width: 33.3333%;
}
}
@media (max-width: 767px) {
#exampleSlider .MS-content .item {
width: 50%;
}
}
#exampleSlider .MS-content .item p {
font-size: 30px;
text-align: center;
line-height: 1;
vertical-align: middle;
margin: 0;
padding: 10px 0;
}
#exampleSlider .MS-controls button {
position: absolute;
border: none;
background: transparent;
font-size: 30px;
outline: 0;
top: 35px;
}
@media (max-width: 767px) {
#exampleSlider .MS-controls button {
display: none;
}
}
#exampleSlider .MS-controls button:hover {
cursor: pointer;
}
#exampleSlider .MS-controls .MS-left {
left: 10px;
}
@media (max-width: 992px) {
#exampleSlider .MS-controls .MS-left {
left: -2px;
}
}
#exampleSlider .MS-controls .MS-right {
right: 10px;
}
@media (max-width: 992px) {
#exampleSlider .MS-controls .MS-right {
right: -2px;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -0,0 +1,173 @@
<html>
<head>
<link href="custom.css" rel="stylesheet"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8"/>
<title>Canada Post Shipping Integration with Odoo</title>
<meta name="description" content="Odoo Canada Post Shipping Integration. Canada Post Shipping connector for Odoo"/>
<meta name="keywords" content="shipping connector, Odoo Canada Post, Delivery Canada Post, Odoo Canada Post shipping, Canada Post Shipping,
integrate Canada Post, Canada Post odoo, shipping solution, accept shipment odoo, sync shipment odoo, Canada Post gateway integration"/>
<meta name="robots" content="index, follow"/>
<link type="text/css" rel="stylesheet" href="/assets/assets.css"/>
<link type="text/css" rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css2?family=Laila:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
</head>
<body>
<div id="top"></div>
<section class="oe_container bg-transparent fixed-top position-relative">
<div class="o_equal_col_sm">
<div class="shadow bg-white pb32 pt32 px-2"
style="border-radius: 15px">
<h3 class="oe_slogan"
style="color: #051f5a; font-family: Raleway; font-weight: 700; text-align: center; opacity: 1; font-size: 35px; margin-bottom: 25px; margin-top: 45px;">
Odoo Canada Post Shipping Integration</h3>
<img src="dash.png" style="width: 100%;">
</div>
</div>
</section>
<section class="oe_container overflow-hidden"
style="position-relative">
<div class="o_equal_col_sm">
<div class="px-2" style="border-radius:15px">
<h3 class="oe_slogan"
style="color:#0f1e40; font-family:Montserrat; font-weight:700; text-align:center; text-transform:uppercase; opacity:1; font-size:28px; margin-bottom:20px; margin-top:20px">
<span style="color:#2971FD">Why</span> Canada Post Odoo Shipping Integration?
</h3>
<p class="mb16 text-center text-black-dark px-3"
style="font-family:; font-weight:normal; color:#11335b; font-size:18px">
Integrate Canada Post to streamline shipping processes by obtaining <b>real-time courier quotes for
various services, generating shipping labels based on order details</b>, and providing <b>tracking
numbers with
links for shipment updates</b>
ensuring a seamless and efficient logistics workflow
</p>
</div>
</div>
</section>
<section class="oe_container pb-5">
<div class="mt64 mb64">
<h2 style="color:#091E42; font-family:'Raleway'; text-align:center; margin:25px auto; text-transform:uppercase"
class="oe_slogan">
<b>Features</b>
</h2>
<div class="row">
<div class="col-md-6 col-sm-12 mt32">
<div class="container shadow" style="border-radius:10px; padding:10px 0px">
<div class="col-md-3 shadow" style="float:left; margin: -10px 0px;">
<img class="img img-responsive"
src="images/icons_live_price.png"
style="width: 100%;height: auto;"></div>
<div class="col-md-9" style="padding-left:0; float:left; width:70%;margin-left: 20px;">
<h3 class="mt16 mb0" style="font-family:Roboto; font-weight:500; font-size:22px">Live Shipping
Price</h3>
<p class=" mt8" style="font-family:Roboto;">Get live price to ensure the best price for
your shipment</p>
</div>
</div>
</div>
<div class="col-md-6 col-sm-12 mt32">
<div class="container shadow" style="border-radius:10px; padding:10px 0px">
<div class="col-md-3 shadow" style="float:left; margin: -10px 0px;">
<img class="img img-responsive"
src="images/icons_shipping_label.png"
style="width: 100%;height: auto;"></div>
<div class="col-md-9" style="padding-left:0; float:left; width:70%;margin-left: 20px;">
<h3 class="mt16 mb0" style="font-family:Roboto; font-weight:500; font-size:22px">Shipping
Label</h3>
<p class="mt8" style="font-family:Roboto;">Generate shipping label using order information</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-12 mt32" style="margin: 0px auto;padding-top: 22px;">
<div class="container shadow" style="border-radius:10px; padding:10px 0px">
<div class="col-md-3 shadow" style="float:left; margin: -10px 0px;"><img class="img img-responsive"
src="images/icons_track_and_trace.png"
style="width: 100%;height: auto;">
</div>
<div class="col-md-9" style="padding-left:0; float:left; width:70%;margin-left: 20px;">
<h3 class="mt16 mb0" style="font-family:Roboto; font-weight:500; font-size:22px">Track &
Trace</h3>
<p class="mt8 mid_p_1200" style="font-family:Roboto;">Live track your shipment using the Canada
Post tracking number</p>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="container">
<div class="oe_row oe_spaced mt8 mb8" style="width: auto;">
<h3 class="oe_slogan">Canada Post Shipping Configuration</h3>
<div class="mt16 mb16" style="margin-left: 10px">
<img src="S1.png"
style="width: 100%; margin-top: 10px; border-radius: 5px; border: solid #E3E4E4; border-radius: 10px; border-width: 30px 7px 7px 7px;">
</div>
</div>
</section>
<section class="container">
<div class="oe_row oe_spaced mt8 mb8" style="width: auto;">
<h3 class="oe_slogan">Create Sale Order With Canada Post Delivery Method</h3>
<div class="mt16 mb16" style="margin-left: 10px">
<img src="S2.png"
style="width: 100%; margin-top: 10px; border-radius: 5px; border: solid #E3E4E4; border-radius: 10px; border-width: 30px 7px 7px 7px;">
</div>
</div>
</section>
<section class="container">
<div class="oe_row oe_spaced mt8 mb8" style="width: auto;">
<h3 class="oe_slogan">Generate Shipment With Tracking Number And Shipping Label</h3>
<div class="mt16 mb16" style="margin-left: 10px">
<img src="S3.png"
style="width: 100%; margin-top: 10px; border-radius: 5px; border: solid #E3E4E4; border-radius: 10px; border-width: 30px 7px 7px 7px;">
</div>
</div>
</section>
<section class="container">
<div class="oe_row oe_spaced mt8 mb8" style="width: auto;">
<h3 class="oe_slogan">Generated Shipment Label</h3>
<div class="mt16 mb16" style="margin: 0px auto;text-align: center;">
<img src="label.png"
style="margin-top: 10px; border-radius: 5px; border: solid #E3E4E4; border-radius: 10px; border-width: 7px 7px 7px 7px;">
</div>
</div>
</section>
<div class="container">
<section class="container">
<h4 style="color:#00008b; text-align:center; margin:25px auto; text-transform:uppercase;font-weight: 600;"
class="oe_slogan">
Contact Us
</h4>
<div style="text-align:center">
<span>Suggestions &amp; Feedback to:</span> <a href="mailto:support@nexasystems.ca">support@nexasystems.ca</a>
</div>
</section>
</div>
<div class="container" align="center">
<section class="container">
<a href="#top">
<img src="up_arrow.png" alt="up-arrow" style="height: 125px;">
</a>
</section>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="choose_delivery_carrier_view_form_fusion" model="ir.ui.view">
<field name="name">choose.delivery.carrier.form.fusion</field>
<field name="model">choose.delivery.carrier</field>
<field name="inherit_id" ref="delivery.choose_delivery_carrier_view_form"/>
<field name="arch" type="xml">
<!-- Hidden state fields -->
<xpath expr="//field[@name='delivery_price']" position="after">
<field name="fusion_selected_service" invisible="1"/>
<field name="fusion_dimension_unit_label" invisible="1"/>
<field name="fusion_weight_unit_label" invisible="1"/>
</xpath>
<!-- Hide base "Get rate" button for Fusion shipping carriers -->
<xpath expr="//button[@name='update_price']" position="attributes">
<attribute name="invisible">delivery_type in ('fixed', 'base_on_rule', 'fusion_canada_post')</attribute>
</xpath>
<!-- "Get Prices" button (inside the Cost / Get-rate row) -->
<xpath expr="//button[@name='update_price']" position="after">
<button name="update_price" type="object"
invisible="delivery_type != 'fusion_canada_post'">
<i class="oi oi-arrow-right me-1"/>Get Prices
</button>
</xpath>
<!--
Full-width sections AFTER the outer <group>.
This avoids them being crammed into the group's
label-field grid and gives them the full dialog width.
-->
<xpath expr="//form/group" position="after">
<!-- Package list -->
<div invisible="delivery_type != 'fusion_canada_post'"
class="px-3 mt-2">
<span class="o_form_label fw-bold mb-1">Packages</span>
<field name="fusion_package_ids" nolabel="1">
<list editable="bottom">
<field name="sequence" widget="handle"/>
<field name="package_type_id" string="Box Type"/>
<field name="package_length" string="L"/>
<field name="package_width" string="W"/>
<field name="package_height" string="H"/>
<field name="weight" string="Weight"/>
<field name="selected_price" string="Cost"
widget="monetary"
options="{'currency_field': 'currency_id'}"
optional="hide"/>
<field name="currency_id" column_invisible="1"/>
</list>
</field>
</div>
<!-- Service selection -->
<div invisible="delivery_type != 'fusion_canada_post' or not fusion_rate_ids"
class="px-3 mt-3">
<span class="o_form_label fw-bold mb-1">Select a Service</span>
<field name="fusion_rate_ids" nolabel="1">
<list create="0" delete="0" no_open="1"
decoration-bf="is_selected"
decoration-success="is_selected">
<field name="is_selected" column_invisible="1"/>
<field name="service_name" string="Service"/>
<field name="price" string="Shipping Cost"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="currency_id" column_invisible="1"/>
<field name="expected_delivery"
string="Expected Delivery"/>
<button name="action_select" type="object"
string="Select"
class="btn-sm btn-link"
icon="fa-circle-o"
invisible="is_selected"/>
<button name="action_select" type="object"
string="Selected"
class="btn-sm btn-primary"
icon="fa-check-circle"
invisible="not is_selected"/>
</list>
</field>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,351 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="delivery_carrier_form_view_fusion_shipping" model="ir.ui.view">
<field name="name">delivery.carrier.form.view.fusion.shipping</field>
<field name="model">delivery.carrier</field>
<field name="inherit_id" ref="delivery.view_delivery_carrier_form"/>
<field name="arch" type="xml">
<!-- ================================================================
Integration Level: custom label for Fusion carriers
================================================================ -->
<xpath expr="//field[@name='integration_level']" position="attributes">
<attribute name="invisible">delivery_type in ('fusion_canada_post', 'fusion_ups', 'fusion_ups_rest', 'fusion_fedex', 'fusion_fedex_rest', 'fusion_dhl', 'fusion_dhl_rest')</attribute>
</xpath>
<xpath expr="//field[@name='integration_level']" position="after">
<field name="integration_level"
invisible="delivery_type not in ('fusion_canada_post', 'fusion_ups', 'fusion_ups_rest', 'fusion_fedex', 'fusion_fedex_rest', 'fusion_dhl', 'fusion_dhl_rest')"
widget="radio"
options="{'horizontal': true}"
help="Get Prices: only fetch shipping prices on sale orders. Get Prices and Create Shipment: also generate labels and tracking when the delivery order is validated."/>
</xpath>
<xpath expr="//page[@name='destination']" position="before">
<!-- ════════════════════════════════════════════════════
CANADA POST Configuration
════════════════════════════════════════════════════ -->
<page string="Canada Post Configuration" name="fusion_cp_configuration"
invisible="delivery_type != 'fusion_canada_post'">
<group string="API Credentials"
invisible="delivery_type != 'fusion_canada_post'">
<group>
<field name="username" string="API Key"
required="delivery_type == 'fusion_canada_post'"
help="API key from the Canada Post Developer Program."/>
<field name="password" string="API Password" password="True"
required="delivery_type == 'fusion_canada_post'"
help="API password from the Canada Post Developer Program."/>
<field name="customer_number" required="delivery_type == 'fusion_canada_post'"
help="Your Canada Post customer number (mailed-by number)."/>
</group>
<group>
<field name="fusion_cp_type" string="Account Type"
required="delivery_type == 'fusion_canada_post'"/>
<field name="fusion_cp_contract_id" required="fusion_cp_type == 'commercial'"
invisible="fusion_cp_type == 'counter'"/>
</group>
</group>
<group>
<group string="Shipping Options">
<field name="service_type" string="Default Service"
required="delivery_type == 'fusion_canada_post'"
help="Default shipping service. Users can choose a different service when adding shipping to a sale order."/>
<field name="option_code" string="Shipping Option"/>
<field name="reason_for_export"/>
<field name="product_packaging_id"
help="Optional default package type. Dimensions will pre-fill in the shipping wizard but can be overridden per shipment."/>
<field name="fusion_cp_dimension_unit"
required="delivery_type == 'fusion_canada_post'"
help="Unit for package dimensions. Values are automatically converted to centimetres for the Canada Post API."/>
</group>
<group string="Payment &amp; Tracking">
<field name="fusion_cp_payment_method"
string="Payment Method"
required="delivery_type == 'fusion_canada_post'"/>
<field name="tracking_link"
help="Custom tracking URL. Leave blank to use the default Canada Post tracking page."/>
</group>
</group>
<group string="Label Settings"
invisible="delivery_type != 'fusion_canada_post'">
<group>
<field name="fusion_cp_output_format"
required="delivery_type == 'fusion_canada_post'"/>
</group>
</group>
</page>
<!-- ════════════════════════════════════════════════════
UPS REST Configuration
════════════════════════════════════════════════════ -->
<page string="UPS REST Configuration" name="fusion_ups_rest_configuration"
invisible="delivery_type != 'fusion_ups_rest'">
<group>
<group>
<field name="ups_shipper_number" required="delivery_type == 'fusion_ups_rest'"/>
<field name="ups_client_id" required="delivery_type == 'fusion_ups_rest'"/>
<field name="ups_client_secret" required="delivery_type == 'fusion_ups_rest'"/>
<field name="ups_default_service_type" required="delivery_type == 'fusion_ups_rest'"/>
</group>
<group>
<field name="ups_default_packaging_id" required="delivery_type == 'fusion_ups_rest'"
domain="[('package_carrier_type', '=', 'fusion_ups_rest')]"/>
<field name="ups_package_weight_unit" string="Package Weight Unit"
required="delivery_type == 'fusion_ups_rest'"/>
<field name="ups_package_dimension_unit"
required="delivery_type == 'fusion_ups_rest'"/>
<field name="ups_label_file_type" string="Label Format"
required="delivery_type == 'fusion_ups_rest'"/>
</group>
<group string="Options" name="ups_rest_vas" colspan="2">
<group>
<field name="ups_bill_my_account"
invisible="delivery_type != 'fusion_ups_rest'"/>
<field name="ups_saturday_delivery" string="Saturday Delivery"
required="delivery_type == 'fusion_ups_rest'"
invisible="ups_default_service_type in ['03','11','13','59','12','65','08']"/>
<field name="allow_cash_on_delivery"
required="delivery_type == 'fusion_ups_rest'"
invisible="ups_default_service_type == '96'"
help="This value added service enables UPS to collect the payment of the shipment from your customer."/>
<field name="ups_cod_funds_code"
required="delivery_type == 'fusion_ups_rest'"
invisible="not allow_cash_on_delivery" widget="radio"/>
<field name="can_generate_return" invisible="1"/>
<field name="return_label_on_delivery" invisible="not can_generate_return"/>
<field name="get_return_label_from_portal" invisible="not return_label_on_delivery"/>
</group>
<group>
<field name="ups_require_signature"/>
<field name="ups_duty_payment" string="Duties paid by"
required="delivery_type == 'fusion_ups_rest'"/>
</group>
</group>
</group>
</page>
<!-- ════════════════════════════════════════════════════
UPS Legacy Configuration
════════════════════════════════════════════════════ -->
<page string="UPS Configuration" name="fusion_ups_configuration"
invisible="delivery_type != 'fusion_ups'">
<group>
<group>
<field name="ups_username" required="delivery_type == 'fusion_ups'"/>
<field name="ups_passwd" required="delivery_type == 'fusion_ups'"/>
<field name="ups_shipper_number" required="delivery_type == 'fusion_ups'"/>
<field name="ups_access_number" required="delivery_type == 'fusion_ups'"/>
<field name="ups_default_service_type" required="delivery_type == 'fusion_ups'"/>
</group>
<group>
<field name="ups_default_package_type_id" required="delivery_type == 'fusion_ups'"
domain="[('package_carrier_type', '=', 'fusion_ups')]"/>
<field name="ups_package_weight_unit" string="Package Weight Unit"
required="delivery_type == 'fusion_ups'"/>
<field name="ups_package_dimension_unit"
required="delivery_type == 'fusion_ups'"/>
<field name="ups_label_file_type" string="Label Format"
required="delivery_type == 'fusion_ups'"/>
</group>
<group string="Options" name="ups_legacy_vas">
<field name="ups_bill_my_account" invisible="delivery_type != 'fusion_ups'"/>
<field name="ups_saturday_delivery" string="Saturday Delivery"
invisible="ups_default_service_type in ['03', '11', '13', '59', '12', '65', '08']"
required="delivery_type == 'fusion_ups'"/>
<field name="allow_cash_on_delivery"
required="delivery_type == 'fusion_ups'"
invisible="ups_default_service_type == '96'"
help="This value added service enables UPS to collect the payment of the shipment from your customer."/>
<field name="ups_cod_funds_code" invisible="not allow_cash_on_delivery"
required="delivery_type == 'fusion_ups'" widget="radio"/>
<field name="can_generate_return" invisible="1"/>
<field name="return_label_on_delivery" invisible="not can_generate_return"/>
<field name="get_return_label_from_portal" invisible="not return_label_on_delivery"/>
<field name="ups_duty_payment" string="Duties paid by"
required="delivery_type == 'fusion_ups'"/>
</group>
</group>
</page>
<!-- ════════════════════════════════════════════════════
FedEx REST Configuration
════════════════════════════════════════════════════ -->
<page string="FedEx REST Configuration" name="fusion_fedex_rest_configuration"
invisible="delivery_type != 'fusion_fedex_rest'">
<group>
<group>
<field name="fedex_rest_developer_key" required="delivery_type == 'fusion_fedex_rest'"/>
<field name="fedex_rest_developer_password" required="delivery_type == 'fusion_fedex_rest'"/>
<field name="fedex_rest_account_number" string="Account Number"
required="delivery_type == 'fusion_fedex_rest'"/>
<field name="fedex_rest_service_type" required="delivery_type == 'fusion_fedex_rest'"/>
<field name="fedex_rest_droppoff_type" required="delivery_type == 'fusion_fedex_rest'"/>
</group>
<group>
<field name="fedex_rest_default_package_type_id"
required="delivery_type == 'fusion_fedex_rest'"
domain="[('package_carrier_type', '=', 'fusion_fedex_rest')]"/>
<field name="fedex_rest_weight_unit" string="Package Weight Unit"
required="delivery_type == 'fusion_fedex_rest'"/>
<div class="o_td_label">
<span class="o_form_label">Package Length Unit</span>
</div>
<div class="o_field_widget">
<span invisible="fedex_rest_weight_unit != 'KG'">Centimeters</span>
<span invisible="fedex_rest_weight_unit != 'LB'">Inches</span>
</div>
<field name="fedex_rest_label_stock_type" required="delivery_type == 'fusion_fedex_rest'"/>
<field name="fedex_rest_label_file_type" string="Label Format"
required="delivery_type == 'fusion_fedex_rest'"/>
</group>
<group string="Options">
<field name="return_label_on_delivery" invisible="not can_generate_return"/>
<field name="get_return_label_from_portal" invisible="not return_label_on_delivery"/>
<field name="fedex_rest_email_notifications"/>
<field name="fedex_rest_duty_payment" string="Duties paid by"
required="delivery_type == 'fusion_fedex_rest'"/>
<field name="fedex_rest_residential_address"/>
<field name="fedex_rest_documentation_type"/>
<field name="fedex_rest_override_shipper_vat"/>
</group>
<group string="Extra Data" name="fedex_rest_extra_data" groups="base.group_no_one">
<field name="fedex_rest_extra_data_rate_request"/>
<field name="fedex_rest_extra_data_ship_request"/>
<field name="fedex_rest_extra_data_return_request"/>
</group>
</group>
</page>
<!-- ════════════════════════════════════════════════════
FedEx Legacy Configuration
════════════════════════════════════════════════════ -->
<page string="FedEx Configuration" name="fusion_fedex_configuration"
invisible="delivery_type != 'fusion_fedex'">
<group>
<group>
<field name="fedex_developer_key" required="delivery_type == 'fusion_fedex'"/>
<field name="fedex_developer_password" required="delivery_type == 'fusion_fedex'"/>
<field name="fedex_account_number" string="Account Number"
required="delivery_type == 'fusion_fedex'"/>
<field name="fedex_meter_number" required="delivery_type == 'fusion_fedex'"/>
<field name="fedex_service_type" required="delivery_type == 'fusion_fedex'"/>
<field name="fedex_droppoff_type" required="delivery_type == 'fusion_fedex'"/>
</group>
<group>
<field name="fedex_default_package_type_id"
required="delivery_type == 'fusion_fedex'"
domain="[('package_carrier_type', '=', 'fusion_fedex')]"/>
<field name="fedex_weight_unit" string="Package Weight Unit"
required="delivery_type == 'fusion_fedex'"/>
<div class="o_td_label">
<span class="o_form_label">Package Length Unit</span>
</div>
<div class="o_field_widget">
<span invisible="fedex_weight_unit != 'KG'">CM</span>
<span invisible="fedex_weight_unit != 'LB'">IN</span>
</div>
<field name="fedex_label_stock_type" required="delivery_type == 'fusion_fedex'"/>
<field name="fedex_label_file_type" string="Label Format"
required="delivery_type == 'fusion_fedex'"/>
<field name="fedex_document_stock_type" required="delivery_type == 'fusion_fedex'"/>
</group>
<group string="Options">
<field name="fedex_saturday_delivery" string="Saturday Delivery"
invisible="fedex_service_type not in ['INTERNATIONAL_PRIORITY', 'FEDEX_2_DAY', 'FIRST_OVERNIGHT', 'PRIORITY_OVERNIGHT']"
required="delivery_type == 'fusion_fedex'"/>
<field name="can_generate_return" invisible="1"/>
<field name="return_label_on_delivery" invisible="not can_generate_return"/>
<field name="get_return_label_from_portal" invisible="not return_label_on_delivery"/>
<field name="fedex_duty_payment" string="Duties paid by"
required="delivery_type == 'fusion_fedex'"/>
</group>
<group string="Extra Data" name="fedex_legacy_extra_data" groups="base.group_no_one">
<field name="fedex_extra_data_rate_request"/>
<field name="fedex_extra_data_ship_request"/>
<field name="fedex_extra_data_return_request"/>
</group>
</group>
</page>
<!-- ════════════════════════════════════════════════════
DHL REST Configuration
════════════════════════════════════════════════════ -->
<page string="DHL REST Configuration" name="fusion_dhl_rest_configuration"
invisible="delivery_type != 'fusion_dhl_rest'">
<group>
<group>
<field name="dhl_api_key" required="delivery_type == 'fusion_dhl_rest'"/>
<field name="dhl_api_secret" required="delivery_type == 'fusion_dhl_rest'"/>
<field name="dhl_account_number" required="delivery_type == 'fusion_dhl_rest'"/>
<field name="dhl_region_code" required="delivery_type == 'fusion_dhl_rest'"/>
<field name="dhl_product_code" required="delivery_type == 'fusion_dhl_rest'"/>
</group>
<group>
<field name="dhl_default_package_type_id"
required="delivery_type == 'fusion_dhl_rest'"
domain="[('package_carrier_type', '=', 'fusion_dhl_rest')]"/>
<field name="dhl_unit_system" required="delivery_type == 'fusion_dhl_rest'"/>
<field name="dhl_label_image_format" string="Label Format"
required="delivery_type == 'fusion_dhl_rest'"/>
<field name="dhl_label_template" required="delivery_type == 'fusion_dhl_rest'"/>
</group>
<group string="Options">
<field name="return_label_on_delivery" invisible="can_generate_return == False"/>
<field name="get_return_label_from_portal" invisible="return_label_on_delivery == False"/>
<field name="dhl_dutiable"/>
<field name="dhl_duty_payment" string="Duties paid by"
invisible="dhl_dutiable == False"
required="delivery_type == 'fusion_dhl_rest'"/>
</group>
</group>
<group string="Custom Data" name="dhl_rest_customs" groups="base.group_no_one">
<field name="dhl_extra_data_rate_request" colspan="2"/>
<field name="dhl_extra_data_ship_request" colspan="2"/>
<field name="dhl_extra_data_return_request" colspan="2"/>
</group>
</page>
<!-- ════════════════════════════════════════════════════
DHL Legacy Configuration
════════════════════════════════════════════════════ -->
<page string="DHL Configuration" name="fusion_dhl_configuration"
invisible="delivery_type != 'fusion_dhl'">
<group>
<group>
<field name="dhl_SiteID" required="delivery_type == 'fusion_dhl'"/>
<field name="dhl_password" required="delivery_type == 'fusion_dhl'"/>
<field name="dhl_account_number" required="delivery_type == 'fusion_dhl'"/>
<field name="dhl_region_code" required="delivery_type == 'fusion_dhl'"/>
<field name="dhl_product_code" required="delivery_type == 'fusion_dhl'"/>
</group>
<group>
<field name="dhl_default_package_type_id"
required="delivery_type == 'fusion_dhl'"
domain="[('package_carrier_type', '=', 'fusion_dhl')]"/>
<field name="dhl_package_weight_unit" required="delivery_type == 'fusion_dhl'"/>
<field name="dhl_package_dimension_unit" required="delivery_type == 'fusion_dhl'"/>
<field name="dhl_label_image_format" string="Label Format"
required="delivery_type == 'fusion_dhl'"/>
<field name="dhl_label_template" required="delivery_type == 'fusion_dhl'"/>
</group>
<group string="Options">
<field name="can_generate_return" invisible="1"/>
<field name="return_label_on_delivery" invisible="not can_generate_return"/>
<field name="get_return_label_from_portal" invisible="not return_label_on_delivery"/>
<field name="dhl_dutiable"/>
<field name="dhl_duty_payment" string="Duties paid by"
invisible="not dhl_dutiable"
required="delivery_type == 'fusion_dhl'"/>
</group>
<group string="Custom Data" name="dhl_legacy_customs" groups="base.group_no_one">
<field name="dhl_custom_data_request" nolabel="1"
placeholder="'rate': {}, 'ship': {}, 'return': {}"/>
</group>
</group>
</page>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,323 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Search View -->
<record id="view_fusion_shipment_search" model="ir.ui.view">
<field name="name">fusion.shipment.search</field>
<field name="model">fusion.shipment</field>
<field name="arch" type="xml">
<search string="Search Shipments">
<field name="name"/>
<field name="tracking_number"/>
<field name="shipment_id"/>
<field name="sale_order_id"/>
<field name="picking_id"/>
<field name="recipient_name"/>
<field name="carrier_type"/>
<filter name="filter_confirmed" string="Confirmed"
domain="[('status','=','confirmed')]"/>
<filter name="filter_shipped" string="Shipped"
domain="[('status','=','shipped')]"/>
<filter name="filter_delivered" string="Delivered"
domain="[('status','=','delivered')]"/>
<filter name="filter_returned" string="Returned"
domain="[('status','=','returned')]"/>
<filter name="filter_cancelled" string="Cancelled"
domain="[('status','=','cancelled')]"/>
<group>
<filter name="group_status" string="Status"
context="{'group_by': 'status'}"/>
<filter name="group_service" string="Service Type"
context="{'group_by': 'service_type'}"/>
<filter name="group_carrier_type" string="Carrier Type"
context="{'group_by': 'carrier_type'}"/>
<filter name="group_date" string="Shipment Date"
context="{'group_by': 'shipment_date:month'}"/>
</group>
</search>
</field>
</record>
<!-- List View -->
<record id="view_fusion_shipment_list" model="ir.ui.view">
<field name="name">fusion.shipment.list</field>
<field name="model">fusion.shipment</field>
<field name="arch" type="xml">
<list string="Shipments" default_order="shipment_date desc">
<field name="name" decoration-bf="1"/>
<field name="tracking_number"/>
<field name="recipient_name"/>
<field name="sale_order_id" optional="show"/>
<field name="picking_id" optional="show"/>
<field name="carrier_type" optional="show"/>
<field name="service_type"/>
<field name="currency_id" column_invisible="1"/>
<field name="shipping_cost" sum="Total Cost"/>
<field name="weight" optional="hide"/>
<field name="package_name" optional="hide"/>
<field name="shipment_date"/>
<field name="status"
decoration-info="status == 'draft'"
decoration-success="status in ('confirmed','shipped')"
decoration-bf="status == 'delivered'"
decoration-warning="status == 'returned'"
decoration-danger="status == 'cancelled'"
widget="badge"/>
</list>
</field>
</record>
<!-- Form View -->
<record id="view_fusion_shipment_form" model="ir.ui.view">
<field name="name">fusion.shipment.form</field>
<field name="model">fusion.shipment</field>
<field name="arch" type="xml">
<form string="Shipment">
<header>
<button name="action_refresh_tracking" type="object"
string="Refresh Tracking"
class="btn-primary"
icon="fa-refresh"
invisible="not tracking_number or status == 'cancelled'"/>
<button name="action_track_on_carrier" type="object"
string="Track Shipment"
class="btn-secondary"
icon="fa-external-link"
invisible="not tracking_number"/>
<button name="action_create_return_label" type="object"
string="Create Return Label"
class="btn-warning"
icon="fa-mail-reply"
invisible="status not in ('delivered', 'shipped') or return_tracking_number"
confirm="Return labels may incur additional charges. Continue?"/>
<button name="action_void_shipment" type="object"
string="Void Shipment"
class="btn-danger"
icon="fa-ban"
invisible="status in ('cancelled', 'delivered', 'returned')"
confirm="Are you sure you want to void this shipment? This cannot be undone."/>
<button name="action_reissue_shipment" type="object"
string="Reissue Shipment"
class="btn-warning"
icon="fa-repeat"
invisible="status != 'cancelled'"/>
<field name="status" widget="statusbar"
statusbar_visible="confirmed,shipped,delivered"/>
</header>
<sheet>
<div name="button_box" class="oe_button_box">
<button name="action_open_sale_order" type="object"
class="oe_stat_button" icon="fa-shopping-cart"
invisible="not sale_order_id">
<div class="o_field_widget o_stat_info">
<span class="o_stat_text">Sale Order</span>
</div>
</button>
<button name="action_open_picking" type="object"
class="oe_stat_button" icon="fa-truck"
invisible="not picking_id">
<div class="o_field_widget o_stat_info">
<span class="o_stat_text">Transfer</span>
</div>
</button>
<button name="action_refresh_tracking" type="object"
class="oe_stat_button" icon="fa-map-marker"
invisible="not tracking_number">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="tracking_event_count"/>
</span>
<span class="o_stat_text">Events</span>
</div>
</button>
</div>
<div class="oe_title">
<label for="name"/>
<h1><field name="name" readonly="1"/></h1>
</div>
<group>
<group string="Shipment Details">
<field name="tracking_number"/>
<field name="shipment_id"/>
<field name="carrier_id"/>
<field name="carrier_type"/>
<field name="service_type"/>
<field name="shipment_date"/>
<field name="shipping_cost"/>
<field name="weight"/>
<field name="package_name"
invisible="not package_name"/>
<field name="return_tracking_number"
invisible="not return_tracking_number"/>
</group>
<group string="Links">
<field name="sale_order_id"/>
<field name="picking_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<group>
<group string="Sender">
<field name="sender_name"/>
<field name="sender_address"/>
</group>
<group string="Recipient">
<field name="recipient_name"/>
<field name="recipient_address"/>
</group>
</group>
<notebook>
<page string="Labels &amp; Documents" name="labels">
<field name="label_attachment_id" invisible="1"/>
<field name="full_label_attachment_id" invisible="1"/>
<field name="receipt_attachment_id" invisible="1"/>
<field name="commercial_invoice_attachment_id" invisible="1"/>
<field name="return_label_attachment_id" invisible="1"/>
<group string="Labels">
<span class="o_form_label" invisible="not label_attachment_id">Printable Label (4x6)</span>
<button name="action_view_label" type="object"
class="btn btn-link p-0"
icon="fa-file-pdf-o"
string="Open"
invisible="not label_attachment_id"/>
<span class="o_form_label" invisible="not full_label_attachment_id">Full Label (8.5x11)</span>
<button name="action_view_full_label" type="object"
class="btn btn-link p-0"
icon="fa-file-pdf-o"
string="Open"
invisible="not full_label_attachment_id"/>
<span class="o_form_label" invisible="not return_label_attachment_id">Return Label</span>
<button name="action_view_return_label" type="object"
class="btn btn-link p-0"
icon="fa-file-pdf-o"
string="Open"
invisible="not return_label_attachment_id"/>
</group>
<group string="Documents">
<span class="o_form_label" invisible="not receipt_attachment_id">Receipt</span>
<button name="action_view_receipt" type="object"
class="btn btn-link p-0"
icon="fa-file-pdf-o"
string="Open"
invisible="not receipt_attachment_id"/>
<span class="o_form_label" invisible="not commercial_invoice_attachment_id">Commercial Invoice</span>
<button name="action_view_commercial_invoice" type="object"
class="btn btn-link p-0"
icon="fa-file-pdf-o"
string="Open"
invisible="not commercial_invoice_attachment_id"/>
</group>
</page>
<page string="Tracking History" name="tracking_history">
<group>
<group>
<field name="last_tracking_update"/>
</group>
<group>
<field name="delivery_date"
invisible="status not in ('delivered', 'returned')"/>
</group>
</group>
<field name="tracking_event_ids" readonly="1" nolabel="1">
<list string="Tracking Events"
default_order="event_datetime desc">
<field name="event_datetime" string="Date/Time"/>
<field name="event_description"/>
<field name="event_site" string="Location"/>
<field name="event_province"/>
<field name="signatory_name" optional="hide"/>
<field name="event_type" optional="hide"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Kanban View -->
<record id="view_fusion_shipment_kanban" model="ir.ui.view">
<field name="name">fusion.shipment.kanban</field>
<field name="model">fusion.shipment</field>
<field name="arch" type="xml">
<kanban default_group_by="status" class="o_kanban_small_column">
<field name="name"/>
<field name="tracking_number"/>
<field name="status"/>
<field name="shipping_cost"/>
<field name="shipment_date"/>
<field name="recipient_name"/>
<field name="service_type"/>
<field name="carrier_type"/>
<templates>
<t t-name="card">
<div class="oe_kanban_details">
<strong><field name="name"/></strong>
<div><field name="recipient_name"/></div>
<div class="text-muted">
<i class="fa fa-barcode"/> <field name="tracking_number"/>
</div>
<div class="text-muted">
<field name="service_type"/> |
<field name="shipment_date" widget="date"/>
</div>
<div>
<strong>$<field name="shipping_cost"/></strong>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Window Actions -->
<record id="action_fusion_shipment" model="ir.actions.act_window">
<field name="name">All Shipments</field>
<field name="res_model">fusion.shipment</field>
<field name="view_mode">list,form,kanban</field>
<field name="search_view_id" ref="view_fusion_shipment_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No shipments yet
</p>
<p>Shipments are created automatically when you validate a delivery
order with a shipping carrier.</p>
</field>
</record>
<record id="action_fusion_shipment_confirmed" model="ir.actions.act_window">
<field name="name">Confirmed</field>
<field name="res_model">fusion.shipment</field>
<field name="view_mode">list,form,kanban</field>
<field name="domain">[('status', '=', 'confirmed')]</field>
<field name="search_view_id" ref="view_fusion_shipment_search"/>
</record>
<record id="action_fusion_shipment_shipped" model="ir.actions.act_window">
<field name="name">Shipped</field>
<field name="res_model">fusion.shipment</field>
<field name="view_mode">list,form,kanban</field>
<field name="domain">[('status', '=', 'shipped')]</field>
<field name="search_view_id" ref="view_fusion_shipment_search"/>
</record>
<record id="action_fusion_shipment_delivered" model="ir.actions.act_window">
<field name="name">Delivered</field>
<field name="res_model">fusion.shipment</field>
<field name="view_mode">list,form,kanban</field>
<field name="domain">[('status', '=', 'delivered')]</field>
<field name="search_view_id" ref="view_fusion_shipment_search"/>
</record>
<record id="action_fusion_shipment_returned" model="ir.actions.act_window">
<field name="name">Returned</field>
<field name="res_model">fusion.shipment</field>
<field name="view_mode">list,form,kanban</field>
<field name="domain">[('status', '=', 'returned')]</field>
<field name="search_view_id" ref="view_fusion_shipment_search"/>
</record>
</odoo>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Top-level app menu -->
<menuitem id="menu_fusion_shipping_root"
name="Shipping"
web_icon="fusion_shipping,static/description/icon.png"
sequence="85"/>
<!-- Shipments parent -->
<menuitem id="menu_fusion_shipments"
name="Shipments"
parent="menu_fusion_shipping_root"
sequence="10"/>
<!-- Shipments sub-menus -->
<menuitem id="menu_fusion_shipments_all"
name="All Shipments"
parent="menu_fusion_shipments"
action="action_fusion_shipment"
sequence="1"/>
<menuitem id="menu_fusion_shipments_confirmed"
name="Confirmed"
parent="menu_fusion_shipments"
action="action_fusion_shipment_confirmed"
sequence="10"/>
<menuitem id="menu_fusion_shipments_shipped"
name="Shipped"
parent="menu_fusion_shipments"
action="action_fusion_shipment_shipped"
sequence="20"/>
<menuitem id="menu_fusion_shipments_delivered"
name="Delivered"
parent="menu_fusion_shipments"
action="action_fusion_shipment_delivered"
sequence="30"/>
<menuitem id="menu_fusion_shipments_returned"
name="Returned"
parent="menu_fusion_shipments"
action="action_fusion_shipment_returned"
sequence="40"/>
<!-- Configuration parent -->
<menuitem id="menu_fusion_config"
name="Configuration"
parent="menu_fusion_shipping_root"
sequence="90"/>
<!-- Carrier Settings action -->
<record id="action_fusion_carrier_settings" model="ir.actions.act_window">
<field name="name">Shipping Carriers</field>
<field name="res_model">delivery.carrier</field>
<field name="view_mode">list,form</field>
<field name="domain">[('delivery_type', 'like', 'fusion_')]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Configure your shipping carriers
</p>
</field>
</record>
<menuitem id="menu_fusion_carrier"
name="Carriers"
parent="menu_fusion_config"
action="action_fusion_carrier_settings"
sequence="10"/>
</odoo>

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Sale Order PDF: Shipping Information -->
<template id="report_saleorder_shipping_info"
inherit_id="sale.report_saleorder_document">
<xpath expr="//div[@name='so_total_summary']" position="after">
<t t-set="shipments" t-value="doc.fusion_shipment_ids.filtered(lambda s: s.status != 'cancelled')"/>
<div t-if="shipments" class="mt-4">
<h5><strong>Shipping Information</strong></h5>
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Tracking Number</th>
<th>Service</th>
<th class="text-end">Weight</th>
<th class="text-end">Cost</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr t-foreach="shipments" t-as="shipment">
<td><span t-field="shipment.tracking_number"/></td>
<td><span t-field="shipment.service_type"/></td>
<td class="text-end">
<span t-field="shipment.weight"/> kg
</td>
<td class="text-end">
<span t-field="shipment.shipping_cost"
t-options="{'widget': 'monetary', 'display_currency': shipment.currency_id}"/>
</td>
<td><span t-field="shipment.status"/></td>
</tr>
</tbody>
</table>
</div>
</xpath>
</template>
<!-- Invoice PDF: Shipping Information -->
<template id="report_invoice_shipping_info"
inherit_id="account.report_invoice_document">
<xpath expr="//div[@id='payment_term']" position="before">
<t t-set="sale_orders" t-value="o.line_ids.sale_line_ids.order_id"/>
<t t-set="shipments" t-value="sale_orders.mapped('fusion_shipment_ids').filtered(lambda s: s.status != 'cancelled')" t-if="sale_orders"/>
<div t-if="shipments" class="mt-4">
<h5><strong>Shipping Information</strong></h5>
<table class="table table-sm table-bordered">
<thead>
<tr>
<th>Tracking Number</th>
<th>Service</th>
<th class="text-end">Weight</th>
<th class="text-end">Cost</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr t-foreach="shipments" t-as="shipment">
<td><span t-field="shipment.tracking_number"/></td>
<td><span t-field="shipment.service_type"/></td>
<td class="text-end">
<span t-field="shipment.weight"/> kg
</td>
<td class="text-end">
<span t-field="shipment.shipping_cost"
t-options="{'widget': 'monetary', 'display_currency': shipment.currency_id}"/>
</td>
<td><span t-field="shipment.status"/></td>
</tr>
</tbody>
</table>
</div>
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form_fusion_shipping" model="ir.ui.view">
<field name="name">res.config.settings.view.form.fusion.shipping</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<app data-string="Shipping" string="Shipping"
name="fusion_shipping">
<block title="Carrier Configuration" name="carrier_config_block">
<setting string="Shipping Carrier Settings"
help="Configure your shipping API credentials, service types, label format, and shipping preferences.">
<div class="content-group">
<div class="mt8">
<button name="%(fusion_shipping.action_fusion_carrier_settings)d"
type="action"
class="btn-link"
icon="oi-arrow-right"
string="Open Carrier Configuration"/>
</div>
</div>
</setting>
<setting string="Shipment History"
help="View and manage all shipments, labels, and tracking information.">
<div class="content-group">
<div class="mt8">
<button name="%(fusion_shipping.action_fusion_shipment)d"
type="action"
class="btn-link"
icon="oi-arrow-right"
string="View Shipments"/>
</div>
</div>
</setting>
</block>
</app>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_partner_form_inherit_fusion_shipping" model="ir.ui.view">
<field name="name">res.partner.form.inherit.fusion.shipping</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='sales_purchases']//group[@name='sale']" position="inside">
<field name="property_ups_carrier_account"
string="UPS Account Number"
help="If set, this customer can have UPS shipping charged to their own account."/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_order_form_fusion_shipping" model="ir.ui.view">
<field name="name">sale.order.form.fusion.shipping</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_view_fusion_shipments" type="object"
class="oe_stat_button" icon="fa-plane"
invisible="fusion_shipment_count == 0">
<field name="fusion_shipment_count" widget="statinfo"
string="Shipments"/>
</button>
</xpath>
<!-- UPS Bill My Account fields -->
<xpath expr="//field[@name='partner_shipping_id']" position="after">
<field name="ups_bill_my_account" invisible="1"/>
<field name="partner_ups_carrier_account"
invisible="not ups_bill_my_account"/>
</xpath>
<!-- Show package details on the sale order -->
<xpath expr="//group[@name='note_group']" position="before">
<group string="Shipping Packages"
invisible="not fusion_package_ids"
colspan="6">
<field name="fusion_package_ids" nolabel="1"
colspan="6" readonly="1">
<list create="0" delete="0" no_open="1">
<field name="package_type_id" string="Box Type"
optional="show"/>
<field name="package_length" string="Length"/>
<field name="package_width" string="Width"/>
<field name="package_height" string="Height"/>
<field name="weight" string="Weight"/>
<field name="service_name" string="Service"/>
<field name="price" string="Cost"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
<field name="currency_id" column_invisible="1"/>
</list>
</field>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_picking_form_fusion_shipping" model="ir.ui.view">
<field name="name">stock.picking.form.fusion.shipping</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_view_fusion_shipments" type="object"
class="oe_stat_button" icon="fa-plane"
invisible="fusion_shipment_count == 0">
<field name="fusion_shipment_count" widget="statinfo"
string="Shipments"/>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
from . import choose_delivery_fusion_rate

View File

@@ -0,0 +1,446 @@
import json
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class ChooseDeliveryFusionPackage(models.TransientModel):
"""One package row in the Add Shipping wizard."""
_name = 'choose.delivery.fusion.package'
_description = 'Shipping Package (Wizard)'
_order = 'sequence, id'
wizard_id = fields.Many2one(
'choose.delivery.carrier',
string='Wizard',
ondelete='cascade',
)
sequence = fields.Integer(default=10)
package_type_id = fields.Many2one(
'stock.package.type',
string='Box Type',
domain="[('package_carrier_type', '=', 'fusion_canada_post')]",
)
package_length = fields.Float(string='Length')
package_width = fields.Float(string='Width')
package_height = fields.Float(string='Height')
weight = fields.Float(string='Weight')
# Per-package cost for the selected service (updated on service select)
selected_price = fields.Float(
string='Cost', digits='Product Price', readonly=True)
currency_id = fields.Many2one(
'res.currency',
related='wizard_id.currency_id',
)
@api.onchange('package_type_id')
def _onchange_package_type_id(self):
"""Pre-fill dimensions from selected box type."""
if self.package_type_id:
self.package_length = self.package_type_id.packaging_length
self.package_width = self.package_type_id.width
self.package_height = self.package_type_id.height
class ChooseDeliveryFusionRate(models.TransientModel):
_name = 'choose.delivery.fusion.rate'
_description = 'Shipping Rate Option'
wizard_id = fields.Many2one(
'choose.delivery.carrier',
string='Wizard',
ondelete='cascade',
)
service_code = fields.Char(string='Service Code')
service_name = fields.Char(string='Service')
price = fields.Float(string='Shipping Cost', digits='Product Price')
expected_delivery = fields.Char(string='Expected Delivery Date')
is_selected = fields.Boolean(string='Selected', default=False)
currency_id = fields.Many2one(
'res.currency',
related='wizard_id.currency_id',
)
# JSON: [{"pkg_id": <int>, "price": <float>}, ...]
per_package_prices = fields.Text(string='Per-Package Prices')
def action_select(self):
"""Select this rate and deselect others. Update per-package costs."""
self.ensure_one()
# Deselect all, then select this one
self.wizard_id.fusion_rate_ids.write({'is_selected': False})
self.is_selected = True
# Update per-package costs from stored JSON
if self.per_package_prices:
try:
pkg_prices = json.loads(self.per_package_prices)
for pp in pkg_prices:
pkg = self.env['choose.delivery.fusion.package'].browse(
pp['pkg_id'])
if pkg.exists():
pkg.selected_price = pp['price']
except (json.JSONDecodeError, KeyError):
pass
# Apply margin from the carrier
carrier = self.wizard_id.carrier_id
price = self.price
if carrier:
price = carrier._apply_margins(price, self.wizard_id.order_id)
# Check free_over
if carrier.free_over:
order = self.wizard_id.order_id
amount = order.currency_id._convert(
order.amount_untaxed,
order.company_id.currency_id,
order.company_id,
fields.Date.today(),
)
if amount >= carrier.amount:
price = 0.0
self.wizard_id.write({
'delivery_price': price,
'display_price': price,
'fusion_selected_service': self.service_code,
'fusion_selected_service_name': self.service_name,
'fusion_selected_expected_delivery': self.expected_delivery,
})
# Re-open the wizard to show updated selection
return {
'name': _('Add a shipping method'),
'type': 'ir.actions.act_window',
'res_model': 'choose.delivery.carrier',
'res_id': self.wizard_id.id,
'view_mode': 'form',
'target': 'new',
}
class ChooseDeliveryCarrier(models.TransientModel):
_inherit = 'choose.delivery.carrier'
fusion_rate_ids = fields.One2many(
'choose.delivery.fusion.rate',
'wizard_id',
string='Available Services',
)
fusion_selected_service = fields.Char(
string='Selected Service Code',
)
fusion_selected_service_name = fields.Char(
string='Selected Service Name',
)
fusion_selected_expected_delivery = fields.Char(
string='Selected Expected Delivery',
)
# -- Package list --
fusion_package_ids = fields.One2many(
'choose.delivery.fusion.package',
'wizard_id',
string='Packages',
)
# -- Unit labels --
fusion_dimension_unit_label = fields.Char(
string='Dimension Unit',
compute='_compute_fusion_dimension_unit_label',
)
fusion_weight_unit_label = fields.Char(
string='Weight Unit',
compute='_compute_fusion_weight_unit_label',
)
@api.depends('carrier_id')
def _compute_fusion_dimension_unit_label(self):
for rec in self:
if (rec.carrier_id
and rec.carrier_id.delivery_type == 'fusion_canada_post'):
rec.fusion_dimension_unit_label = (
rec.carrier_id.fusion_cp_dimension_unit or 'cm')
else:
rec.fusion_dimension_unit_label = ''
@api.depends('carrier_id')
def _compute_fusion_weight_unit_label(self):
for rec in self:
if (rec.carrier_id
and rec.carrier_id.delivery_type == 'fusion_canada_post'):
uom = (rec.order_id.company_id
.weight_unit_of_measurement_id)
rec.fusion_weight_unit_label = uom.name if uom else 'kg'
else:
rec.fusion_weight_unit_label = ''
@api.onchange('carrier_id')
def _onchange_carrier_id_fusion_packages(self):
"""When a CP carrier is selected, create one default package."""
if (self.carrier_id
and self.carrier_id.delivery_type == 'fusion_canada_post'
and not self.fusion_package_ids):
vals = {
'sequence': 10,
'weight': self.total_weight or 0.0,
}
# Pre-fill from default package type if set on carrier
if self.carrier_id.product_packaging_id:
pkg = self.carrier_id.product_packaging_id
vals['package_type_id'] = pkg.id
vals['package_length'] = pkg.packaging_length
vals['package_width'] = pkg.width
vals['package_height'] = pkg.height
self.fusion_package_ids = [(5, 0, 0), (0, 0, vals)]
# -- Rate fetching --
def update_price(self):
"""Override: for Canada Post, fetch all rates for all packages."""
if self.carrier_id.delivery_type == 'fusion_canada_post':
return self._update_fusion_rates()
return super().update_price()
def _get_fusion_package_info_for_pkg(self, pkg):
"""Build package_info dict for a single package, converted to cm."""
carrier = self.carrier_id
return {
'length': round(carrier._fusion_cp_convert_dimension_to_cm(
pkg.package_length), 1),
'width': round(carrier._fusion_cp_convert_dimension_to_cm(
pkg.package_width), 1),
'height': round(carrier._fusion_cp_convert_dimension_to_cm(
pkg.package_height), 1),
}
def _update_fusion_rates(self):
"""Fetch shipping service rates for every package and aggregate."""
carrier = self.carrier_id
packages = self.fusion_package_ids
if not packages:
raise UserError(_(
"Please add at least one package with dimensions."))
from_unit = (self.order_id.company_id
.weight_unit_of_measurement_id)
# -- Validate every package --
for pkg in packages:
if not (pkg.package_length and pkg.package_width
and pkg.package_height):
raise UserError(_(
"Please enter dimensions (L x W x H) "
"for all packages."))
if not pkg.weight:
raise UserError(_(
"Please enter weight for all packages."))
package_info = self._get_fusion_package_info_for_pkg(pkg)
weight_kg = pkg.weight
if from_unit:
weight_kg = round(carrier.convert_weight(
from_unit, carrier.weight_uom_id, weight_kg), 2)
carrier._fusion_cp_validate_package(weight_kg, package_info)
# -- Clear old rates --
self.fusion_rate_ids.unlink()
# -- Fetch rates per package --
# {service_code: {service_name, packages: [{pkg_id, price, exp}]}}
all_service_rates = {}
for pkg in packages:
package_info = self._get_fusion_package_info_for_pkg(pkg)
carrier_ctx = carrier.with_context(
order_weight=pkg.weight, # raw, in company UOM
cp_package_info=package_info,
)
rates = carrier_ctx.fusion_canada_post_rate_shipment_all(
self.order_id)
if isinstance(rates, dict) and rates.get('error_message'):
raise UserError(rates['error_message'])
if not rates:
raise UserError(_(
"No shipping prices available for this order."))
for rate in rates:
code = rate['service_code']
if code not in all_service_rates:
all_service_rates[code] = {
'service_name': rate['service_name'],
'packages': [],
}
all_service_rates[code]['packages'].append({
'pkg_id': pkg.id,
'price': rate['price'],
'expected_delivery': rate.get(
'expected_delivery', ''),
})
# -- Keep only services available for ALL packages --
num_packages = len(packages)
available = {
code: data
for code, data in all_service_rates.items()
if len(data['packages']) == num_packages
}
if not available:
raise UserError(_(
"No single shipping service covers all packages. "
"Try adjusting package dimensions or weight."))
# -- Find cheapest service --
cheapest_code = min(
available.keys(),
key=lambda c: sum(
p['price'] for p in available[c]['packages']))
# -- Create combined rate lines --
vals_list = []
for code, data in available.items():
total_price = sum(p['price'] for p in data['packages'])
expected_dates = [
p['expected_delivery'] for p in data['packages']
if p['expected_delivery']
]
expected = max(expected_dates) if expected_dates else ''
is_sel = (code == cheapest_code)
vals_list.append({
'wizard_id': self.id,
'service_code': code,
'service_name': data['service_name'],
'price': total_price,
'expected_delivery': expected,
'is_selected': is_sel,
'per_package_prices': json.dumps(data['packages']),
})
self.env['choose.delivery.fusion.rate'].create(vals_list)
# -- Auto-select cheapest: update packages and wizard --
cheapest_data = available[cheapest_code]
selected_price = sum(
p['price'] for p in cheapest_data['packages'])
for pkg_data in cheapest_data['packages']:
pkg_rec = packages.filtered(
lambda p, pid=pkg_data['pkg_id']: p.id == pid)
if pkg_rec:
pkg_rec.selected_price = pkg_data['price']
# Apply carrier margins
price = selected_price
if self.carrier_id:
price = self.carrier_id._apply_margins(
price, self.order_id)
if self.carrier_id.free_over:
amount = self.order_id.currency_id._convert(
self.order_id.amount_untaxed,
self.order_id.company_id.currency_id,
self.order_id.company_id,
fields.Date.today(),
)
if amount >= self.carrier_id.amount:
price = 0.0
expected_dates = [
p['expected_delivery'] for p in cheapest_data['packages']
if p['expected_delivery']
]
self.write({
'delivery_price': price,
'display_price': price,
'fusion_selected_service': cheapest_code,
'fusion_selected_service_name': (
cheapest_data['service_name']),
'fusion_selected_expected_delivery': (
max(expected_dates) if expected_dates else ''),
})
return {
'name': _('Add a shipping method'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'choose.delivery.carrier',
'res_id': self.id,
'target': 'new',
}
# -- Confirm --
def button_confirm(self):
"""Override: store per-package info on the sale order and enhance
the delivery line description."""
if self.carrier_id.delivery_type == 'fusion_canada_post':
order = self.order_id
# Clear previous package records
order.fusion_package_ids.unlink()
selected_code = self.fusion_selected_service or ''
selected_name = self.fusion_selected_service_name or ''
selected_delivery = (
self.fusion_selected_expected_delivery or '')
pkg_vals = []
for idx, pkg in enumerate(
self.fusion_package_ids.sorted('sequence')):
pkg_vals.append((0, 0, {
'sequence': (idx + 1) * 10,
'package_type_id': (
pkg.package_type_id.id
if pkg.package_type_id else False),
'package_length': pkg.package_length,
'package_width': pkg.package_width,
'package_height': pkg.package_height,
'weight': pkg.weight,
'service_code': selected_code,
'service_name': selected_name,
'price': pkg.selected_price,
'expected_delivery': selected_delivery,
}))
write_vals = {
'fusion_package_ids': pkg_vals,
'fusion_cp_service_code': selected_code,
}
# Backward compat: first-package dims in legacy fields
first_pkg = self.fusion_package_ids.sorted('sequence')[:1]
if first_pkg:
write_vals['fusion_cp_package_length'] = (
first_pkg.package_length)
write_vals['fusion_cp_package_width'] = (
first_pkg.package_width)
write_vals['fusion_cp_package_height'] = (
first_pkg.package_height)
order.write(write_vals)
res = super().button_confirm()
# Enhance delivery line description with service details
if (self.carrier_id.delivery_type == 'fusion_canada_post'
and self.fusion_selected_service_name):
delivery_line = self.order_id.order_line.filtered(
'is_delivery')
if delivery_line:
line = delivery_line[-1]
parts = [line.name]
parts.append(
"Service: %s"
% self.fusion_selected_service_name)
num_pkgs = len(self.fusion_package_ids)
if num_pkgs > 1:
parts.append("Packages: %d" % num_pkgs)
if self.fusion_selected_expected_delivery:
parts.append(
"Expected Delivery: %s"
% self.fusion_selected_expected_delivery)
line.name = '\n'.join(parts)
return res