From c66a27b56c366c68c7c2697b4fd26ee4606fceb2 Mon Sep 17 00:00:00 2001 From: Raph Date: Fri, 2 Dec 2016 14:12:27 +0100 Subject: [PATCH 01/92] Add delivery_roulier --- delivery_roulier/README.rst | 1 + delivery_roulier/__init__.py | 1 + delivery_roulier/__openerp__.py | 36 +++ delivery_roulier/data/delivery.xml | 55 +++++ delivery_roulier/demo/product.xml | 95 ++++++++ delivery_roulier/models/__init__.py | 6 + delivery_roulier/models/delivery_dummy.py | 16 ++ delivery_roulier/models/delivery_option.py | 12 + delivery_roulier/models/stock.py | 251 +++++++++++++++++++++ delivery_roulier/models/stock_dummy.py | 24 ++ delivery_roulier/models/stock_package.py | 223 ++++++++++++++++++ delivery_roulier/models/stock_transfert.py | 31 +++ delivery_roulier/tests/__init__.py | 1 + delivery_roulier/tests/test_dummy.py | 165 ++++++++++++++ 14 files changed, 917 insertions(+) create mode 100644 delivery_roulier/README.rst create mode 100644 delivery_roulier/__init__.py create mode 100644 delivery_roulier/__openerp__.py create mode 100644 delivery_roulier/data/delivery.xml create mode 100644 delivery_roulier/demo/product.xml create mode 100644 delivery_roulier/models/__init__.py create mode 100644 delivery_roulier/models/delivery_dummy.py create mode 100644 delivery_roulier/models/delivery_option.py create mode 100644 delivery_roulier/models/stock.py create mode 100644 delivery_roulier/models/stock_dummy.py create mode 100644 delivery_roulier/models/stock_package.py create mode 100644 delivery_roulier/models/stock_transfert.py create mode 100644 delivery_roulier/tests/__init__.py create mode 100644 delivery_roulier/tests/test_dummy.py diff --git a/delivery_roulier/README.rst b/delivery_roulier/README.rst new file mode 100644 index 0000000000..e07009c90c --- /dev/null +++ b/delivery_roulier/README.rst @@ -0,0 +1 @@ +Integration of multiple carriers (base) \ No newline at end of file diff --git a/delivery_roulier/__init__.py b/delivery_roulier/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/delivery_roulier/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/delivery_roulier/__openerp__.py b/delivery_roulier/__openerp__.py new file mode 100644 index 0000000000..86b21f6d60 --- /dev/null +++ b/delivery_roulier/__openerp__.py @@ -0,0 +1,36 @@ +# coding: utf-8 +# @author Raphael Reverdy +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Delivery Carrier Roulier', + 'version': '9.0.0.0.0', + 'author': 'Akretion', + 'summary': 'Integration of multiple carriers (base)', + 'maintainer': 'Akretion, Odoo Community Association (OCA)', + 'category': 'Warehouse', + 'depends': [ + 'partner_helper', + 'base_phone', + 'document', + # 'intrastat_product', #not ported yet, customs will not work + 'delivery_carrier_b2c', + ], + 'website': 'http://www.akretion.com/', + 'data': [ + #'data/delivery.xml', + ], + 'demo': [ + #'demo/product.xml', + ], + 'external_dependencies': { + 'python': [ + 'roulier', # 'git+https://github.com/akretion/roulier.git' + ], + }, + 'tests': [], + 'installable': True, + 'auto_install': False, + 'license': 'AGPL-3', + 'application': False, +} diff --git a/delivery_roulier/data/delivery.xml b/delivery_roulier/data/delivery.xml new file mode 100644 index 0000000000..b1da1f65dc --- /dev/null +++ b/delivery_roulier/data/delivery.xml @@ -0,0 +1,55 @@ + + + + + + + SHIP_DUMMY + service + Test only - DUMMY + 1 + + + + + DUMMY + + + parcel street + 99999 + XXXXXX PFC + + + + + DUMMY + dummy + DUMMY + + + + + + + + Acknowledgment + ACK + + + + Free of charges and rights + FCR + + + + Shipping Cash On Delivery + COD + + + + Insurance + INS + + + + diff --git a/delivery_roulier/demo/product.xml b/delivery_roulier/demo/product.xml new file mode 100644 index 0000000000..e0191e707b --- /dev/null +++ b/delivery_roulier/demo/product.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/delivery_roulier/models/__init__.py b/delivery_roulier/models/__init__.py new file mode 100644 index 0000000000..1b859ff80a --- /dev/null +++ b/delivery_roulier/models/__init__.py @@ -0,0 +1,6 @@ +from . import stock +from . import stock_transfert +from . import stock_package +from . import delivery_option +from . import delivery_dummy +from . import stock_dummy diff --git a/delivery_roulier/models/delivery_dummy.py b/delivery_roulier/models/delivery_dummy.py new file mode 100644 index 0000000000..650886ffe0 --- /dev/null +++ b/delivery_roulier/models/delivery_dummy.py @@ -0,0 +1,16 @@ +# coding: utf-8 +# @author Raphael Reverdy +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, api + + +class DeliveryCarrier(models.Model): + _inherit = 'delivery.carrier' + + @api.model + def _get_carrier_type_selection(self): + """Add Dummy carrier type.""" + res = super(DeliveryCarrier, self)._get_carrier_type_selection() + res.append(('dummy', 'DUMMY'),) + return res diff --git a/delivery_roulier/models/delivery_option.py b/delivery_roulier/models/delivery_option.py new file mode 100644 index 0000000000..70a7d6df8e --- /dev/null +++ b/delivery_roulier/models/delivery_option.py @@ -0,0 +1,12 @@ +# coding: utf-8 +# © 2016 David BEAL @ Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, fields + + +class DeliveryCarrierTemplateOption(models.Model): + """ Available options for a carrier (partner) """ + _inherit = 'delivery.carrier.template.option' + + name = fields.Char(translate=True) diff --git a/delivery_roulier/models/stock.py b/delivery_roulier/models/stock.py new file mode 100644 index 0000000000..cbfdac2f33 --- /dev/null +++ b/delivery_roulier/models/stock.py @@ -0,0 +1,251 @@ +# coding: utf-8 +# @author Raphael Reverdy @ Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from datetime import datetime, timedelta +from functools import wraps +import logging + +from openerp import models, fields, api +from openerp.tools.translate import _ +from openerp.exceptions import Warning as UserError + +_logger = logging.getLogger(__name__) +try: + from roulier import roulier +except ImportError: + _logger.debug('Cannot `import roulier`.') + +# if you want to integrate a new carrier with Roulier Library +# start from roulier_template.py and read the doc of +# implemented_by_carrier decorator + + +def implemented_by_carrier(func): + """Decorator: call _carrier_prefixed method instead. + + Usage: + @implemented_by_carrier + def _do_something() + def _laposte_do_something() + def _gls_do_something() + + At runtime, picking._do_something() will try to call + the carrier spectific method or fallback to generic _do_something + + """ + @wraps(func) + def wrapper(cls, *args, **kwargs): + fun_name = func.__name__ + fun = '_%s%s' % (cls.carrier_type, fun_name) + if not hasattr(cls, fun): + fun = '_roulier%s' % (fun_name) + # return func(cls, *args, **kwargs) + return getattr(cls, fun)(*args, **kwargs) + return wrapper + + +class StockPicking(models.Model): + _inherit = 'stock.picking' + + # base_delivery_carrier_label API implementataion + + # @api.multi + # def generate_default_label(self, package_ids=None): + # useless method + + customs_category = fields.Selection( + selection=[ + ('gift', _("Gift")), + ('sample', _("Samples")), + ('commercial', _("Commercial Goods")), + ('document', _("Documents")), + ('other', _("Other")), + ('return', _("Goods return")), + ], + default='commercial', + help="Type of sending for the customs") + display_insurance = fields.Boolean( + compute='_compute_check_options', + string="Define a condition to display/hide your custom Insurance" + "field with a decated view") + + @api.multi + @api.depends('option_ids') + def _compute_check_options(self): + insurance_opt = self.env.ref( + 'delivery_roulier.carrier_opt_tmpl_INS', False) + for rec in self: + if insurance_opt in [x.tmpl_option_id for x in rec.option_ids]: + rec.display_insurance = True + else: + rec.display_insurance = False + _logger.info(" >>> in _compute_check_options() %s" % + rec.display_insurance) + + @implemented_by_carrier + def _get_sender(self, package): + pass + + @implemented_by_carrier + def _get_receiver(self, package): + pass + + @implemented_by_carrier + def _get_shipping_date(self, package): + pass + + @implemented_by_carrier + def _map_options(self): + pass + + @implemented_by_carrier + def _get_options(self, package): + pass + + @implemented_by_carrier + def _get_auth(self, package): + pass + + @implemented_by_carrier + def _get_service(self, package): + pass + + @implemented_by_carrier + def _convert_address(self, partner): + pass + + @api.multi + def _is_roulier(self): + self.ensure_one() + return self.carrier_type in roulier.get_carriers() + + @api.multi + def generate_labels(self, package_ids=None): + """See base_delivery_carrier_label/stock.py.""" + # entry point + self.ensure_one() + if self._is_roulier(): + return self._roulier_generate_labels() + _super = super(StockPicking, self) + return _super.generate_labels(package_ids=package_ids) + + @api.multi + def generate_shipping_labels(self, package_ids=None): + """See base_delivery_carrier_label/stock.py.""" + self.ensure_one() + + if self._is_roulier(): + raise UserError(_("Don't call me directly")) + _super = super(StockPicking, self) + return _super.generate_shipping_labels(package_ids=package_ids) + + @api.multi + def _roulier_generate_labels(self): + """Create as many labels as package_ids or in self.""" + self.ensure_one() + packages = self._get_packages_from_picking() + if not packages: + # It's not our responsibility to create the packages + raise UserError(_('No package found for this picking')) + return packages._generate_labels(self) + + # default implementations + def _roulier_get_auth(self, package): + """Login/password of the carrier account. + + Returns: + a dict with login and password keys + """ + auth = { + 'login': '', + 'password': '', + } + return auth + + def _roulier_get_service(self, package): + shipping_date = self._get_shipping_date(package) + + service = { + 'product': self.carrier_code, + 'shippingDate': shipping_date, + } + return service + + def _roulier_get_sender(self, package): + """Sender of the picking (for the label). + + Return: + (res.partner) + """ + return self.company_id.partner_id + + def _roulier_get_receiver(self, package): + """The guy who the shippment is for. + + At home or at a distribution point, it's always + the same receiver address. + + Return: + (res.partner) + """ + return self.partner_id + + def _roulier_get_shipping_date(self, package): + tomorrow = datetime.now() + timedelta(1) + return tomorrow.strftime('%Y-%m-%d') + + @api.model + def _roulier_map_options(self): + """ Customize this mapping with your own carrier as this example: + return { + 'FCR': 'fcr', + 'COD': 'cod', + 'INS': 'ins', + } + """ + return {} + + def _roulier_get_options(self, package): + mapping_options = self._map_options() + options = {} + if self.option_ids: + for opt in self.option_ids: + opt_key = str(opt.tmpl_option_id['code']) + if opt_key in mapping_options: + options[mapping_options[opt_key]] = True + else: + options[opt_key] = True + return options + + @api.model + def _roulier_convert_address(self, partner): + """Convert a partner to an address for roulier. + + params: + partner: a res.partner + return: + dict + """ + address = {} + extract_fields = [ + 'company', 'name', 'zip', 'city', 'phone', 'mobile', + 'email', 'street2'] + for elm in extract_fields: + if elm in partner: + # because a value can't be None in odoo's ORM + # you don't want to mix (bool) False and None + if partner._fields[elm].type != fields.Boolean.type: + if partner[elm]: + address[elm] = partner[elm] + # else: + # it's a None: nothing to do + else: # it's a boolean: keep the value + address[elm] = partner[elm] + if not address.get('company', False) and partner.parent_id.is_company: + address['company'] = partner.parent_id.name + # Roulier needs street1 not street + address['street1'] = partner.street + # Codet ISO 3166-1-alpha-2 (2 letters code) + address['country'] = partner.country_id.code + return address diff --git a/delivery_roulier/models/stock_dummy.py b/delivery_roulier/models/stock_dummy.py new file mode 100644 index 0000000000..cb577e8b1f --- /dev/null +++ b/delivery_roulier/models/stock_dummy.py @@ -0,0 +1,24 @@ +# coding: utf-8 +# © 2016 Raphael Reverdy +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models + + +class StockQuantPackage(models.Model): + _inherit = 'stock.quant.package' + + def _dummy_before_call(self, picking_id, request): + request['parcel']['reference'] = ( + "%s/%s" % ( + picking_id, len(picking_id._get_packages_from_picking()) + ) + ) + return request + + def _dummy_after_call(self, picking_id, response): + return [{ + "data": response['zpl'], + "tracking_id": "", + "name": picking_id.name, + }] diff --git a/delivery_roulier/models/stock_package.py b/delivery_roulier/models/stock_package.py new file mode 100644 index 0000000000..5bef1b7952 --- /dev/null +++ b/delivery_roulier/models/stock_package.py @@ -0,0 +1,223 @@ +# coding: utf-8 +# @author Raphael Reverdy @ Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from functools import wraps +import logging + +from openerp import models, api +from openerp.tools.translate import _ +from openerp.exceptions import Warning as UserError + +_logger = logging.getLogger(__name__) +try: + from roulier import roulier + from roulier.exception import InvalidApiInput +except ImportError: + _logger.debug('Cannot `import roulier`.') + +# if you want to integrate a new carrier with Roulier Library +# start from roulier_template.py and read the doc of +# implemented_by_carrier decorator + + +def implemented_by_carrier(func): + """Decorator: call _carrier_prefixed method instead. + + Usage: + @implemented_by_carrier + def _do_something() + def _laposte_do_something() + def _gls_do_something() + + At runtime, picking._do_something() will try to call + the carrier spectific method or fallback to generic _do_something + + """ + @wraps(func) + def wrapper(cls, *args, **kwargs): + fun_name = func.__name__ + fun = '_%s%s' % (cls.carrier_type, fun_name) + if not hasattr(cls, fun): + fun = '_roulier%s' % (fun_name) + # return func(cls, *args, **kwargs) + return getattr(cls, fun)(*args, **kwargs) + return wrapper + + +class StockQuantPackage(models.Model): + _inherit = 'stock.quant.package' + + # helper : move it to base ? + @api.multi + def get_operations(self): + """Get operations of the package. + + Usefull for having products and quantities + """ + self.ensure_one() + return self.env['stock.pack.operation'].search([ + ('result_package_id', '=', self.id), + ('product_id', '!=', False), + ]) + + # API + # Each method in this class have at least picking arg to directly + # deal with stock.picking if required by your carrier use case + @implemented_by_carrier + def _before_call(self, picking, payload): + pass + + @implemented_by_carrier + def _after_call(self, picking, response): + pass + + @implemented_by_carrier + def _get_cash_on_delivery(self, picking): + pass + + @implemented_by_carrier + def _get_customs(self, picking): + pass + + @implemented_by_carrier + def _should_include_customs(self, picking): + pass + + @implemented_by_carrier + def _get_parcel(self, picking): + pass + + @implemented_by_carrier + def _error_handling(self, payload, response): + pass + + # end of API + + # Core functions + + @api.multi + def _generate_labels(self, picking): + ret = [] + for package in self: + labels = package._call_roulier_api(picking) + if isinstance(labels, dict): + labels = [labels] + for label in labels: + data = { + 'name': label['name'], + 'res_id': picking.id, + 'res_model': 'stock.picking', + 'package_id': package.id, + } + if label.get('url'): + data['url'] = label['url'] + data['type'] = 'url' + elif label.get('data'): + data['datas'] = label['data'].encode('base64') + data['type'] = 'binary' + + ret.append(self.env['shipping.label'].create(data)) + + return ret + + def _call_roulier_api(self, picking): + """Create a label for a given package_id (self).""" + # There is low chance you need to override it. + # Don't forget to implement _a-carrier_before_call + # and _a-carrier_after_call + self.ensure_one() + + self.carrier_type = picking.carrier_type # on memory value ! + + roulier_instance = roulier.get(picking.carrier_type) + payload = roulier_instance.api() + + sender = picking._get_sender(self) + receiver = picking._get_receiver(self) + + payload['auth'] = picking._get_auth(self) + + payload['from_address'] = picking._convert_address(sender) + payload['to_address'] = picking._convert_address(receiver) + if self._should_include_customs(picking): + payload['customs'] = self._get_customs(picking) + + payload['service'] = picking._get_service(self) + payload['parcel'] = self._get_parcel(picking) + + # hook to override request / payload + payload = self._before_call(picking, payload) + try: + # api call + ret = roulier_instance.get_label(payload) + except InvalidApiInput as e: + raise UserError(self._error_handling(payload, e.message)) + except Exception as e: + raise UserError(e.message) + + # minimum error handling + if ret.get('status', '') == 'error': + raise UserError(self._error_handling(payload, ret)) + # give result to someone else + return self._after_call(picking, ret) + + # default implementations + + def _roulier_get_parcel(self, picking): + weight = self.weight + parcel = { + 'weight': weight, + } + return parcel + + def _roulier_get_cash_on_delivery(self, picking): + """ called by 'cod' option + """ + # TODO improve to take account Sale if picking created from sale + amount = 0 + for oper in self.get_operations(): + amount += oper.product_id.list_price * oper.product_qty + return amount + + def _roulier_get_customs(self, picking): + """Format customs infos for each product in the package. + + The decision whether to include these infos or not is + taken in _should_include_customs() + + Returns: + dict.'articles' : list with qty, weight, hs_code + int category: gift 1, sample 2, commercial 3, ... + """ + articles = [] + for operation in self.get_operations(): + article = {} + articles.append(article) + product = operation.product_id + # stands for harmonized_system + hs = product.product_tmpl_id.get_hs_code_recursively() + + article['quantity'] = '%.f' % operation.product_qty + article['weight'] = ( + operation.get_weight() / operation.product_qty) + article['originCountry'] = product.origin_country_id.code + article['description'] = hs.description + article['hs'] = hs.hs_code + article['value'] = product.list_price # unit price is expected + + category = picking.customs_category + return { + "articles": articles, + "category": category, + } + + def _roulier_should_include_customs(self, picking): + sender = picking._get_sender(self) + receiver = picking._get_receiver(self) + return sender.country_id.code != receiver.country_id.code + + @api.model + def _roulier_error_handling(self, payload, response): + return _(u'Sent data:\n%s\n\nException raised:\n%s\n' % ( + payload, response)) diff --git a/delivery_roulier/models/stock_transfert.py b/delivery_roulier/models/stock_transfert.py new file mode 100644 index 0000000000..7bc4b9d615 --- /dev/null +++ b/delivery_roulier/models/stock_transfert.py @@ -0,0 +1,31 @@ +# coding: utf-8 +# © 2016 David BEAL @ Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# +#import logging +# +#from openerp import _, api, models +#from openerp.exceptions import Warning as UserError +# +#_logger = logging.getLogger(__name__) +# +# +#class StockTransferDetails(models.TransientModel): +# _inherit = 'stock.transfer_details' +# +# @api.multi +# def do_detailed_transfer(self): +# """ All carriers using roulier needs package +# This code prevent validate picking with roulier carrier +# """ +# needs_package = self.picking_id._is_roulier() +# for rec in self: +# for item in rec.item_ids: +# if needs_package and not ( +# item.package_id or item.result_package_id): +# raise UserError( +# _("All products to deliver for carrier '%s' \n" +# "must be put in a parcel.") +# % rec.picking_id.carrier_id.name) +# return super(StockTransferDetails, self).do_detailed_transfer() +# \ No newline at end of file diff --git a/delivery_roulier/tests/__init__.py b/delivery_roulier/tests/__init__.py new file mode 100644 index 0000000000..9eb66b83c4 --- /dev/null +++ b/delivery_roulier/tests/__init__.py @@ -0,0 +1 @@ +from . import test_dummy diff --git a/delivery_roulier/tests/test_dummy.py b/delivery_roulier/tests/test_dummy.py new file mode 100644 index 0000000000..7cb41a55e8 --- /dev/null +++ b/delivery_roulier/tests/test_dummy.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +from openerp.tests.common import TransactionCase +from openerp.exceptions import Warning as UserError + + +class TestDummy(TransactionCase): + """Test dumy functions.""" + + def setUp(self): + super(TestDummy, self).setUp() + self.products = self.env.ref('delivery_roulier.product_dummy_small') + self.products |= self.env.ref('delivery_roulier.product_dummy_normal') + self.products |= self.env.ref('delivery_roulier.product_dummy_big') + + # some helpers + def _create_sale(self, customer, carrier=None): + vals = { + 'partner_id': customer.id, + } + if carrier: + vals['carrier_id'] = carrier + return self.env['sale.order'].create(vals) + + def _create_sale_line(self, sale, products): + ol = [] + for product in products: + ol.append(self.env['sale.order.line'].create({ + 'product_id': product.id, + 'order_id': sale.id, + })) + return ol + + def _create_operation(self, picking, values): + vals = { + 'picking_id': picking.id, + 'location_id': picking.location_id.id, + 'location_dest_id': picking.location_dest_id.id, + } + vals.update(values) + return self.env['stock.pack.operation'].create(vals) + + def _generate_picking(self, products, is_dummy=True): + """Create a picking from products.""" + dummy_carrier = self.env.ref( + 'delivery_roulier.delivery_carrier_dummy').id + carrier = False + if is_dummy: + carrier = dummy_carrier + + customer = self.env['res.partner'].search([], limit=3)[2] + sale = self._create_sale(customer, carrier) + self._create_sale_line(sale, products) + sale.action_button_confirm() + picking = sale.picking_ids + picking.do_transfer() + return picking + + def test_dummy_example(self): + """Ensure tests are running.""" + self.assertEqual(False, False) + + def test_is_roulier(self): + """It should return true when handled.""" + # we need to have weigths on product + # because there is some get_weight on the list + + dummy_picking = self._generate_picking(self.products) + other_picking = self._generate_picking(self.products, False) + + # ensure we get the specific function + # in python 2.x we can't have the decorated function + # but get only the decorator + # (in python 3.x there is inspect.unwrap()) + # + # Not a real good method, but it's ok to test the returned result + + # _is_roulier which returns true on roulier implementations + # and false on the others + + self.assertEqual( + dummy_picking._is_roulier(), + True) + + # btw is_roulier should work only on roulier' managed pickings + self.assertNotEqual( + dummy_picking._is_roulier(), + other_picking._is_roulier()) + + def test_generate_shipping_labels_no_package(self): + """It should fail because there is no package.""" + picking = self._generate_picking(self.products) + + try: + labels = picking.generate_labels() + except UserError: + self.assertTrue(True) + return + # when automatic package creation will be there + # test will be: + self.assertEqual(len(labels), 1) + return True + + def test_generate_shipping_labels_one_package_explicit(self): + """It should create 1 label if there is 1 package.""" + picking = self._generate_picking(self.products) + package = self.env['stock.quant.package'].create({}) + + operations = [] + for idx, product in enumerate(self.products): + operations.append(self._create_operation(picking, { + 'product_qty': 1, + 'product_id': product.id, + 'product_uom_id': product.uom_id.id, + 'result_package_id': package.id, + })) + packages = picking._get_packages_from_picking() + labels = packages._generate_labels(picking) + self.assertEqual(len(labels), 1) + + def test_generate_shipping_labels_all_packages(self): + """It should create many label as packages.""" + picking = self._generate_picking(self.products) + + packages = [ + self.env['stock.quant.package'].create({}), + self.env['stock.quant.package'].create({}) + ] + + operations = [] + for idx, product in enumerate(self.products): + operations.append(self._create_operation(picking, { + 'product_qty': 1, + 'product_id': product.id, + 'product_uom_id': product.uom_id.id, + 'result_package_id': packages[idx % len(packages)].id, + })) + + # dummy create one label per package + labels = picking.generate_labels() + self.assertEqual(len(labels), len(packages)) + + def test_generate_shipping_labels_some_packages(self): + """It should use self instead of package_ids.""" + picking = self._generate_picking(self.products) + + packages = [ + self.env['stock.quant.package'].create({}), + self.env['stock.quant.package'].create({}) + ] + + operations = [] + for idx, product in enumerate(self.products): + operations.append(self._create_operation(picking, { + 'product_qty': 1, + 'product_id': product.id, + 'product_uom_id': product.uom_id.id, + 'result_package_id': packages[idx % len(packages)].id, + })) + + # dummy create one label per package + package_ids = [packages[0]] + + labels = picking.generate_labels(package_ids) + self.assertNotEqual(len(labels), len(package_ids)) # =1 From f82a8738c070803046890f29d9d6bcda21d03cfd Mon Sep 17 00:00:00 2001 From: Raph Date: Mon, 12 Dec 2016 11:27:49 +0100 Subject: [PATCH 02/92] Clean roulier demo --- delivery_roulier/data/delivery.xml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/delivery_roulier/data/delivery.xml b/delivery_roulier/data/delivery.xml index b1da1f65dc..e692406dcc 100644 --- a/delivery_roulier/data/delivery.xml +++ b/delivery_roulier/data/delivery.xml @@ -4,15 +4,11 @@ - SHIP_DUMMY - service - Test only - DUMMY - 1 - DUMMY + DUMMY Carrier parcel street @@ -24,10 +20,11 @@ DUMMY dummy - DUMMY - + SHIP_DUMMY + Test only - DUMMY + 1 + - From 931b2a5e2f64170061ab059e6e0a47371c5f36da Mon Sep 17 00:00:00 2001 From: Raph Date: Mon, 12 Dec 2016 15:41:10 +0100 Subject: [PATCH 03/92] Move account selection in roulier instead of carrier spectifics. Add a dependency on keychain --- delivery_roulier/__openerp__.py | 3 ++- delivery_roulier/data/delivery.xml | 23 +---------------------- delivery_roulier/demo/product.xml | 19 +++++++++++++++++++ delivery_roulier/models/stock.py | 21 +++++++++++++++++++-- 4 files changed, 41 insertions(+), 25 deletions(-) diff --git a/delivery_roulier/__openerp__.py b/delivery_roulier/__openerp__.py index 86b21f6d60..0cf67e6b65 100644 --- a/delivery_roulier/__openerp__.py +++ b/delivery_roulier/__openerp__.py @@ -13,12 +13,13 @@ 'partner_helper', 'base_phone', 'document', + 'keychain', # 'intrastat_product', #not ported yet, customs will not work 'delivery_carrier_b2c', ], 'website': 'http://www.akretion.com/', 'data': [ - #'data/delivery.xml', + 'data/delivery.xml', ], 'demo': [ #'demo/product.xml', diff --git a/delivery_roulier/data/delivery.xml b/delivery_roulier/data/delivery.xml index e692406dcc..ffbfa580ec 100644 --- a/delivery_roulier/data/delivery.xml +++ b/delivery_roulier/data/delivery.xml @@ -3,29 +3,8 @@ - - - - - DUMMY Carrier - - - parcel street - 99999 - XXXXXX PFC - - - - - DUMMY - dummy - SHIP_DUMMY - Test only - DUMMY - 1 - - - + diff --git a/delivery_roulier/demo/product.xml b/delivery_roulier/demo/product.xml index e0191e707b..ce7205a5f4 100644 --- a/delivery_roulier/demo/product.xml +++ b/delivery_roulier/demo/product.xml @@ -7,6 +7,25 @@ + + DUMMY Carrier + + + parcel street + 99999 + XXXXXX PFC + + + + + DUMMY + dummy + SHIP_DUMMY + 1 + + + + - - - - - - DUMMY Carrier - - - parcel street - 99999 - XXXXXX PFC - - - - - DUMMY - dummy - SHIP_DUMMY - 1 - - - - - - - - - - - - - - - - - - - - diff --git a/delivery_roulier/models/__init__.py b/delivery_roulier/models/__init__.py index 1233fa5d68..d93a8c74b7 100644 --- a/delivery_roulier/models/__init__.py +++ b/delivery_roulier/models/__init__.py @@ -2,5 +2,3 @@ from . import stock_transfert from . import stock_quant_package from . import delivery_option -from . import delivery_dummy -from . import stock_dummy diff --git a/delivery_roulier/models/delivery_dummy.py b/delivery_roulier/models/delivery_dummy.py deleted file mode 100644 index 650886ffe0..0000000000 --- a/delivery_roulier/models/delivery_dummy.py +++ /dev/null @@ -1,16 +0,0 @@ -# coding: utf-8 -# @author Raphael Reverdy -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from openerp import models, api - - -class DeliveryCarrier(models.Model): - _inherit = 'delivery.carrier' - - @api.model - def _get_carrier_type_selection(self): - """Add Dummy carrier type.""" - res = super(DeliveryCarrier, self)._get_carrier_type_selection() - res.append(('dummy', 'DUMMY'),) - return res diff --git a/delivery_roulier/models/stock_dummy.py b/delivery_roulier/models/stock_dummy.py deleted file mode 100644 index cb577e8b1f..0000000000 --- a/delivery_roulier/models/stock_dummy.py +++ /dev/null @@ -1,24 +0,0 @@ -# coding: utf-8 -# © 2016 Raphael Reverdy -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from openerp import models - - -class StockQuantPackage(models.Model): - _inherit = 'stock.quant.package' - - def _dummy_before_call(self, picking_id, request): - request['parcel']['reference'] = ( - "%s/%s" % ( - picking_id, len(picking_id._get_packages_from_picking()) - ) - ) - return request - - def _dummy_after_call(self, picking_id, response): - return [{ - "data": response['zpl'], - "tracking_id": "", - "name": picking_id.name, - }] diff --git a/delivery_roulier/tests/__init__.py b/delivery_roulier/tests/__init__.py index 9eb66b83c4..e69de29bb2 100644 --- a/delivery_roulier/tests/__init__.py +++ b/delivery_roulier/tests/__init__.py @@ -1 +0,0 @@ -from . import test_dummy diff --git a/delivery_roulier/tests/test_dummy.py b/delivery_roulier/tests/test_dummy.py deleted file mode 100644 index 7cb41a55e8..0000000000 --- a/delivery_roulier/tests/test_dummy.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- - -from openerp.tests.common import TransactionCase -from openerp.exceptions import Warning as UserError - - -class TestDummy(TransactionCase): - """Test dumy functions.""" - - def setUp(self): - super(TestDummy, self).setUp() - self.products = self.env.ref('delivery_roulier.product_dummy_small') - self.products |= self.env.ref('delivery_roulier.product_dummy_normal') - self.products |= self.env.ref('delivery_roulier.product_dummy_big') - - # some helpers - def _create_sale(self, customer, carrier=None): - vals = { - 'partner_id': customer.id, - } - if carrier: - vals['carrier_id'] = carrier - return self.env['sale.order'].create(vals) - - def _create_sale_line(self, sale, products): - ol = [] - for product in products: - ol.append(self.env['sale.order.line'].create({ - 'product_id': product.id, - 'order_id': sale.id, - })) - return ol - - def _create_operation(self, picking, values): - vals = { - 'picking_id': picking.id, - 'location_id': picking.location_id.id, - 'location_dest_id': picking.location_dest_id.id, - } - vals.update(values) - return self.env['stock.pack.operation'].create(vals) - - def _generate_picking(self, products, is_dummy=True): - """Create a picking from products.""" - dummy_carrier = self.env.ref( - 'delivery_roulier.delivery_carrier_dummy').id - carrier = False - if is_dummy: - carrier = dummy_carrier - - customer = self.env['res.partner'].search([], limit=3)[2] - sale = self._create_sale(customer, carrier) - self._create_sale_line(sale, products) - sale.action_button_confirm() - picking = sale.picking_ids - picking.do_transfer() - return picking - - def test_dummy_example(self): - """Ensure tests are running.""" - self.assertEqual(False, False) - - def test_is_roulier(self): - """It should return true when handled.""" - # we need to have weigths on product - # because there is some get_weight on the list - - dummy_picking = self._generate_picking(self.products) - other_picking = self._generate_picking(self.products, False) - - # ensure we get the specific function - # in python 2.x we can't have the decorated function - # but get only the decorator - # (in python 3.x there is inspect.unwrap()) - # - # Not a real good method, but it's ok to test the returned result - - # _is_roulier which returns true on roulier implementations - # and false on the others - - self.assertEqual( - dummy_picking._is_roulier(), - True) - - # btw is_roulier should work only on roulier' managed pickings - self.assertNotEqual( - dummy_picking._is_roulier(), - other_picking._is_roulier()) - - def test_generate_shipping_labels_no_package(self): - """It should fail because there is no package.""" - picking = self._generate_picking(self.products) - - try: - labels = picking.generate_labels() - except UserError: - self.assertTrue(True) - return - # when automatic package creation will be there - # test will be: - self.assertEqual(len(labels), 1) - return True - - def test_generate_shipping_labels_one_package_explicit(self): - """It should create 1 label if there is 1 package.""" - picking = self._generate_picking(self.products) - package = self.env['stock.quant.package'].create({}) - - operations = [] - for idx, product in enumerate(self.products): - operations.append(self._create_operation(picking, { - 'product_qty': 1, - 'product_id': product.id, - 'product_uom_id': product.uom_id.id, - 'result_package_id': package.id, - })) - packages = picking._get_packages_from_picking() - labels = packages._generate_labels(picking) - self.assertEqual(len(labels), 1) - - def test_generate_shipping_labels_all_packages(self): - """It should create many label as packages.""" - picking = self._generate_picking(self.products) - - packages = [ - self.env['stock.quant.package'].create({}), - self.env['stock.quant.package'].create({}) - ] - - operations = [] - for idx, product in enumerate(self.products): - operations.append(self._create_operation(picking, { - 'product_qty': 1, - 'product_id': product.id, - 'product_uom_id': product.uom_id.id, - 'result_package_id': packages[idx % len(packages)].id, - })) - - # dummy create one label per package - labels = picking.generate_labels() - self.assertEqual(len(labels), len(packages)) - - def test_generate_shipping_labels_some_packages(self): - """It should use self instead of package_ids.""" - picking = self._generate_picking(self.products) - - packages = [ - self.env['stock.quant.package'].create({}), - self.env['stock.quant.package'].create({}) - ] - - operations = [] - for idx, product in enumerate(self.products): - operations.append(self._create_operation(picking, { - 'product_qty': 1, - 'product_id': product.id, - 'product_uom_id': product.uom_id.id, - 'result_package_id': packages[idx % len(packages)].id, - })) - - # dummy create one label per package - package_ids = [packages[0]] - - labels = picking.generate_labels(package_ids) - self.assertNotEqual(len(labels), len(package_ids)) # =1 From b465f70ebe2a8ebe7f48fe782b0c5545040c8c7f Mon Sep 17 00:00:00 2001 From: Raph Date: Wed, 22 Mar 2017 17:06:42 +0100 Subject: [PATCH 21/92] Fix return attachments --- delivery_roulier/models/stock_quant_package.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 5674463801..3f8bcb0139 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -126,8 +126,9 @@ def _generate_labels(self, picking): ret = [] for package in self: response = package._call_roulier_api(picking) - package._handle_attachments(picking, response) package._handle_tracking(picking, response) + attach = package._handle_attachments(picking, response) + ret.append(attach.get('label')) return ret def _call_roulier_api(self, picking): @@ -172,13 +173,16 @@ def _call_roulier_api(self, picking): def _roulier_handle_attachments(self, picking, response): main_label = self._roulier_prepare_label(picking, response) - self.env['shipping.label'].create(main_label) + label = self.env['shipping.label'].create(main_label) attachments = self._roulier_prepare_attachments(picking, response) - return [ - self.env['ir.attachment'].create(attachment) - for attachment in attachments - ] + return { + 'label': label, + 'attachments': [ + self.env['ir.attachment'].create(attachment) + for attachment in attachments + ] + } def _roulier_handle_tracking(self, picking, response): tracking = response.get('tracking') From f11ba0bdd2f6916c31f64eb80ebed9b513c2890c Mon Sep 17 00:00:00 2001 From: Raph Date: Fri, 31 Mar 2017 17:13:34 +0200 Subject: [PATCH 22/92] Add package name in attachemnts It's way better for the user when she has multiple packages for a given picking --- delivery_roulier/models/stock_quant_package.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 3f8bcb0139..e8f641edfb 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -198,7 +198,7 @@ def _roulier_prepare_label(self, picking, response): 'res_id': picking.id, 'res_model': 'stock.picking', 'package_id': self.id, - 'name': label['name'], + 'name': "%s %s" % (self.name, label['name']), 'datas': base64.b64encode(label['data']), 'type': 'binary', 'datas_fname': "%s.%s" % (label['name'], label['type']), @@ -209,7 +209,7 @@ def _roulier_prepare_attachments(self, picking, response): return [{ 'res_id': picking.id, 'res_model': 'stock.picking', - 'name': attachment['name'], + 'name': "%s %s" % (self.name, attachment['name']), 'datas': base64.b64encode(attachment['data']), 'type': 'binary', 'datas_fname': "%s.%s" % (attachment['name'], attachment['type']), From cc0d1db24556cb33837435f6bd4c53b37003575a Mon Sep 17 00:00:00 2001 From: Raph Date: Fri, 31 Mar 2017 17:31:17 +0200 Subject: [PATCH 23/92] Add pack name in datas_fname (file name) --- delivery_roulier/models/stock_quant_package.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index e8f641edfb..e12e9a40f5 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -201,7 +201,8 @@ def _roulier_prepare_label(self, picking, response): 'name': "%s %s" % (self.name, label['name']), 'datas': base64.b64encode(label['data']), 'type': 'binary', - 'datas_fname': "%s.%s" % (label['name'], label['type']), + 'datas_fname': "%s-%s.%s" % ( + self.name, label['name'], label['type']), } def _roulier_prepare_attachments(self, picking, response): @@ -212,7 +213,8 @@ def _roulier_prepare_attachments(self, picking, response): 'name': "%s %s" % (self.name, attachment['name']), 'datas': base64.b64encode(attachment['data']), 'type': 'binary', - 'datas_fname': "%s.%s" % (attachment['name'], attachment['type']), + 'datas_fname': "%s-%s.%s" % ( + self.name, attachment['name'], attachment['type']), } for attachment in attachments] def _roulier_get_parcel(self, picking): From d67f451379c1889831865e7fe257311cead8edb6 Mon Sep 17 00:00:00 2001 From: Raph Date: Wed, 5 Apr 2017 16:59:32 +0200 Subject: [PATCH 24/92] Remove laposte demo --- delivery_roulier/__openerp__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/delivery_roulier/__openerp__.py b/delivery_roulier/__openerp__.py index 1ed2dfdc7c..8f434f3e2b 100644 --- a/delivery_roulier/__openerp__.py +++ b/delivery_roulier/__openerp__.py @@ -12,25 +12,20 @@ 'depends': [ 'partner_helper', 'base_phone', # from oca/telephony - 'document', 'keychain', # from oca/server-tools 'base_suspend_security', 'product_harmonized_system', # from oca/intrastat - 'delivery_carrier_b2c', ], 'website': 'http://www.akretion.com/', 'data': [ 'data/delivery.xml', 'views/stock_quant_package.xml', ], - 'demo': [ - ], 'external_dependencies': { 'python': [ - 'roulier', # '>0.2' + 'roulier', # '>0.1.4' ], }, - 'tests': [], 'installable': True, 'auto_install': False, 'license': 'AGPL-3', From 4c444776ee4e55bc12465a7fbb2cedd32f02a8f3 Mon Sep 17 00:00:00 2001 From: Raph Date: Wed, 5 Apr 2017 17:21:38 +0200 Subject: [PATCH 25/92] Update readme --- delivery_roulier/README.rst | 16 +++++++++++++++- delivery_roulier/__openerp__.py | 2 +- delivery_roulier/tests/__init__.py | 0 3 files changed, 16 insertions(+), 2 deletions(-) delete mode 100644 delivery_roulier/tests/__init__.py diff --git a/delivery_roulier/README.rst b/delivery_roulier/README.rst index e07009c90c..6bc90d5c0a 100644 --- a/delivery_roulier/README.rst +++ b/delivery_roulier/README.rst @@ -1 +1,15 @@ -Integration of multiple carriers (base) \ No newline at end of file +Integration of multiple carriers with Roulier +============================================= + + +Base module for intregation with Roulier. + +`Roulier `_ is a python library which implements carriers API. +This modules contains the core functions for this implementation. + +You should install one of the specific modules : + +- delivery_roulier_laposte +- delivery_roulier_dpd +- delivery_roulier_geodis +- more to come diff --git a/delivery_roulier/__openerp__.py b/delivery_roulier/__openerp__.py index 8f434f3e2b..3a6a966137 100644 --- a/delivery_roulier/__openerp__.py +++ b/delivery_roulier/__openerp__.py @@ -6,7 +6,7 @@ 'name': 'Delivery Carrier Roulier', 'version': '9.0.0.0.0', 'author': 'Akretion', - 'summary': 'Integration of multiple carriers (base)', + 'summary': 'Integration of multiple carriers', 'maintainer': 'Akretion, Odoo Community Association (OCA)', 'category': 'Warehouse', 'depends': [ diff --git a/delivery_roulier/tests/__init__.py b/delivery_roulier/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From c36bfcb78cf1f363f594d31cd798f9175f32eabd Mon Sep 17 00:00:00 2001 From: Raph Date: Fri, 7 Apr 2017 15:57:50 +0200 Subject: [PATCH 26/92] Add main dep --- delivery_roulier/__openerp__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/delivery_roulier/__openerp__.py b/delivery_roulier/__openerp__.py index 3a6a966137..2720f55f73 100644 --- a/delivery_roulier/__openerp__.py +++ b/delivery_roulier/__openerp__.py @@ -15,6 +15,7 @@ 'keychain', # from oca/server-tools 'base_suspend_security', 'product_harmonized_system', # from oca/intrastat + 'base_delivery_carrier_label', ], 'website': 'http://www.akretion.com/', 'data': [ From 278531465e3592cca6f4f95199f87e4bfa2af961 Mon Sep 17 00:00:00 2001 From: Raph Date: Mon, 10 Apr 2017 15:20:49 +0200 Subject: [PATCH 27/92] Fix bad input --- delivery_roulier/models/stock_quant_package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index e12e9a40f5..2fd4f84d60 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -289,7 +289,7 @@ def _roulier_carrier_error_handling(self, payload, exception): payload, exception.message)) def _roulier_invalid_api_input_handling(self, payload, exception): - return _(u'Bad input: %s\n', exception.message) + return _(u'Bad input: %s\n' % exception.message) def _roulier_open_tracking_url(self): _logger.warning("not implemented") From 86d74a1b98bc3d48120a2770d43efd3f9761e86f Mon Sep 17 00:00:00 2001 From: Raph Date: Mon, 10 Apr 2017 17:02:19 +0200 Subject: [PATCH 28/92] Move options to a dedicated module Improve docs Change methods order --- delivery_roulier/__openerp__.py | 6 +- delivery_roulier/data/delivery.xml | 31 --- delivery_roulier/models/__init__.py | 2 - delivery_roulier/models/delivery_option.py | 12 - delivery_roulier/models/stock_picking.py | 109 +++----- .../models/stock_quant_package.py | 244 ++++++++++-------- delivery_roulier/models/stock_transfert.py | 31 --- .../views/stock_quant_package.xml | 22 +- 8 files changed, 188 insertions(+), 269 deletions(-) delete mode 100644 delivery_roulier/data/delivery.xml delete mode 100644 delivery_roulier/models/delivery_option.py delete mode 100644 delivery_roulier/models/stock_transfert.py diff --git a/delivery_roulier/__openerp__.py b/delivery_roulier/__openerp__.py index 2720f55f73..b740180265 100644 --- a/delivery_roulier/__openerp__.py +++ b/delivery_roulier/__openerp__.py @@ -1,7 +1,7 @@ # coding: utf-8 -# @author Raphael Reverdy +# @author Raphael Reverdy +# David BEAL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - { 'name': 'Delivery Carrier Roulier', 'version': '9.0.0.0.0', @@ -16,10 +16,10 @@ 'base_suspend_security', 'product_harmonized_system', # from oca/intrastat 'base_delivery_carrier_label', + 'delivery_carrier_deposit', ], 'website': 'http://www.akretion.com/', 'data': [ - 'data/delivery.xml', 'views/stock_quant_package.xml', ], 'external_dependencies': { diff --git a/delivery_roulier/data/delivery.xml b/delivery_roulier/data/delivery.xml deleted file mode 100644 index ffbfa580ec..0000000000 --- a/delivery_roulier/data/delivery.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - Acknowledgment - ACK - - - - Free of charges and rights - FCR - - - - Shipping Cash On Delivery - COD - - - - Insurance - INS - - - - diff --git a/delivery_roulier/models/__init__.py b/delivery_roulier/models/__init__.py index d93a8c74b7..f005d93463 100644 --- a/delivery_roulier/models/__init__.py +++ b/delivery_roulier/models/__init__.py @@ -1,4 +1,2 @@ from . import stock_picking -from . import stock_transfert from . import stock_quant_package -from . import delivery_option diff --git a/delivery_roulier/models/delivery_option.py b/delivery_roulier/models/delivery_option.py deleted file mode 100644 index 70a7d6df8e..0000000000 --- a/delivery_roulier/models/delivery_option.py +++ /dev/null @@ -1,12 +0,0 @@ -# coding: utf-8 -# © 2016 David BEAL @ Akretion -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from openerp import models, fields - - -class DeliveryCarrierTemplateOption(models.Model): - """ Available options for a carrier (partner) """ - _inherit = 'delivery.carrier.template.option' - - name = fields.Char(translate=True) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index ea7b881e7e..be71736bff 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -1,5 +1,6 @@ # coding: utf-8 -# @author Raphael Reverdy @ Akretion +# @author Raphael Reverdy +# David BEAL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from datetime import datetime, timedelta @@ -16,10 +17,6 @@ except ImportError: _logger.debug('Cannot `import roulier`.') -# if you want to integrate a new carrier with Roulier Library -# start from roulier_template.py and read the doc of -# implemented_by_carrier decorator - def implemented_by_carrier(func): """Decorator: call _carrier_prefixed method instead. @@ -32,7 +29,6 @@ def _gls_do_something() At runtime, picking._do_something() will try to call the carrier spectific method or fallback to generic _do_something - """ @wraps(func) def wrapper(cls, *args, **kwargs): @@ -48,12 +44,6 @@ def wrapper(cls, *args, **kwargs): class StockPicking(models.Model): _inherit = 'stock.picking' - # base_delivery_carrier_label API implementataion - - # @api.multi - # def generate_default_label(self, package_ids=None): - # useless method - customs_category = fields.Selection( selection=[ ('gift', _("Gift")), @@ -65,23 +55,13 @@ class StockPicking(models.Model): ], default='commercial', help="Type of sending for the customs") - display_insurance = fields.Boolean( - compute='_compute_check_options', - string="Define a condition to display/hide your custom Insurance" - "field with a decated view") - @api.multi - @api.depends('option_ids') - def _compute_check_options(self): - insurance_opt = self.env.ref( - 'delivery_roulier.carrier_opt_tmpl_INS', False) - for rec in self: - if insurance_opt in [x.tmpl_option_id for x in rec.option_ids]: - rec.display_insurance = True - else: - rec.display_insurance = False - _logger.info(" >>> in _compute_check_options() %s" % - rec.display_insurance) + # base_delivery_carrier_label API implementataion + + # def generate_default_label(self, package_ids=None): + # useless method + + # API: @implemented_by_carrier def _get_sender(self, package): @@ -95,14 +75,6 @@ def _get_receiver(self, package): def _get_shipping_date(self, package): pass - @implemented_by_carrier - def _map_options(self): - pass - - @implemented_by_carrier - def _get_options(self, package): - pass - @implemented_by_carrier def _get_account(self, package): pass @@ -119,6 +91,9 @@ def _get_service(self, package): def _convert_address(self, partner): pass + # End of API. + + # Implementations for base_delivery_carrier_label @api.multi def _is_roulier(self): self.ensure_one() @@ -126,7 +101,7 @@ def _is_roulier(self): @api.multi def generate_labels(self, package_ids=None): - """See base_delivery_carrier_label/stock.py.""" + """See base_delivery_carrier_label/models/stock_picking.py.""" # entry point self.ensure_one() if self._is_roulier(): @@ -138,7 +113,6 @@ def generate_labels(self, package_ids=None): def generate_shipping_labels(self, package_ids=None): """See base_delivery_carrier_label/stock.py.""" self.ensure_one() - if self._is_roulier(): raise UserError(_("Don't call me directly")) _super = super(StockPicking, self) @@ -156,7 +130,7 @@ def _roulier_generate_labels(self): self.carrier_tracking_ref = True # display button in view return packages._generate_labels(self) - # default implementations + # Default implementations of _roulier_*() def _roulier_get_auth(self, package): """Login/password of the carrier account. @@ -188,15 +162,6 @@ def _roulier_get_account(self, package): [['namespace', '=', 'roulier_%s' % self.carrier_type]]) return accounts[0] - def _roulier_get_service(self, package): - shipping_date = self._get_shipping_date(package) - - service = { - 'product': self.carrier_code, - 'shippingDate': shipping_date, - } - return service - def _roulier_get_sender(self, package): """Sender of the picking (for the label). @@ -206,7 +171,7 @@ def _roulier_get_sender(self, package): return self.company_id.partner_id def _roulier_get_receiver(self, package): - """The guy who the shippment is for. + """The guy whom the shippment is for. At home or at a distribution point, it's always the same receiver address. @@ -217,35 +182,12 @@ def _roulier_get_receiver(self, package): return self.partner_id def _roulier_get_shipping_date(self, package): + """Choose a shipping date. + + By default, it's tomorrow.""" tomorrow = datetime.now() + timedelta(1) return tomorrow.strftime('%Y-%m-%d') - @api.model - def _roulier_map_options(self): - """Customize this mapping with your own carrier. - - Like - return { - 'FCR': 'fcr', - 'COD': 'cod', - 'INS': 'ins', - } - """ - return {} - - def _roulier_get_options(self, package): - mapping_options = self._map_options() - options = {} - if self.option_ids: - for opt in self.option_ids: - opt_key = str(opt.tmpl_option_id['code']) - if opt_key in mapping_options: - options[mapping_options[opt_key]] = True - else: - options[opt_key] = True - return options - - @api.model def _roulier_convert_address(self, partner): """Convert a partner to an address for roulier. @@ -284,6 +226,23 @@ def _roulier_convert_address(self, partner): return address + def _roulier_get_service(self, package): + """Return a basic dict. + + The carrier implementation may add stuff + like agency or options. + + return: + dict + """ + shipping_date = self._get_shipping_date(package) + + service = { + 'product': self.carrier_code, + 'shippingDate': shipping_date, + } + return service + @api.multi def open_website_url(self): """Open tracking page. diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 2fd4f84d60..fc11e8592f 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -1,5 +1,6 @@ # coding: utf-8 -# @author Raphael Reverdy @ Akretion +# @author Raphael Reverdy +# David BEAL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from functools import wraps @@ -20,10 +21,6 @@ except ImportError: _logger.debug('Cannot `import roulier`.') -# if you want to integrate a new carrier with Roulier Library -# start from roulier_template.py and read the doc of -# implemented_by_carrier decorator - def implemented_by_carrier(func): """Decorator: call _carrier_prefixed method instead. @@ -78,10 +75,6 @@ def _before_call(self, picking, payload): def _after_call(self, picking, response): pass - @implemented_by_carrier - def _get_cash_on_delivery(self, picking): - pass - @implemented_by_carrier def _get_customs(self, picking): pass @@ -120,7 +113,6 @@ def _get_tracking_link(self): # end of API # Core functions - @api.multi def _generate_labels(self, picking): ret = [] @@ -131,6 +123,26 @@ def _generate_labels(self, picking): ret.append(attach.get('label')) return ret + @api.multi + def open_website_url(self): + """Open website for parcel tracking. + Each carrier should implement _get_tracking_link + There is low chance you need to override this method. + returns: + action + """ + self.ensure_one() + self.carrier_type = self.carrier_id.carrier_type + url = self._get_tracking_link() + client_action = { + 'type': 'ir.actions.act_url', + 'name': "Shipment Tracking Page", + 'target': 'new', + 'url': url, + } + return client_action + + @api.multi def _call_roulier_api(self, picking): """Create a label for a given package_id (self).""" # There is low chance you need to override it. @@ -170,53 +182,6 @@ def _call_roulier_api(self, picking): return self._after_call(picking, ret) # default implementations - - def _roulier_handle_attachments(self, picking, response): - main_label = self._roulier_prepare_label(picking, response) - label = self.env['shipping.label'].create(main_label) - - attachments = self._roulier_prepare_attachments(picking, response) - return { - 'label': label, - 'attachments': [ - self.env['ir.attachment'].create(attachment) - for attachment in attachments - ] - } - - def _roulier_handle_tracking(self, picking, response): - tracking = response.get('tracking') - if tracking: - number = tracking.get('number') - if number: - self.parcel_tracking = number - - @api.model - def _roulier_prepare_label(self, picking, response): - label = response.get('label') - return { - 'res_id': picking.id, - 'res_model': 'stock.picking', - 'package_id': self.id, - 'name': "%s %s" % (self.name, label['name']), - 'datas': base64.b64encode(label['data']), - 'type': 'binary', - 'datas_fname': "%s-%s.%s" % ( - self.name, label['name'], label['type']), - } - - def _roulier_prepare_attachments(self, picking, response): - attachments = response.get('annexes') - return [{ - 'res_id': picking.id, - 'res_model': 'stock.picking', - 'name': "%s %s" % (self.name, attachment['name']), - 'datas': base64.b64encode(attachment['data']), - 'type': 'binary', - 'datas_fname': "%s-%s.%s" % ( - self.name, attachment['name'], attachment['type']), - } for attachment in attachments] - def _roulier_get_parcel(self, picking): weight = self.weight parcel = { @@ -224,14 +189,70 @@ def _roulier_get_parcel(self, picking): } return parcel - def _roulier_get_cash_on_delivery(self, picking): - """ called by 'cod' option + def _roulier_before_call(self, picking, payload): + """Add stuff to payload just before api call. + + Put here what you can't put in other methods + (like _get_parcel, _get_service...) + + It's totally ok to do nothing here. + + returns: + dict + """ + return payload + + def _roulier_after_call(self, picking, response): + """Do stuff just after api call. + + It's totally ok to do nothing here. + """ + return response + + def _roulier_get_tracking_link(self): + """Build a tracking url. + + You have to implement it for your carrier. + + It's like : + 'https://the-carrier.com/?track=%s' % self.parcel_tracking + returns: + string (url) """ - # TODO improve to take account Sale if picking created from sale - amount = 0 - for oper in self.get_operations(): - amount += oper.product_id.list_price * oper.product_qty - return amount + _logger.warning("not implemented") + pass + + def _roulier_should_include_customs(self, picking): + """Choose if custom docs should be sent. + + Really dumb implementation. + You may improve this for your carrier. + """ + sender = picking._get_sender(self) + receiver = picking._get_receiver(self) + return sender.country_id.code != receiver.country_id.code + + def _roulier_carrier_error_handling(self, payload, exception): + """Build exception message for carrier error. + It's happen when the carrier WS returns something unexpected. + + You may improve this for your carrier. + returns: + string""" + return _(u'Sent data:\n%s\n\nException raised:\n%s\n' % ( + payload, exception.message)) + + def _roulier_invalid_api_input_handling(self, payload, exception): + """Build exception message for bad input. + + It's happend when your data is not valid, like a missing value + in the payload. + + You may improve this for your carrier. + returns: + string + """ + return _(u'Bad input: %s\n' % exception.message) def _roulier_get_customs(self, picking): """Format customs infos for each product in the package. @@ -243,13 +264,6 @@ def _roulier_get_customs(self, picking): dict.'articles' : list with qty, weight, hs_code int category: gift 1, sample 2, commercial 3, ... """ - def ensure_hs_codes_available(): - try: - self.env['product.template'].get_hs_code_recursively - except AttributeError: - raise UserError(_("Missing module 'intrastat' for customs")) - - ensure_hs_codes_available() articles = [] for operation in self.get_operations(): article = {} @@ -272,42 +286,64 @@ def ensure_hs_codes_available(): "category": category, } - def _roulier_before_call(self, picking, payload): - return payload - - def _roulier_after_call(self, picking, response): - return response + # There is low chance you need to override the following methods. + def _roulier_handle_attachments(self, picking, response): + main_label = self._roulier_prepare_label(picking, response) + label = self.env['shipping.label'].create(main_label) + attachments = self._roulier_prepare_attachments(picking, response) + return { + 'label': label, + 'attachments': [ + self.env['ir.attachment'].create(attachment) + for attachment in attachments + ] + } - def _roulier_should_include_customs(self, picking): - sender = picking._get_sender(self) - receiver = picking._get_receiver(self) - return sender.country_id.code != receiver.country_id.code + def _roulier_prepare_label(self, picking, response): + """Prepare a dict for building a shipping.label. + + The shipping label is what you stick on your packages. + returns: + dict + """ + label = response.get('label') + return { + 'res_id': picking.id, + 'res_model': 'stock.picking', + 'package_id': self.id, + 'name': "%s %s" % (self.name, label['name']), + 'datas': base64.b64encode(label['data']), + 'type': 'binary', + 'datas_fname': "%s-%s.%s" % ( + self.name, label['name'], label['type']), + } - @api.model - def _roulier_carrier_error_handling(self, payload, exception): - return _(u'Sent data:\n%s\n\nException raised:\n%s\n' % ( - payload, exception.message)) + def _roulier_prepare_attachments(self, picking, response): + """Prepare a list of dicts for building ir.attachemens. - def _roulier_invalid_api_input_handling(self, payload, exception): - return _(u'Bad input: %s\n' % exception.message) + Attachements are annexes like customs declarations, summary + etc. - def _roulier_open_tracking_url(self): - _logger.warning("not implemented") - pass + returns: + list + """ + attachments = response.get('annexes') + return [{ + 'res_id': picking.id, + 'res_model': 'stock.picking', + 'name': "%s %s" % (self.name, attachment['name']), + 'datas': base64.b64encode(attachment['data']), + 'type': 'binary', + 'datas_fname': "%s-%s.%s" % ( + self.name, attachment['name'], attachment['type']), + } for attachment in attachments] - def _roulier_get_tracking_link(self): - _logger.warning("not implemented") - pass - @api.multi - def open_website_url(self): - self.ensure_one() - self.carrier_type = self.carrier_id.carrier_type - url = self._get_tracking_link() - client_action = { - 'type': 'ir.actions.act_url', - 'name': "Shipment Tracking Page", - 'target': 'new', - 'url': url, - } - return client_action + def _roulier_handle_tracking(self, picking, response): + """Extract tracking number from response call to parcel_tracking.""" + tracking = response.get('tracking') + if tracking: + number = tracking.get('number') + if number: + self.parcel_tracking = number + \ No newline at end of file diff --git a/delivery_roulier/models/stock_transfert.py b/delivery_roulier/models/stock_transfert.py deleted file mode 100644 index c5dc9b59ef..0000000000 --- a/delivery_roulier/models/stock_transfert.py +++ /dev/null @@ -1,31 +0,0 @@ -# coding: utf-8 -# © 2016 David BEAL @ Akretion -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -# -# import logging -# -# from openerp import _, api, models -# from openerp.exceptions import Warning as UserError -# -# _logger = logging.getLogger(__name__) -# -# -# class StockTransferDetails(models.TransientModel): -# _inherit = 'stock.transfer_details' -# -# @api.multi -# def do_detailed_transfer(self): -# """ All carriers using roulier needs package -# This code prevent validate picking with roulier carrier -# """ -# needs_package = self.picking_id._is_roulier() -# for rec in self: -# for item in rec.item_ids: -# if needs_package and not ( -# item.package_id or item.result_package_id): -# raise UserError( -# _("All products to deliver for carrier '%s' \n" -# "must be put in a parcel.") -# % rec.picking_id.carrier_id.name) -# return super(StockTransferDetails, self).do_detailed_transfer() -# diff --git a/delivery_roulier/views/stock_quant_package.xml b/delivery_roulier/views/stock_quant_package.xml index 8d4b8b473f..796b6bf342 100644 --- a/delivery_roulier/views/stock_quant_package.xml +++ b/delivery_roulier/views/stock_quant_package.xml @@ -1,17 +1,17 @@ - - - stock.quant.package - - -
-
-
-
+ + + stock.quant.package + + +
+
+
+
\ No newline at end of file From e64fd4c6b17197ada31719fe9ccc2c4815b9d68b Mon Sep 17 00:00:00 2001 From: Raph Date: Mon, 10 Apr 2017 17:27:00 +0200 Subject: [PATCH 29/92] Fix pylin --- delivery_roulier/models/stock_quant_package.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index fc11e8592f..835a24a413 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -126,6 +126,7 @@ def _generate_labels(self, picking): @api.multi def open_website_url(self): """Open website for parcel tracking. + Each carrier should implement _get_tracking_link There is low chance you need to override this method. returns: @@ -213,7 +214,6 @@ def _roulier_get_tracking_link(self): """Build a tracking url. You have to implement it for your carrier. - It's like : 'https://the-carrier.com/?track=%s' % self.parcel_tracking returns: @@ -234,11 +234,12 @@ def _roulier_should_include_customs(self, picking): def _roulier_carrier_error_handling(self, payload, exception): """Build exception message for carrier error. - It's happen when the carrier WS returns something unexpected. + It's happen when the carrier WS returns something unexpected. You may improve this for your carrier. returns: - string""" + string + """ return _(u'Sent data:\n%s\n\nException raised:\n%s\n' % ( payload, exception.message)) @@ -301,7 +302,7 @@ def _roulier_handle_attachments(self, picking, response): def _roulier_prepare_label(self, picking, response): """Prepare a dict for building a shipping.label. - + The shipping label is what you stick on your packages. returns: dict @@ -319,7 +320,7 @@ def _roulier_prepare_label(self, picking, response): } def _roulier_prepare_attachments(self, picking, response): - """Prepare a list of dicts for building ir.attachemens. + """Prepare a list of dicts for building ir.attachemens. Attachements are annexes like customs declarations, summary etc. @@ -338,7 +339,6 @@ def _roulier_prepare_attachments(self, picking, response): self.name, attachment['name'], attachment['type']), } for attachment in attachments] - def _roulier_handle_tracking(self, picking, response): """Extract tracking number from response call to parcel_tracking.""" tracking = response.get('tracking') @@ -346,4 +346,3 @@ def _roulier_handle_tracking(self, picking, response): number = tracking.get('number') if number: self.parcel_tracking = number - \ No newline at end of file From eecd70d04421abc166a0270a0c1fd91c7ed43f05 Mon Sep 17 00:00:00 2001 From: Raph Date: Mon, 10 Apr 2017 17:35:57 +0200 Subject: [PATCH 30/92] Pylint stuff --- delivery_roulier/__openerp__.py | 2 +- delivery_roulier/models/stock_picking.py | 2 +- delivery_roulier/views/stock_quant_package.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/delivery_roulier/__openerp__.py b/delivery_roulier/__openerp__.py index b740180265..0e7ff8a4aa 100644 --- a/delivery_roulier/__openerp__.py +++ b/delivery_roulier/__openerp__.py @@ -5,7 +5,7 @@ { 'name': 'Delivery Carrier Roulier', 'version': '9.0.0.0.0', - 'author': 'Akretion', + 'author': 'Akretion,Odoo Community Association (OCA)', 'summary': 'Integration of multiple carriers', 'maintainer': 'Akretion, Odoo Community Association (OCA)', 'category': 'Warehouse', diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index be71736bff..18e12d14fc 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -256,7 +256,7 @@ def open_website_url(self): packages = self._get_packages_from_picking() if len(packages) == 0: - raise UserError('No packages found for this picking') + raise UserError(_('No packages found for this picking')) elif len(packages) == 1: return packages.open_website_url() # shortpath diff --git a/delivery_roulier/views/stock_quant_package.xml b/delivery_roulier/views/stock_quant_package.xml index 796b6bf342..9957857bb2 100644 --- a/delivery_roulier/views/stock_quant_package.xml +++ b/delivery_roulier/views/stock_quant_package.xml @@ -14,4 +14,4 @@
- \ No newline at end of file + From d1b3374bd53d75e4040d1051f8a0dd75cc635d37 Mon Sep 17 00:00:00 2001 From: Raph Date: Tue, 16 May 2017 16:51:53 +0200 Subject: [PATCH 31/92] Fix new api with parcels instead of parcel --- delivery_roulier/models/stock_quant_package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 835a24a413..488ebea9a7 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -167,7 +167,7 @@ def _call_roulier_api(self, picking): payload['customs'] = self._get_customs(picking) payload['service'] = picking._get_service(self) - payload['parcel'] = self._get_parcel(picking) + payload['parcels'] = [self._get_parcel(picking)] # hook to override request / payload payload = self._before_call(picking, payload) From 5b73bcef661a6fcff54c39ec8f54ae5906b71b22 Mon Sep 17 00:00:00 2001 From: Raph Date: Wed, 17 May 2017 11:03:50 +0200 Subject: [PATCH 32/92] Incr lib version --- delivery_roulier/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/__openerp__.py b/delivery_roulier/__openerp__.py index 0e7ff8a4aa..26bc6393f1 100644 --- a/delivery_roulier/__openerp__.py +++ b/delivery_roulier/__openerp__.py @@ -24,7 +24,7 @@ ], 'external_dependencies': { 'python': [ - 'roulier', # '>0.1.4' + 'roulier', # '>0.2.0' ], }, 'installable': True, From e87a1630bfc0e41058b1db18ef815ce80e67f8ad Mon Sep 17 00:00:00 2001 From: Raph Date: Thu, 18 May 2017 18:41:05 +0200 Subject: [PATCH 33/92] api.multi for parcels --- .../models/stock_quant_package.py | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 488ebea9a7..ab3f396f6c 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -110,18 +110,30 @@ def _handle_tracking(self, label, response): @implemented_by_carrier def _get_tracking_link(self): pass + + @implemented_by_carrier + def _generate_labels(self, picking): + pass + + @implemented_by_carrier + def _get_parcels(self, picking): + pass # end of API # Core functions @api.multi - def _generate_labels(self, picking): - ret = [] + def _roulier_generate_labels(self, picking): + # by default, only one pack per call for package in self: response = package._call_roulier_api(picking) package._handle_tracking(picking, response) - attach = package._handle_attachments(picking, response) - ret.append(attach.get('label')) - return ret + package._handle_attachments(picking, response) + + @api.multi + def _roulier_get_parcels(self, picking): + # by default, only one pack per call + self.ensure_one() + return [self._get_parcel(picking)] @api.multi def open_website_url(self): @@ -187,6 +199,7 @@ def _roulier_get_parcel(self, picking): weight = self.weight parcel = { 'weight': weight, + 'reference': self.name } return parcel @@ -288,18 +301,28 @@ def _roulier_get_customs(self, picking): } # There is low chance you need to override the following methods. + @api.multi def _roulier_handle_attachments(self, picking, response): - main_label = self._roulier_prepare_label(picking, response) - label = self.env['shipping.label'].create(main_label) - attachments = self._roulier_prepare_attachments(picking, response) + labels = [] + parcels = iter(response.get('parcels', [])) + for rec in self: + response = parcels.next() + main_label = rec._roulier_prepare_label(picking, response) + labels.append( + self.env['shipping.label'].create(main_label) + ) + + attachments = [ + self.env['ir.attachment'].create(attachment) + for attachment in + self._roulier_prepare_attachments(picking, response) + ] return { - 'label': label, - 'attachments': [ - self.env['ir.attachment'].create(attachment) - for attachment in attachments - ] + 'labels': labels, + 'attachments': attachments, } + @api.multi def _roulier_prepare_label(self, picking, response): """Prepare a dict for building a shipping.label. @@ -307,6 +330,7 @@ def _roulier_prepare_label(self, picking, response): returns: dict """ + self.ensure_one() label = response.get('label') return { 'res_id': picking.id, @@ -319,6 +343,7 @@ def _roulier_prepare_label(self, picking, response): self.name, label['name'], label['type']), } + @api.multi def _roulier_prepare_attachments(self, picking, response): """Prepare a list of dicts for building ir.attachemens. @@ -328,6 +353,7 @@ def _roulier_prepare_attachments(self, picking, response): returns: list """ + self.ensure_one() attachments = response.get('annexes') return [{ 'res_id': picking.id, @@ -339,10 +365,11 @@ def _roulier_prepare_attachments(self, picking, response): self.name, attachment['name'], attachment['type']), } for attachment in attachments] - def _roulier_handle_tracking(self, picking, response): - """Extract tracking number from response call to parcel_tracking.""" + @api.multi + def _handle_tracking(self, picking, response): + number = False tracking = response.get('tracking') if tracking: number = tracking.get('number') - if number: - self.parcel_tracking = number + for rec in self: + rec.parcel_tracking = number From 64fcd8cbe8cd21986ab65b1dff031bde0c83a7f3 Mon Sep 17 00:00:00 2001 From: Raph Date: Mon, 22 May 2017 15:24:44 +0200 Subject: [PATCH 34/92] Move shipping id generation to picking Improve @implemented_by_carrier by searching in a picking in arg list instead of relying on set by caller (which is impratical when you have recordsets) --- .../models/stock_quant_package.py | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index ab3f396f6c..82288c500f 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -38,7 +38,15 @@ def _gls_do_something() @wraps(func) def wrapper(cls, *args, **kwargs): fun_name = func.__name__ - fun = '_%s%s' % (cls.carrier_type, fun_name) + + def get_carrier_type(cls, *args, **kwargs): + if hasattr(cls, 'carrier_type'): + return cls.carrier_type + picking = [obj for obj in args if obj._name == 'stock.picking'][0] + return picking.carrier_type + + carrier_type = get_carrier_type(cls, *args, **kwargs) + fun = '_%s%s' % (carrier_type, fun_name) if not hasattr(cls, fun): fun = '_roulier%s' % (fun_name) # return func(cls, *args, **kwargs) @@ -145,7 +153,6 @@ def open_website_url(self): action """ self.ensure_one() - self.carrier_type = self.carrier_id.carrier_type url = self._get_tracking_link() client_action = { 'type': 'ir.actions.act_url', @@ -161,10 +168,6 @@ def _call_roulier_api(self, picking): # There is low chance you need to override it. # Don't forget to implement _a-carrier_before_call # and _a-carrier_after_call - self.ensure_one() - - self.carrier_id = picking.carrier_id - self.carrier_type = picking.carrier_type # on memory value ! roulier_instance = roulier.get(picking.carrier_type) payload = roulier_instance.api() @@ -179,7 +182,7 @@ def _call_roulier_api(self, picking): payload['customs'] = self._get_customs(picking) payload['service'] = picking._get_service(self) - payload['parcels'] = [self._get_parcel(picking)] + payload['parcels'] = self._get_parcels(picking) # hook to override request / payload payload = self._before_call(picking, payload) @@ -195,7 +198,9 @@ def _call_roulier_api(self, picking): return self._after_call(picking, ret) # default implementations + @api.multi def _roulier_get_parcel(self, picking): + self.ensure_one() weight = self.weight parcel = { 'weight': weight, @@ -268,6 +273,7 @@ def _roulier_invalid_api_input_handling(self, payload, exception): """ return _(u'Bad input: %s\n' % exception.message) + @api.multi def _roulier_get_customs(self, picking): """Format customs infos for each product in the package. @@ -278,6 +284,8 @@ def _roulier_get_customs(self, picking): dict.'articles' : list with qty, weight, hs_code int category: gift 1, sample 2, commercial 3, ... """ + self.ensure_one() + articles = [] for operation in self.get_operations(): article = {} @@ -306,17 +314,19 @@ def _roulier_handle_attachments(self, picking, response): labels = [] parcels = iter(response.get('parcels', [])) for rec in self: - response = parcels.next() - main_label = rec._roulier_prepare_label(picking, response) + parcel_rep = parcels.next() + main_label = rec._roulier_prepare_label(picking, parcel_rep) labels.append( self.env['shipping.label'].create(main_label) ) + import pdb + pdb.set_trace() attachments = [ self.env['ir.attachment'].create(attachment) for attachment in - self._roulier_prepare_attachments(picking, response) - ] + self[0]._roulier_prepare_attachments(picking, response) + ] # do it once for all return { 'labels': labels, 'attachments': attachments, @@ -366,7 +376,7 @@ def _roulier_prepare_attachments(self, picking, response): } for attachment in attachments] @api.multi - def _handle_tracking(self, picking, response): + def _roulier_handle_tracking(self, picking, response): number = False tracking = response.get('tracking') if tracking: From 1b1c4f0b7635f405a221096c0c8190a031d2517e Mon Sep 17 00:00:00 2001 From: Raph Date: Mon, 22 May 2017 15:34:38 +0200 Subject: [PATCH 35/92] Fix decorator --- delivery_roulier/models/stock_quant_package.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 82288c500f..42629835e0 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -42,7 +42,9 @@ def wrapper(cls, *args, **kwargs): def get_carrier_type(cls, *args, **kwargs): if hasattr(cls, 'carrier_type'): return cls.carrier_type - picking = [obj for obj in args if obj._name == 'stock.picking'][0] + picking = [ + obj for obj in args + if getattr(obj, '_name', '') == 'stock.picking'][0] return picking.carrier_type carrier_type = get_carrier_type(cls, *args, **kwargs) From 3ad4af6fdf96822c4e420e8c2ec8ac2c526339fb Mon Sep 17 00:00:00 2001 From: Raph Date: Tue, 23 May 2017 17:52:48 +0200 Subject: [PATCH 36/92] Remove pdb and improve edi --- delivery_roulier/models/stock_quant_package.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 42629835e0..b532833200 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -321,9 +321,6 @@ def _roulier_handle_attachments(self, picking, response): labels.append( self.env['shipping.label'].create(main_label) ) - import pdb - pdb.set_trace() - attachments = [ self.env['ir.attachment'].create(attachment) for attachment in From 7072c95f89071ad546d777196a1a7447d5ab7dc2 Mon Sep 17 00:00:00 2001 From: Raph Date: Tue, 30 May 2017 16:36:38 +0200 Subject: [PATCH 37/92] Fix get_carrier_type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dans le cas où on déclanche une erreur visiblement ya pas de pickign en parametre --- delivery_roulier/models/stock_quant_package.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index b532833200..e89458eee1 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -42,10 +42,14 @@ def wrapper(cls, *args, **kwargs): def get_carrier_type(cls, *args, **kwargs): if hasattr(cls, 'carrier_type'): return cls.carrier_type - picking = [ + # TODO: est-ce bien utile si on carrier_id ? + pickings = [ obj for obj in args - if getattr(obj, '_name', '') == 'stock.picking'][0] - return picking.carrier_type + if getattr(obj, '_name', '') == 'stock.picking'] + if len(pickings) > 0: + return pickings[0].carrier_type + if cls[0].carrier_id: + return cls[0].carrier_id.type carrier_type = get_carrier_type(cls, *args, **kwargs) fun = '_%s%s' % (carrier_type, fun_name) From 13d83193a5c195a35e79ed64fc3729832e69128e Mon Sep 17 00:00:00 2001 From: Raph Date: Fri, 7 Jul 2017 12:12:09 +0200 Subject: [PATCH 38/92] Add logging when exceptions, add incoterms and codesa --- delivery_roulier/models/stock_quant_package.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index e89458eee1..03ebebb4be 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -264,6 +264,11 @@ def _roulier_carrier_error_handling(self, payload, exception): returns: string """ + try: + _logger.debug(exception.response.text) + _logger.debug(exception.response.request.body) + except AttributeError: + _logger.debug('No request available') return _(u'Sent data:\n%s\n\nException raised:\n%s\n' % ( payload, exception.message)) From 452c3124c79fdd1850e5cc465858a9b0e4c7ef02 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 30 Nov 2017 15:50:27 +0100 Subject: [PATCH 39/92] Fix variable name --- delivery_roulier/models/stock_quant_package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 03ebebb4be..adc188e679 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -49,7 +49,7 @@ def get_carrier_type(cls, *args, **kwargs): if len(pickings) > 0: return pickings[0].carrier_type if cls[0].carrier_id: - return cls[0].carrier_id.type + return cls[0].carrier_id.carrier_type carrier_type = get_carrier_type(cls, *args, **kwargs) fun = '_%s%s' % (carrier_type, fun_name) From ef438553af24a8e5b5821b47a0b10f53f684dfd3 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 30 Nov 2017 16:27:48 +0100 Subject: [PATCH 40/92] Fill carrier_id on package when creating labels --- delivery_roulier/models/stock_quant_package.py | 1 + 1 file changed, 1 insertion(+) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index adc188e679..a53a25904d 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -174,6 +174,7 @@ def _call_roulier_api(self, picking): # There is low chance you need to override it. # Don't forget to implement _a-carrier_before_call # and _a-carrier_after_call + self.write({'carrier_id': picking.carrier_id.id}) roulier_instance = roulier.get(picking.carrier_type) payload = roulier_instance.api() From 67579118c40b7be189eb417344da5abeec85b40c Mon Sep 17 00:00:00 2001 From: hpar Date: Wed, 13 Dec 2017 17:52:46 +0100 Subject: [PATCH 41/92] Port laposte to 10 --- delivery_roulier/__init__.py | 2 + .../{__openerp__.py => __manifest__.py} | 2 +- delivery_roulier/decorator.py | 41 ++++++++++++++++ delivery_roulier/models/stock_picking.py | 32 ++----------- .../models/stock_quant_package.py | 47 ++----------------- .../views/stock_quant_package.xml | 7 +-- 6 files changed, 56 insertions(+), 75 deletions(-) rename delivery_roulier/{__openerp__.py => __manifest__.py} (97%) create mode 100644 delivery_roulier/decorator.py diff --git a/delivery_roulier/__init__.py b/delivery_roulier/__init__.py index 0650744f6b..052f63d6f0 100644 --- a/delivery_roulier/__init__.py +++ b/delivery_roulier/__init__.py @@ -1 +1,3 @@ from . import models + +from .decorator import implemented_by_carrier \ No newline at end of file diff --git a/delivery_roulier/__openerp__.py b/delivery_roulier/__manifest__.py similarity index 97% rename from delivery_roulier/__openerp__.py rename to delivery_roulier/__manifest__.py index 26bc6393f1..69e1d78486 100644 --- a/delivery_roulier/__openerp__.py +++ b/delivery_roulier/__manifest__.py @@ -4,7 +4,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Delivery Carrier Roulier', - 'version': '9.0.0.0.0', + 'version': '10.0.1.0.0', 'author': 'Akretion,Odoo Community Association (OCA)', 'summary': 'Integration of multiple carriers', 'maintainer': 'Akretion, Odoo Community Association (OCA)', diff --git a/delivery_roulier/decorator.py b/delivery_roulier/decorator.py new file mode 100644 index 0000000000..339d77ec95 --- /dev/null +++ b/delivery_roulier/decorator.py @@ -0,0 +1,41 @@ +# coding: utf-8 +# @author Raphael Reverdy +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from functools import wraps + + +def implemented_by_carrier(func): + """Decorator: call _carrier_prefixed method instead. + + Usage: + @implemented_by_carrier + def _do_something() + def _laposte_do_something() + def _gls_do_something() + + At runtime, picking._do_something() will try to call + the carrier spectific method or fallback to generic _do_something + + """ + @wraps(func) + def wrapper(cls, *args, **kwargs): + fun_name = func.__name__ + + def get_carrier_type(cls, *args, **kwargs): + if hasattr(cls, 'carrier_type'): + return cls.carrier_type + pickings = [ + obj for obj in args + if getattr(obj, '_name', '') == 'stock.picking'] + if len(pickings) > 0: + return pickings[0].carrier_type + if cls[0].carrier_id: + return cls[0].carrier_id.carrier_type + + carrier_type = get_carrier_type(cls, *args, **kwargs) + fun = '_%s%s' % (carrier_type, fun_name) + if not hasattr(cls, fun): + fun = '_roulier%s' % (fun_name) + return getattr(cls, fun)(*args, **kwargs) + return wrapper diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 18e12d14fc..3ab4b06bc8 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -4,12 +4,13 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from datetime import datetime, timedelta -from functools import wraps import logging -from openerp import models, fields, api -from openerp.tools.translate import _ -from openerp.exceptions import Warning as UserError +from odoo import models, fields, api +from odoo.tools.translate import _ +from odoo.exceptions import UserError + +from ..decorator import implemented_by_carrier _logger = logging.getLogger(__name__) try: @@ -18,29 +19,6 @@ _logger.debug('Cannot `import roulier`.') -def implemented_by_carrier(func): - """Decorator: call _carrier_prefixed method instead. - - Usage: - @implemented_by_carrier - def _do_something() - def _laposte_do_something() - def _gls_do_something() - - At runtime, picking._do_something() will try to call - the carrier spectific method or fallback to generic _do_something - """ - @wraps(func) - def wrapper(cls, *args, **kwargs): - fun_name = func.__name__ - fun = '_%s%s' % (cls.carrier_type, fun_name) - if not hasattr(cls, fun): - fun = '_roulier%s' % (fun_name) - # return func(cls, *args, **kwargs) - return getattr(cls, fun)(*args, **kwargs) - return wrapper - - class StockPicking(models.Model): _inherit = 'stock.picking' diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index a53a25904d..e9687c446d 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -3,13 +3,14 @@ # David BEAL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from functools import wraps import logging import base64 -from openerp import models, api, fields -from openerp.tools.translate import _ -from openerp.exceptions import UserError +from odoo import models, api, fields +from odoo.tools.translate import _ +from odoo.exceptions import UserError + +from ..decorator import implemented_by_carrier _logger = logging.getLogger(__name__) try: @@ -22,44 +23,6 @@ _logger.debug('Cannot `import roulier`.') -def implemented_by_carrier(func): - """Decorator: call _carrier_prefixed method instead. - - Usage: - @implemented_by_carrier - def _do_something() - def _laposte_do_something() - def _gls_do_something() - - At runtime, picking._do_something() will try to call - the carrier spectific method or fallback to generic _do_something - - """ - @wraps(func) - def wrapper(cls, *args, **kwargs): - fun_name = func.__name__ - - def get_carrier_type(cls, *args, **kwargs): - if hasattr(cls, 'carrier_type'): - return cls.carrier_type - # TODO: est-ce bien utile si on carrier_id ? - pickings = [ - obj for obj in args - if getattr(obj, '_name', '') == 'stock.picking'] - if len(pickings) > 0: - return pickings[0].carrier_type - if cls[0].carrier_id: - return cls[0].carrier_id.carrier_type - - carrier_type = get_carrier_type(cls, *args, **kwargs) - fun = '_%s%s' % (carrier_type, fun_name) - if not hasattr(cls, fun): - fun = '_roulier%s' % (fun_name) - # return func(cls, *args, **kwargs) - return getattr(cls, fun)(*args, **kwargs) - return wrapper - - class StockQuantPackage(models.Model): _inherit = 'stock.quant.package' diff --git a/delivery_roulier/views/stock_quant_package.xml b/delivery_roulier/views/stock_quant_package.xml index 9957857bb2..5a54b6289e 100644 --- a/delivery_roulier/views/stock_quant_package.xml +++ b/delivery_roulier/views/stock_quant_package.xml @@ -1,6 +1,5 @@ - - + stock.quant.package @@ -12,6 +11,4 @@ - - - + From 229fca2bdb526e842aa702a940cda580ba202880 Mon Sep 17 00:00:00 2001 From: Hpar Date: Tue, 6 Feb 2018 10:06:45 +0100 Subject: [PATCH 42/92] update get_password to new api --- delivery_roulier/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 3ab4b06bc8..02088d5c00 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -118,7 +118,7 @@ def _roulier_get_auth(self, package): account = self._get_account(package) auth = { 'login': account.login, - 'password': account.get_password(), + 'password': account._get_password(), } return auth From b527fdf7905ef3ba3a73d1cabc470be2d2110583 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 20 Apr 2018 14:40:54 +0200 Subject: [PATCH 43/92] Avoid crash when HS code is missing --- delivery_roulier/models/stock_quant_package.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index e9687c446d..492d7f1e83 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -6,8 +6,7 @@ import logging import base64 -from odoo import models, api, fields -from odoo.tools.translate import _ +from odoo import models, api, fields, _ from odoo.exceptions import UserError from ..decorator import implemented_by_carrier @@ -268,6 +267,11 @@ def _roulier_get_customs(self, picking): product = operation.product_id # stands for harmonized_system hs = product.product_tmpl_id.get_hs_code_recursively() + if not hs: + raise UserError(_( + "No H.S. Code on product '%s' nor on it's " + "product category '%s'.") + % (product.display_name, product.categ_id.display_name)) article['quantity'] = '%.f' % operation.product_qty article['weight'] = ( From 4f85b4d80c70a1ba468551b319e588efaeee3610 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 20 Apr 2018 17:23:50 +0200 Subject: [PATCH 44/92] delivery_roulier: accurate computation of unit price for CN23 --- delivery_roulier/models/__init__.py | 1 + .../models/stock_pack_operation.py | 39 +++++++++++++++++++ .../models/stock_quant_package.py | 2 +- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 delivery_roulier/models/stock_pack_operation.py diff --git a/delivery_roulier/models/__init__.py b/delivery_roulier/models/__init__.py index f005d93463..ba875968ab 100644 --- a/delivery_roulier/models/__init__.py +++ b/delivery_roulier/models/__init__.py @@ -1,2 +1,3 @@ from . import stock_picking from . import stock_quant_package +from . import stock_pack_operation diff --git a/delivery_roulier/models/stock_pack_operation.py b/delivery_roulier/models/stock_pack_operation.py new file mode 100644 index 0000000000..513055d7fd --- /dev/null +++ b/delivery_roulier/models/stock_pack_operation.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models, api +from odoo.tools import float_is_zero + + +class StockPackOperation(models.Model): + _inherit = 'stock.pack.operation' + + @api.multi + def _roulier_get_unit_price_for_customs(self): + """This method is designed to be inherited for specific scenarios""" + self.ensure_one() + prec = self.env['decimal.precision'].precision_get( + 'Product Unit of Measure') + if ( + self.linked_move_operation_ids and + self.linked_move_operation_ids[0].move_id and + self.linked_move_operation_ids[0].move_id.procurement_id and + self.linked_move_operation_ids[0].move_id.procurement_id. + sale_line_id and + not float_is_zero( + self.linked_move_operation_ids[0].move_id.procurement_id. + sale_line_id.product_uom_qty, precision_digits=prec)): + sol = self.linked_move_operation_ids[0].move_id.\ + procurement_id.sale_line_id + price_unit_so_uom = sol.price_subtotal / sol.product_uom_qty + price_unit = sol.product_uom._compute_price( + price_unit_so_uom, self.product_uom_id) + else: + product = self.product_id + ato = self.env['account.tax'] + price_unit = ato._fix_tax_included_price_company( + product.list_price, product.taxes_id, ato, + self.picking_id.company_id) + return price_unit diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 492d7f1e83..a7a2596f3b 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -279,7 +279,7 @@ def _roulier_get_customs(self, picking): article['originCountry'] = product.origin_country_id.code article['description'] = hs.description article['hs'] = hs.hs_code - article['value'] = product.list_price # unit price is expected + article['value'] = operation._roulier_get_unit_price_for_customs() category = picking.customs_category return { From 7312184af8c7639c2b2127254ccc4af9cc53eca9 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 30 May 2018 20:31:49 +0200 Subject: [PATCH 45/92] Rename method Move code that get SO from move to dedicated method --- .../models/stock_pack_operation.py | 31 +++++++++---------- .../models/stock_quant_package.py | 2 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/delivery_roulier/models/stock_pack_operation.py b/delivery_roulier/models/stock_pack_operation.py index 513055d7fd..143c96b83c 100644 --- a/delivery_roulier/models/stock_pack_operation.py +++ b/delivery_roulier/models/stock_pack_operation.py @@ -3,32 +3,23 @@ # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, api +from odoo import models from odoo.tools import float_is_zero class StockPackOperation(models.Model): _inherit = 'stock.pack.operation' - @api.multi - def _roulier_get_unit_price_for_customs(self): + def get_unit_price_for_customs(self): """This method is designed to be inherited for specific scenarios""" self.ensure_one() prec = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') - if ( - self.linked_move_operation_ids and - self.linked_move_operation_ids[0].move_id and - self.linked_move_operation_ids[0].move_id.procurement_id and - self.linked_move_operation_ids[0].move_id.procurement_id. - sale_line_id and - not float_is_zero( - self.linked_move_operation_ids[0].move_id.procurement_id. - sale_line_id.product_uom_qty, precision_digits=prec)): - sol = self.linked_move_operation_ids[0].move_id.\ - procurement_id.sale_line_id - price_unit_so_uom = sol.price_subtotal / sol.product_uom_qty - price_unit = sol.product_uom._compute_price( + soline = self.get_sale_order_line() + if soline and not float_is_zero( + soline.product_uom_qty, precision_digits=prec): + price_unit_so_uom = soline.price_subtotal / soline.product_uom_qty + price_unit = soline.product_uom._compute_price( price_unit_so_uom, self.product_uom_id) else: product = self.product_id @@ -37,3 +28,11 @@ def _roulier_get_unit_price_for_customs(self): product.list_price, product.taxes_id, ato, self.picking_id.company_id) return price_unit + + def get_sale_order_line(self): + soline = self.linked_move_operation_ids and\ + self.linked_move_operation_ids[0].move_id and\ + self.linked_move_operation_ids[0].move_id.procurement_id and\ + self.linked_move_operation_ids[0].move_id.procurement_id.\ + sale_line_id or False + return soline diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index a7a2596f3b..dae29a9646 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -279,7 +279,7 @@ def _roulier_get_customs(self, picking): article['originCountry'] = product.origin_country_id.code article['description'] = hs.description article['hs'] = hs.hs_code - article['value'] = operation._roulier_get_unit_price_for_customs() + article['value'] = operation.get_unit_price_for_customs() category = picking.customs_category return { From 9ac4a7390eee3758d53feb527be24f1c8821afa4 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Tue, 30 Oct 2018 22:29:21 +0100 Subject: [PATCH 46/92] Adapt to changes in product_harmonized_system --- delivery_roulier/models/stock_quant_package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index dae29a9646..1da6cf6697 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -266,7 +266,7 @@ def _roulier_get_customs(self, picking): articles.append(article) product = operation.product_id # stands for harmonized_system - hs = product.product_tmpl_id.get_hs_code_recursively() + hs = product.get_hs_code_recursively() if not hs: raise UserError(_( "No H.S. Code on product '%s' nor on it's " From 6ea80cb0b0f962c606ff2a4f59f0b4249b417682 Mon Sep 17 00:00:00 2001 From: Pierrick Brun Date: Tue, 6 Nov 2018 11:47:18 +0100 Subject: [PATCH 47/92] [FIX] use shipping weight if defined (more accurate) --- delivery_roulier/models/stock_quant_package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 1da6cf6697..a6b357e4ce 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -170,7 +170,7 @@ def _call_roulier_api(self, picking): @api.multi def _roulier_get_parcel(self, picking): self.ensure_one() - weight = self.weight + weight = self.shipping_weight or self.weight parcel = { 'weight': weight, 'reference': self.name From d8ecef175a6bb9742cf279bf39b1c26d28fd3ed2 Mon Sep 17 00:00:00 2001 From: David Beal Date: Wed, 28 Aug 2019 19:52:50 +0200 Subject: [PATCH 48/92] MIGR v12 delivery_roulier --- delivery_roulier/__manifest__.py | 9 +--- delivery_roulier/decorator.py | 15 +++---- delivery_roulier/models/__init__.py | 3 +- ...k_pack_operation.py => stock_move_line.py} | 13 ++---- delivery_roulier/models/stock_picking.py | 43 ++++++------------- .../models/stock_quant_package.py | 3 +- 6 files changed, 30 insertions(+), 56 deletions(-) rename delivery_roulier/models/{stock_pack_operation.py => stock_move_line.py} (73%) diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index 69e1d78486..188b192896 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -1,10 +1,9 @@ -# coding: utf-8 # @author Raphael Reverdy # David BEAL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Delivery Carrier Roulier', - 'version': '10.0.1.0.0', + 'version': '12.0.1.0.0', 'author': 'Akretion,Odoo Community Association (OCA)', 'summary': 'Integration of multiple carriers', 'maintainer': 'Akretion, Odoo Community Association (OCA)', @@ -12,13 +11,11 @@ 'depends': [ 'partner_helper', 'base_phone', # from oca/telephony - 'keychain', # from oca/server-tools 'base_suspend_security', 'product_harmonized_system', # from oca/intrastat 'base_delivery_carrier_label', - 'delivery_carrier_deposit', ], - 'website': 'http://www.akretion.com/', + 'website': 'https://github.com/delivery-carrier', 'data': [ 'views/stock_quant_package.xml', ], @@ -28,7 +25,5 @@ ], }, 'installable': True, - 'auto_install': False, 'license': 'AGPL-3', - 'application': False, } diff --git a/delivery_roulier/decorator.py b/delivery_roulier/decorator.py index 339d77ec95..ddc57145c2 100644 --- a/delivery_roulier/decorator.py +++ b/delivery_roulier/decorator.py @@ -1,4 +1,3 @@ -# coding: utf-8 # @author Raphael Reverdy # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). @@ -22,19 +21,19 @@ def _gls_do_something() def wrapper(cls, *args, **kwargs): fun_name = func.__name__ - def get_carrier_type(cls, *args, **kwargs): - if hasattr(cls, 'carrier_type'): - return cls.carrier_type + def get_delivery_type(cls, *args, **kwargs): + if hasattr(cls, 'delivery_type'): + return cls.delivery_type pickings = [ obj for obj in args if getattr(obj, '_name', '') == 'stock.picking'] if len(pickings) > 0: - return pickings[0].carrier_type + return pickings[0].delivery_type if cls[0].carrier_id: - return cls[0].carrier_id.carrier_type + return cls[0].carrier_id.delivery_type - carrier_type = get_carrier_type(cls, *args, **kwargs) - fun = '_%s%s' % (carrier_type, fun_name) + delivery_type = get_delivery_type(cls, *args, **kwargs) + fun = '_%s%s' % (delivery_type, fun_name) if not hasattr(cls, fun): fun = '_roulier%s' % (fun_name) return getattr(cls, fun)(*args, **kwargs) diff --git a/delivery_roulier/models/__init__.py b/delivery_roulier/models/__init__.py index ba875968ab..2547b51a31 100644 --- a/delivery_roulier/models/__init__.py +++ b/delivery_roulier/models/__init__.py @@ -1,3 +1,4 @@ from . import stock_picking from . import stock_quant_package -from . import stock_pack_operation +from . import stock_move_line +from . import carrier_account diff --git a/delivery_roulier/models/stock_pack_operation.py b/delivery_roulier/models/stock_move_line.py similarity index 73% rename from delivery_roulier/models/stock_pack_operation.py rename to delivery_roulier/models/stock_move_line.py index 143c96b83c..3274e2160f 100644 --- a/delivery_roulier/models/stock_pack_operation.py +++ b/delivery_roulier/models/stock_move_line.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2018 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -7,8 +6,8 @@ from odoo.tools import float_is_zero -class StockPackOperation(models.Model): - _inherit = 'stock.pack.operation' +class StockMoveLine(models.Model): + _inherit = 'stock.move.line' def get_unit_price_for_customs(self): """This method is designed to be inherited for specific scenarios""" @@ -30,9 +29,5 @@ def get_unit_price_for_customs(self): return price_unit def get_sale_order_line(self): - soline = self.linked_move_operation_ids and\ - self.linked_move_operation_ids[0].move_id and\ - self.linked_move_operation_ids[0].move_id.procurement_id and\ - self.linked_move_operation_ids[0].move_id.procurement_id.\ - sale_line_id or False - return soline + self.ensure_one() + return self.move_id and self.move_id.sale_line_id or False diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 02088d5c00..170e97f7fe 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -1,4 +1,3 @@ -# coding: utf-8 # @author Raphael Reverdy # David BEAL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). @@ -6,7 +5,7 @@ from datetime import datetime, timedelta import logging -from odoo import models, fields, api +from odoo import models, fields from odoo.tools.translate import _ from odoo.exceptions import UserError @@ -72,38 +71,30 @@ def _convert_address(self, partner): # End of API. # Implementations for base_delivery_carrier_label - @api.multi def _is_roulier(self): self.ensure_one() - return self.carrier_type in roulier.get_carriers() + return self.delivery_type in roulier.get_carriers() - @api.multi - def generate_labels(self, package_ids=None): + def generate_labels(self): """See base_delivery_carrier_label/models/stock_picking.py.""" # entry point self.ensure_one() if self._is_roulier(): return self._roulier_generate_labels() - _super = super(StockPicking, self) - return _super.generate_labels(package_ids=package_ids) + return super().generate_labels() - @api.multi - def generate_shipping_labels(self, package_ids=None): + def generate_shipping_labels(self): """See base_delivery_carrier_label/stock.py.""" self.ensure_one() if self._is_roulier(): raise UserError(_("Don't call me directly")) - _super = super(StockPicking, self) - return _super.generate_shipping_labels(package_ids=package_ids) + return super().generate_shipping_labels() - @api.multi def _roulier_generate_labels(self): """Create as many labels as package_ids or in self.""" self.ensure_one() packages = self._get_packages_from_picking() - if not packages: - # It's not our responsibility to create the packages - raise UserError(_('No package found for this picking')) + # base_delivery_carrier_label module ensure packages is available self.number_of_packages = len(packages) self.carrier_tracking_ref = True # display button in view return packages._generate_labels(self) @@ -131,16 +122,11 @@ def _roulier_get_account(self, package): Accounts are resolved at runtime (can be != for dev/prod) """ - keychain = self.env['keychain.account'] - if self.env.user.has_group('stock.group_stock_user'): - retrieve = keychain.suspend_security().retrieve - else: - retrieve = keychain.retrieve - accounts = retrieve( - [['namespace', '=', 'roulier_%s' % self.carrier_type]]) - return accounts[0] - - def _roulier_get_sender(self, package): + self.ensure_one() + domain = [("name", "=", self.carrier_id.delivery_type)] + return self.env["carrier.account"].search(domain, limit=1) + + def _roulier_get_sender(self, package=None): """Sender of the picking (for the label). Return: @@ -148,7 +134,7 @@ def _roulier_get_sender(self, package): """ return self.company_id.partner_id - def _roulier_get_receiver(self, package): + def _roulier_get_receiver(self, package=None): """The guy whom the shippment is for. At home or at a distribution point, it's always @@ -221,7 +207,6 @@ def _roulier_get_service(self, package): } return service - @api.multi def open_website_url(self): """Open tracking page. @@ -230,7 +215,7 @@ def open_website_url(self): """ self.ensure_one() if not self._is_roulier(): - return super(StockPicking, self).open_website_url() + return super().open_website_url() packages = self._get_packages_from_picking() if len(packages) == 0: diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index a6b357e4ce..2cb3ee5ba5 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -1,4 +1,3 @@ -# coding: utf-8 # @author Raphael Reverdy # David BEAL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). @@ -137,7 +136,7 @@ def _call_roulier_api(self, picking): # Don't forget to implement _a-carrier_before_call # and _a-carrier_after_call self.write({'carrier_id': picking.carrier_id.id}) - roulier_instance = roulier.get(picking.carrier_type) + roulier_instance = roulier.get(picking.delivery_type) payload = roulier_instance.api() sender = picking._get_sender(self) From 78ce0b0a5a23376d0148804be99794b8fd4732c0 Mon Sep 17 00:00:00 2001 From: David Beal Date: Thu, 29 Aug 2019 19:25:15 +0200 Subject: [PATCH 49/92] fixup! --- delivery_roulier/README.rst | 16 +-------- delivery_roulier/models/carrier_account.py | 27 +++++++++++++++ delivery_roulier/models/stock_picking.py | 27 ++++++++++----- delivery_roulier/readme/CONTRIBUTORS.rst | 2 ++ delivery_roulier/readme/DESCRIPTION.rst | 14 ++++++++ delivery_roulier/readme/USAGE.rst | 40 ++++++++++++++++++++++ 6 files changed, 103 insertions(+), 23 deletions(-) create mode 100644 delivery_roulier/models/carrier_account.py create mode 100644 delivery_roulier/readme/CONTRIBUTORS.rst create mode 100644 delivery_roulier/readme/DESCRIPTION.rst create mode 100644 delivery_roulier/readme/USAGE.rst diff --git a/delivery_roulier/README.rst b/delivery_roulier/README.rst index 6bc90d5c0a..f24a174a7d 100644 --- a/delivery_roulier/README.rst +++ b/delivery_roulier/README.rst @@ -1,15 +1 @@ -Integration of multiple carriers with Roulier -============================================= - - -Base module for intregation with Roulier. - -`Roulier `_ is a python library which implements carriers API. -This modules contains the core functions for this implementation. - -You should install one of the specific modules : - -- delivery_roulier_laposte -- delivery_roulier_dpd -- delivery_roulier_geodis -- more to come +autogenerated diff --git a/delivery_roulier/models/carrier_account.py b/delivery_roulier/models/carrier_account.py new file mode 100644 index 0000000000..d306b9dcf2 --- /dev/null +++ b/delivery_roulier/models/carrier_account.py @@ -0,0 +1,27 @@ +# @author David BEAL +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging + +from odoo import models, api, _ +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +MESSAGE = _( + "Account name must be in %s as Provider field value (delivery_type)") + + +class CarrierAccount(models.Model): + _inherit = 'carrier.account' + + @api.constrains("name") + def name_constraint(self): + values = [ + x[0] for x in + self.env["delivery.carrier"]._fields['delivery_type'].selection + if x[0] not in ("fixed", "base_on_rule")] + for rec in self: + if rec.name not in values: + raise UserError(MESSAGE % values) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 170e97f7fe..f733f70ef4 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -41,11 +41,11 @@ class StockPicking(models.Model): # API: @implemented_by_carrier - def _get_sender(self, package): + def _get_sender(self, package=None): pass @implemented_by_carrier - def _get_receiver(self, package): + def _get_receiver(self, package=None): pass @implemented_by_carrier @@ -53,15 +53,15 @@ def _get_shipping_date(self, package): pass @implemented_by_carrier - def _get_account(self, package): + def _get_account(self, package=None): pass @implemented_by_carrier - def _get_auth(self, package): + def _get_auth(self, package=None): pass @implemented_by_carrier - def _get_service(self, package): + def _get_service(self, package=None): pass @implemented_by_carrier @@ -110,10 +110,11 @@ def _roulier_get_auth(self, package): auth = { 'login': account.login, 'password': account._get_password(), + 'isTest': not self.carrier_id.prod_environment, } return auth - def _roulier_get_account(self, package): + def _roulier_get_account(self, package=None): """Return an 'account'. By default, the first account encoutered for this type. @@ -123,8 +124,18 @@ def _roulier_get_account(self, package): Accounts are resolved at runtime (can be != for dev/prod) """ self.ensure_one() - domain = [("name", "=", self.carrier_id.delivery_type)] - return self.env["carrier.account"].search(domain, limit=1) + domain = [ + ("name", "=", self.carrier_id.delivery_type), + "|", + ("company_id", "=", self.company_id.id), + ("company_id", "=", False) + ] + account = self.env["carrier.account"].search(domain, limit=1) + if not account: + raise UserError( + _("No account available with name '%s' " + "for this carrier" % self.carrier_id.delivery_type)) + return account def _roulier_get_sender(self, package=None): """Sender of the picking (for the label). diff --git a/delivery_roulier/readme/CONTRIBUTORS.rst b/delivery_roulier/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..849f52eb53 --- /dev/null +++ b/delivery_roulier/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Raphaël Reverdy +* David Béal diff --git a/delivery_roulier/readme/DESCRIPTION.rst b/delivery_roulier/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..763c96887f --- /dev/null +++ b/delivery_roulier/readme/DESCRIPTION.rst @@ -0,0 +1,14 @@ +Integration of multiple carriers with Roulier library + +Base module for integration with Roulier. + +`Roulier `_ is a python library which implements carriers API. +This modules contains the core functions for this implementation. + +You should install one of the specific modules : + +- delivery_roulier_laposte +- delivery_roulier_dpd +- delivery_roulier_geodis +- delivery_carrier_label_gls +- more to come diff --git a/delivery_roulier/readme/USAGE.rst b/delivery_roulier/readme/USAGE.rst new file mode 100644 index 0000000000..3bf09c4954 --- /dev/null +++ b/delivery_roulier/readme/USAGE.rst @@ -0,0 +1,40 @@ +Here is some methods you can use for your carrier implementation +allowing to have a consistent code accross different carrier modules: + +.. code-block:: python + + def _mycarrier_get_sender(...): + + + def _mycarrier_get_receiver(...): + + + def _mycarrier_get_shipping_date(...): + + + def _mycarrier_get_account(...): + + + def _mycarrier_get_auth(...): + + + def _mycarrier_get_service(...): + + + def _mycarrier_convert_address(...): + + +| + + +Instead of calling `super()` you can use: + +.. code-block:: python + + def _mycarrier_get_service(...): + + result = _roulier_get_service(...) + + result["specific_key"] = "blabla" + + return result From 6faa19649cbd42e0c5a9c355d982b67ec86de0b8 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 5 Sep 2019 12:34:58 +0200 Subject: [PATCH 50/92] [REF] Continue migration and some refactores --- delivery_roulier/__manifest__.py | 2 +- delivery_roulier/models/stock_picking.py | 71 ++++---- .../models/stock_quant_package.py | 171 ++++-------------- delivery_roulier/views/carrier_account.xml | 24 +++ 4 files changed, 102 insertions(+), 166 deletions(-) create mode 100644 delivery_roulier/views/carrier_account.xml diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index 188b192896..f1b527e9d5 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -12,12 +12,12 @@ 'partner_helper', 'base_phone', # from oca/telephony 'base_suspend_security', - 'product_harmonized_system', # from oca/intrastat 'base_delivery_carrier_label', ], 'website': 'https://github.com/delivery-carrier', 'data': [ 'views/stock_quant_package.xml', + 'views/carrier_account.xml', ], 'external_dependencies': { 'python': [ diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index f733f70ef4..ab63742cd7 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -21,22 +21,6 @@ class StockPicking(models.Model): _inherit = 'stock.picking' - customs_category = fields.Selection( - selection=[ - ('gift', _("Gift")), - ('sample', _("Samples")), - ('commercial', _("Commercial Goods")), - ('document', _("Documents")), - ('other', _("Other")), - ('return', _("Goods return")), - ], - default='commercial', - help="Type of sending for the customs") - - # base_delivery_carrier_label API implementataion - - # def generate_default_label(self, package_ids=None): - # useless method # API: @@ -57,17 +41,29 @@ def _get_account(self, package=None): pass @implemented_by_carrier - def _get_auth(self, package=None): + def _get_auth(self, account, package=None): pass @implemented_by_carrier - def _get_service(self, package=None): + def _get_service(self, account, package=None): pass @implemented_by_carrier def _convert_address(self, partner): pass + @implemented_by_carrier + def _get_label_format(self, account): + pass + + @implemented_by_carrier + def _get_from_address(self): + pass + + @implemented_by_carrier + def _get_to_address(self): + pass + # End of API. # Implementations for base_delivery_carrier_label @@ -75,19 +71,10 @@ def _is_roulier(self): self.ensure_one() return self.delivery_type in roulier.get_carriers() - def generate_labels(self): - """See base_delivery_carrier_label/models/stock_picking.py.""" - # entry point - self.ensure_one() - if self._is_roulier(): - return self._roulier_generate_labels() - return super().generate_labels() - def generate_shipping_labels(self): - """See base_delivery_carrier_label/stock.py.""" self.ensure_one() if self._is_roulier(): - raise UserError(_("Don't call me directly")) + return self._roulier_generate_labels() return super().generate_shipping_labels() def _roulier_generate_labels(self): @@ -100,16 +87,15 @@ def _roulier_generate_labels(self): return packages._generate_labels(self) # Default implementations of _roulier_*() - def _roulier_get_auth(self, package): + def _roulier_get_auth(self, account, package=None): """Login/password of the carrier account. Returns: a dict with login and password keys """ - account = self._get_account(package) auth = { - 'login': account.login, - 'password': account._get_password(), + 'login': account.account, + 'password': account.password, 'isTest': not self.carrier_id.prod_environment, } return auth @@ -145,6 +131,14 @@ def _roulier_get_sender(self, package=None): """ return self.company_id.partner_id + def _roulier_get_label_format(self, account): + """format of the label asked for carrier + + Return: + label format (string) + """ + return getattr(account, '%s_file_format' % self.delivery_type) + def _roulier_get_receiver(self, package=None): """The guy whom the shippment is for. @@ -156,7 +150,7 @@ def _roulier_get_receiver(self, package=None): """ return self.partner_id - def _roulier_get_shipping_date(self, package): + def _roulier_get_shipping_date(self, package=None): """Choose a shipping date. By default, it's tomorrow.""" @@ -201,7 +195,15 @@ def _roulier_convert_address(self, partner): return address - def _roulier_get_service(self, package): + def _roulier_get_from_address(self, package=None): + sender = self._get_sender(package=package) + return self._convert_address(sender) + + def _roulier_get_to_address(self, package=None): + receiver = self._get_receiver(package=package) + return self._convert_address(receiver) + + def _roulier_get_service(self, account, package=None): """Return a basic dict. The carrier implementation may add stuff @@ -215,6 +217,7 @@ def _roulier_get_service(self, package): service = { 'product': self.carrier_code, 'shippingDate': shipping_date, + 'labelFormat': self._get_label_format(account) } return service diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 2cb3ee5ba5..19bf3d63de 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -27,16 +27,17 @@ class StockQuantPackage(models.Model): carrier_id = fields.Many2one("delivery.carrier", string="Carrier") # helper : move it to base ? - @api.multi def get_operations(self): """Get operations of the package. Usefull for having products and quantities """ self.ensure_one() - return self.env['stock.pack.operation'].search([ - ('result_package_id', '=', self.id), + return self.env['stock.move.line'].search([ ('product_id', '!=', False), + '|', + ('package_id', '=', self.id), + ('result_package_id', '=', self.id), ]) # API @@ -50,14 +51,6 @@ def _before_call(self, picking, payload): def _after_call(self, picking, response): pass - @implemented_by_carrier - def _get_customs(self, picking): - pass - - @implemented_by_carrier - def _should_include_customs(self, picking): - pass - @implemented_by_carrier def _get_parcel(self, picking): pass @@ -71,17 +64,13 @@ def _invalid_api_input_handling(self, payload, response): pass @implemented_by_carrier - def _prepare_label(self, label, picking): + def _prepare_attachments(self, picking, response): pass @implemented_by_carrier def _handle_attachments(self, label, response): pass - @implemented_by_carrier - def _handle_tracking(self, label, response): - pass - @implemented_by_carrier def _get_tracking_link(self): pass @@ -93,24 +82,44 @@ def _generate_labels(self, picking): @implemented_by_carrier def _get_parcels(self, picking): pass + + @implemented_by_carrier + def _parse_response(self, picking, response): + pass # end of API # Core functions - @api.multi def _roulier_generate_labels(self, picking): + result = [] # by default, only one pack per call + # for now roulier manage one pack at a time by default for package in self: response = package._call_roulier_api(picking) - package._handle_tracking(picking, response) package._handle_attachments(picking, response) + result.append(self._parse_response(picking, response)) + return result + + def _roulier_parse_response(self, picking, response): + parcels = response.get('parcels') + parcel = parcels and parcels[0] + tracking_number = parcel.get('tracking', {}).get('number') + # expected format by base_delivery_carrier_label module + label = parcel.get('label') + return { + 'tracking_number': tracking_number, + 'package_id': len(self) == 1 and self.id or False, + 'name': (parcel.get('reference') or tracking_number or + label.get('name')), + 'file': label.get('data'), + 'filename': label.get('name'), + 'file_type': label.get('type') + } - @api.multi def _roulier_get_parcels(self, picking): # by default, only one pack per call self.ensure_one() return [self._get_parcel(picking)] - @api.multi def open_website_url(self): """Open website for parcel tracking. @@ -129,27 +138,22 @@ def open_website_url(self): } return client_action - @api.multi def _call_roulier_api(self, picking): """Create a label for a given package_id (self).""" # There is low chance you need to override it. # Don't forget to implement _a-carrier_before_call # and _a-carrier_after_call + account = picking._get_account(self) self.write({'carrier_id': picking.carrier_id.id}) roulier_instance = roulier.get(picking.delivery_type) payload = roulier_instance.api() - sender = picking._get_sender(self) - receiver = picking._get_receiver(self) + payload['auth'] = picking._get_auth(account, package=self) - payload['auth'] = picking._get_auth(self) + payload['from_address'] = picking._get_from_address(package=self) + payload['to_address'] = picking._get_to_address(package=self) - payload['from_address'] = picking._convert_address(sender) - payload['to_address'] = picking._convert_address(receiver) - if self._should_include_customs(picking): - payload['customs'] = self._get_customs(picking) - - payload['service'] = picking._get_service(self) + payload['service'] = picking._get_service(account, package=self) payload['parcels'] = self._get_parcels(picking) # hook to override request / payload @@ -166,7 +170,6 @@ def _call_roulier_api(self, picking): return self._after_call(picking, ret) # default implementations - @api.multi def _roulier_get_parcel(self, picking): self.ensure_one() weight = self.shipping_weight or self.weight @@ -208,16 +211,6 @@ def _roulier_get_tracking_link(self): _logger.warning("not implemented") pass - def _roulier_should_include_customs(self, picking): - """Choose if custom docs should be sent. - - Really dumb implementation. - You may improve this for your carrier. - """ - sender = picking._get_sender(self) - receiver = picking._get_receiver(self) - return sender.country_id.code != receiver.country_id.code - def _roulier_carrier_error_handling(self, payload, exception): """Build exception message for carrier error. @@ -231,8 +224,8 @@ def _roulier_carrier_error_handling(self, payload, exception): _logger.debug(exception.response.request.body) except AttributeError: _logger.debug('No request available') - return _(u'Sent data:\n%s\n\nException raised:\n%s\n' % ( - payload, exception.message)) + return _('Sent data:\n%s\n\nException raised:\n%s\n' % ( + payload, str(exception))) def _roulier_invalid_api_input_handling(self, payload, exception): """Build exception message for bad input. @@ -244,97 +237,22 @@ def _roulier_invalid_api_input_handling(self, payload, exception): returns: string """ - return _(u'Bad input: %s\n' % exception.message) - - @api.multi - def _roulier_get_customs(self, picking): - """Format customs infos for each product in the package. - - The decision whether to include these infos or not is - taken in _should_include_customs() - - Returns: - dict.'articles' : list with qty, weight, hs_code - int category: gift 1, sample 2, commercial 3, ... - """ - self.ensure_one() - - articles = [] - for operation in self.get_operations(): - article = {} - articles.append(article) - product = operation.product_id - # stands for harmonized_system - hs = product.get_hs_code_recursively() - if not hs: - raise UserError(_( - "No H.S. Code on product '%s' nor on it's " - "product category '%s'.") - % (product.display_name, product.categ_id.display_name)) - - article['quantity'] = '%.f' % operation.product_qty - article['weight'] = ( - operation.get_weight() / operation.product_qty) - article['originCountry'] = product.origin_country_id.code - article['description'] = hs.description - article['hs'] = hs.hs_code - article['value'] = operation.get_unit_price_for_customs() - - category = picking.customs_category - return { - "articles": articles, - "category": category, - } + return _('Bad input: %s\n' % exception.message) # There is low chance you need to override the following methods. - @api.multi def _roulier_handle_attachments(self, picking, response): - labels = [] - parcels = iter(response.get('parcels', [])) - for rec in self: - parcel_rep = parcels.next() - main_label = rec._roulier_prepare_label(picking, parcel_rep) - labels.append( - self.env['shipping.label'].create(main_label) - ) attachments = [ self.env['ir.attachment'].create(attachment) for attachment in self[0]._roulier_prepare_attachments(picking, response) ] # do it once for all - return { - 'labels': labels, - 'attachments': attachments, - } - - @api.multi - def _roulier_prepare_label(self, picking, response): - """Prepare a dict for building a shipping.label. - - The shipping label is what you stick on your packages. - returns: - dict - """ - self.ensure_one() - label = response.get('label') - return { - 'res_id': picking.id, - 'res_model': 'stock.picking', - 'package_id': self.id, - 'name': "%s %s" % (self.name, label['name']), - 'datas': base64.b64encode(label['data']), - 'type': 'binary', - 'datas_fname': "%s-%s.%s" % ( - self.name, label['name'], label['type']), - } + return attachments @api.multi def _roulier_prepare_attachments(self, picking, response): - """Prepare a list of dicts for building ir.attachemens. - + """Prepare a list of dicts for building ir.attachments. Attachements are annexes like customs declarations, summary etc. - returns: list """ @@ -344,17 +262,8 @@ def _roulier_prepare_attachments(self, picking, response): 'res_id': picking.id, 'res_model': 'stock.picking', 'name': "%s %s" % (self.name, attachment['name']), - 'datas': base64.b64encode(attachment['data']), + 'datas': attachment['data'], 'type': 'binary', 'datas_fname': "%s-%s.%s" % ( self.name, attachment['name'], attachment['type']), } for attachment in attachments] - - @api.multi - def _roulier_handle_tracking(self, picking, response): - number = False - tracking = response.get('tracking') - if tracking: - number = tracking.get('number') - for rec in self: - rec.parcel_tracking = number diff --git a/delivery_roulier/views/carrier_account.xml b/delivery_roulier/views/carrier_account.xml new file mode 100644 index 0000000000..1c9d3ceb80 --- /dev/null +++ b/delivery_roulier/views/carrier_account.xml @@ -0,0 +1,24 @@ + + + + + carrier.account + + + + 1 + + + + + + carrier.account + + + + 1 + + + + + From ec38ffb51e5e0f5647d6e5a0a5b238584a476c48 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 10 Sep 2019 20:06:48 +0200 Subject: [PATCH 51/92] Make roulier compatible with native odoo carrier process --- delivery_roulier/models/__init__.py | 1 + delivery_roulier/models/delivery_carrier.py | 45 +++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 delivery_roulier/models/delivery_carrier.py diff --git a/delivery_roulier/models/__init__.py b/delivery_roulier/models/__init__.py index 2547b51a31..fa507e570b 100644 --- a/delivery_roulier/models/__init__.py +++ b/delivery_roulier/models/__init__.py @@ -2,3 +2,4 @@ from . import stock_quant_package from . import stock_move_line from . import carrier_account +from . import delivery_carrier diff --git a/delivery_roulier/models/delivery_carrier.py b/delivery_roulier/models/delivery_carrier.py new file mode 100644 index 0000000000..ccc5d7957a --- /dev/null +++ b/delivery_roulier/models/delivery_carrier.py @@ -0,0 +1,45 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging +from odoo import models + +_logger = logging.getLogger(__name__) +try: + from roulier import roulier +except ImportError: + _logger.debug('Cannot `import roulier`.') + + +class DeliveryCarrier(models.Model): + _inherit = 'delivery.carrier' + + # module using roulier don't use native method to get labels + # pass False value to avoid failure. + def send_shipping(self, pickings): + res = super().send_shipping(pickings) + if not res: + return [{'exact_price': False, 'tracking_number': False}] + + def _is_roulier(self): + self.ensure_one() + return self.delivery_type in roulier.get_carriers() + + def cancel_shipment(self, pickings): + if self._is_roulier: + return NotImplementedError() + else: + return super().cancel_shipment(pickings) + + # For now we keep our own roulier method _get_tracking_link instead of the + # native one because the roulier logic is on packages when the Odoo logic + # is on picking. An we could have multiple urls for 1 picking, if there + # are multiple package... + # Maybe we will merge all this in future versions + def get_tracking_link(self, picking): + if self._is_roulier: + packages = picking._get_packages_from_picking() + first_package = packages and packages[0] + if first_package: + return first_package._get_tracking_link(picking) + else: + return super().get_tracking_link() From a0ad91b86b09c8d9a9ff9751d32ec73caeadc9cb Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 11 Sep 2019 14:02:43 +0200 Subject: [PATCH 52/92] fixup! [REF] Continue migration and some refactores --- delivery_roulier/models/stock_picking.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index ab63742cd7..f012675fae 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -137,7 +137,7 @@ def _roulier_get_label_format(self, account): Return: label format (string) """ - return getattr(account, '%s_file_format' % self.delivery_type) + return getattr(account, '%s_file_format' % self.delivery_type, None) def _roulier_get_receiver(self, package=None): """The guy whom the shippment is for. @@ -189,7 +189,8 @@ def _roulier_convert_address(self, partner): for tel in ['mobile', 'phone']: if address.get(tel): - address[tel] = address[tel].replace(u'\u00A0', '') + address[tel] = address[tel].replace(u'\u00A0', '').\ + replace(' ', '') address['phone'] = address.get('mobile', address.get('phone')) From 76d421ba6817098037cb87329b7b4a6e625cf6d0 Mon Sep 17 00:00:00 2001 From: David Beal Date: Fri, 13 Sep 2019 16:09:39 +0200 Subject: [PATCH 53/92] IMP delivery_roulier: add demo data --- delivery_roulier/__manifest__.py | 3 ++ delivery_roulier/demo/product.xml | 48 +++++++++++++++++++ .../models/stock_quant_package.py | 7 ++- 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 delivery_roulier/demo/product.xml diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index f1b527e9d5..3706e39530 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -19,6 +19,9 @@ 'views/stock_quant_package.xml', 'views/carrier_account.xml', ], + 'demo': [ + 'demo/product.xml', + ], 'external_dependencies': { 'python': [ 'roulier', # '>0.2.0' diff --git a/delivery_roulier/demo/product.xml b/delivery_roulier/demo/product.xml new file mode 100644 index 0000000000..59e2a7511a --- /dev/null +++ b/delivery_roulier/demo/product.xml @@ -0,0 +1,48 @@ + + + + + + carrier 3.7 kg + product + 1000 + 3.7 + + + carrier 1.3 kg + product + 1000 + 1.3 + + + + + + Inventory for Roulier + partial + + + + + + + + + + + 1000.0 + + + + + + + + 1000.0 + + + + + + + diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 19bf3d63de..292d388db3 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -224,8 +224,11 @@ def _roulier_carrier_error_handling(self, payload, exception): _logger.debug(exception.response.request.body) except AttributeError: _logger.debug('No request available') - return _('Sent data:\n%s\n\nException raised:\n%s\n' % ( - payload, str(exception))) + carrier = dict(self.env['delivery.carrier']._fields[ + 'delivery_type'].selection).get(self.carrier_id.delivery_type) + return _( + "Roulier library Exception for '%s' carrier:\n" + "\n%s\n\nSent data:\n%s" % (carrier, str(exception), payload)) def _roulier_invalid_api_input_handling(self, payload, exception): """Build exception message for bad input. From 6df8e2fffa24b30d79e7c67f4a867f359f378e74 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 22 Oct 2019 18:28:30 +0200 Subject: [PATCH 54/92] Add extension to file name --- delivery_roulier/models/stock_quant_package.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 292d388db3..d38d08e621 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -111,7 +111,8 @@ def _roulier_parse_response(self, picking, response): 'name': (parcel.get('reference') or tracking_number or label.get('name')), 'file': label.get('data'), - 'filename': label.get('name'), + 'filename': '%s.%s' % (label.get('name'), + label.get('type', '').lower()), 'file_type': label.get('type') } From fcb8ce4b1b5f8c6f28172a128d88964cc3491789 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Fri, 29 May 2020 19:41:47 +0200 Subject: [PATCH 55/92] Adapt to roulier refactore + manage multiple label --- delivery_roulier/models/stock_picking.py | 3 +- .../models/stock_quant_package.py | 55 +++++++++---------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index f012675fae..ec3747491c 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -69,7 +69,8 @@ def _get_to_address(self): # Implementations for base_delivery_carrier_label def _is_roulier(self): self.ensure_one() - return self.delivery_type in roulier.get_carriers() + available_carrier_actions = roulier.get_carriers_action_available() or {} + return 'get_label' in available_carrier_actions.get(self.delivery_type, []) def generate_shipping_labels(self): self.ensure_one() diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index d38d08e621..b8fe8270ab 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -90,36 +90,33 @@ def _parse_response(self, picking, response): # Core functions def _roulier_generate_labels(self, picking): - result = [] - # by default, only one pack per call - # for now roulier manage one pack at a time by default - for package in self: - response = package._call_roulier_api(picking) - package._handle_attachments(picking, response) - result.append(self._parse_response(picking, response)) - return result + # send all packs to roulier. It will decide if it makes one call per pack or + # one call for all pack depending on the carrier. + response = self._call_roulier_api(picking) + self._handle_attachments(picking, response) + return self._parse_response(picking, response) def _roulier_parse_response(self, picking, response): + parcels_data = [] parcels = response.get('parcels') - parcel = parcels and parcels[0] - tracking_number = parcel.get('tracking', {}).get('number') - # expected format by base_delivery_carrier_label module - label = parcel.get('label') - return { - 'tracking_number': tracking_number, - 'package_id': len(self) == 1 and self.id or False, - 'name': (parcel.get('reference') or tracking_number or - label.get('name')), - 'file': label.get('data'), - 'filename': '%s.%s' % (label.get('name'), - label.get('type', '').lower()), - 'file_type': label.get('type') - } + for parcel in parcels: + tracking_number = parcel.get('tracking', {}).get('number') + # expected format by base_delivery_carrier_label module + label = parcel.get('label') + parcels_data.append({ + 'tracking_number': tracking_number, + 'package_id': len(self) == 1 and self.id or False, + 'name': (parcel.get('reference') or tracking_number or + label.get('name')), + 'file': label.get('data'), + 'filename': '%s.%s' % (label.get('name'), + label.get('type', '').lower()), + 'file_type': label.get('type') + }) + return parcels_data def _roulier_get_parcels(self, picking): - # by default, only one pack per call - self.ensure_one() - return [self._get_parcel(picking)] + return [pack._get_parcel(picking) for pack in self] def open_website_url(self): """Open website for parcel tracking. @@ -146,8 +143,8 @@ def _call_roulier_api(self, picking): # and _a-carrier_after_call account = picking._get_account(self) self.write({'carrier_id': picking.carrier_id.id}) - roulier_instance = roulier.get(picking.delivery_type) - payload = roulier_instance.api() + + payload = {} payload['auth'] = picking._get_auth(account, package=self) @@ -161,7 +158,7 @@ def _call_roulier_api(self, picking): payload = self._before_call(picking, payload) try: # api call - ret = roulier_instance.get_label(payload) + ret = roulier.get(picking.delivery_type, 'get_label', payload) except InvalidApiInput as e: raise UserError(self._invalid_api_input_handling(payload, e)) except CarrierError as e: @@ -241,7 +238,7 @@ def _roulier_invalid_api_input_handling(self, payload, exception): returns: string """ - return _('Bad input: %s\n' % exception.message) + return _('Bad input: %s\n' % str(exception)) # There is low chance you need to override the following methods. def _roulier_handle_attachments(self, picking, response): From c67a55e69aa96337d36c6a4d9d48e93c162dd9da Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 3 Sep 2020 13:59:04 +0200 Subject: [PATCH 56/92] Clean some code after hbrunn review --- delivery_roulier/models/__init__.py | 1 - delivery_roulier/models/carrier_account.py | 27 --------------------- delivery_roulier/models/delivery_carrier.py | 3 ++- delivery_roulier/models/stock_move_line.py | 2 +- delivery_roulier/models/stock_picking.py | 9 +++---- 5 files changed, 6 insertions(+), 36 deletions(-) delete mode 100644 delivery_roulier/models/carrier_account.py diff --git a/delivery_roulier/models/__init__.py b/delivery_roulier/models/__init__.py index fa507e570b..d486ae45a7 100644 --- a/delivery_roulier/models/__init__.py +++ b/delivery_roulier/models/__init__.py @@ -1,5 +1,4 @@ from . import stock_picking from . import stock_quant_package from . import stock_move_line -from . import carrier_account from . import delivery_carrier diff --git a/delivery_roulier/models/carrier_account.py b/delivery_roulier/models/carrier_account.py deleted file mode 100644 index d306b9dcf2..0000000000 --- a/delivery_roulier/models/carrier_account.py +++ /dev/null @@ -1,27 +0,0 @@ -# @author David BEAL -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -import logging - -from odoo import models, api, _ -from odoo.exceptions import UserError - -_logger = logging.getLogger(__name__) - - -MESSAGE = _( - "Account name must be in %s as Provider field value (delivery_type)") - - -class CarrierAccount(models.Model): - _inherit = 'carrier.account' - - @api.constrains("name") - def name_constraint(self): - values = [ - x[0] for x in - self.env["delivery.carrier"]._fields['delivery_type'].selection - if x[0] not in ("fixed", "base_on_rule")] - for rec in self: - if rec.name not in values: - raise UserError(MESSAGE % values) diff --git a/delivery_roulier/models/delivery_carrier.py b/delivery_roulier/models/delivery_carrier.py index ccc5d7957a..7b3c062a7b 100644 --- a/delivery_roulier/models/delivery_carrier.py +++ b/delivery_roulier/models/delivery_carrier.py @@ -19,6 +19,7 @@ def send_shipping(self, pickings): res = super().send_shipping(pickings) if not res: return [{'exact_price': False, 'tracking_number': False}] + return res def _is_roulier(self): self.ensure_one() @@ -26,7 +27,7 @@ def _is_roulier(self): def cancel_shipment(self, pickings): if self._is_roulier: - return NotImplementedError() + raise NotImplementedError() else: return super().cancel_shipment(pickings) diff --git a/delivery_roulier/models/stock_move_line.py b/delivery_roulier/models/stock_move_line.py index 3274e2160f..bf76d2621d 100644 --- a/delivery_roulier/models/stock_move_line.py +++ b/delivery_roulier/models/stock_move_line.py @@ -30,4 +30,4 @@ def get_unit_price_for_customs(self): def get_sale_order_line(self): self.ensure_one() - return self.move_id and self.move_id.sale_line_id or False + return self.move_id.sale_line_id diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index ec3747491c..48671d4f0a 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -112,7 +112,7 @@ def _roulier_get_account(self, package=None): """ self.ensure_one() domain = [ - ("name", "=", self.carrier_id.delivery_type), + ("delivery_type", "=", self.carrier_id.delivery_type), "|", ("company_id", "=", self.company_id.id), ("company_id", "=", False) @@ -241,9 +241,6 @@ def open_website_url(self): # display a list of pickings action = self.env.ref('stock.action_package_view').read()[0] - action['res_id'] = packages.ids - action['domain'] = "[('id', 'in', [%s])]" % ( - ",".join(map(str, packages.ids)) - ) - action['context'] = "{'picking_id': %s }" % str(self.id) + action['domain'] = [('id', 'in', packages.ids)] + action['context'] = {'picking_id': self.id} return action From 2ea6c4446bcb5fd2d774c229de2c092ae4f895b4 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Mon, 26 Oct 2020 19:52:15 +0100 Subject: [PATCH 57/92] Avoid calling send_shipping super in case of roulier It fails since an addition on base_delivery_carrier_label : https://github.com/OCA/delivery-carrier/pull/295/files#diff-6c5e9093f4446ba539eaa4186f6384ec28516a5964d1f2a94a3b61d8e5da0578R31 --- delivery_roulier/models/delivery_carrier.py | 8 ++++---- delivery_roulier/models/stock_picking.py | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/delivery_roulier/models/delivery_carrier.py b/delivery_roulier/models/delivery_carrier.py index 7b3c062a7b..999410fd97 100644 --- a/delivery_roulier/models/delivery_carrier.py +++ b/delivery_roulier/models/delivery_carrier.py @@ -16,14 +16,14 @@ class DeliveryCarrier(models.Model): # module using roulier don't use native method to get labels # pass False value to avoid failure. def send_shipping(self, pickings): - res = super().send_shipping(pickings) - if not res: + if self._is_roulier: return [{'exact_price': False, 'tracking_number': False}] - return res + return super().send_shipping(pickings) def _is_roulier(self): self.ensure_one() - return self.delivery_type in roulier.get_carriers() + available_carrier_actions = roulier.get_carriers_action_available() or {} + return 'get_label' in available_carrier_actions.get(self.delivery_type, []) def cancel_shipment(self, pickings): if self._is_roulier: diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 48671d4f0a..b1ce2f02ba 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -69,8 +69,7 @@ def _get_to_address(self): # Implementations for base_delivery_carrier_label def _is_roulier(self): self.ensure_one() - available_carrier_actions = roulier.get_carriers_action_available() or {} - return 'get_label' in available_carrier_actions.get(self.delivery_type, []) + return self.carrier_id.is_roulier() def generate_shipping_labels(self): self.ensure_one() From 004ccc9912f267e4c28fe6eb14cc9dd24061158a Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Fri, 30 Oct 2020 19:40:55 +0100 Subject: [PATCH 58/92] Fix method name --- delivery_roulier/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index b1ce2f02ba..bf88117e89 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -69,7 +69,7 @@ def _get_to_address(self): # Implementations for base_delivery_carrier_label def _is_roulier(self): self.ensure_one() - return self.carrier_id.is_roulier() + return self.carrier_id._is_roulier() def generate_shipping_labels(self): self.ensure_one() From c7a24ef5722a78563eb4198728c695780ecac165 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 12 Nov 2020 14:01:08 +0100 Subject: [PATCH 59/92] Add basic test to delivery_roulier --- delivery_roulier/tests/__init__.py | 1 + delivery_roulier/tests/models.py | 8 ++ .../tests/test_delivery_roulier.py | 114 ++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 delivery_roulier/tests/__init__.py create mode 100644 delivery_roulier/tests/models.py create mode 100644 delivery_roulier/tests/test_delivery_roulier.py diff --git a/delivery_roulier/tests/__init__.py b/delivery_roulier/tests/__init__.py new file mode 100644 index 0000000000..4fa381897e --- /dev/null +++ b/delivery_roulier/tests/__init__.py @@ -0,0 +1 @@ +from . import test_delivery_roulier diff --git a/delivery_roulier/tests/models.py b/delivery_roulier/tests/models.py new file mode 100644 index 0000000000..1d6907b121 --- /dev/null +++ b/delivery_roulier/tests/models.py @@ -0,0 +1,8 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class FakeDeliveryCarrier(models.Model): + _inherit = "delivery.carrier" + + delivery_type = fields.Selection(selection_add=[("test", "Test Carrier")]) diff --git a/delivery_roulier/tests/test_delivery_roulier.py b/delivery_roulier/tests/test_delivery_roulier.py new file mode 100644 index 0000000000..ea5f8fea9b --- /dev/null +++ b/delivery_roulier/tests/test_delivery_roulier.py @@ -0,0 +1,114 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from unittest.mock import patch, MagicMock + +from odoo.tests.common import SavepointCase +from roulier import roulier +from odoo_test_helper import FakeModelLoader + + +roulier_ret = { + "parcels": [ + { + "reference": "", + "tracking": {"url": "", "number": "Test tracking"}, + "label": { + "name": "label_test", + "data": b"dGVzdCBsYWJlbA==", + "type": "zpl2", + }, + "id": 1, + } + ], + "annexes": [{"name": "annexe name", "type": "txt", "data": b"dGVzdCBhbm5leGU="}], +} + + +class DeliveryRoulierCase(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + + # The fake class is imported here !! After the backup_registry + from .models import FakeDeliveryCarrier + + cls.loader.update_registry((FakeDeliveryCarrier,)) + + def setUp(self): + super().setUp() + delivery_product = self.env["product.product"].create( + {"name": "test shipping product", "type": "service"} + ) + self.test_carrier = self.env["delivery.carrier"].create( + { + "name": "Test Carrier", + "delivery_type": "test", + "product_id": delivery_product.id, + } + ) + self.account = self.env["carrier.account"].create( + { + "name": "Test Carrier Account", + "delivery_type": "test", + "account": "test", + "password": "test", + } + ) + partner = self.env["res.partner"].create( + { + "name": "Carrier label test customer", + "customer": True, + "country_id": self.env.ref("base.fr").id, + "street": "test street", + "street2": "test street2", + "city": "test city", + "phone": "0000000000", + "email": "test@test.com", + "zip": "00000", + } + ) + product = self.env["product.product"].create( + {"name": "Carrier test product", "type": "product", "weight": 1.2} + ) + self.order = self.env["sale.order"].create( + { + "carrier_id": self.test_carrier.id, + "partner_id": partner.id, + "order_line": [ + (0, 0, {"product_id": product.id, "product_uom_qty": 1}) + ], + } + ) + self.env["stock.change.product.qty"].create( + {"product_id": product.id, "new_quantity": 1} + ).change_product_qty() + self.order.action_confirm() + self.picking = self.order.picking_ids + self.env["stock.immediate.transfer"].create( + {"pick_ids": [(6, 0, self.picking.ids)]} + ).process() + + @classmethod + def tearDownClass(cls): + cls.loader.restore_registry() + super().tearDownClass() + + def test_roulier(self): + roulier.get_carriers_action_available = MagicMock( + return_value={"test": ["get_label"]} + ) + with patch("roulier.roulier.get") as mock_roulier: + mock_roulier.return_value = roulier_ret + self.picking.action_generate_carrier_label() + roulier_args = mock_roulier.mock_calls[0][1] + self.assertEqual("get_label", roulier_args[1]) + roulier_payload = roulier_args[2] + self.assertEqual(len(roulier_payload["parcels"]), 1) + self.assertEqual(roulier_payload["parcels"][0].get("weight"), 1.2) + self.assertEqual( + roulier_payload["to_address"].get("street1"), "test street" + ) + self.assertEqual(roulier_payload["to_address"].get("country"), "FR") + self.assertEqual(roulier_payload["auth"].get("isTest"), True) + self.assertEqual(roulier_payload["auth"].get("login"), "test") From b13176c3d58675b7413c179b4301a64bf46bd962 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 8 Dec 2020 15:17:25 +0100 Subject: [PATCH 60/92] [IMP] delivery_roulier: black, isort, prettier --- delivery_roulier/__init__.py | 2 +- delivery_roulier/__manifest__.py | 44 +++---- delivery_roulier/decorator.py | 12 +- delivery_roulier/demo/product.xml | 30 +++-- delivery_roulier/models/delivery_carrier.py | 9 +- delivery_roulier/models/stock_move_line.py | 17 ++- delivery_roulier/models/stock_picking.py | 70 +++++----- .../models/stock_quant_package.py | 124 +++++++++--------- delivery_roulier/readme/DESCRIPTION.rst | 2 +- delivery_roulier/readme/USAGE.rst | 2 +- .../tests/test_delivery_roulier.py | 6 +- delivery_roulier/views/carrier_account.xml | 12 +- .../views/stock_quant_package.xml | 17 ++- 13 files changed, 191 insertions(+), 156 deletions(-) diff --git a/delivery_roulier/__init__.py b/delivery_roulier/__init__.py index 052f63d6f0..ebff83db95 100644 --- a/delivery_roulier/__init__.py +++ b/delivery_roulier/__init__.py @@ -1,3 +1,3 @@ from . import models -from .decorator import implemented_by_carrier \ No newline at end of file +from .decorator import implemented_by_carrier diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index 3706e39530..92fd5d2b97 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -2,31 +2,31 @@ # David BEAL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { - 'name': 'Delivery Carrier Roulier', - 'version': '12.0.1.0.0', - 'author': 'Akretion,Odoo Community Association (OCA)', - 'summary': 'Integration of multiple carriers', - 'maintainer': 'Akretion, Odoo Community Association (OCA)', - 'category': 'Warehouse', - 'depends': [ - 'partner_helper', - 'base_phone', # from oca/telephony - 'base_suspend_security', - 'base_delivery_carrier_label', + "name": "Delivery Carrier Roulier", + "version": "12.0.1.0.0", + "author": "Akretion,Odoo Community Association (OCA)", + "summary": "Integration of multiple carriers", + "maintainer": "Akretion, Odoo Community Association (OCA)", + "category": "Warehouse", + "depends": [ + "partner_helper", + "base_phone", # from oca/telephony + "base_suspend_security", + "base_delivery_carrier_label", ], - 'website': 'https://github.com/delivery-carrier', - 'data': [ - 'views/stock_quant_package.xml', - 'views/carrier_account.xml', + "website": "https://github.com/OCA/delivery-carrier", + "data": [ + "views/stock_quant_package.xml", + "views/carrier_account.xml", ], - 'demo': [ - 'demo/product.xml', + "demo": [ + "demo/product.xml", ], - 'external_dependencies': { - 'python': [ - 'roulier', # '>0.2.0' + "external_dependencies": { + "python": [ + "roulier", # '>0.2.0' ], }, - 'installable': True, - 'license': 'AGPL-3', + "installable": True, + "license": "AGPL-3", } diff --git a/delivery_roulier/decorator.py b/delivery_roulier/decorator.py index ddc57145c2..f61cc69253 100644 --- a/delivery_roulier/decorator.py +++ b/delivery_roulier/decorator.py @@ -17,24 +17,26 @@ def _gls_do_something() the carrier spectific method or fallback to generic _do_something """ + @wraps(func) def wrapper(cls, *args, **kwargs): fun_name = func.__name__ def get_delivery_type(cls, *args, **kwargs): - if hasattr(cls, 'delivery_type'): + if hasattr(cls, "delivery_type"): return cls.delivery_type pickings = [ - obj for obj in args - if getattr(obj, '_name', '') == 'stock.picking'] + obj for obj in args if getattr(obj, "_name", "") == "stock.picking" + ] if len(pickings) > 0: return pickings[0].delivery_type if cls[0].carrier_id: return cls[0].carrier_id.delivery_type delivery_type = get_delivery_type(cls, *args, **kwargs) - fun = '_%s%s' % (delivery_type, fun_name) + fun = "_{}{}".format(delivery_type, fun_name) if not hasattr(cls, fun): - fun = '_roulier%s' % (fun_name) + fun = "_roulier%s" % (fun_name) return getattr(cls, fun)(*args, **kwargs) + return wrapper diff --git a/delivery_roulier/demo/product.xml b/delivery_roulier/demo/product.xml index 59e2a7511a..bb1792e3ef 100644 --- a/delivery_roulier/demo/product.xml +++ b/delivery_roulier/demo/product.xml @@ -1,4 +1,4 @@ - + @@ -22,27 +22,35 @@ partial - + - - - + + + 1000.0 - + - - - + + + 1000.0 - + - + diff --git a/delivery_roulier/models/delivery_carrier.py b/delivery_roulier/models/delivery_carrier.py index 999410fd97..1653a3a880 100644 --- a/delivery_roulier/models/delivery_carrier.py +++ b/delivery_roulier/models/delivery_carrier.py @@ -1,29 +1,30 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging + from odoo import models _logger = logging.getLogger(__name__) try: from roulier import roulier except ImportError: - _logger.debug('Cannot `import roulier`.') + _logger.debug("Cannot `import roulier`.") class DeliveryCarrier(models.Model): - _inherit = 'delivery.carrier' + _inherit = "delivery.carrier" # module using roulier don't use native method to get labels # pass False value to avoid failure. def send_shipping(self, pickings): if self._is_roulier: - return [{'exact_price': False, 'tracking_number': False}] + return [{"exact_price": False, "tracking_number": False}] return super().send_shipping(pickings) def _is_roulier(self): self.ensure_one() available_carrier_actions = roulier.get_carriers_action_available() or {} - return 'get_label' in available_carrier_actions.get(self.delivery_type, []) + return "get_label" in available_carrier_actions.get(self.delivery_type, []) def cancel_shipment(self, pickings): if self._is_roulier: diff --git a/delivery_roulier/models/stock_move_line.py b/delivery_roulier/models/stock_move_line.py index bf76d2621d..8f688794a8 100644 --- a/delivery_roulier/models/stock_move_line.py +++ b/delivery_roulier/models/stock_move_line.py @@ -7,25 +7,24 @@ class StockMoveLine(models.Model): - _inherit = 'stock.move.line' + _inherit = "stock.move.line" def get_unit_price_for_customs(self): """This method is designed to be inherited for specific scenarios""" self.ensure_one() - prec = self.env['decimal.precision'].precision_get( - 'Product Unit of Measure') + prec = self.env["decimal.precision"].precision_get("Product Unit of Measure") soline = self.get_sale_order_line() - if soline and not float_is_zero( - soline.product_uom_qty, precision_digits=prec): + if soline and not float_is_zero(soline.product_uom_qty, precision_digits=prec): price_unit_so_uom = soline.price_subtotal / soline.product_uom_qty price_unit = soline.product_uom._compute_price( - price_unit_so_uom, self.product_uom_id) + price_unit_so_uom, self.product_uom_id + ) else: product = self.product_id - ato = self.env['account.tax'] + ato = self.env["account.tax"] price_unit = ato._fix_tax_included_price_company( - product.list_price, product.taxes_id, ato, - self.picking_id.company_id) + product.list_price, product.taxes_id, ato, self.picking_id.company_id + ) return price_unit def get_sale_order_line(self): diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index bf88117e89..65fa678930 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -2,12 +2,12 @@ # David BEAL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from datetime import datetime, timedelta import logging +from datetime import datetime, timedelta -from odoo import models, fields -from odoo.tools.translate import _ +from odoo import fields, models from odoo.exceptions import UserError +from odoo.tools.translate import _ from ..decorator import implemented_by_carrier @@ -15,12 +15,11 @@ try: from roulier import roulier except ImportError: - _logger.debug('Cannot `import roulier`.') + _logger.debug("Cannot `import roulier`.") class StockPicking(models.Model): - _inherit = 'stock.picking' - + _inherit = "stock.picking" # API: @@ -94,9 +93,9 @@ def _roulier_get_auth(self, account, package=None): a dict with login and password keys """ auth = { - 'login': account.account, - 'password': account.password, - 'isTest': not self.carrier_id.prod_environment, + "login": account.account, + "password": account.password, + "isTest": not self.carrier_id.prod_environment, } return auth @@ -114,13 +113,16 @@ def _roulier_get_account(self, package=None): ("delivery_type", "=", self.carrier_id.delivery_type), "|", ("company_id", "=", self.company_id.id), - ("company_id", "=", False) + ("company_id", "=", False), ] account = self.env["carrier.account"].search(domain, limit=1) if not account: raise UserError( - _("No account available with name '%s' " - "for this carrier" % self.carrier_id.delivery_type)) + _( + "No account available with name '%s' " + "for this carrier" % self.carrier_id.delivery_type + ) + ) return account def _roulier_get_sender(self, package=None): @@ -137,7 +139,7 @@ def _roulier_get_label_format(self, account): Return: label format (string) """ - return getattr(account, '%s_file_format' % self.delivery_type, None) + return getattr(account, "%s_file_format" % self.delivery_type, None) def _roulier_get_receiver(self, package=None): """The guy whom the shippment is for. @@ -155,7 +157,7 @@ def _roulier_get_shipping_date(self, package=None): By default, it's tomorrow.""" tomorrow = datetime.now() + timedelta(1) - return tomorrow.strftime('%Y-%m-%d') + return tomorrow.strftime("%Y-%m-%d") def _roulier_convert_address(self, partner): """Convert a partner to an address for roulier. @@ -167,8 +169,15 @@ def _roulier_convert_address(self, partner): """ address = {} extract_fields = [ - 'company', 'name', 'zip', 'city', 'phone', 'mobile', - 'email', 'street2'] + "company", + "name", + "zip", + "city", + "phone", + "mobile", + "email", + "street2", + ] for elm in extract_fields: if elm in partner: # because a value can't be None in odoo's ORM @@ -180,19 +189,18 @@ def _roulier_convert_address(self, partner): # it's a None: nothing to do else: # it's a boolean: keep the value address[elm] = partner[elm] - if not address.get('company', False) and partner.parent_id.is_company: - address['company'] = partner.parent_id.name + if not address.get("company", False) and partner.parent_id.is_company: + address["company"] = partner.parent_id.name # Roulier needs street1 (mandatory) not street - address['street1'] = partner.street + address["street1"] = partner.street # Codet ISO 3166-1-alpha-2 (2 letters code) - address['country'] = partner.country_id.code + address["country"] = partner.country_id.code - for tel in ['mobile', 'phone']: + for tel in ["mobile", "phone"]: if address.get(tel): - address[tel] = address[tel].replace(u'\u00A0', '').\ - replace(' ', '') + address[tel] = address[tel].replace(u"\u00A0", "").replace(" ", "") - address['phone'] = address.get('mobile', address.get('phone')) + address["phone"] = address.get("mobile", address.get("phone")) return address @@ -216,9 +224,9 @@ def _roulier_get_service(self, account, package=None): shipping_date = self._get_shipping_date(package) service = { - 'product': self.carrier_code, - 'shippingDate': shipping_date, - 'labelFormat': self._get_label_format(account) + "product": self.carrier_code, + "shippingDate": shipping_date, + "labelFormat": self._get_label_format(account), } return service @@ -234,12 +242,12 @@ def open_website_url(self): packages = self._get_packages_from_picking() if len(packages) == 0: - raise UserError(_('No packages found for this picking')) + raise UserError(_("No packages found for this picking")) elif len(packages) == 1: return packages.open_website_url() # shortpath # display a list of pickings - action = self.env.ref('stock.action_package_view').read()[0] - action['domain'] = [('id', 'in', packages.ids)] - action['context'] = {'picking_id': self.id} + action = self.env.ref("stock.action_package_view").read()[0] + action["domain"] = [("id", "in", packages.ids)] + action["context"] = {"picking_id": self.id} return action diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index b8fe8270ab..284746dbcf 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -3,9 +3,8 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging -import base64 -from odoo import models, api, fields, _ +from odoo import _, api, fields, models from odoo.exceptions import UserError from ..decorator import implemented_by_carrier @@ -13,16 +12,13 @@ _logger = logging.getLogger(__name__) try: from roulier import roulier - from roulier.exception import ( - InvalidApiInput, - CarrierError - ) + from roulier.exception import CarrierError, InvalidApiInput except ImportError: - _logger.debug('Cannot `import roulier`.') + _logger.debug("Cannot `import roulier`.") class StockQuantPackage(models.Model): - _inherit = 'stock.quant.package' + _inherit = "stock.quant.package" carrier_id = fields.Many2one("delivery.carrier", string="Carrier") @@ -33,12 +29,14 @@ def get_operations(self): Usefull for having products and quantities """ self.ensure_one() - return self.env['stock.move.line'].search([ - ('product_id', '!=', False), - '|', - ('package_id', '=', self.id), - ('result_package_id', '=', self.id), - ]) + return self.env["stock.move.line"].search( + [ + ("product_id", "!=", False), + "|", + ("package_id", "=", self.id), + ("result_package_id", "=", self.id), + ] + ) # API # Each method in this class have at least picking arg to directly @@ -86,6 +84,7 @@ def _get_parcels(self, picking): @implemented_by_carrier def _parse_response(self, picking, response): pass + # end of API # Core functions @@ -98,21 +97,24 @@ def _roulier_generate_labels(self, picking): def _roulier_parse_response(self, picking, response): parcels_data = [] - parcels = response.get('parcels') + parcels = response.get("parcels") for parcel in parcels: - tracking_number = parcel.get('tracking', {}).get('number') + tracking_number = parcel.get("tracking", {}).get("number") # expected format by base_delivery_carrier_label module - label = parcel.get('label') - parcels_data.append({ - 'tracking_number': tracking_number, - 'package_id': len(self) == 1 and self.id or False, - 'name': (parcel.get('reference') or tracking_number or - label.get('name')), - 'file': label.get('data'), - 'filename': '%s.%s' % (label.get('name'), - label.get('type', '').lower()), - 'file_type': label.get('type') - }) + label = parcel.get("label") + parcels_data.append( + { + "tracking_number": tracking_number, + "package_id": len(self) == 1 and self.id or False, + "name": ( + parcel.get("reference") or tracking_number or label.get("name") + ), + "file": label.get("data"), + "filename": "%s.%s" + % (label.get("name"), label.get("type", "").lower()), + "file_type": label.get("type"), + } + ) return parcels_data def _roulier_get_parcels(self, picking): @@ -129,10 +131,10 @@ def open_website_url(self): self.ensure_one() url = self._get_tracking_link() client_action = { - 'type': 'ir.actions.act_url', - 'name': "Shipment Tracking Page", - 'target': 'new', - 'url': url, + "type": "ir.actions.act_url", + "name": "Shipment Tracking Page", + "target": "new", + "url": url, } return client_action @@ -142,23 +144,23 @@ def _call_roulier_api(self, picking): # Don't forget to implement _a-carrier_before_call # and _a-carrier_after_call account = picking._get_account(self) - self.write({'carrier_id': picking.carrier_id.id}) + self.write({"carrier_id": picking.carrier_id.id}) payload = {} - payload['auth'] = picking._get_auth(account, package=self) + payload["auth"] = picking._get_auth(account, package=self) - payload['from_address'] = picking._get_from_address(package=self) - payload['to_address'] = picking._get_to_address(package=self) + payload["from_address"] = picking._get_from_address(package=self) + payload["to_address"] = picking._get_to_address(package=self) - payload['service'] = picking._get_service(account, package=self) - payload['parcels'] = self._get_parcels(picking) + payload["service"] = picking._get_service(account, package=self) + payload["parcels"] = self._get_parcels(picking) # hook to override request / payload payload = self._before_call(picking, payload) try: # api call - ret = roulier.get(picking.delivery_type, 'get_label', payload) + ret = roulier.get(picking.delivery_type, "get_label", payload) except InvalidApiInput as e: raise UserError(self._invalid_api_input_handling(payload, e)) except CarrierError as e: @@ -171,10 +173,7 @@ def _call_roulier_api(self, picking): def _roulier_get_parcel(self, picking): self.ensure_one() weight = self.shipping_weight or self.weight - parcel = { - 'weight': weight, - 'reference': self.name - } + parcel = {"weight": weight, "reference": self.name} return parcel def _roulier_before_call(self, picking, payload): @@ -207,7 +206,6 @@ def _roulier_get_tracking_link(self): string (url) """ _logger.warning("not implemented") - pass def _roulier_carrier_error_handling(self, payload, exception): """Build exception message for carrier error. @@ -221,12 +219,14 @@ def _roulier_carrier_error_handling(self, payload, exception): _logger.debug(exception.response.text) _logger.debug(exception.response.request.body) except AttributeError: - _logger.debug('No request available') - carrier = dict(self.env['delivery.carrier']._fields[ - 'delivery_type'].selection).get(self.carrier_id.delivery_type) + _logger.debug("No request available") + carrier = dict( + self.env["delivery.carrier"]._fields["delivery_type"].selection + ).get(self.carrier_id.delivery_type) return _( "Roulier library Exception for '%s' carrier:\n" - "\n%s\n\nSent data:\n%s" % (carrier, str(exception), payload)) + "\n%s\n\nSent data:\n%s" % (carrier, str(exception), payload) + ) def _roulier_invalid_api_input_handling(self, payload, exception): """Build exception message for bad input. @@ -238,14 +238,13 @@ def _roulier_invalid_api_input_handling(self, payload, exception): returns: string """ - return _('Bad input: %s\n' % str(exception)) + return _("Bad input: %s\n" % str(exception)) # There is low chance you need to override the following methods. def _roulier_handle_attachments(self, picking, response): attachments = [ - self.env['ir.attachment'].create(attachment) - for attachment in - self[0]._roulier_prepare_attachments(picking, response) + self.env["ir.attachment"].create(attachment) + for attachment in self[0]._roulier_prepare_attachments(picking, response) ] # do it once for all return attachments @@ -258,13 +257,16 @@ def _roulier_prepare_attachments(self, picking, response): list """ self.ensure_one() - attachments = response.get('annexes') - return [{ - 'res_id': picking.id, - 'res_model': 'stock.picking', - 'name': "%s %s" % (self.name, attachment['name']), - 'datas': attachment['data'], - 'type': 'binary', - 'datas_fname': "%s-%s.%s" % ( - self.name, attachment['name'], attachment['type']), - } for attachment in attachments] + attachments = response.get("annexes") + return [ + { + "res_id": picking.id, + "res_model": "stock.picking", + "name": "{} {}".format(self.name, attachment["name"]), + "datas": attachment["data"], + "type": "binary", + "datas_fname": "%s-%s.%s" + % (self.name, attachment["name"], attachment["type"]), + } + for attachment in attachments + ] diff --git a/delivery_roulier/readme/DESCRIPTION.rst b/delivery_roulier/readme/DESCRIPTION.rst index 763c96887f..a6b0e65bc0 100644 --- a/delivery_roulier/readme/DESCRIPTION.rst +++ b/delivery_roulier/readme/DESCRIPTION.rst @@ -5,7 +5,7 @@ Base module for integration with Roulier. `Roulier `_ is a python library which implements carriers API. This modules contains the core functions for this implementation. -You should install one of the specific modules : +You should install one of the specific modules : - delivery_roulier_laposte - delivery_roulier_dpd diff --git a/delivery_roulier/readme/USAGE.rst b/delivery_roulier/readme/USAGE.rst index 3bf09c4954..84d1c0e75a 100644 --- a/delivery_roulier/readme/USAGE.rst +++ b/delivery_roulier/readme/USAGE.rst @@ -1,4 +1,4 @@ -Here is some methods you can use for your carrier implementation +Here is some methods you can use for your carrier implementation allowing to have a consistent code accross different carrier modules: .. code-block:: python diff --git a/delivery_roulier/tests/test_delivery_roulier.py b/delivery_roulier/tests/test_delivery_roulier.py index ea5f8fea9b..581a70f19f 100644 --- a/delivery_roulier/tests/test_delivery_roulier.py +++ b/delivery_roulier/tests/test_delivery_roulier.py @@ -1,10 +1,10 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch -from odoo.tests.common import SavepointCase -from roulier import roulier from odoo_test_helper import FakeModelLoader +from roulier import roulier +from odoo.tests.common import SavepointCase roulier_ret = { "parcels": [ diff --git a/delivery_roulier/views/carrier_account.xml b/delivery_roulier/views/carrier_account.xml index 1c9d3ceb80..00721fb21c 100644 --- a/delivery_roulier/views/carrier_account.xml +++ b/delivery_roulier/views/carrier_account.xml @@ -1,9 +1,12 @@ - + carrier.account - + 1 @@ -13,7 +16,10 @@ carrier.account - + 1 diff --git a/delivery_roulier/views/stock_quant_package.xml b/delivery_roulier/views/stock_quant_package.xml index 5a54b6289e..644bca777f 100644 --- a/delivery_roulier/views/stock_quant_package.xml +++ b/delivery_roulier/views/stock_quant_package.xml @@ -1,13 +1,22 @@ - + stock.quant.package - +
-
From f3449c6f32dfa080dd805345fe691f82fa5110ac Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 8 Dec 2020 15:17:25 +0100 Subject: [PATCH 61/92] [MIG] delivery_roulier: Migration to 14.0 --- delivery_roulier/__manifest__.py | 5 ++--- delivery_roulier/demo/product.xml | 9 ++------- delivery_roulier/models/stock_picking.py | 5 +---- delivery_roulier/models/stock_quant_package.py | 8 +++----- delivery_roulier/tests/models.py | 2 +- delivery_roulier/tests/test_delivery_roulier.py | 9 +++++---- 6 files changed, 14 insertions(+), 24 deletions(-) diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index 92fd5d2b97..4d5e78c241 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Delivery Carrier Roulier", - "version": "12.0.1.0.0", + "version": "14.0.1.0.0", "author": "Akretion,Odoo Community Association (OCA)", "summary": "Integration of multiple carriers", "maintainer": "Akretion, Odoo Community Association (OCA)", @@ -11,8 +11,7 @@ "depends": [ "partner_helper", "base_phone", # from oca/telephony - "base_suspend_security", - "base_delivery_carrier_label", + "base_delivery_carrier_label", ], "website": "https://github.com/OCA/delivery-carrier", "data": [ diff --git a/delivery_roulier/demo/product.xml b/delivery_roulier/demo/product.xml index bb1792e3ef..5e0f165ad6 100644 --- a/delivery_roulier/demo/product.xml +++ b/delivery_roulier/demo/product.xml @@ -19,15 +19,10 @@ Inventory for Roulier - partial + confirm + - - diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 65fa678930..05c3b06b43 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -80,9 +80,6 @@ def _roulier_generate_labels(self): """Create as many labels as package_ids or in self.""" self.ensure_one() packages = self._get_packages_from_picking() - # base_delivery_carrier_label module ensure packages is available - self.number_of_packages = len(packages) - self.carrier_tracking_ref = True # display button in view return packages._generate_labels(self) # Default implementations of _roulier_*() @@ -240,7 +237,7 @@ def open_website_url(self): if not self._is_roulier(): return super().open_website_url() - packages = self._get_packages_from_picking() + packages = self.package_ids if len(packages) == 0: raise UserError(_("No packages found for this picking")) elif len(packages) == 1: diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 284746dbcf..2d38998804 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -110,8 +110,8 @@ def _roulier_parse_response(self, picking, response): parcel.get("reference") or tracking_number or label.get("name") ), "file": label.get("data"), - "filename": "%s.%s" - % (label.get("name"), label.get("type", "").lower()), + "name": "%s.%s" + % (parcel.get("reference") or tracking_number or label.get("name"), label.get("type", "").lower()), "file_type": label.get("type"), } ) @@ -248,7 +248,6 @@ def _roulier_handle_attachments(self, picking, response): ] # do it once for all return attachments - @api.multi def _roulier_prepare_attachments(self, picking, response): """Prepare a list of dicts for building ir.attachments. Attachements are annexes like customs declarations, summary @@ -262,10 +261,9 @@ def _roulier_prepare_attachments(self, picking, response): { "res_id": picking.id, "res_model": "stock.picking", - "name": "{} {}".format(self.name, attachment["name"]), "datas": attachment["data"], "type": "binary", - "datas_fname": "%s-%s.%s" + "name": "%s-%s.%s" % (self.name, attachment["name"], attachment["type"]), } for attachment in attachments diff --git a/delivery_roulier/tests/models.py b/delivery_roulier/tests/models.py index 1d6907b121..9e8b62b703 100644 --- a/delivery_roulier/tests/models.py +++ b/delivery_roulier/tests/models.py @@ -5,4 +5,4 @@ class FakeDeliveryCarrier(models.Model): _inherit = "delivery.carrier" - delivery_type = fields.Selection(selection_add=[("test", "Test Carrier")]) + delivery_type = fields.Selection(selection_add=[("test", "Test Carrier")], ondelete={'test': 'set default'}) diff --git a/delivery_roulier/tests/test_delivery_roulier.py b/delivery_roulier/tests/test_delivery_roulier.py index 581a70f19f..e59be55324 100644 --- a/delivery_roulier/tests/test_delivery_roulier.py +++ b/delivery_roulier/tests/test_delivery_roulier.py @@ -58,7 +58,6 @@ def setUp(self): partner = self.env["res.partner"].create( { "name": "Carrier label test customer", - "customer": True, "country_id": self.env.ref("base.fr").id, "street": "test street", "street2": "test street2", @@ -80,9 +79,11 @@ def setUp(self): ], } ) - self.env["stock.change.product.qty"].create( - {"product_id": product.id, "new_quantity": 1} - ).change_product_qty() + self.env['stock.quant'].with_context(inventory_mode=True).create({ + "product_id": product.id, + 'location_id': self.order.warehouse_id.lot_stock_id.id, + 'inventory_quantity': 1, + }) self.order.action_confirm() self.picking = self.order.picking_ids self.env["stock.immediate.transfer"].create( From 0a9d3e167b7ad1f9fb5b0ace1a2ae8cc420aafe8 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 8 Dec 2020 18:02:19 +0100 Subject: [PATCH 62/92] Find the right label shipping package even in case of multiple packags --- delivery_roulier/models/stock_quant_package.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 2d38998804..1ce4ae8733 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -102,13 +102,20 @@ def _roulier_parse_response(self, picking, response): tracking_number = parcel.get("tracking", {}).get("number") # expected format by base_delivery_carrier_label module label = parcel.get("label") + # find for which package the label is. tracking number will be updated on + # this pack later on (in base_delivery_carrier_label) + package_id = False + if len(self) == 1: + package_id = self.id + else: + pack = self.filtered(lambda p: p.name == parcel.get('reference')) + if len(pack) == 1: + package_id = pack.id + parcels_data.append( { "tracking_number": tracking_number, - "package_id": len(self) == 1 and self.id or False, - "name": ( - parcel.get("reference") or tracking_number or label.get("name") - ), + "package_id": package_id, "file": label.get("data"), "name": "%s.%s" % (parcel.get("reference") or tracking_number or label.get("name"), label.get("type", "").lower()), From b82d9bdc72ac961f113838fae2981ef4195087e9 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 8 Dec 2020 18:57:02 +0100 Subject: [PATCH 63/92] Fix get tracking link --- delivery_roulier/models/delivery_carrier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/delivery_roulier/models/delivery_carrier.py b/delivery_roulier/models/delivery_carrier.py index 1653a3a880..759fc6bdd4 100644 --- a/delivery_roulier/models/delivery_carrier.py +++ b/delivery_roulier/models/delivery_carrier.py @@ -38,10 +38,10 @@ def cancel_shipment(self, pickings): # are multiple package... # Maybe we will merge all this in future versions def get_tracking_link(self, picking): - if self._is_roulier: + if self._is_roulier(): packages = picking._get_packages_from_picking() first_package = packages and packages[0] if first_package: return first_package._get_tracking_link(picking) else: - return super().get_tracking_link() + return super().get_tracking_link(picking) From 329b1e2a0d6df316b049b6322804b8adf990153a Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 11 Feb 2021 19:31:12 +0100 Subject: [PATCH 64/92] Fix shipping date format according to roulier update --- delivery_roulier/models/stock_picking.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 05c3b06b43..f21949d5ee 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -2,8 +2,7 @@ # David BEAL # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -import logging -from datetime import datetime, timedelta +from datetime import date, timedelta from odoo import fields, models from odoo.exceptions import UserError @@ -11,12 +10,6 @@ from ..decorator import implemented_by_carrier -_logger = logging.getLogger(__name__) -try: - from roulier import roulier -except ImportError: - _logger.debug("Cannot `import roulier`.") - class StockPicking(models.Model): _inherit = "stock.picking" @@ -153,8 +146,8 @@ def _roulier_get_shipping_date(self, package=None): """Choose a shipping date. By default, it's tomorrow.""" - tomorrow = datetime.now() + timedelta(1) - return tomorrow.strftime("%Y-%m-%d") + tomorrow = date.today() + timedelta(1) + return tomorrow def _roulier_convert_address(self, partner): """Convert a partner to an address for roulier. From f5944a882d631bb7b8d3eb28d2dfc01641837c8a Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 9 Mar 2021 23:27:47 +0100 Subject: [PATCH 65/92] Adapt to base_delivery_carrier_label refactore --- delivery_roulier/models/delivery_carrier.py | 12 +++---- delivery_roulier/models/stock_picking.py | 34 +++++++++++++------ .../models/stock_quant_package.py | 11 +++++- .../tests/test_delivery_roulier.py | 2 +- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/delivery_roulier/models/delivery_carrier.py b/delivery_roulier/models/delivery_carrier.py index 759fc6bdd4..d9f4bce487 100644 --- a/delivery_roulier/models/delivery_carrier.py +++ b/delivery_roulier/models/delivery_carrier.py @@ -14,12 +14,12 @@ class DeliveryCarrier(models.Model): _inherit = "delivery.carrier" - # module using roulier don't use native method to get labels - # pass False value to avoid failure. - def send_shipping(self, pickings): + def alternative_send_shipping(self, pickings): + self.ensure_one() if self._is_roulier: - return [{"exact_price": False, "tracking_number": False}] - return super().send_shipping(pickings) + return pickings._roulier_generate_labels() + else: + return super().alternative_send_shipping(pickings) def _is_roulier(self): self.ensure_one() @@ -28,7 +28,7 @@ def _is_roulier(self): def cancel_shipment(self, pickings): if self._is_roulier: - raise NotImplementedError() + pickings._cancel_shipment() else: return super().cancel_shipment(pickings) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index f21949d5ee..0e9062ea6a 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -56,6 +56,10 @@ def _get_from_address(self): def _get_to_address(self): pass + @implemented_by_carrier + def _cancel_shipment(self): + pass + # End of API. # Implementations for base_delivery_carrier_label @@ -63,17 +67,20 @@ def _is_roulier(self): self.ensure_one() return self.carrier_id._is_roulier() - def generate_shipping_labels(self): - self.ensure_one() - if self._is_roulier(): - return self._roulier_generate_labels() - return super().generate_shipping_labels() - def _roulier_generate_labels(self): - """Create as many labels as package_ids or in self.""" - self.ensure_one() - packages = self._get_packages_from_picking() - return packages._generate_labels(self) + """ + Return format expected by send_shipping : a list of dict (one dict per + picking). + { + 'exact_price': 0.0, + 'tracking_number': "concatenated numbers", + 'labels': list of dict of labels, managed by base_delivery_carrier_label + } + """ + label_info = [] + for picking in self: + label_info.append(picking.package_ids._generate_labels(picking)) + return label_info # Default implementations of _roulier_*() def _roulier_get_auth(self, account, package=None): @@ -89,6 +96,13 @@ def _roulier_get_auth(self, account, package=None): } return auth + def _roulier_cancel_shipment(self): + self.write({'carrier_tracking_ref': False}) + labels = self.env['shipping.label'].search( + [("res_id", "in", self.ids), ("res_model", "=", "stock.picking")] + ) + labels.mapped('attachment_id').unlink() + def _roulier_get_account(self, package=None): """Return an 'account'. diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 1ce4ae8733..f4c9f745dc 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -96,10 +96,17 @@ def _roulier_generate_labels(self, picking): return self._parse_response(picking, response) def _roulier_parse_response(self, picking, response): + res = { + # price is not managed in roulier...not yet at least + "exact_price": 0.0, + } parcels_data = [] parcels = response.get("parcels") + tracking_refs = [] for parcel in parcels: tracking_number = parcel.get("tracking", {}).get("number") + if tracking_number: + tracking_refs.append(tracking_number) # expected format by base_delivery_carrier_label module label = parcel.get("label") # find for which package the label is. tracking number will be updated on @@ -122,7 +129,9 @@ def _roulier_parse_response(self, picking, response): "file_type": label.get("type"), } ) - return parcels_data + res["tracking_number"] = ';'.join(tracking_refs) + res['labels'] = parcels_data + return res def _roulier_get_parcels(self, picking): return [pack._get_parcel(picking) for pack in self] diff --git a/delivery_roulier/tests/test_delivery_roulier.py b/delivery_roulier/tests/test_delivery_roulier.py index e59be55324..0e2f2eb7c6 100644 --- a/delivery_roulier/tests/test_delivery_roulier.py +++ b/delivery_roulier/tests/test_delivery_roulier.py @@ -101,7 +101,7 @@ def test_roulier(self): ) with patch("roulier.roulier.get") as mock_roulier: mock_roulier.return_value = roulier_ret - self.picking.action_generate_carrier_label() + self.picking.send_to_shipper() roulier_args = mock_roulier.mock_calls[0][1] self.assertEqual("get_label", roulier_args[1]) roulier_payload = roulier_args[2] From 147b7a2c411bc098d68148a6f5440a61a4c1e07c Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 26 May 2021 17:22:14 +0200 Subject: [PATCH 66/92] Fix code linting and test dependencies --- delivery_roulier/__manifest__.py | 2 +- delivery_roulier/demo/product.xml | 5 ++++- delivery_roulier/models/stock_picking.py | 20 +++++++++---------- .../models/stock_quant_package.py | 13 +++++++----- delivery_roulier/tests/models.py | 4 +++- .../tests/test_delivery_roulier.py | 12 ++++++----- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index 4d5e78c241..76ac3aa5e0 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -11,7 +11,7 @@ "depends": [ "partner_helper", "base_phone", # from oca/telephony - "base_delivery_carrier_label", + "base_delivery_carrier_label", ], "website": "https://github.com/OCA/delivery-carrier", "data": [ diff --git a/delivery_roulier/demo/product.xml b/delivery_roulier/demo/product.xml index 5e0f165ad6..134017dfbd 100644 --- a/delivery_roulier/demo/product.xml +++ b/delivery_roulier/demo/product.xml @@ -20,7 +20,10 @@ Inventory for Roulier confirm - + diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 0e9062ea6a..a45f68e626 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -69,13 +69,13 @@ def _is_roulier(self): def _roulier_generate_labels(self): """ - Return format expected by send_shipping : a list of dict (one dict per - picking). - { - 'exact_price': 0.0, - 'tracking_number': "concatenated numbers", - 'labels': list of dict of labels, managed by base_delivery_carrier_label - } + Return format expected by send_shipping : a list of dict (one dict per + picking). + { + 'exact_price': 0.0, + 'tracking_number': "concatenated numbers", + 'labels': list of dict of labels, managed by base_delivery_carrier_label + } """ label_info = [] for picking in self: @@ -97,11 +97,11 @@ def _roulier_get_auth(self, account, package=None): return auth def _roulier_cancel_shipment(self): - self.write({'carrier_tracking_ref': False}) - labels = self.env['shipping.label'].search( + self.write({"carrier_tracking_ref": False}) + labels = self.env["shipping.label"].search( [("res_id", "in", self.ids), ("res_model", "=", "stock.picking")] ) - labels.mapped('attachment_id').unlink() + labels.mapped("attachment_id").unlink() def _roulier_get_account(self, package=None): """Return an 'account'. diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index f4c9f745dc..7166d16e2d 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -4,7 +4,7 @@ import logging -from odoo import _, api, fields, models +from odoo import _, fields, models from odoo.exceptions import UserError from ..decorator import implemented_by_carrier @@ -115,7 +115,7 @@ def _roulier_parse_response(self, picking, response): if len(self) == 1: package_id = self.id else: - pack = self.filtered(lambda p: p.name == parcel.get('reference')) + pack = self.filtered(lambda p: p.name == parcel.get("reference")) if len(pack) == 1: package_id = pack.id @@ -125,12 +125,15 @@ def _roulier_parse_response(self, picking, response): "package_id": package_id, "file": label.get("data"), "name": "%s.%s" - % (parcel.get("reference") or tracking_number or label.get("name"), label.get("type", "").lower()), + % ( + parcel.get("reference") or tracking_number or label.get("name"), + label.get("type", "").lower(), + ), "file_type": label.get("type"), } ) - res["tracking_number"] = ';'.join(tracking_refs) - res['labels'] = parcels_data + res["tracking_number"] = ";".join(tracking_refs) + res["labels"] = parcels_data return res def _roulier_get_parcels(self, picking): diff --git a/delivery_roulier/tests/models.py b/delivery_roulier/tests/models.py index 9e8b62b703..5d1d31da98 100644 --- a/delivery_roulier/tests/models.py +++ b/delivery_roulier/tests/models.py @@ -5,4 +5,6 @@ class FakeDeliveryCarrier(models.Model): _inherit = "delivery.carrier" - delivery_type = fields.Selection(selection_add=[("test", "Test Carrier")], ondelete={'test': 'set default'}) + delivery_type = fields.Selection( + selection_add=[("test", "Test Carrier")], ondelete={"test": "set default"} + ) diff --git a/delivery_roulier/tests/test_delivery_roulier.py b/delivery_roulier/tests/test_delivery_roulier.py index 0e2f2eb7c6..570354ed4f 100644 --- a/delivery_roulier/tests/test_delivery_roulier.py +++ b/delivery_roulier/tests/test_delivery_roulier.py @@ -79,11 +79,13 @@ def setUp(self): ], } ) - self.env['stock.quant'].with_context(inventory_mode=True).create({ - "product_id": product.id, - 'location_id': self.order.warehouse_id.lot_stock_id.id, - 'inventory_quantity': 1, - }) + self.env["stock.quant"].with_context(inventory_mode=True).create( + { + "product_id": product.id, + "location_id": self.order.warehouse_id.lot_stock_id.id, + "inventory_quantity": 1, + } + ) self.order.action_confirm() self.picking = self.order.picking_ids self.env["stock.immediate.transfer"].create( From 8d3913f8e0ed18ae88a709417bcb72b080ad8a56 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 2 Jun 2021 17:03:14 +0200 Subject: [PATCH 67/92] Avoid logging or displaying carrier account password if an error occurs --- delivery_roulier/models/stock_quant_package.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 7166d16e2d..92df5db6da 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -234,6 +234,8 @@ def _roulier_carrier_error_handling(self, payload, exception): returns: string """ + if payload.get("auth", {}).get("password"): + payload["auth"]["password"] = "*****" try: _logger.debug(exception.response.text) _logger.debug(exception.response.request.body) From 48482535a6e988ad6ca015d4bd7db9fe7a05b0f4 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 2 Jun 2021 19:00:20 +0200 Subject: [PATCH 68/92] [FIX] Avoid trying to print label with roulier if delivery type is not supported by roulier --- delivery_roulier/__manifest__.py | 2 +- delivery_roulier/models/delivery_carrier.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index 76ac3aa5e0..1d6d76b76a 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -6,7 +6,7 @@ "version": "14.0.1.0.0", "author": "Akretion,Odoo Community Association (OCA)", "summary": "Integration of multiple carriers", - "maintainer": "Akretion, Odoo Community Association (OCA)", + "maintainers": ["florian-dacosta"], "category": "Warehouse", "depends": [ "partner_helper", diff --git a/delivery_roulier/models/delivery_carrier.py b/delivery_roulier/models/delivery_carrier.py index d9f4bce487..83e5d1c6b1 100644 --- a/delivery_roulier/models/delivery_carrier.py +++ b/delivery_roulier/models/delivery_carrier.py @@ -16,7 +16,7 @@ class DeliveryCarrier(models.Model): def alternative_send_shipping(self, pickings): self.ensure_one() - if self._is_roulier: + if self._is_roulier(): return pickings._roulier_generate_labels() else: return super().alternative_send_shipping(pickings) From 099dafade1ec43cb617b66f7b2e3ba625323b560 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Mon, 7 Jun 2021 20:11:49 +0200 Subject: [PATCH 69/92] [REF] Use method defined in base_delivery_carrier_label to avoid code duplication --- delivery_roulier/models/stock_picking.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index a45f68e626..7397a641c2 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -113,13 +113,7 @@ def _roulier_get_account(self, package=None): Accounts are resolved at runtime (can be != for dev/prod) """ self.ensure_one() - domain = [ - ("delivery_type", "=", self.carrier_id.delivery_type), - "|", - ("company_id", "=", self.company_id.id), - ("company_id", "=", False), - ] - account = self.env["carrier.account"].search(domain, limit=1) + account = self._get_carrier_account() if not account: raise UserError( _( From cb34c474088b188bc262ce5e0806e5ac60a5e5fa Mon Sep 17 00:00:00 2001 From: oca-travis Date: Sat, 3 Jul 2021 23:12:49 +0000 Subject: [PATCH 70/92] [UPD] Update delivery_roulier.pot --- delivery_roulier/i18n/delivery_roulier.pot | 118 +++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 delivery_roulier/i18n/delivery_roulier.pot diff --git a/delivery_roulier/i18n/delivery_roulier.pot b/delivery_roulier/i18n/delivery_roulier.pot new file mode 100644 index 0000000000..bb3e82f5f4 --- /dev/null +++ b/delivery_roulier/i18n/delivery_roulier.pot @@ -0,0 +1,118 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * delivery_roulier +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: delivery_roulier +#: code:addons/delivery_roulier/models/stock_quant_package.py:0 +#, python-format +msgid "Bad input: %s\n" +msgstr "" + +#. module: delivery_roulier +#: model:ir.model.fields,field_description:delivery_roulier.field_stock_quant_package__carrier_id +msgid "Carrier" +msgstr "" + +#. module: delivery_roulier +#: model:ir.model.fields,field_description:delivery_roulier.field_delivery_carrier__display_name +#: model:ir.model.fields,field_description:delivery_roulier.field_stock_move_line__display_name +#: model:ir.model.fields,field_description:delivery_roulier.field_stock_picking__display_name +#: model:ir.model.fields,field_description:delivery_roulier.field_stock_quant_package__display_name +msgid "Display Name" +msgstr "" + +#. module: delivery_roulier +#: model:ir.model.fields,field_description:delivery_roulier.field_delivery_carrier__id +#: model:ir.model.fields,field_description:delivery_roulier.field_stock_move_line__id +#: model:ir.model.fields,field_description:delivery_roulier.field_stock_picking__id +#: model:ir.model.fields,field_description:delivery_roulier.field_stock_quant_package__id +msgid "ID" +msgstr "" + +#. module: delivery_roulier +#: model:ir.model.fields,field_description:delivery_roulier.field_delivery_carrier____last_update +#: model:ir.model.fields,field_description:delivery_roulier.field_stock_move_line____last_update +#: model:ir.model.fields,field_description:delivery_roulier.field_stock_picking____last_update +#: model:ir.model.fields,field_description:delivery_roulier.field_stock_quant_package____last_update +msgid "Last Modified on" +msgstr "" + +#. module: delivery_roulier +#: code:addons/delivery_roulier/models/stock_picking.py:0 +#, python-format +msgid "No account available with name '%s' for this carrier" +msgstr "" + +#. module: delivery_roulier +#: code:addons/delivery_roulier/models/stock_picking.py:0 +#, python-format +msgid "No packages found for this picking" +msgstr "" + +#. module: delivery_roulier +#: model:ir.model,name:delivery_roulier.model_stock_quant_package +msgid "Packages" +msgstr "" + +#. module: delivery_roulier +#: model:ir.model,name:delivery_roulier.model_stock_move_line +msgid "Product Moves (Stock Move Line)" +msgstr "" + +#. module: delivery_roulier +#: code:addons/delivery_roulier/models/stock_quant_package.py:0 +#, python-format +msgid "" +"Roulier library Exception for '%s' carrier:\n" +"\n" +"%s\n" +"\n" +"Sent data:\n" +"%s" +msgstr "" + +#. module: delivery_roulier +#: model:ir.model,name:delivery_roulier.model_delivery_carrier +msgid "Shipping Methods" +msgstr "" + +#. module: delivery_roulier +#: model_terms:ir.ui.view,arch_db:delivery_roulier.view_quant_package_form +msgid "Tracking" +msgstr "" + +#. module: delivery_roulier +#: model:ir.model,name:delivery_roulier.model_stock_picking +msgid "Transfer" +msgstr "" + +#. module: delivery_roulier +#: model:product.product,uom_name:delivery_roulier.product_big +#: model:product.product,uom_name:delivery_roulier.product_small +#: model:product.template,uom_name:delivery_roulier.product_big_product_template +#: model:product.template,uom_name:delivery_roulier.product_small_product_template +msgid "Units" +msgstr "" + +#. module: delivery_roulier +#: model:product.product,name:delivery_roulier.product_small +#: model:product.template,name:delivery_roulier.product_small_product_template +msgid "carrier 1.3 kg" +msgstr "" + +#. module: delivery_roulier +#: model:product.product,name:delivery_roulier.product_big +#: model:product.template,name:delivery_roulier.product_big_product_template +msgid "carrier 3.7 kg" +msgstr "" From 3b0ef45a671ff7e1d6226bb44f967c9e4d6b2e4d Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sun, 4 Jul 2021 00:14:10 +0000 Subject: [PATCH 71/92] [UPD] README.rst --- delivery_roulier/README.rst | 140 +++++- .../static/description/index.html | 473 ++++++++++++++++++ 2 files changed, 612 insertions(+), 1 deletion(-) create mode 100644 delivery_roulier/static/description/index.html diff --git a/delivery_roulier/README.rst b/delivery_roulier/README.rst index f24a174a7d..76a121f972 100644 --- a/delivery_roulier/README.rst +++ b/delivery_roulier/README.rst @@ -1 +1,139 @@ -autogenerated +======================== +Delivery Carrier Roulier +======================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fdelivery--carrier-lightgray.png?logo=github + :target: https://github.com/OCA/delivery-carrier/tree/14.0/delivery_roulier + :alt: OCA/delivery-carrier +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/delivery-carrier-14-0/delivery-carrier-14-0-delivery_roulier + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/99/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Integration of multiple carriers with Roulier library + +Base module for integration with Roulier. + +`Roulier `_ is a python library which implements carriers API. +This modules contains the core functions for this implementation. + +You should install one of the specific modules : + +- delivery_roulier_laposte +- delivery_roulier_dpd +- delivery_roulier_geodis +- delivery_carrier_label_gls +- more to come + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Here is some methods you can use for your carrier implementation +allowing to have a consistent code accross different carrier modules: + +.. code-block:: python + + def _mycarrier_get_sender(...): + + + def _mycarrier_get_receiver(...): + + + def _mycarrier_get_shipping_date(...): + + + def _mycarrier_get_account(...): + + + def _mycarrier_get_auth(...): + + + def _mycarrier_get_service(...): + + + def _mycarrier_convert_address(...): + + +| + + +Instead of calling `super()` you can use: + +.. code-block:: python + + def _mycarrier_get_service(...): + + result = _roulier_get_service(...) + + result["specific_key"] = "blabla" + + return result + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Raphaël Reverdy +* David Béal + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-florian-dacosta| image:: https://github.com/florian-dacosta.png?size=40px + :target: https://github.com/florian-dacosta + :alt: florian-dacosta + +Current `maintainer `__: + +|maintainer-florian-dacosta| + +This module is part of the `OCA/delivery-carrier `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/delivery_roulier/static/description/index.html b/delivery_roulier/static/description/index.html new file mode 100644 index 0000000000..d8e7ab023f --- /dev/null +++ b/delivery_roulier/static/description/index.html @@ -0,0 +1,473 @@ + + + + + + +Delivery Carrier Roulier + + + +
+

