changes
7
delivery_canadapost/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Synodica Solutions
|
||||
|
||||
## Changelog
|
||||
|
||||
### 16.0.0.0.0
|
||||
|
||||
- Initial version
|
||||
2
delivery_canadapost/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import canada_post_api
|
||||
29
delivery_canadapost/__manifest__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
# App information
|
||||
"name": "Canada Post Shipping",
|
||||
"version": "19.0.1.0.0",
|
||||
"category": "Inventory/Delivery",
|
||||
"summary": "Integration of Canada Post delivery services in Odoo "
|
||||
"to handle shipment operations, including get "
|
||||
"live rate, generating shipping labels, "
|
||||
"retrieving tracking numbers",
|
||||
"license": "OPL-1",
|
||||
"depends": ["stock_delivery", "mail"],
|
||||
# Views
|
||||
"data": [
|
||||
"data/delivery_canada_post_data.xml",
|
||||
"views/delivery_carrier_view.xml",
|
||||
],
|
||||
"images": ["static/description/canada_post_banner.gif"],
|
||||
# Author
|
||||
"author": "Synodica Solutions Pvt. Ltd.",
|
||||
"website": "https://synodica.com",
|
||||
"maintainer": "Synodica Solutions Pvt. Ltd.",
|
||||
"support": "support@synodica.com",
|
||||
# Technical
|
||||
"installable": True,
|
||||
"auto_install": False,
|
||||
"application": True,
|
||||
"price": "149.00",
|
||||
"currency": "USD",
|
||||
}
|
||||
2
delivery_canadapost/canada_post_api/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import canada_post_response
|
||||
from . import utils
|
||||
168
delivery_canadapost/canada_post_api/canada_post_response.py
Normal file
@@ -0,0 +1,168 @@
|
||||
import lxml
|
||||
import datetime
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from odoo.addons.delivery_canadapost.canada_post_api.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)
|
||||
|
||||
# print(self._dict)
|
||||
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, I find them meaningless
|
||||
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()))}
|
||||
#d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.items()}}
|
||||
|
||||
# TODO: Optimizations? Forces a node to type list
|
||||
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:
|
||||
# create and return a cElementTree DOM
|
||||
pass
|
||||
return self._dom
|
||||
|
||||
def dict(self):
|
||||
return self._dict
|
||||
|
||||
def json(self):
|
||||
return json.dumps(self.dict())
|
||||
219
delivery_canadapost/canada_post_api/utils.py
Normal file
@@ -0,0 +1,219 @@
|
||||
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
|
||||
#return str(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
|
||||
# print(("perftest_dict2xml() %s" % \
|
||||
# timeit.timeit("perftest_dict2xml()", number=50000,
|
||||
# setup="from __main__ import perftest_dict2xml")))
|
||||
|
||||
import doctest
|
||||
failure_count, test_count = doctest.testmod()
|
||||
sys.exit(failure_count)
|
||||
39
delivery_canadapost/data/delivery_canada_post_data.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo noupdate="0">
|
||||
<!-- Canada post Package -->
|
||||
<record id="canadapost_packaging_canadapost_BOX" model="stock.package.type">
|
||||
<field name="name">canadapost BOX</field>
|
||||
<field name="package_carrier_type">canada_post</field>
|
||||
<field name="packaging_length">100</field>
|
||||
<field name="width">100</field>
|
||||
<field name="height">100</field>
|
||||
<field name="max_weight">20.00</field>
|
||||
</record>
|
||||
|
||||
<!-- Canada post Delivery Carriers -->
|
||||
<record id="product_product_delivery_canadapost" model="product.product">
|
||||
<field name="name">Canada Post</field>
|
||||
<field name="default_code">Delivery</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>
|
||||
|
||||
<record id="delivery_carrier_canadapost" model="delivery.carrier">
|
||||
<field name="name">Canada Post</field>
|
||||
<field
|
||||
name="product_id"
|
||||
ref="delivery_canadapost.product_product_delivery_canadapost"
|
||||
/>
|
||||
<field name="delivery_type">canada_post</field>
|
||||
<field name="canadapost_type">commercial</field>
|
||||
<field name="option_code">SO</field>
|
||||
<field name="service_type">DOM.RP</field>
|
||||
<field
|
||||
name="product_packaging_id"
|
||||
ref="delivery_canadapost.canadapost_packaging_canadapost_BOX"
|
||||
/>
|
||||
</record>
|
||||
</odoo>
|
||||
3
delivery_canadapost/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import delivery_carrier
|
||||
from . import product_packaging
|
||||
from . import res_company
|
||||
509
delivery_canadapost/models/delivery_carrier.py
Normal file
@@ -0,0 +1,509 @@
|
||||
import string
|
||||
import random
|
||||
from requests import request
|
||||
import xml.etree.ElementTree as etree
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import RedirectWarning,ValidationError
|
||||
from odoo.addons.delivery_canadapost.canada_post_api.canada_post_response import Response
|
||||
|
||||
|
||||
class DeliveryCarrier(models.Model):
|
||||
_inherit = 'delivery.carrier'
|
||||
|
||||
delivery_type = fields.Selection(selection_add=[('canada_post', 'Canada Post')], ondelete={
|
||||
'canada_post': lambda recs: recs.write({'delivery_type': 'fixed', 'fixed_price': 0})})
|
||||
option_code = fields.Selection([('SO','SO - Signature'),
|
||||
('COV','COV - Coverage'),
|
||||
('COD','COD - Collect on delivery'),
|
||||
('PA18','PA18 - Proof of Age Required - 18'),
|
||||
('PA19','PA19 - Proof of Age Required - 19'),
|
||||
('HFP','HFP - Card for pickup'),
|
||||
('DNS','DNS - Do not safe drop'),
|
||||
('LAD','LAD - Leave at door - do not card'),
|
||||
],string="Option Code",
|
||||
help="Required if the corresponding parent XML element option exists. This is the option code indicating which option applies to this shipment.\n Note: The D2PO option indicates that the parcel will be delivered directly to a nearby Post Office. For the D2PO option, the following XML elements are required: \n name (under destination) \n client-voice-number (under destination) \n notification \n option-qualifier-2 \n Note: If you select Collect on Delivery (COD), specify Card for Pickup (HFP) or Deliver to Post Office (D2PO). This is to facilitate the collection of COD funds at a post office. If not specified, the system will default to HFP. \n Non-delivery handling codes (required for some U.S.A. and international shipments)\n RASE - Return at Sender’s Expense \n RTS - Return to Sender \n ABAN - Abandon")
|
||||
service_type = fields.Selection([('DOM.RP','DOM.RP - Regular Parcel'),
|
||||
('DOM.EP','DOM.EP - Expedited Parcel'),
|
||||
('DOM.XP','DOM.XP - Xpresspost'),
|
||||
('DOM.PC','DOM.PC - Priority'),
|
||||
('USA.XP','USA.XP - Xpresspost USA'),
|
||||
('USA.EP','USA.EP - Expedited Parcel USA'),
|
||||
('INT.IP.SURF','INT.IP.SURF - International Parcel Surface'),
|
||||
('INT.PW.PARCEL','INT.PW.PARCEL - Priority Worldwide parcel Int’l'),
|
||||
('INT.XP','INT.XP - Xpresspost International'),
|
||||
], string="Service Type",
|
||||
help="Canada Post delivery service used for shipping the item")
|
||||
product_packaging_id = fields.Many2one('stock.package.type', string="Default Package Type",help="Selected packaging type, used in the request parameter")
|
||||
|
||||
reason_for_export = fields.Selection([('DOC', 'DOC = document'),
|
||||
('SAM', 'SAM = commercial sample'),
|
||||
('REP', 'REP = repair or warranty'),
|
||||
('SOG', 'SOG = sale of goods'),
|
||||
('OTH', 'OTH = other')], string="Reason For Export",default="SOG",
|
||||
help="This is a code that represents the reason for export, which assists with border crossing.")
|
||||
|
||||
username = fields.Char("Username", copy=False, help="UserName provided by canada post.")
|
||||
password = fields.Char("Password", copy=False, help="Password provided by canada post.")
|
||||
customer_number = fields.Char("Customer Number", copy=False, help="The mailed by customer, Customer number provided by canada post.")
|
||||
tracking_link = fields.Char(string="Tracking Link",help="Tracking link(URL) useful to track the shipment or package from this URL.",size=256)
|
||||
|
||||
canadapost_type = fields.Selection(selection=[('commercial', 'Contract Shipping'), ('counter', 'Non-Contract Shipping')], string="Customer Type", required=False)
|
||||
canadapost_contract_id = fields.Char(string="Contract ID")
|
||||
canadapost_payment_method = fields.Selection([('CreditCard', 'Credit Card'), ('Account', 'Account')],
|
||||
string="Payment Method", default='CreditCard',
|
||||
help="This is the method of payment for the shipment. The default value is CreditCard.")
|
||||
|
||||
#set default weight_uom_id
|
||||
def _default_uom_in_delive(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_uom_id = fields.Many2one('uom.uom', string='Shipping UoM according to API UoM',help="Set equivalent unit of measurement according to provider unit of measurement. For Example, if the provider unit of measurement is KG then you have to select KG unit of measurement in the Shipping Unit of Measurement field.",default=_default_uom_in_delive)
|
||||
|
||||
#Get Canada post URL
|
||||
@api.model
|
||||
def get_canadapost_url(self):
|
||||
if self.prod_environment:
|
||||
return "https://soa-gw.canadapost.ca/rs/"
|
||||
else:
|
||||
return "https://ct.soa-gw.canadapost.ca/rs/"
|
||||
|
||||
def _compute_can_generate_return(self):
|
||||
super(DeliveryCarrier, self)._compute_can_generate_return()
|
||||
for carrier in self:
|
||||
if carrier.delivery_type == 'canada_post':
|
||||
carrier.can_generate_return = True
|
||||
|
||||
#Check Address filed is validating or not and return boolean value
|
||||
@api.model
|
||||
def validating_address(self, partner, additional_fields=[]):
|
||||
missing_value = []
|
||||
mandatory_fields = ['country_id', 'city', 'zip']
|
||||
mandatory_fields.extend(additional_fields)
|
||||
if not partner.street and not partner.street2 :
|
||||
mandatory_fields.append('street')
|
||||
for field in mandatory_fields :
|
||||
if not getattr(partner, field) :
|
||||
missing_value.append(field)
|
||||
return missing_value
|
||||
|
||||
#Check required value proper or not for Shipping
|
||||
def check_required_value_to_ship(self, orders):
|
||||
for order in orders :
|
||||
if not order.order_line:
|
||||
return _("You have not any item to ship. Please provide item first")
|
||||
else :
|
||||
order_lines_without_weight = order.order_line.filtered(lambda line_item: not line_item.product_id.type in ['service', 'digital'] and not line_item.product_id.weight and not line_item.is_delivery)
|
||||
for order_line in order_lines_without_weight :
|
||||
return _("Please define weight in product : \n %s") % order_line.product_id.name
|
||||
|
||||
# validating customer address
|
||||
missing_value = self.validating_address(order.partner_shipping_id)
|
||||
if missing_value :
|
||||
fields = ", ".join(missing_value)
|
||||
return (_("Missing the values of the Customer address. \n Missing field(s) : %s ") % fields)
|
||||
|
||||
# validation shipper address
|
||||
missing_value = self.validating_address(order.warehouse_id.partner_id)
|
||||
if missing_value :
|
||||
fields = ", ".join(missing_value)
|
||||
return (_("Missing the values of the Warehouse address. \n Missing field(s) : %s ") % fields)
|
||||
return False
|
||||
|
||||
# Return Weight
|
||||
def convert_weight(self,from_uom_unit ,to_uom_unit, weight):
|
||||
if not from_uom_unit:
|
||||
from_uom_unit = self.env[
|
||||
"product.template"
|
||||
]._get_weight_uom_id_from_ir_config_parameter()
|
||||
return from_uom_unit._compute_quantity(weight, to_uom_unit)
|
||||
|
||||
#Check Validate weight or not
|
||||
def check_max_weight(self, order, shipment_weight):
|
||||
for order_line in order.order_line:
|
||||
if order_line.product_id and order_line.product_id.weight > shipment_weight:
|
||||
return (_("Product weight is more than maximum weight."))
|
||||
return False
|
||||
|
||||
#Get Rate from API than set Rate
|
||||
def canada_post_rate_shipment(self, order):
|
||||
# check the address validation
|
||||
check_value = self.check_required_value_to_ship(order)
|
||||
# check the product weight is appropriate to maximum weight.
|
||||
if check_value:
|
||||
return {'success': False, 'price': 0.0, 'error_message': check_value, 'warning_message': False}
|
||||
# check the product weight is appropriate to maximum weight.
|
||||
shipment_weight = self.product_packaging_id.max_weight
|
||||
check_weight = {}
|
||||
if shipment_weight!=0.0:
|
||||
check_weight = self.check_max_weight(order, shipment_weight)
|
||||
if check_weight:
|
||||
return {'success': False, 'price': 0.0, 'error_message': check_weight, 'warning_message': False}
|
||||
|
||||
shipper_address = order.warehouse_id.partner_id or order.company_id.partner_id
|
||||
recipient_address = order.partner_shipping_id or order.partner_id
|
||||
# Convert weight in to the delivery method's weight UOM
|
||||
carrier_ctx = self.env.context
|
||||
weight = carrier_ctx.get("order_weight", 0.0) or order._get_estimated_weight() or 0.0
|
||||
total_weight = self.convert_weight(order.company_id and order.company_id.weight_unit_of_measurement_id,
|
||||
self.weight_uom_id,
|
||||
weight)
|
||||
declared_value = round(order.amount_untaxed, 2)
|
||||
declared_currency = order.currency_id.name
|
||||
price=0.0
|
||||
rate_dict = self.canada_post_get_shipping_rate(shipper_address, recipient_address, total_weight,
|
||||
picking_bulk_weight=False, packages=False,
|
||||
declared_value=declared_value, declared_currency=declared_currency,
|
||||
company_id=order.company_id)
|
||||
_logger.info("Rate Response Data : %s" % (rate_dict))
|
||||
if rate_dict.get('messages', False):
|
||||
return {'success': False, 'price':price, 'error_message': rate_dict['messages']['message']['description'],
|
||||
'warning_message': False}
|
||||
if rate_dict.get('price-quotes',False) and rate_dict.get('price-quotes').get('price-quote',False) :
|
||||
cnt = 0;
|
||||
quotes = rate_dict['price-quotes']['price-quote']
|
||||
if isinstance(quotes, dict):
|
||||
quotes = [quotes]
|
||||
for quote in quotes:
|
||||
if quote['service-code']==self.service_type:
|
||||
price = quote['price-details']['due']
|
||||
cnt+=1
|
||||
if cnt==0:
|
||||
return {'success': False, 'price': price, 'error_message': "Rate API dosen't provide this service type price",
|
||||
'warning_message': False}
|
||||
|
||||
if self.canadapost_type == 'counter' and self.service_type in ['DOM.RP','DOM.EP','DOM.XP','DOM.PC'] and self.option_code == 'COD':
|
||||
if order.amount_total + float(price)>= 999.99:
|
||||
raise ValidationError(
|
||||
_("Get Rate Request Fail : \n The COD amount cannot exceed 1000.00 in Non-Contract Shipping.")
|
||||
)
|
||||
if self.canadapost_type == 'commercial' and self.service_type in ['DOM.RP','DOM.EP','DOM.XP','DOM.PC'] and self.option_code == 'COD':
|
||||
if order.amount_total + float(price) >= 5000.00:
|
||||
raise ValidationError(
|
||||
_("Get Rate Request Fail : \n The COD amount cannot exceed 5000.00 in Contract Shipping.")
|
||||
)
|
||||
return {'success': True, 'price': float(price), 'error_message': False, 'warning_message': False}
|
||||
|
||||
#Send required XML data and than response from Canada Post API
|
||||
def canada_post_get_shipping_rate(self, shipper_address, recipient_address, total_weight, picking_bulk_weight,
|
||||
packages=False, declared_value=False, declared_currency=False, company_id=False):
|
||||
result = {}
|
||||
# built request data
|
||||
service_root = etree.Element("mailing-scenario")
|
||||
|
||||
if self.canadapost_type == 'commercial':
|
||||
service_root.attrib["xmlns"] = "http://www.canadapost.ca/ws/ship/rate-v4"
|
||||
else:
|
||||
service_root.attrib["xmlns"] = "http://www.canadapost.ca/ws/ship/rate-v3"
|
||||
etree.SubElement(service_root, "customer-number").text = self.customer_number
|
||||
parcel = etree.SubElement(service_root, "parcel-characteristics")
|
||||
etree.SubElement(parcel, "weight").text = str(total_weight)
|
||||
|
||||
etree.SubElement(service_root, "origin-postal-code").text = "%s" % (shipper_address.zip.replace(" ","").upper())
|
||||
|
||||
destination = etree.SubElement(service_root, "destination")
|
||||
if str(self.service_type[:3])=='DOM':
|
||||
domestic = etree.SubElement(destination, "domestic")
|
||||
etree.SubElement(domestic, "postal-code").text = "%s" % (recipient_address.zip.replace(" ","").upper())
|
||||
elif str(self.service_type[:3])=='USA':
|
||||
united_states = etree.SubElement(destination, "united-states")
|
||||
etree.SubElement(united_states, "zip-code").text = "%s" % (recipient_address.zip.upper())
|
||||
|
||||
elif str(self.service_type[:3])=='INT':
|
||||
international = etree.SubElement(destination, "international")
|
||||
etree.SubElement(international, "country-code").text = "%s" % (recipient_address.country_id and recipient_address.country_id.code)
|
||||
|
||||
url='%sship/price'%(self.get_canadapost_url())
|
||||
|
||||
base_data = etree.tostring(service_root).decode('utf-8')
|
||||
|
||||
if self.canadapost_type == 'commercial':
|
||||
headers = {"Accept": "application/vnd.cpc.ship.rate-v4+xml","Content-Type":"application/vnd.cpc.ship.rate-v4+xml"}
|
||||
else:
|
||||
headers = {"Accept": "application/vnd.cpc.ship.rate-v3+xml","Content-Type":"application/vnd.cpc.ship.rate-v3+xml"}
|
||||
_logger.info("Rate Request Data : %s" % (base_data))
|
||||
try:
|
||||
response_body = request(method='POST', url=url, data=base_data, headers=headers, auth=(self.username,self.password))
|
||||
api = Response(response_body)
|
||||
result = api.dict()
|
||||
_logger.info("Rate Response Data : %s" % (result))
|
||||
except Exception as e:
|
||||
result['error_message'] = e.message
|
||||
return result
|
||||
return result
|
||||
|
||||
# Random generate string and return
|
||||
def get_group_id(self):
|
||||
size = 15
|
||||
chars = string.ascii_uppercase
|
||||
return ''.join(random.choice(chars) for _ in range(size))
|
||||
|
||||
# Create Shipment and return generate label and receipt from Canada post API
|
||||
def canada_post_send_shipping(self, pickings):
|
||||
response = []
|
||||
for picking in pickings:
|
||||
weight_get = picking.shipping_weight or picking.weight
|
||||
from_unit = picking.company_id and picking.company_id.weight_unit_of_measurement_id or ""
|
||||
total_weight = round(self.convert_weight(from_unit,
|
||||
self.weight_uom_id,
|
||||
weight_get), 2)
|
||||
# get package type
|
||||
package_type = self.product_packaging_id
|
||||
for stock_quant in picking.move_line_ids.result_package_id:
|
||||
if not stock_quant.package_type_id:
|
||||
package_type = self.product_packaging_id
|
||||
else:
|
||||
package_type = stock_quant.package_type_id
|
||||
break
|
||||
package_info = self.get_canadapost_parcel(package_type)
|
||||
# Get the address of the sender and recipient
|
||||
destination_address = picking.partner_id
|
||||
sender_address = picking.picking_type_id and picking.picking_type_id.warehouse_id and picking.picking_type_id.warehouse_id.partner_id
|
||||
if self.canadapost_type == 'commercial':
|
||||
root_node = etree.Element("shipment")
|
||||
root_node.attrib["xmlns"] = "http://www.canadapost.ca/ws/shipment-v8"
|
||||
|
||||
etree.SubElement(root_node, "transmit-shipment").text ="true"
|
||||
etree.SubElement(root_node, "provide-receipt-info").text ="true"
|
||||
else:
|
||||
root_node = etree.Element("non-contract-shipment")
|
||||
root_node.attrib["xmlns"] = "http://www.canadapost.ca/ws/ncshipment-v4"
|
||||
|
||||
etree.SubElement(root_node, "requested-shipping-point").text="%s"%(sender_address.zip.replace(" ","").upper() or "")
|
||||
|
||||
delivery_spec_node=etree.SubElement(root_node, "delivery-spec")
|
||||
etree.SubElement(delivery_spec_node, "service-code").text="%s"%(self.service_type)
|
||||
|
||||
sender_node = etree.SubElement(delivery_spec_node, "sender")
|
||||
etree.SubElement(sender_node, "company").text=sender_address.name
|
||||
etree.SubElement(sender_node, "contact-phone").text ="%s"%(sender_address.phone or "")
|
||||
address_details=etree.SubElement(sender_node, "address-details")
|
||||
etree.SubElement(address_details, "address-line-1").text =sender_address.street or ""
|
||||
etree.SubElement(address_details, "city").text =sender_address.city or ""
|
||||
etree.SubElement(address_details, "prov-state").text ="%s"%(sender_address.state_id and sender_address.state_id.code or "")
|
||||
if self.canadapost_type == 'commercial':
|
||||
etree.SubElement(address_details , "country-code").text = "%s" % (sender_address.country_id and sender_address.country_id.code or "")
|
||||
etree.SubElement(address_details, "postal-zip-code").text ="%s"%(sender_address.zip.replace(" ","").upper() or "")
|
||||
|
||||
destination_node = etree.SubElement(delivery_spec_node, "destination")
|
||||
etree.SubElement(destination_node, "name").text =destination_address.name
|
||||
etree.SubElement(destination_node, "company").text =destination_address.name
|
||||
etree.SubElement(destination_node, "client-voice-number").text = destination_address.phone
|
||||
destination_address_details = etree.SubElement(destination_node, "address-details")
|
||||
etree.SubElement(destination_address_details , "address-line-1").text =destination_address.street or ""
|
||||
etree.SubElement(destination_address_details , "city").text =destination_address.city or ""
|
||||
etree.SubElement(destination_address_details , "prov-state").text ="%s"%(destination_address.state_id and destination_address.state_id.code or "")
|
||||
etree.SubElement(destination_address_details , "country-code").text = "%s" % (destination_address.country_id and destination_address.country_id.code or "")
|
||||
etree.SubElement(destination_address_details , "postal-zip-code").text ="%s"%(destination_address.zip.replace(" ","").upper() or "")
|
||||
|
||||
if self.option_code and not self.service_type in ['USA.XP','USA.EP','INT.IP.SURF','INT.PW.PARCEL','INT.XP']:
|
||||
options = etree.SubElement(delivery_spec_node, "options")
|
||||
option = etree.SubElement(options, "option")
|
||||
etree.SubElement(option , "option-code").text = str(self.option_code or "")
|
||||
|
||||
|
||||
if self.option_code in ['PA18','PA19']:
|
||||
option_pa = etree.SubElement(options, "option")
|
||||
etree.SubElement(option_pa , "option-code").text = 'SO'
|
||||
|
||||
if self.option_code == 'COD' or self.option_code == 'COV':
|
||||
etree.SubElement(option , "option-amount").text = str(picking.sale_id.amount_total or "")
|
||||
etree.SubElement(option , "option-qualifier-1").text ="true"
|
||||
else:
|
||||
options = etree.SubElement(delivery_spec_node, "options")
|
||||
option_usa = etree.SubElement(options, "option")
|
||||
etree.SubElement(option_usa , "option-code").text = 'RASE'
|
||||
|
||||
|
||||
parcel_characteristics= etree.SubElement(delivery_spec_node, "parcel-characteristics")
|
||||
etree.SubElement(parcel_characteristics, "weight").text ="%s"%(total_weight)
|
||||
|
||||
dimensions= etree.SubElement(parcel_characteristics, "dimensions ")
|
||||
etree.SubElement(dimensions, "length").text ="%s"%(package_info.get('length', 1))
|
||||
etree.SubElement(dimensions, "width").text ="%s"%(package_info.get('width', 1))
|
||||
etree.SubElement(dimensions, "height").text ="%s"%(package_info.get('height', 1))
|
||||
|
||||
preferences= etree.SubElement(delivery_spec_node, "preferences")
|
||||
etree.SubElement(preferences, "show-packing-instructions").text ="true"
|
||||
|
||||
customs = etree.SubElement(delivery_spec_node, "customs")
|
||||
etree.SubElement(customs, "currency").text = str(picking.sale_id.currency_id.name)
|
||||
if picking.sale_id.currency_id.rate:
|
||||
rate=picking.sale_id.currency_id.rate
|
||||
rate=round(rate,2)
|
||||
etree.SubElement(customs, "conversion-from-cad").text = str(rate or '')
|
||||
etree.SubElement(customs, "reason-for-export").text = "%s"%(self.reason_for_export)
|
||||
|
||||
sku_list = etree.SubElement(customs, "sku-list")
|
||||
for move_line in picking.move_ids:
|
||||
item = etree.SubElement(sku_list,"item")
|
||||
etree.SubElement(item, "customs-description").text = str(move_line.product_id.name)
|
||||
etree.SubElement(item, "unit-weight").text = str(move_line.weight)
|
||||
etree.SubElement(item, "customs-value-per-unit").text = str(move_line.product_id.lst_price)
|
||||
etree.SubElement(item, "customs-number-of-units").text = str(int(move_line.product_uom_qty))
|
||||
|
||||
if self.canadapost_type == 'commercial':
|
||||
settlement_info = etree.SubElement(delivery_spec_node, "settlement-info")
|
||||
etree.SubElement(settlement_info, "contract-id").text = self.canadapost_contract_id
|
||||
etree.SubElement(settlement_info, "intended-method-of-payment").text = self.canadapost_payment_method
|
||||
api_url=self.get_canadapost_url()
|
||||
if self.canadapost_type == 'commercial':
|
||||
url="%s%s/%s/shipment"%(api_url,self.customer_number,self.customer_number)
|
||||
else:
|
||||
url="%s%s/ncshipment"%(api_url,self.customer_number)
|
||||
|
||||
base_data= etree.tostring(root_node).decode('utf-8')
|
||||
|
||||
if self.canadapost_type == 'commercial':
|
||||
headers = {"Accept": "application/vnd.cpc.shipment-v8+xml","Content-Type":"application/vnd.cpc.shipment-v8+xml","Accept-language":"en-CA"}
|
||||
else:
|
||||
headers = {"Accept": "application/vnd.cpc.ncshipment-v4+xml","Content-Type":"application/vnd.cpc.ncshipment-v4+xml","Accept-language":"en-CA"}
|
||||
#try:
|
||||
_logger.info("Create Shipment Request Data : %s" % (base_data))
|
||||
response_body = request(method='POST', url=url, data=base_data, headers=headers, auth=(self.username,self.password))
|
||||
if response_body.status_code == 200:
|
||||
api = Response(response_body)
|
||||
result = api.dict()
|
||||
_logger.info("Create Shipment Response Data : %s" % (result))
|
||||
else:
|
||||
error_code = "%s" % (response_body.status_code)
|
||||
error_message = response_body.reason
|
||||
message = error_code + " " + error_message
|
||||
|
||||
api = Response(response_body)
|
||||
result = api.dict()
|
||||
|
||||
if result['messages']['message']['description']:
|
||||
raise ValidationError(_("ShipmentRequest Fail : \n %s" % (result['messages']['message']['description'])))
|
||||
else:
|
||||
raise ValidationError(_("ShipmentRequest Fail : %s \n More Information \n %s" % (message, response_body.text)))
|
||||
if self.canadapost_type == 'commercial':
|
||||
if not result['shipment-info']['shipment-id'] or not result['shipment-info']['links']['link']:
|
||||
raise RedirectWarning("ShipmentRequest Fail \n More Information \n %s" % (result))
|
||||
|
||||
shipment_id=str(result['shipment-info']['shipment-id'])
|
||||
else:
|
||||
if not result['non-contract-shipment-info']['shipment-id'] or not result['non-contract-shipment-info']['links']['link']:
|
||||
raise RedirectWarning("ShipmentRequest Fail \n More Information \n %s" % (result))
|
||||
|
||||
shipment_id=str(result['non-contract-shipment-info']['shipment-id'])
|
||||
commercial_invoice_url_attchment=""
|
||||
commercial_invoice = False
|
||||
url_attchment=""
|
||||
|
||||
if self.canadapost_type == 'commercial':
|
||||
for link in result['shipment-info']['links']['link']:
|
||||
if link['_rel'] == 'label':
|
||||
url_attchment = link['_href']
|
||||
if link['_rel'] =='commercialInvoice':
|
||||
commercial_invoice_url_attchment = link['_href']
|
||||
commercial_invoice=True
|
||||
else:
|
||||
for link in result['non-contract-shipment-info']['links']['link']:
|
||||
if link['_rel'] == 'label':
|
||||
url_attchment = link['_href']
|
||||
if link['_rel'] =='commercialInvoice':
|
||||
commercial_invoice_url_attchment = link['_href']
|
||||
commercial_invoice=True
|
||||
headers_attchment = {'Accept': 'application/pdf'}
|
||||
try:
|
||||
attachment_response = request(method='GET', url=url_attchment, headers=headers_attchment, auth=(
|
||||
self.username, self.password))
|
||||
_logger.info("Label Response Data : %s" % (attachment_response))
|
||||
picking.message_post(attachments=[
|
||||
('Shipment Label - %s.PDF' % (shipment_id),
|
||||
attachment_response.content)])
|
||||
|
||||
if commercial_invoice:
|
||||
commercial_invoice_attachment_response = request(method='GET', url=commercial_invoice_url_attchment, headers=headers_attchment, auth=(
|
||||
self.username, self.password))
|
||||
picking.message_post(attachments=[
|
||||
('Shipment Commercial Invoice - %s.PDF' % (shipment_id),
|
||||
commercial_invoice_attachment_response.content)])
|
||||
except Exception as e:
|
||||
raise RedirectWarning(e)
|
||||
if self.canadapost_type == 'commercial':
|
||||
headers = {"Accept": "application/vnd.cpc.shipment-v8+xml","Accept-language":"en-CA"}
|
||||
url_receipt = "%s"%(self.get_canadapost_url())+ str(self.customer_number)+"/"+str(self.customer_number)+"/shipment/" + str(shipment_id) + "/receipt"
|
||||
else:
|
||||
url_receipt = "%s"%(self.get_canadapost_url())+ str(self.customer_number) +"/ncshipment/" + str(shipment_id) + "/receipt"
|
||||
try:
|
||||
receipt_response= request(method='GET', url=url_receipt, headers=headers, auth=(self.username,self.password))
|
||||
if receipt_response.status_code == 200:
|
||||
api_receipt = Response(receipt_response)
|
||||
result_receipt = api_receipt.dict()
|
||||
_logger.info("Get shipment Detail Response Data : %s" % (result_receipt))
|
||||
else:
|
||||
result_receipt = {}
|
||||
error_code = "%s" % (receipt_response.status_code)
|
||||
error_message = response_body.reason
|
||||
message = error_code + " " + error_message
|
||||
mesage="ShipmentAcceptRequest Fail : %s \n More Information \n %s" % (message, response_body.text)
|
||||
picking.message_post(body=mesage)
|
||||
except Exception as e:
|
||||
picking.message_post(body=e)
|
||||
if self.canadapost_type == 'commercial':
|
||||
if result_receipt:
|
||||
extra_price = result_receipt['shipment-receipt'] and result_receipt['shipment-receipt']['cc-receipt-details'] and result_receipt['shipment-receipt']['cc-receipt-details']['charge-amount'] or 0.0
|
||||
else:
|
||||
extra_price = 0.0
|
||||
tracking_pin= result['shipment-info'].get('tracking-pin',False)
|
||||
else:
|
||||
if result_receipt:
|
||||
extra_price = result_receipt['non-contract-shipment-receipt'] and result_receipt['non-contract-shipment-receipt']['cc-receipt-details'] and result_receipt['non-contract-shipment-receipt']['cc-receipt-details']['charge-amount'] or 0.0
|
||||
else:
|
||||
extra_price = 0.0
|
||||
tracking_pin= result['non-contract-shipment-info'].get('tracking-pin',False)
|
||||
if not tracking_pin:
|
||||
_logger.info("This Service not provide the tracking no.Service is : %s"%self.service_type)
|
||||
shipping_data = {
|
||||
'exact_price': float(extra_price) or 0.0,
|
||||
'tracking_number': tracking_pin}
|
||||
response += [shipping_data]
|
||||
return response
|
||||
|
||||
# Tracking link return
|
||||
def canada_post_get_tracking_link(self, picking):
|
||||
link = picking.carrier_id.tracking_link or 'https://www.canadapost.ca/trackweb/en#/resultList?searchFor='
|
||||
res = '%s %s' % (link, picking.carrier_tracking_ref)
|
||||
return res
|
||||
|
||||
def canada_post_cancel_shipment(self, picking):
|
||||
raise ValidationError(_("Canada Post does not provide Shipment Cancel API!"))
|
||||
|
||||
def get_canadapost_parcel(self, package):
|
||||
packaging_length = (
|
||||
self.sudo()._canadapost_convert_dimension_to_uom(
|
||||
package.packaging_length, package.length_uom_name
|
||||
)
|
||||
)
|
||||
width = (
|
||||
self.sudo()._canadapost_convert_dimension_to_uom(
|
||||
package.width, package.length_uom_name
|
||||
)
|
||||
)
|
||||
height = (
|
||||
self.sudo()._canadapost_convert_dimension_to_uom(
|
||||
package.height, package.length_uom_name
|
||||
)
|
||||
)
|
||||
return {
|
||||
"length": packaging_length,
|
||||
"width": width,
|
||||
"height": height
|
||||
}
|
||||
|
||||
def _canadapost_convert_dimension_to_uom(self, dimension, length_uom_name):
|
||||
target_uom = self.env.ref("uom.product_uom_cm")
|
||||
from_uom = self.env["uom.uom"].sudo().search([("name", "=", length_uom_name)])
|
||||
if not from_uom:
|
||||
from_uom = self.env[
|
||||
"product.template"
|
||||
]._get_length_uom_id_from_ir_config_parameter()
|
||||
# Convert dimensions
|
||||
return from_uom._compute_quantity(dimension, target_uom)
|
||||
7
delivery_canadapost/models/product_packaging.py
Executable file
@@ -0,0 +1,7 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class PackageType(models.Model):
|
||||
_inherit = "stock.package.type"
|
||||
|
||||
package_carrier_type = fields.Selection([('canada_post', 'Canada Post')])
|
||||
16
delivery_canadapost/models/res_company.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- 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)
|
||||
|
||||
3
delivery_canadapost/pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["whool"]
|
||||
build-backend = "whool.buildapi"
|
||||
BIN
delivery_canadapost/static/description/GoShippo.gif
Normal file
|
After Width: | Height: | Size: 830 KiB |
BIN
delivery_canadapost/static/description/S1.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
delivery_canadapost/static/description/S2.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
delivery_canadapost/static/description/S3.png
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
delivery_canadapost/static/description/Shippit.gif
Normal file
|
After Width: | Height: | Size: 968 KiB |
BIN
delivery_canadapost/static/description/advanced_search.gif
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
delivery_canadapost/static/description/canada_post_005.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
delivery_canadapost/static/description/canada_post_banner.gif
Normal file
|
After Width: | Height: | Size: 990 KiB |
BIN
delivery_canadapost/static/description/canadapost_thumbnail.png
Normal file
|
After Width: | Height: | Size: 426 KiB |
BIN
delivery_canadapost/static/description/chitchat_shipping.gif
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
delivery_canadapost/static/description/company_icon.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
95
delivery_canadapost/static/description/custom.css
Normal 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;
|
||||
}
|
||||
}
|
||||
BIN
delivery_canadapost/static/description/dash.png
Normal file
|
After Width: | Height: | Size: 374 KiB |
BIN
delivery_canadapost/static/description/dpd.gif
Normal file
|
After Width: | Height: | Size: 983 KiB |
BIN
delivery_canadapost/static/description/dsv_banner.gif
Normal file
|
After Width: | Height: | Size: 917 KiB |
BIN
delivery_canadapost/static/description/icon.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
321
delivery_canadapost/static/description/index.html
Normal file
@@ -0,0 +1,321 @@
|
||||
<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"/>
|
||||
<meta name="google-site-verification" content="NGpd7kJ1PumTrCwdhQdBLlYQDa7HzofXBtuQ4cJCtHw"/>
|
||||
<title>Canada Post Shipping Integration with Odoo</title>
|
||||
<meta name="description" content="Odoo Canada Post shipping Shipping gatway. Canada Post Shipping connector Odoo"/>
|
||||
<meta name="keywords" content="shipping connector,Odoo Canada Post gatway, Delivery Canada Post, Odoo Canada Post shipping, Canada Post Shipping located across Global, Canada Post Shipping connect,
|
||||
integrate Canada Post ,Canada Post odoo, Canada Post store, shipping solution to your site, accept shipment odoo, accept shipping odoo, sync shipment odoo, sync order odoo, Canada Post gateway settings,Canada Post Shipping gateway integration, Shipping Gateway Providers In Global,
|
||||
Canada Post: The New Digital Shipping Solution for Retailer"/>
|
||||
<meta name="robots" content="index, follow"/>
|
||||
<link type="text/css" rel="stylesheet" href="/assets/assets.css"/>
|
||||
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"/>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.js"
|
||||
integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
|
||||
|
||||
<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 App From Synodica?
|
||||
</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 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 class="pb32 pt32" style="display:block; margin:0 auto; text-align:center">
|
||||
<div class="d-inline-block mx-3 my-3" style="max-width:348px">
|
||||
<div class="s_panel_video position-relative text-center" data-video-id="w17dnsMTt8M">
|
||||
<h3 class="oe_slogan"
|
||||
style="color:#091E42; font-family:Montserrat; font-weight:600; text-align:center; font-size:18px; opacity:1; margin:8px 0 16px">
|
||||
WATCH QUICK DEMO HERE
|
||||
</h3>
|
||||
|
||||
<!-- Play Button Centered -->
|
||||
<a target="_new" href="https://youtu.be/MIt4t8h71aQ"
|
||||
class="position-absolute top-50 start-50 translate-middle" style="padding-top: 50px;">
|
||||
<img class="img img-fluid" src="play_button.png" alt="Play Button">
|
||||
</a>
|
||||
|
||||
<!-- Thumbnail -->
|
||||
<a target="_new" href="https://youtu.be/MIt4t8h71aQ">
|
||||
<img class="img img-fluid shadow" src="canadapost_thumbnail.png"
|
||||
style="border-radius:10px; width:100%;">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 In 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>
|
||||
|
||||
|
||||
<section class="container">
|
||||
<div class="oe_row oe_spaced mt8 mb8" style="width: auto;">
|
||||
<div class="mt16 mb16" style="margin-left: 10px">
|
||||
<img src="synodica_services.png" style="width: 100%; height: 100%;">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Carousel -->
|
||||
<div class="row" style="padding: 0px 60px;">
|
||||
<section class="container">
|
||||
<h2 style="color:#00008b; text-align:center; margin:25px auto; text-transform:uppercase;font-size: 50px;font-weight: 600;"
|
||||
class="oe_slogan">
|
||||
Suggested Products
|
||||
</h2>
|
||||
<div id="suggested_products" class="row carousel slide mt64 mb32" data-bs-ride="carousel">
|
||||
<!-- The slideshow -->
|
||||
<div class="carousel-inner">
|
||||
<div class="carousel-item active" style="min-height: 292.4px;">
|
||||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
|
||||
<a href="https://apps.odoo.com/apps/modules/17.0/odoo_advance_search/">
|
||||
<div style="border-radius:10px" class="shadow-sm">
|
||||
<img class="img img-responsive center-block"
|
||||
style="border-top-left-radius:10px; border-top-right-radius:10px;height: 200px;width: -webkit-fill-available;width: -moz-available;width: -webkit-fill-available;"
|
||||
src="advanced_search.gif">
|
||||
<h4 class="mt0 text-truncate"
|
||||
style="padding:6% 4%; text-align:center; width:100%">
|
||||
Odoo Advanced Search
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left;padding-left: 20px;">
|
||||
<a href="https://apps.odoo.com/apps/modules/17.0/delivery_dpd_shipping/">
|
||||
<div style="border-radius:10px" class="shadow-sm">
|
||||
<img class="img img-responsive center-block"
|
||||
style="border-top-left-radius:10px; border-top-right-radius:10px;height: 200px;width: -webkit-fill-available;width: -moz-available;width: -webkit-fill-available;"
|
||||
src="dpd.gif">
|
||||
<h4 class="mt0 text-truncate"
|
||||
style="padding:6% 4%; text-align:center; width:100%;">
|
||||
DPD Shipping
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left;padding-left: 20px;">
|
||||
<a href="https://apps.odoo.com/apps/modules/17.0/delivery_shippo_ss/">
|
||||
<div style="border-radius:10px" class="shadow-sm">
|
||||
<img class="img img-responsive center-block"
|
||||
style="border-top-left-radius:10px; border-top-right-radius:10px;height: 200px;width: -webkit-fill-available;width: -moz-available;width: -webkit-fill-available;"
|
||||
src="GoShippo.gif">
|
||||
<h4 class="mt0 text-truncate"
|
||||
style="padding:6% 4%; text-align:center; width:100%">
|
||||
GoShippo Shipping
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="carousel-item" style="min-height: 292.4px;">
|
||||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
|
||||
<a href="https://apps.odoo.com/apps/modules/17.0/delivery_dsv_express/">
|
||||
<div style="border-radius:10px" class="shadow-sm">
|
||||
<img class="img img-responsive center-block"
|
||||
style="border-top-left-radius:10px; border-top-right-radius:10px;height: 200px;width: -webkit-fill-available;width: -moz-available;width: -webkit-fill-available;"
|
||||
src="dsv_banner.gif">
|
||||
<h4 class="mt0 text-truncate"
|
||||
style="padding:6% 4%; text-align:center; width:100%">
|
||||
DSV Express Shipping
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left;padding-left: 20px;">
|
||||
<a href="https://apps.odoo.com/apps/modules/17.0/delivery_chitchats/">
|
||||
<div style="border-radius:10px" class="shadow-sm">
|
||||
<img class="img img-responsive center-block"
|
||||
style="border-top-left-radius:10px; border-top-right-radius:10px;height: 200px;width: -webkit-fill-available;width: -moz-available;width: -webkit-fill-available;"
|
||||
src="chitchat_shipping.gif">
|
||||
<h4 class="mt0 text-truncate"
|
||||
style="padding:6% 4%; text-align:center; width:100%">
|
||||
ChitChats Shipping
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left;padding-left: 20px;">
|
||||
<a href="https://apps.odoo.com/apps/modules/17.0/delivery_shippit_ss/">
|
||||
<div style="border-radius:10px" class="shadow-sm">
|
||||
<img class="img img-responsive center-block"
|
||||
style="border-top-left-radius:10px; border-top-right-radius:10px;height: 200px;width: -webkit-fill-available;width: -moz-available;width: -webkit-fill-available;"
|
||||
src="Shippit.gif">
|
||||
<h4 class="mt0 text-truncate"
|
||||
style="padding:6% 4%; text-align:center; width:100%">
|
||||
Shippit Shipping
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Left and right controls -->
|
||||
<a class="carousel-control-prev" href="#suggested_products" data-bs-slide="prev"
|
||||
style="width:35px; color:#000; margin-left:-30px">
|
||||
<span class="carousel-control-prev-icon"><i class="fa fa-chevron-left"
|
||||
style="font-size:24px"></i></span>
|
||||
</a>
|
||||
<a class="carousel-control-next" href="#suggested_products" data-bs-slide="next"
|
||||
style="width:35px; color:#000; margin-right:-30px">
|
||||
<span class="carousel-control-next-icon"><i class="fa fa-chevron-right"
|
||||
style="font-size:24px"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<!-- End Carousel -->
|
||||
|
||||
<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 & Feedback to:</span> <a href="mailto:support@synodica.com">support@synodica.com </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>
|
||||
|
||||
BIN
delivery_canadapost/static/description/label.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
delivery_canadapost/static/description/play_button.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
delivery_canadapost/static/description/synodica_services.png
Normal file
|
After Width: | Height: | Size: 5.0 MiB |
BIN
delivery_canadapost/static/description/up_arrow.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
45
delivery_canadapost/views/delivery_carrier_view.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="delivery_carrier_form_view_canada_post" model="ir.ui.view">
|
||||
<field name="name">delivery.carrier.form.view.canada.post</field>
|
||||
<field name="model">delivery.carrier</field>
|
||||
<field name="inherit_id" ref="delivery.view_delivery_carrier_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='destination']" position='before'>
|
||||
<page string="Configuration" name="configuration" invisible="delivery_type != 'canada_post'">
|
||||
<p class="alert alert-danger" role="alert" invisible="delivery_type != 'canada_post'">
|
||||
Note : Weight UOM must be select KG in Canada Post provider.
|
||||
</p>
|
||||
<group>
|
||||
<group>
|
||||
<field name="canadapost_type" required="delivery_type == 'canada_post'"/>
|
||||
<field name="canadapost_contract_id" required="canadapost_type == 'commercial'"
|
||||
invisible="canadapost_type == 'counter'"/>
|
||||
<field name="service_type" required="delivery_type == 'canada_post'"/>
|
||||
<field name="option_code"/>
|
||||
<field name="reason_for_export"/>
|
||||
<field name="product_packaging_id"
|
||||
required="delivery_type == 'canada_post'"
|
||||
/>
|
||||
</group>
|
||||
<group string="Shipping Option">
|
||||
<field
|
||||
name="canadapost_payment_method"
|
||||
string="Payment Method"
|
||||
required="delivery_type == 'canada_post'"
|
||||
/>
|
||||
</group>
|
||||
</group>
|
||||
<group invisible="delivery_type != 'canada_post'">
|
||||
<field name="username" required="delivery_type == 'canada_post'"/>
|
||||
<field name="password" password="True"
|
||||
required="delivery_type == 'canada_post'"/>
|
||||
<field name="customer_number" required="delivery_type == 'canada_post'"/>
|
||||
<field name="tracking_link"/>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||