From a4626f2ef0a39a0e6cf1b3bd8b77afde98747334 Mon Sep 17 00:00:00 2001 From: Cedric Collins Date: Mon, 29 Aug 2022 18:50:39 -0500 Subject: [PATCH] [ADD] delivery_purolator H10820 --- delivery_purolator/__init__.py | 1 + delivery_purolator/__manifest__.py | 28 +++++++ .../data/delivery_purolator_demo.xml | 23 +++++ delivery_purolator/models/__init__.py | 1 + .../models/delivery_purolator.py | 41 +++++++++ .../models/purolator_services.py | 83 +++++++++++++++++++ delivery_purolator/tests/__init__.py | 1 + delivery_purolator/tests/test_purolator.py | 50 +++++++++++ .../views/delivery_purolator_views.xml | 25 ++++++ 9 files changed, 253 insertions(+) create mode 100644 delivery_purolator/__init__.py create mode 100644 delivery_purolator/__manifest__.py create mode 100644 delivery_purolator/data/delivery_purolator_demo.xml create mode 100644 delivery_purolator/models/__init__.py create mode 100644 delivery_purolator/models/delivery_purolator.py create mode 100644 delivery_purolator/models/purolator_services.py create mode 100644 delivery_purolator/tests/__init__.py create mode 100644 delivery_purolator/tests/test_purolator.py create mode 100644 delivery_purolator/views/delivery_purolator_views.xml diff --git a/delivery_purolator/__init__.py b/delivery_purolator/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/delivery_purolator/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/delivery_purolator/__manifest__.py b/delivery_purolator/__manifest__.py new file mode 100644 index 00000000..b3c7394e --- /dev/null +++ b/delivery_purolator/__manifest__.py @@ -0,0 +1,28 @@ +{ + 'name': 'Purolator Shipping', + 'summary': 'Send your shippings through Purolator and track them online.', + 'version': '15.0.1.0.1', + 'author': "Hibou Corp.", + 'category': 'Warehouse', + 'license': 'OPL-1', + 'images': [], + 'website': "https://hibou.io", + 'description': """ +Purolator Shipping +================== + +* Provides estimates on shipping costs. +* Send your shippings and track packages. +""", + 'depends': [ + 'delivery_hibou', + ], + 'demo': [ + 'data/delivery_purolator_demo.xml', + ], + 'data': [ + 'views/delivery_purolator_views.xml', + ], + 'auto_install': False, + 'installable': True, +} diff --git a/delivery_purolator/data/delivery_purolator_demo.xml b/delivery_purolator/data/delivery_purolator_demo.xml new file mode 100644 index 00000000..2b8cbcc2 --- /dev/null +++ b/delivery_purolator/data/delivery_purolator_demo.xml @@ -0,0 +1,23 @@ + + + + + + + Purolator Delivery + Delivery_Puro + service + + + + 0.0 + order + + + Purolator Test Carrier + + purolator + + + + diff --git a/delivery_purolator/models/__init__.py b/delivery_purolator/models/__init__.py new file mode 100644 index 00000000..ab7a929c --- /dev/null +++ b/delivery_purolator/models/__init__.py @@ -0,0 +1 @@ +from . import delivery_purolator diff --git a/delivery_purolator/models/delivery_purolator.py b/delivery_purolator/models/delivery_purolator.py new file mode 100644 index 00000000..8138c337 --- /dev/null +++ b/delivery_purolator/models/delivery_purolator.py @@ -0,0 +1,41 @@ +from odoo import fields, models, _ +from .purolator_services import PurolatorClient + + +class ProviderPurolator(models.Model): + _inherit = 'delivery.carrier' + + delivery_type = fields.Selection(selection_add=[('purolator', 'Purolator')], ondelete={'purolator': 'cascade'}) + purolator_api_key = fields.Char(string='Purolator API Key', groups='base.group_system') + purolator_password = fields.Char(string='Purolator Password', groups='base.group_system') + purolator_activation_key = fields.Char(string='Purolator Activation Key', groups='base.group_system') + purolator_account_number = fields.Char(string='Purolator Account Number', groups='base.group_system') + purolator_service_type = fields.Selection([('PurolatorExpress', 'PurolatorExpress')], default='PurolatorExpress') + + def purolator_rate_shipment(self, order): + # sudoself = self.sudo() + sender = self.get_shipper_warehouse(order=order) + receiver = self.get_recipient(order=order) + receiver_address = { + 'City': receiver.city, + 'Province': receiver.state_id.code, + 'Country': receiver.country_id.code, + 'PostalCode': receiver.zip, + } + weight_uom_id = self.env['product.template']._get_weight_uom_id_from_ir_config_parameter() + weight = weight_uom_id._compute_quantity(order._get_estimated_weight(), self.env.ref('uom.product_uom_lb'), round=False) + client = PurolatorClient(self) + res = client.get_quick_estimate(sender.zip, receiver_address, 'CustomerPackaging', weight) + if res['error']: + return { + 'success': False, + 'price': 0.0, + 'error_message': _(res['error']), + 'warning_message': False, + } + return { + 'success': True, + 'price': res['price'], + 'error_message': False, + 'warning_message': False, + } diff --git a/delivery_purolator/models/purolator_services.py b/delivery_purolator/models/purolator_services.py new file mode 100644 index 00000000..0fc09c33 --- /dev/null +++ b/delivery_purolator/models/purolator_services.py @@ -0,0 +1,83 @@ +from requests import Session +from requests.auth import HTTPBasicAuth +from zeep import Client +from zeep.cache import SqliteCache +from zeep.transports import Transport +from odoo.exceptions import UserError +import logging +_logger = logging.getLogger(__name__) + + +class PurolatorClient(object): + def __init__(self, carrier): + if carrier.delivery_type != 'purolator': + raise UserError('Invalid carrier: %s' % carrier.name) + self.api_key = carrier.purolator_api_key + self.password = carrier.purolator_password + self.activation_key = carrier.purolator_activation_key + self.account_number = carrier.purolator_account_number + self.service_type = carrier.purolator_service_type + self._wsdl_base = "https://devwebservices.purolator.com" + if carrier.prod_environment: + self._wsdl_base = "https://webservices.purolator.com" + + session = Session() + session.auth = HTTPBasicAuth(self.api_key, self.password) + self.transport = Transport(cache=SqliteCache(), session=session) + + def _get_client(self, wsdl_path): + client = Client(self._wsdl_base + wsdl_path, + transport=self.transport) + request_context = client.get_element('ns1:RequestContext') + header_value = request_context( + Version='2.0', + Language='en', + GroupID='xxx', + RequestReference='RatingExample', + UserToken=self.activation_key, + ) + # _logger.warning('*** header_value:\n%s' % header_value) + client.set_default_soapheaders([header_value]) + return client + + def get_quick_estimate(self, sender_postal_code, receiver_address, package_type, total_weight): + """ Call GetQuickEstimate + + :param sender_postal_code: string + :param receiver_address: dict {'City': string, + 'Province': string, + 'Country': string, + 'PostalCode': string} + :param package_type: string + :param total_weight: float (in pounds) + :returns: dict + """ + client = self._get_client('/EWS/V2/Estimating/EstimatingService.asmx?wsdl') + response = client.service.GetQuickEstimate( + BillingAccountNumber=self.account_number, + SenderPostalCode=sender_postal_code, + ReceiverAddress=receiver_address, + PackageType=package_type, + TotalWeight={ + 'Value': 10.0, + 'WeightUnit': 'lb', + }, + ) + # _logger.warning('**** GetQuickEstimate response:\n%s', response) + errors = response['body']['ResponseInformation']['Errors'] + if errors: + return { + 'price': 0.0, + 'error': '\n'.join(['%s: %s' % (error['Code'], error['Description']) for error in errors['Error']]), + } + shipments = response['body']['ShipmentEstimates']['ShipmentEstimate'] + shipment = list(filter(lambda s: s['ServiceID'] == self.service_type, shipments)) + if shipment: + return { + 'price': shipment[0]['TotalPrice'], + 'error': False, + } + return { + 'price': 0.0, + 'error': 'Purolator ServiceID not found', + } diff --git a/delivery_purolator/tests/__init__.py b/delivery_purolator/tests/__init__.py new file mode 100644 index 00000000..e35bb8ef --- /dev/null +++ b/delivery_purolator/tests/__init__.py @@ -0,0 +1 @@ +from . import test_purolator diff --git a/delivery_purolator/tests/test_purolator.py b/delivery_purolator/tests/test_purolator.py new file mode 100644 index 00000000..9e4e546e --- /dev/null +++ b/delivery_purolator/tests/test_purolator.py @@ -0,0 +1,50 @@ + +from odoo.tests.common import Form, TransactionCase + + +class TestPurolator(TransactionCase): + def setUp(self): + super().setUp() + self.carrier = self.env.ref('delivery_purolator.purolator_carrier', raise_if_not_found=False) + if not self.carrier or not self.carrier.purolator_api_key: + self.skipTest('Purolator Shipping not configured, skipping tests.') + if self.carrier.prod_environment: + self.skipTest('Purolator Shipping configured to use production credentials, skipping tests.') + + self.shipper_partner = self.env['res.partner'].create({ + 'name': 'Canadian Address', + 'zip': 'L4W5M8', + }) + self.shipper_warehouse = self.env['stock.warehouse'].create({ + 'partner_id': self.shipper_partner.id, + 'name': 'Canadian Warehouse', + 'code': 'CWH', + }) + self.receiver_partner = self.env['res.partner'].create({ + 'name': 'Receiver Address', + 'city': 'Burnaby', + 'state_id': self.ref('base.state_ca_bc'), + 'country_id': self.ref('base.ca'), + 'zip': 'V5C5A9', + }) + self.storage_box = self.env.ref('product.product_product_6') + self.sale_order = self.env['sale.order'].create({ + 'partner_id': self.receiver_partner.id, + 'warehouse_id': self.shipper_warehouse.id, + 'order_line': [(0, 0, { + 'name': self.storage_box.name, + 'product_id': self.storage_box.id, + 'product_uom_qty': 3.0, + 'product_uom': self.storage_box.uom_id.id, + 'price_unit': self.storage_box.lst_price, + })], + }) + + def test_00_rate_order(self): + delivery_wizard = Form(self.env['choose.delivery.carrier'].with_context({ + 'default_order_id': self.sale_order.id, + 'default_carrier_id': self.ref('delivery_purolator.purolator_carrier'), + })) + choose_delivery_carrier = delivery_wizard.save() + choose_delivery_carrier.update_price() + self.assertGreater(choose_delivery_carrier.delivery_price, 0.0, "Purolator delivery cost for this SO has not been correctly estimated.") diff --git a/delivery_purolator/views/delivery_purolator_views.xml b/delivery_purolator/views/delivery_purolator_views.xml new file mode 100644 index 00000000..fd57bea5 --- /dev/null +++ b/delivery_purolator/views/delivery_purolator_views.xml @@ -0,0 +1,25 @@ + + + + + delivery.carrier.form.provider.purolator + delivery.carrier + + + + + + + + + + + + + + + + + + +