Delivery Carrier Roulier

+ + +

Beta License: AGPL-3 OCA/delivery-carrier Translate me on Weblate Try me on Runbot

+

Integration of multiple carriers with Roulier library

+

Base module for integration with Roulier.

+

Roulier is a python library which implements carriers API. +This modules contains the core functions for this implementation.

+

You should install one of the specific modules :

+
    +
  • delivery_roulier_laposte
  • +
  • delivery_roulier_dpd
  • +
  • delivery_roulier_geodis
  • +
  • delivery_carrier_label_gls
  • +
  • more to come
  • +
+

Table of contents

+ +
+

Usage

+

Here is some methods you can use for your carrier implementation +allowing to have a consistent code accross different carrier modules:

+
+def _mycarrier_get_sender(...):
+
+
+def _mycarrier_get_receiver(...):
+
+
+def _mycarrier_get_shipping_date(...):
+
+
+def _mycarrier_get_account(...):
+
+
+def _mycarrier_get_auth(...):
+
+
+def _mycarrier_get_service(...):
+
+
+def _mycarrier_convert_address(...):
+
+
+

+
+

Instead of calling super() you can use:

+
+def _mycarrier_get_service(...):
+
+    result = _roulier_get_service(...)
+
+    result["specific_key"] = "blabla"
+
+    return result
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

florian-dacosta

+

This module is part of the OCA/delivery-carrier project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + From 3ac2e6166d4fb821fd0e3733ee3701d661aeecd6 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sun, 4 Jul 2021 00:14:10 +0000 Subject: [PATCH 72/92] [ADD] icon.png --- delivery_roulier/static/description/icon.png | Bin 0 -> 9455 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 delivery_roulier/static/description/icon.png diff --git a/delivery_roulier/static/description/icon.png b/delivery_roulier/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 zcmW++2RxMjAAjx~&dlBk9S+%}OXg)AGE&Cb*&}d0jUxM@u(PQx^-s)697TX`ehR4?GS^qbkof1cslKgkU)h65qZ9Oc=ml_0temigYLJfnz{IDzUf>bGs4N!v3=Z3jMq&A#7%rM5eQ#dc?k~! zVpnB`o+K7|Al`Q_U;eD$B zfJtP*jH`siUq~{KE)`jP2|#TUEFGRryE2`i0**z#*^6~AI|YzIWy$Cu#CSLW3q=GA z6`?GZymC;dCPk~rBS%eCb`5OLr;RUZ;D`}um=H)BfVIq%7VhiMr)_#G0N#zrNH|__ zc+blN2UAB0=617@>_u;MPHN;P;N#YoE=)R#i$k_`UAA>WWCcEVMh~L_ zj--gtp&|K1#58Yz*AHCTMziU1Jzt_jG0I@qAOHsk$2}yTmVkBp_eHuY$A9)>P6o~I z%aQ?!(GqeQ-Y+b0I(m9pwgi(IIZZzsbMv+9w{PFtd_<_(LA~0H(xz{=FhLB@(1&qHA5EJw1>>=%q2f&^X>IQ{!GJ4e9U z&KlB)z(84HmNgm2hg2C0>WM{E(DdPr+EeU_N@57;PC2&DmGFW_9kP&%?X4}+xWi)( z;)z%wI5>D4a*5XwD)P--sPkoY(a~WBw;E~AW`Yue4kFa^LM3X`8x|}ZUeMnqr}>kH zG%WWW>3ml$Yez?i%)2pbKPI7?5o?hydokgQyZsNEr{a|mLdt;X2TX(#B1j35xPnPW z*bMSSOauW>o;*=kO8ojw91VX!qoOQb)zHJ!odWB}d+*K?#sY_jqPdg{Sm2HdYzdEx zOGVPhVRTGPtv0o}RfVP;Nd(|CB)I;*t&QO8h zFfekr30S!-LHmV_Su-W+rEwYXJ^;6&3|L$mMC8*bQptyOo9;>Qb9Q9`ySe3%V$A*9 zeKEe+b0{#KWGp$F+tga)0RtI)nhMa-K@JS}2krK~n8vJ=Ngm?R!9G<~RyuU0d?nz# z-5EK$o(!F?hmX*2Yt6+coY`6jGbb7tF#6nHA zuKk=GGJ;ZwON1iAfG$E#Y7MnZVmrY|j0eVI(DN_MNFJmyZ|;w4tf@=CCDZ#5N_0K= z$;R~bbk?}TpfDjfB&aiQ$VA}s?P}xPERJG{kxk5~R`iRS(SK5d+Xs9swCozZISbnS zk!)I0>t=A<-^z(cmSFz3=jZ23u13X><0b)P)^1T_))Kr`e!-pb#q&J*Q`p+B6la%C zuVl&0duN<;uOsB3%T9Fp8t{ED108<+W(nOZd?gDnfNBC3>M8WE61$So|P zVvqH0SNtDTcsUdzaMDpT=Ty0pDHHNL@Z0w$Y`XO z2M-_r1S+GaH%pz#Uy0*w$Vdl=X=rQXEzO}d6J^R6zjM1u&c9vYLvLp?W7w(?np9x1 zE_0JSAJCPB%i7p*Wvg)pn5T`8k3-uR?*NT|J`eS#_#54p>!p(mLDvmc-3o0mX*mp_ zN*AeS<>#^-{S%W<*mz^!X$w_2dHWpcJ6^j64qFBft-o}o_Vx80o0>}Du;>kLts;$8 zC`7q$QI(dKYG`Wa8#wl@V4jVWBRGQ@1dr-hstpQL)Tl+aqVpGpbSfN>5i&QMXfiZ> zaA?T1VGe?rpQ@;+pkrVdd{klI&jVS@I5_iz!=UMpTsa~mBga?1r}aRBm1WS;TT*s0f0lY=JBl66Upy)-k4J}lh=P^8(SXk~0xW=T9v*B|gzIhN z>qsO7dFd~mgxAy4V?&)=5ieYq?zi?ZEoj)&2o)RLy=@hbCRcfT5jigwtQGE{L*8<@Yd{zg;CsL5mvzfDY}P-wos_6PfprFVaeqNE%h zKZhLtcQld;ZD+>=nqN~>GvROfueSzJD&BE*}XfU|H&(FssBqY=hPCt`d zH?@s2>I(|;fcW&YM6#V#!kUIP8$Nkdh0A(bEVj``-AAyYgwY~jB zT|I7Bf@%;7aL7Wf4dZ%VqF$eiaC38OV6oy3Z#TER2G+fOCd9Iaoy6aLYbPTN{XRPz z;U!V|vBf%H!}52L2gH_+j;`bTcQRXB+y9onc^wLm5wi3-Be}U>k_u>2Eg$=k!(l@I zcCg+flakT2Nej3i0yn+g+}%NYb?ta;R?(g5SnwsQ49U8Wng8d|{B+lyRcEDvR3+`O{zfmrmvFrL6acVP%yG98X zo&+VBg@px@i)%o?dG(`T;n*$S5*rnyiR#=wW}}GsAcfyQpE|>a{=$Hjg=-*_K;UtD z#z-)AXwSRY?OPefw^iI+ z)AXz#PfEjlwTes|_{sB?4(O@fg0AJ^g8gP}ex9Ucf*@_^J(s_5jJV}c)s$`Myn|Kd z$6>}#q^n{4vN@+Os$m7KV+`}c%4)4pv@06af4-x5#wj!KKb%caK{A&Y#Rfs z-po?Dcb1({W=6FKIUirH&(yg=*6aLCekcKwyfK^JN5{wcA3nhO(o}SK#!CINhI`-I z1)6&n7O&ZmyFMuNwvEic#IiOAwNkR=u5it{B9n2sAJV5pNhar=j5`*N!Na;c7g!l$ z3aYBqUkqqTJ=Re-;)s!EOeij=7SQZ3Hq}ZRds%IM*PtM$wV z@;rlc*NRK7i3y5BETSKuumEN`Xu_8GP1Ri=OKQ$@I^ko8>H6)4rjiG5{VBM>B|%`&&s^)jS|-_95&yc=GqjNo{zFkw%%HHhS~e=s zD#sfS+-?*t|J!+ozP6KvtOl!R)@@-z24}`9{QaVLD^9VCSR2b`b!KC#o;Ki<+wXB6 zx3&O0LOWcg4&rv4QG0)4yb}7BFSEg~=IR5#ZRj8kg}dS7_V&^%#Do==#`u zpy6{ox?jWuR(;pg+f@mT>#HGWHAJRRDDDv~@(IDw&R>9643kK#HN`!1vBJHnC+RM&yIh8{gG2q zA%e*U3|N0XSRa~oX-3EAneep)@{h2vvd3Xvy$7og(sayr@95+e6~Xvi1tUqnIxoIH zVWo*OwYElb#uyW{Imam6f2rGbjR!Y3`#gPqkv57dB6K^wRGxc9B(t|aYDGS=m$&S!NmCtrMMaUg(c zc2qC=2Z`EEFMW-me5B)24AqF*bV5Dr-M5ig(l-WPS%CgaPzs6p_gnCIvTJ=Y<6!gT zVt@AfYCzjjsMEGi=rDQHo0yc;HqoRNnNFeWZgcm?f;cp(6CNylj36DoL(?TS7eU#+ z7&mfr#y))+CJOXQKUMZ7QIdS9@#-}7y2K1{8)cCt0~-X0O!O?Qx#E4Og+;A2SjalQ zs7r?qn0H044=sDN$SRG$arw~n=+T_DNdSrarmu)V6@|?1-ZB#hRn`uilTGPJ@fqEy zGt(f0B+^JDP&f=r{#Y_wi#AVDf-y!RIXU^0jXsFpf>=Ji*TeqSY!H~AMbJdCGLhC) zn7Rx+sXw6uYj;WRYrLd^5IZq@6JI1C^YkgnedZEYy<&4(z%Q$5yv#Boo{AH8n$a zhb4Y3PWdr269&?V%uI$xMcUrMzl=;w<_nm*qr=c3Rl@i5wWB;e-`t7D&c-mcQl7x! zZWB`UGcw=Y2=}~wzrfLx=uet<;m3~=8I~ZRuzvMQUQdr+yTV|ATf1Uuomr__nDf=X zZ3WYJtHp_ri(}SQAPjv+Y+0=fH4krOP@S&=zZ-t1jW1o@}z;xk8 z(Nz1co&El^HK^NrhVHa-_;&88vTU>_J33=%{if;BEY*J#1n59=07jrGQ#IP>@u#3A z;!q+E1Rj3ZJ+!4bq9F8PXJ@yMgZL;>&gYA0%_Kbi8?S=XGM~dnQZQ!yBSgcZhY96H zrWnU;k)qy`rX&&xlDyA%(a1Hhi5CWkmg(`Gb%m(HKi-7Z!LKGRP_B8@`7&hdDy5n= z`OIxqxiVfX@OX1p(mQu>0Ai*v_cTMiw4qRt3~NBvr9oBy0)r>w3p~V0SCm=An6@3n)>@z!|o-$HvDK z|3D2ZMJkLE5loMKl6R^ez@Zz%S$&mbeoqH5`Bb){Ei21q&VP)hWS2tjShfFtGE+$z zzCR$P#uktu+#!w)cX!lWN1XU%K-r=s{|j?)Akf@q#3b#{6cZCuJ~gCxuMXRmI$nGtnH+-h z+GEi!*X=AP<|fG`1>MBdTb?28JYc=fGvAi2I<$B(rs$;eoJCyR6_bc~p!XR@O-+sD z=eH`-ye})I5ic1eL~TDmtfJ|8`0VJ*Yr=hNCd)G1p2MMz4C3^Mj?7;!w|Ly%JqmuW zlIEW^Ft%z?*|fpXda>Jr^1noFZEwFgVV%|*XhH@acv8rdGxeEX{M$(vG{Zw+x(ei@ zmfXb22}8-?Fi`vo-YVrTH*C?a8%M=Hv9MqVH7H^J$KsD?>!SFZ;ZsvnHr_gn=7acz z#W?0eCdVhVMWN12VV^$>WlQ?f;P^{(&pYTops|btm6aj>_Uz+hqpGwB)vWp0Cf5y< zft8-je~nn?W11plq}N)4A{l8I7$!ks_x$PXW-2XaRFswX_BnF{R#6YIwMhAgd5F9X zGmwdadS6(a^fjHtXg8=l?Rc0Sm%hk6E9!5cLVloEy4eh(=FwgP`)~I^5~pBEWo+F6 zSf2ncyMurJN91#cJTy_u8Y}@%!bq1RkGC~-bV@SXRd4F{R-*V`bS+6;W5vZ(&+I<9$;-V|eNfLa5n-6% z2(}&uGRF;p92eS*sE*oR$@pexaqr*meB)VhmIg@h{uzkk$9~qh#cHhw#>O%)b@+(| z^IQgqzuj~Sk(J;swEM-3TrJAPCq9k^^^`q{IItKBRXYe}e0Tdr=Huf7da3$l4PdpwWDop%^}n;dD#K4s#DYA8SHZ z&1!riV4W4R7R#C))JH1~axJ)RYnM$$lIR%6fIVA@zV{XVyx}C+a-Dt8Y9M)^KU0+H zR4IUb2CJ{Hg>CuaXtD50jB(_Tcx=Z$^WYu2u5kubqmwp%drJ6 z?Fo40g!Qd<-l=TQxqHEOuPX0;^z7iX?Ke^a%XT<13TA^5`4Xcw6D@Ur&VT&CUe0d} z1GjOVF1^L@>O)l@?bD~$wzgf(nxX1OGD8fEV?TdJcZc2KoUe|oP1#=$$7ee|xbY)A zDZq+cuTpc(fFdj^=!;{k03C69lMQ(|>uhRfRu%+!k&YOi-3|1QKB z z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zl&#s4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6# z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f# zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv! zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG z-wfS zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9 z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE# z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1 zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$ zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV( z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4 z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{ zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx} z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22 zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t< z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{} zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 From 7829be12265a06cd3e21359c6634e89910a309d5 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Tue, 13 Jul 2021 12:35:09 +0200 Subject: [PATCH 73/92] Revert roulier magic mock once tests are done in delivery_roulier because not mocked method may be used in the tests of another module --- delivery_roulier/tests/test_delivery_roulier.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/delivery_roulier/tests/test_delivery_roulier.py b/delivery_roulier/tests/test_delivery_roulier.py index 570354ed4f..8a6cfa572a 100644 --- a/delivery_roulier/tests/test_delivery_roulier.py +++ b/delivery_roulier/tests/test_delivery_roulier.py @@ -34,6 +34,7 @@ def setUpClass(cls): from .models import FakeDeliveryCarrier cls.loader.update_registry((FakeDeliveryCarrier,)) + cls.real_get_carriers_action_available = roulier.get_carriers_action_available def setUp(self): super().setUp() @@ -95,6 +96,7 @@ def setUp(self): @classmethod def tearDownClass(cls): cls.loader.restore_registry() + roulier.get_carriers_action_available = cls.real_get_carriers_action_available super().tearDownClass() def test_roulier(self): From 579a1790a3b2ae8fa6a90b0847a02e0421067d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Mon, 31 Jan 2022 09:48:52 +0100 Subject: [PATCH 74/92] [FIX] delivery_roulier: Fix access right issues when using actions --- delivery_roulier/models/stock_picking.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 7397a641c2..64539ca3c8 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -245,7 +245,8 @@ def open_website_url(self): return packages.open_website_url() # shortpath # display a list of pickings - action = self.env.ref("stock.action_package_view").read()[0] + xmlid = "stock.action_package_view" + action = self.env["ir.actions.act_window"]._for_xml_id(xmlid) action["domain"] = [("id", "in", packages.ids)] action["context"] = {"picking_id": self.id} return action From e3ddcfe6424cea1126b915a75219393f1f90021f Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 2 Feb 2022 06:51:07 +0000 Subject: [PATCH 75/92] delivery_roulier 14.0.1.0.1 --- delivery_roulier/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index 1d6d76b76a..d89573a8a4 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Delivery Carrier Roulier", - "version": "14.0.1.0.0", + "version": "14.0.1.0.1", "author": "Akretion,Odoo Community Association (OCA)", "summary": "Integration of multiple carriers", "maintainers": ["florian-dacosta"], From fe9bd055e39eb6b8fcb15d64190373a13dcac878 Mon Sep 17 00:00:00 2001 From: oca-git-bot Date: Wed, 30 Mar 2022 22:15:46 +0200 Subject: [PATCH 76/92] [IMP] update dotfiles [ci skip] --- delivery_roulier/models/stock_picking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 64539ca3c8..16791de274 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -196,7 +196,7 @@ def _roulier_convert_address(self, partner): for tel in ["mobile", "phone"]: if address.get(tel): - address[tel] = address[tel].replace(u"\u00A0", "").replace(" ", "") + address[tel] = address[tel].replace("\u00A0", "").replace(" ", "") address["phone"] = address.get("mobile", address.get("phone")) From 6b813dffdea415f3dee7db89b5099e09245e6b0c Mon Sep 17 00:00:00 2001 From: oca-ci Date: Thu, 3 Nov 2022 14:15:12 +0000 Subject: [PATCH 77/92] [UPD] Update delivery_roulier.pot --- delivery_roulier/i18n/delivery_roulier.pot | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/delivery_roulier/i18n/delivery_roulier.pot b/delivery_roulier/i18n/delivery_roulier.pot index bb3e82f5f4..c24354807e 100644 --- a/delivery_roulier/i18n/delivery_roulier.pot +++ b/delivery_roulier/i18n/delivery_roulier.pot @@ -116,3 +116,19 @@ msgstr "" #: model:product.template,name:delivery_roulier.product_big_product_template msgid "carrier 3.7 kg" msgstr "" + +#. module: delivery_roulier +#: model:product.product,weight_uom_name:delivery_roulier.product_big +#: model:product.product,weight_uom_name:delivery_roulier.product_small +#: model:product.template,weight_uom_name:delivery_roulier.product_big_product_template +#: model:product.template,weight_uom_name:delivery_roulier.product_small_product_template +msgid "kg" +msgstr "" + +#. module: delivery_roulier +#: model:product.product,volume_uom_name:delivery_roulier.product_big +#: model:product.product,volume_uom_name:delivery_roulier.product_small +#: model:product.template,volume_uom_name:delivery_roulier.product_big_product_template +#: model:product.template,volume_uom_name:delivery_roulier.product_small_product_template +msgid "m³" +msgstr "" From 880079b2de7506bfa9d832d86a01b6b36f8ead7e Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 25 Aug 2021 13:32:34 +0200 Subject: [PATCH 78/92] Avoid failure if getting price from carrier webservice is not implemented/not possible --- delivery_roulier/models/delivery_carrier.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/delivery_roulier/models/delivery_carrier.py b/delivery_roulier/models/delivery_carrier.py index 83e5d1c6b1..a6134ca610 100644 --- a/delivery_roulier/models/delivery_carrier.py +++ b/delivery_roulier/models/delivery_carrier.py @@ -45,3 +45,16 @@ def get_tracking_link(self, picking): return first_package._get_tracking_link(picking) else: return super().get_tracking_link(picking) + + def rate_shipment(self, order): + res = super().rate_shipment(order) + # for roulier carrier, usually getting the price by carrier webservice + # is usually not available for now. Avoid failure in that case. + if not res and self.is_roulier(): + res = { + "success": True, + "price": 0.0, + "error_message": False, + "warning_message": False, + } + return res From bf3ab46f5b2eb78ae6e0a9b866b15a1932f0cdc6 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Fri, 7 Jan 2022 18:09:47 +0100 Subject: [PATCH 79/92] [IMP] Improve default address sent to roulier Take mail and mobile on parent if not set on picking address All data added from the parent if set in a separated method to easily override if the behavior is not wanted for specific cases --- delivery_roulier/models/stock_picking.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 16791de274..eac30bdd53 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -157,6 +157,18 @@ def _roulier_get_shipping_date(self, package=None): tomorrow = date.today() + timedelta(1) return tomorrow + def _get_address_info_from_parent(self, partner, address): + res = {} + if not address.get("company") and partner.parent_id.is_company: + res["company"] = partner.parent_id.name + + # these fields could be filled only on parent + if not address.get("email") and partner.parent_id.email: + res["email"] = partner.parent_id.email + if not address.get("mobile") and partner.parent_id.mobile: + res["mobile"] = partner.parent_id.mobile + return res + def _roulier_convert_address(self, partner): """Convert a partner to an address for roulier. @@ -187,18 +199,19 @@ def _roulier_convert_address(self, partner): # it's a None: nothing to do else: # it's a boolean: keep the value address[elm] = partner[elm] - if not address.get("company", False) and partner.parent_id.is_company: - address["company"] = partner.parent_id.name # Roulier needs street1 (mandatory) not street address["street1"] = partner.street # Codet ISO 3166-1-alpha-2 (2 letters code) address["country"] = partner.country_id.code + # keep in a separated method to easily override if not a desirable behavior + address.update(self._get_address_info_from_parent(partner, address)) + for tel in ["mobile", "phone"]: if address.get(tel): address[tel] = address[tel].replace("\u00A0", "").replace(" ", "") - address["phone"] = address.get("mobile", address.get("phone")) + address["phone"] = address.get("mobile", address.get("phone")) or "" return address From c1f81af92cb3b3c0e3ccaccc645b78a3b1f90bce Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Fri, 18 Nov 2022 13:58:44 +0100 Subject: [PATCH 80/92] Improve price_unit we send for customs documents to manage phantom bom + manage company if destination partner is not a contact --- delivery_roulier/models/stock_move_line.py | 11 ++++++++++- delivery_roulier/models/stock_picking.py | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_move_line.py b/delivery_roulier/models/stock_move_line.py index 8f688794a8..40f913cb11 100644 --- a/delivery_roulier/models/stock_move_line.py +++ b/delivery_roulier/models/stock_move_line.py @@ -14,7 +14,13 @@ def get_unit_price_for_customs(self): self.ensure_one() prec = self.env["decimal.precision"].precision_get("Product Unit of Measure") soline = self.get_sale_order_line() - if soline and not float_is_zero(soline.product_uom_qty, precision_digits=prec): + # if product is different, it must be a phantom bom we then take price on the + # product and apply discount + if ( + soline + and not float_is_zero(soline.product_uom_qty, precision_digits=prec) + and soline.product_id == self.product_id + ): price_unit_so_uom = soline.price_subtotal / soline.product_uom_qty price_unit = soline.product_uom._compute_price( price_unit_so_uom, self.product_uom_id @@ -25,6 +31,9 @@ def get_unit_price_for_customs(self): price_unit = ato._fix_tax_included_price_company( product.list_price, product.taxes_id, ato, self.picking_id.company_id ) + # case of phantom bom + if soline.discount: + price_unit = price_unit * (1 - (soline.discount or 0.0) / 100.0) return price_unit def get_sale_order_line(self): diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index eac30bdd53..43229e8a2b 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -204,6 +204,10 @@ def _roulier_convert_address(self, partner): # Codet ISO 3166-1-alpha-2 (2 letters code) address["country"] = partner.country_id.code + # case the partner is not a contact + if partner.is_company and not address.get("company"): + address["company"] = partner.name + # keep in a separated method to easily override if not a desirable behavior address.update(self._get_address_info_from_parent(partner, address)) From 31bca655d062ec6a4da9c2bedaa8a037a0d8da8d Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 24 Nov 2022 09:37:03 +0100 Subject: [PATCH 81/92] [IMP] delivery_roulier: black, isort, prettier --- requirements.txt | 1 + setup/delivery_roulier/odoo/addons/delivery_roulier | 1 + setup/delivery_roulier/setup.py | 6 ++++++ 3 files changed, 8 insertions(+) create mode 120000 setup/delivery_roulier/odoo/addons/delivery_roulier create mode 100644 setup/delivery_roulier/setup.py diff --git a/requirements.txt b/requirements.txt index 9cd1629223..b25984a80d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ # generated from manifests external_dependencies +roulier diff --git a/setup/delivery_roulier/odoo/addons/delivery_roulier b/setup/delivery_roulier/odoo/addons/delivery_roulier new file mode 120000 index 0000000000..f53d6ebd12 --- /dev/null +++ b/setup/delivery_roulier/odoo/addons/delivery_roulier @@ -0,0 +1 @@ +../../../../delivery_roulier \ No newline at end of file diff --git a/setup/delivery_roulier/setup.py b/setup/delivery_roulier/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/delivery_roulier/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From f395b243752db3830c3120bf65c525f68f84b168 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 24 Nov 2022 10:25:26 +0100 Subject: [PATCH 82/92] [MIG] Migrate delivery_roulier to v16 --- delivery_roulier/__manifest__.py | 7 ++-- delivery_roulier/demo/product.xml | 38 ------------------- delivery_roulier/models/delivery_carrier.py | 2 +- .../tests/test_delivery_roulier.py | 6 +-- delivery_roulier/views/carrier_account.xml | 4 +- 5 files changed, 9 insertions(+), 48 deletions(-) diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index d89573a8a4..7de7462d35 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -3,15 +3,14 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { "name": "Delivery Carrier Roulier", - "version": "14.0.1.0.1", + "version": "16.0.1.0.0", "author": "Akretion,Odoo Community Association (OCA)", "summary": "Integration of multiple carriers", "maintainers": ["florian-dacosta"], - "category": "Warehouse", + "category": "Delivery", "depends": [ - "partner_helper", - "base_phone", # from oca/telephony "base_delivery_carrier_label", + "delivery_carrier_account", ], "website": "https://github.com/OCA/delivery-carrier", "data": [ diff --git a/delivery_roulier/demo/product.xml b/delivery_roulier/demo/product.xml index 134017dfbd..76fed5214b 100644 --- a/delivery_roulier/demo/product.xml +++ b/delivery_roulier/demo/product.xml @@ -5,50 +5,12 @@ carrier 3.7 kg product - 1000 3.7 carrier 1.3 kg product - 1000 1.3 - - - - Inventory for Roulier - confirm - - - - - - - - - - 1000.0 - - - - - - - - 1000.0 - - - - - -
diff --git a/delivery_roulier/models/delivery_carrier.py b/delivery_roulier/models/delivery_carrier.py index a6134ca610..d31f55adb2 100644 --- a/delivery_roulier/models/delivery_carrier.py +++ b/delivery_roulier/models/delivery_carrier.py @@ -39,7 +39,7 @@ def cancel_shipment(self, pickings): # Maybe we will merge all this in future versions def get_tracking_link(self, picking): if self._is_roulier(): - packages = picking._get_packages_from_picking() + packages = picking.package_ids first_package = packages and packages[0] if first_package: return first_package._get_tracking_link(picking) diff --git a/delivery_roulier/tests/test_delivery_roulier.py b/delivery_roulier/tests/test_delivery_roulier.py index 8a6cfa572a..9ef2718d80 100644 --- a/delivery_roulier/tests/test_delivery_roulier.py +++ b/delivery_roulier/tests/test_delivery_roulier.py @@ -4,7 +4,7 @@ from odoo_test_helper import FakeModelLoader from roulier import roulier -from odoo.tests.common import SavepointCase +from odoo.tests.common import TransactionCase roulier_ret = { "parcels": [ @@ -23,7 +23,7 @@ } -class DeliveryRoulierCase(SavepointCase): +class DeliveryRoulierCase(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -86,7 +86,7 @@ def setUp(self): "location_id": self.order.warehouse_id.lot_stock_id.id, "inventory_quantity": 1, } - ) + )._apply_inventory() self.order.action_confirm() self.picking = self.order.picking_ids self.env["stock.immediate.transfer"].create( diff --git a/delivery_roulier/views/carrier_account.xml b/delivery_roulier/views/carrier_account.xml index 00721fb21c..31cc2d2f28 100644 --- a/delivery_roulier/views/carrier_account.xml +++ b/delivery_roulier/views/carrier_account.xml @@ -5,7 +5,7 @@ carrier.account @@ -18,7 +18,7 @@ carrier.account From d76f5ce2af90d616f40ea478e14f89171fde733a Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Sun, 11 Dec 2022 20:04:42 +0100 Subject: [PATCH 83/92] [FIX] Remove carrier account view override since unwanted field has been removed --- delivery_roulier/__manifest__.py | 1 - delivery_roulier/views/carrier_account.xml | 30 ---------------------- 2 files changed, 31 deletions(-) delete mode 100644 delivery_roulier/views/carrier_account.xml diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index 7de7462d35..bb0cdad8f5 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -15,7 +15,6 @@ "website": "https://github.com/OCA/delivery-carrier", "data": [ "views/stock_quant_package.xml", - "views/carrier_account.xml", ], "demo": [ "demo/product.xml", diff --git a/delivery_roulier/views/carrier_account.xml b/delivery_roulier/views/carrier_account.xml deleted file mode 100644 index 31cc2d2f28..0000000000 --- a/delivery_roulier/views/carrier_account.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - carrier.account - - - - 1 - - - - - - carrier.account - - - - 1 - - - - - From 59efe7dfd591de55967f99a0476bc690e1c1d7b5 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 21 Dec 2022 16:40:11 +0100 Subject: [PATCH 84/92] [FIX] price taken on product for customs document should be total price in case of variant --- delivery_roulier/models/stock_move_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_move_line.py b/delivery_roulier/models/stock_move_line.py index 40f913cb11..5dfabde7ea 100644 --- a/delivery_roulier/models/stock_move_line.py +++ b/delivery_roulier/models/stock_move_line.py @@ -29,7 +29,7 @@ def get_unit_price_for_customs(self): product = self.product_id ato = self.env["account.tax"] price_unit = ato._fix_tax_included_price_company( - product.list_price, product.taxes_id, ato, self.picking_id.company_id + product.lst_price, product.taxes_id, ato, self.picking_id.company_id ) # case of phantom bom if soline.discount: From 110dbb80312ff4e83cc554df1c0a811ce5da7e62 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 25 Jan 2023 16:18:24 +0100 Subject: [PATCH 85/92] Remove duplicated view from base_delivery_carrier_label --- delivery_roulier/__manifest__.py | 1 - .../views/stock_quant_package.xml | 23 ------------------- 2 files changed, 24 deletions(-) delete mode 100644 delivery_roulier/views/stock_quant_package.xml diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index bb0cdad8f5..9b602b1a9a 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -14,7 +14,6 @@ ], "website": "https://github.com/OCA/delivery-carrier", "data": [ - "views/stock_quant_package.xml", ], "demo": [ "demo/product.xml", diff --git a/delivery_roulier/views/stock_quant_package.xml b/delivery_roulier/views/stock_quant_package.xml deleted file mode 100644 index 644bca777f..0000000000 --- a/delivery_roulier/views/stock_quant_package.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - stock.quant.package - - -
-
-
-
-
From 5f55c3291c4043ea00521dcb29753fe37f8b18da Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Fri, 24 Feb 2023 15:03:51 +0100 Subject: [PATCH 86/92] Fix delivery get tracking link to match manage multiple pickings as native delivery module --- delivery_roulier/models/delivery_carrier.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/delivery_roulier/models/delivery_carrier.py b/delivery_roulier/models/delivery_carrier.py index d31f55adb2..482e0bde2b 100644 --- a/delivery_roulier/models/delivery_carrier.py +++ b/delivery_roulier/models/delivery_carrier.py @@ -37,14 +37,17 @@ def cancel_shipment(self, pickings): # is on picking. An we could have multiple urls for 1 picking, if there # are multiple package... # Maybe we will merge all this in future versions - def get_tracking_link(self, picking): + def get_tracking_link(self, pickings): if self._is_roulier(): - packages = picking.package_ids - first_package = packages and packages[0] - if first_package: - return first_package._get_tracking_link(picking) + trackings = [] + for picking in pickings: + packages = picking.package_ids + first_package = packages and packages[0] + if first_package: + trackings.append(first_package._get_tracking_link()) + return trackings else: - return super().get_tracking_link(picking) + return super().get_tracking_link(pickings) def rate_shipment(self, order): res = super().rate_shipment(order) From 3847235c1ba884e9ac07036f2358d8502fb9859e Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 24 May 2023 12:04:16 +0200 Subject: [PATCH 87/92] [IMP] Avoid duplicates tracking number in picking + display clean error if tracking url is not available --- delivery_roulier/models/stock_quant_package.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 92df5db6da..886dd2abae 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -105,7 +105,7 @@ def _roulier_parse_response(self, picking, response): tracking_refs = [] for parcel in parcels: tracking_number = parcel.get("tracking", {}).get("number") - if tracking_number: + if tracking_number and tracking_number not in tracking_refs: tracking_refs.append(tracking_number) # expected format by base_delivery_carrier_label module label = parcel.get("label") @@ -149,6 +149,8 @@ def open_website_url(self): """ self.ensure_one() url = self._get_tracking_link() + if not url: + raise UserError(_("The tracking url is not available.")) client_action = { "type": "ir.actions.act_url", "name": "Shipment Tracking Page", From 824465eed3f475c1886f7c19e17a24bf347bd96b Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 31 May 2023 15:15:23 +0200 Subject: [PATCH 88/92] [IMP] delivery_roulier : raise if product with no package since it needs it to generate carrier labels --- delivery_roulier/__manifest__.py | 3 +-- delivery_roulier/models/stock_picking.py | 21 +++++++++++++++---- .../models/stock_quant_package.py | 12 +++++------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/delivery_roulier/__manifest__.py b/delivery_roulier/__manifest__.py index 9b602b1a9a..b7a4fbfdfa 100644 --- a/delivery_roulier/__manifest__.py +++ b/delivery_roulier/__manifest__.py @@ -13,8 +13,7 @@ "delivery_carrier_account", ], "website": "https://github.com/OCA/delivery-carrier", - "data": [ - ], + "data": [], "demo": [ "demo/product.xml", ], diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index 43229e8a2b..a4fd28811e 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -79,6 +79,21 @@ def _roulier_generate_labels(self): """ label_info = [] for picking in self: + move_line_no_pack = picking.move_line_ids.filtered( + lambda ml: ml.qty_done > 0.0 and not ml.result_package_id + ) + if move_line_no_pack: + # For automatic package creation, for use cases where we only have one + # pack per picking and we don't want to force the user to use pack + # consider installing delivery_automatic_package OCA module + raise UserError( + _( + "Some products have no destination package in picking %s, " + "please add a destination package in order to be able to " + "generate the carrier label." + ) + % picking.name + ) label_info.append(picking.package_ids._generate_labels(picking)) return label_info @@ -116,10 +131,8 @@ def _roulier_get_account(self, package=None): account = self._get_carrier_account() if not account: raise UserError( - _( - "No account available with name '%s' " - "for this carrier" % self.carrier_id.delivery_type - ) + _("No account available with name '%s' " "for this carrier") + % self.carrier_id.delivery_type ) return account diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 886dd2abae..0290ee1679 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -183,9 +183,9 @@ def _call_roulier_api(self, picking): # api call ret = roulier.get(picking.delivery_type, "get_label", payload) except InvalidApiInput as e: - raise UserError(self._invalid_api_input_handling(payload, e)) + raise UserError(self._invalid_api_input_handling(payload, e)) from e except CarrierError as e: - raise UserError(self._carrier_error_handling(payload, e)) + raise UserError(self._carrier_error_handling(payload, e)) from e # give result to someone else return self._after_call(picking, ret) @@ -247,9 +247,9 @@ def _roulier_carrier_error_handling(self, payload, exception): self.env["delivery.carrier"]._fields["delivery_type"].selection ).get(self.carrier_id.delivery_type) return _( - "Roulier library Exception for '%s' carrier:\n" - "\n%s\n\nSent data:\n%s" % (carrier, str(exception), payload) - ) + "Roulier library Exception for '%(carrier)s' carrier:\n" + "\n%(exception)s\n\nSent data:\n%(payload)s" + ) % {"carrier": carrier, "exception": str(exception), "payload": payload} def _roulier_invalid_api_input_handling(self, payload, exception): """Build exception message for bad input. @@ -261,7 +261,7 @@ def _roulier_invalid_api_input_handling(self, payload, exception): returns: string """ - return _("Bad input: %s\n" % str(exception)) + return _("Bad input: %s\n") % str(exception) # There is low chance you need to override the following methods. def _roulier_handle_attachments(self, picking, response): From 169fcad253dc44ae8f8f0f0f4916275e9b4260d2 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 31 May 2023 16:40:19 +0200 Subject: [PATCH 89/92] [FIX] delivery_roulier fix tests --- delivery_roulier/tests/test_delivery_roulier.py | 15 ++++++++------- test-requirements.txt | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/delivery_roulier/tests/test_delivery_roulier.py b/delivery_roulier/tests/test_delivery_roulier.py index 9ef2718d80..c04aab2c50 100644 --- a/delivery_roulier/tests/test_delivery_roulier.py +++ b/delivery_roulier/tests/test_delivery_roulier.py @@ -41,13 +41,6 @@ def setUp(self): delivery_product = self.env["product.product"].create( {"name": "test shipping product", "type": "service"} ) - self.test_carrier = self.env["delivery.carrier"].create( - { - "name": "Test Carrier", - "delivery_type": "test", - "product_id": delivery_product.id, - } - ) self.account = self.env["carrier.account"].create( { "name": "Test Carrier Account", @@ -56,6 +49,14 @@ def setUp(self): "password": "test", } ) + self.test_carrier = self.env["delivery.carrier"].create( + { + "name": "Test Carrier", + "delivery_type": "test", + "product_id": delivery_product.id, + "carrier_account_id": self.account.id, + } + ) partner = self.env["res.partner"].create( { "name": "Carrier label test customer", diff --git a/test-requirements.txt b/test-requirements.txt index 33be0fbec5..65700ca2fa 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,2 +1,3 @@ +odoo-test-helper vcrpy vcrpy-unittest From 6159960186211a1a1981ef54296dc67a53f51e0f Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 8 Jun 2023 12:58:48 +0200 Subject: [PATCH 90/92] [IMP] delivery_roulier : allow to easily by pass the tracking url per pack for a carrier if it does not handle it --- delivery_roulier/models/stock_picking.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index a4fd28811e..de7c472db2 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -60,6 +60,10 @@ def _get_to_address(self): def _cancel_shipment(self): pass + @implemented_by_carrier + def _support_multi_tracking(self): + pass + # End of API. # Implementations for base_delivery_carrier_label @@ -258,6 +262,12 @@ def _roulier_get_service(self, account, package=None): } return service + def _roulier_support_multi_tracking(self): + # By default roulier carrier may have one tracking ref per pack. + # override this method for your carrier if you always have a unique + # tracking per picking + return True + def open_website_url(self): """Open tracking page. @@ -271,8 +281,11 @@ def open_website_url(self): packages = self.package_ids if len(packages) == 0: raise UserError(_("No packages found for this picking")) - elif len(packages) == 1: - return packages.open_website_url() # shortpath + else: + if not self._support_multi_tracking(): + packages = packages[0] + if len(packages) == 1: + return packages.open_website_url() # shortpath # display a list of pickings xmlid = "stock.action_package_view" From 39a28a9b0a628ca7822f293362411c415a2d0cb7 Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Thu, 8 Jun 2023 15:09:27 +0200 Subject: [PATCH 91/92] [IMP] delivery_roulier : manage package tracking uri --- delivery_roulier/models/stock_quant_package.py | 1 + 1 file changed, 1 insertion(+) diff --git a/delivery_roulier/models/stock_quant_package.py b/delivery_roulier/models/stock_quant_package.py index 0290ee1679..c0fe30c958 100644 --- a/delivery_roulier/models/stock_quant_package.py +++ b/delivery_roulier/models/stock_quant_package.py @@ -122,6 +122,7 @@ def _roulier_parse_response(self, picking, response): parcels_data.append( { "tracking_number": tracking_number, + "parcel_tracking_uri": parcel.get("tracking", {}).get("url", False), "package_id": package_id, "file": label.get("data"), "name": "%s.%s" From f9a9a7b2ad8a9309cbb3259c0dba8ea62be9dacc Mon Sep 17 00:00:00 2001 From: Florian da Costa Date: Wed, 14 Jun 2023 14:11:48 +0200 Subject: [PATCH 92/92] [FIX] remove comment and improve readme to talk about the delivery_automatic_package that may be quite usefull with delivery_roulier --- delivery_roulier/models/stock_picking.py | 3 --- delivery_roulier/readme/CONFIGURE.rst | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 delivery_roulier/readme/CONFIGURE.rst diff --git a/delivery_roulier/models/stock_picking.py b/delivery_roulier/models/stock_picking.py index de7c472db2..382741c028 100644 --- a/delivery_roulier/models/stock_picking.py +++ b/delivery_roulier/models/stock_picking.py @@ -87,9 +87,6 @@ def _roulier_generate_labels(self): lambda ml: ml.qty_done > 0.0 and not ml.result_package_id ) if move_line_no_pack: - # For automatic package creation, for use cases where we only have one - # pack per picking and we don't want to force the user to use pack - # consider installing delivery_automatic_package OCA module raise UserError( _( "Some products have no destination package in picking %s, " diff --git a/delivery_roulier/readme/CONFIGURE.rst b/delivery_roulier/readme/CONFIGURE.rst new file mode 100644 index 0000000000..e7cf93ad3c --- /dev/null +++ b/delivery_roulier/readme/CONFIGURE.rst @@ -0,0 +1,2 @@ +This module needs package in order to work. +To generate the carrier labels on the picking, each products need to be assigned to a destination package. In case you don't want to bother with the use of package because the whole content of the picking usually fit in a unique package, you may consider installing the module delivery_automatic_package from the same repository (delivery-carrier). The package will be assigned automatically when asking the carrier labels.