diff --git a/stock_cubiscan/__manifest__.py b/stock_cubiscan/__manifest__.py index 5e6c6ff3c..e3eb53685 100644 --- a/stock_cubiscan/__manifest__.py +++ b/stock_cubiscan/__manifest__.py @@ -1,25 +1,25 @@ # Copyright 2019 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { - 'name': 'Stock Cubiscan', - 'summary': 'Implement inteface with Cubiscan devices for packaging', - 'version': '12.0.1.0.0', - 'category': 'Stock', - 'author': 'Camptocamp', - 'license': 'AGPL-3', - 'depends': [ - 'barcodes', - 'stock', - 'web_tree_dynamic_colored_field', - 'product_packaging_dimension', - 'product_packaging_type_required', + "name": "Stock Cubiscan", + "summary": "Implement inteface with Cubiscan devices for packaging", + "version": "12.0.1.0.0", + "category": "Stock", + "author": "Camptocamp", + "license": "AGPL-3", + "depends": [ + "barcodes", + "stock", + "web_tree_dynamic_colored_field", + "product_packaging_dimension", + "product_packaging_type_required", ], - 'website': 'http://www.camptocamp.com', - 'data': [ - 'views/assets.xml', - 'views/cubiscan_view.xml', - 'wizard/cubiscan_wizard.xml', - 'security/ir.model.access.csv', + "website": "http://www.camptocamp.com", + "data": [ + "views/assets.xml", + "views/cubiscan_view.xml", + "wizard/cubiscan_wizard.xml", + "security/ir.model.access.csv", ], - 'installable': True, + "installable": True, } diff --git a/stock_cubiscan/models/cubiscan.py b/stock_cubiscan/models/cubiscan.py index dd6b68120..802892bd1 100644 --- a/stock_cubiscan/models/cubiscan.py +++ b/stock_cubiscan/models/cubiscan.py @@ -4,58 +4,59 @@ import ssl as SSL from cubiscan.cubiscan import CubiScan + from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError class CubiscanDevice(models.Model): - _name = 'cubiscan.device' - _description = 'Cubiscan Device' - _order = 'warehouse_id, name' + _name = "cubiscan.device" + _description = "Cubiscan Device" + _order = "warehouse_id, name" - name = fields.Char('Name', required=True) - device_address = fields.Char('Device IP Address', required=True) - port = fields.Integer('Port', required=True) + name = fields.Char("Name", required=True) + device_address = fields.Char("Device IP Address", required=True) + port = fields.Integer("Port", required=True) timeout = fields.Integer( - 'Timeout', help="Timeout in seconds", required=True, default=30 + "Timeout", help="Timeout in seconds", required=True, default=30 ) - warehouse_id = fields.Many2one('stock.warehouse', 'Warehouse') + warehouse_id = fields.Many2one("stock.warehouse", "Warehouse") state = fields.Selection( - [('not_ready', 'Not Ready'), ('ready', 'Ready')], - default='not_ready', + [("not_ready", "Not Ready"), ("ready", "Ready")], + default="not_ready", readonly=True, ) @api.multi - @api.constrains('device_address', 'port') + @api.constrains("device_address", "port") def _check_connection_infos(self): self.ensure_one() if not 1 <= self.port <= 65535: - raise ValidationError('Port must be in range 1-65535') + raise ValidationError("Port must be in range 1-65535") @api.multi def copy(self, default=None): if not default: default = dict() - default['state'] = 'not_ready' + default["state"] = "not_ready" return super().copy(default) @api.multi def open_wizard(self): self.ensure_one() return { - 'name': _('CubiScan Wizard'), - 'res_model': 'cubiscan.wizard', - 'type': 'ir.actions.act_window', - 'view_id': False, - 'view_mode': 'form', - 'view_type': 'form', - 'context': {'default_device_id': self.id}, - 'target': 'fullscreen', - 'flags': { - 'headless': True, - 'form_view_initial_mode': 'edit', - 'no_breadcrumbs': True, + "name": _("CubiScan Wizard"), + "res_model": "cubiscan.wizard", + "type": "ir.actions.act_window", + "view_id": False, + "view_mode": "form", + "view_type": "form", + "context": {"default_device_id": self.id}, + "target": "fullscreen", + "flags": { + "headless": True, + "form_view_initial_mode": "edit", + "no_breadcrumbs": True, }, } @@ -63,7 +64,7 @@ class CubiscanDevice(models.Model): def _get_interface(self): self.ensure_one() ctx = SSL.create_default_context() - ctx.load_cert_chain('/usr/lib/ssl/certs/camptocamp.pem') + ctx.load_cert_chain("/usr/lib/ssl/certs/camptocamp.pem") ctx.check_hostname = False ctx.verify_mode = SSL.CERT_NONE return CubiScan(self.device_address, self.port, self.timeout, ssl=ctx) @@ -72,16 +73,17 @@ class CubiscanDevice(models.Model): def test_device(self): for device in self: res = device._get_interface().test() - if res and 'error' not in res and device.state == 'not_ready': - device.state = 'ready' - elif res and 'error' in res and device.state == 'ready': - device.state = 'not_ready' + if res and "error" not in res and device.state == "not_ready": + device.state = "ready" + elif res and "error" in res and device.state == "ready": + device.state = "not_ready" @api.multi def get_measure(self): self.ensure_one() - if self.state != 'ready': + if self.state != "ready": raise UserError( - "Device is not ready. Please use the 'Test' button before using the device." + "Device is not ready. Please use the 'Test'" + " button before using the device." ) return self._get_interface().measure() diff --git a/stock_cubiscan/models/stock.py b/stock_cubiscan/models/stock.py index 973ad6c54..e28248e4c 100644 --- a/stock_cubiscan/models/stock.py +++ b/stock_cubiscan/models/stock.py @@ -5,10 +5,10 @@ from odoo import fields, models class StockWarehouse(models.Model): - _inherit = 'stock.warehouse' + _inherit = "stock.warehouse" cubiscan_device_ids = fields.One2many( - 'cubiscan.device', 'warehouse_id', string="Cubiscan Devices" + "cubiscan.device", "warehouse_id", string="Cubiscan Devices" ) @@ -17,9 +17,9 @@ class ProductPackaging(models.Model): # FIXME: Not sure this is still the best place for this constraint _sql_constraints = [ ( - 'product_packaging_type_unique', - 'unique (product_id, packaging_type_id)', - 'It is forbidden to have different packagings ' - 'with the same type for a given product.', + "product_packaging_type_unique", + "unique (product_id, packaging_type_id)", + "It is forbidden to have different packagings " + "with the same type for a given product.", ) ] diff --git a/stock_cubiscan/tests/test_cubiscan.py b/stock_cubiscan/tests/test_cubiscan.py index 42d21de5d..feafda1b5 100644 --- a/stock_cubiscan/tests/test_cubiscan.py +++ b/stock_cubiscan/tests/test_cubiscan.py @@ -2,6 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from cubiscan.cubiscan import CubiScan from mock import patch + from odoo.exceptions import ValidationError from odoo.tests.common import SavepointCase @@ -10,27 +11,23 @@ class TestCubiscan(SavepointCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.device_obj = cls.env['cubiscan.device'] + cls.device_obj = cls.env["cubiscan.device"] def test_constraints(self): - vals = {'name': 'Test Device'} + vals = {"name": "Test Device"} # Wrong port - vals.update({'device_address': '10.10.0.42', 'port': -42}) + vals.update({"device_address": "10.10.0.42", "port": -42}) with self.assertRaises(ValidationError): self.device_obj.create(vals) def test_device_test(self): - vals = { - 'name': 'Test Device', - 'device_address': '10.10.0.42', - 'port': 5982, - } + vals = {"name": "Test Device", "device_address": "10.10.0.42", "port": 5982} device = self.device_obj.create(vals) - self.assertEquals(device.state, 'not_ready') + self.assertEquals(device.state, "not_ready") - with patch.object(CubiScan, '_make_request') as mocked: - mocked.return_value = {'identifier': 42} + with patch.object(CubiScan, "_make_request") as mocked: + mocked.return_value = {"identifier": 42} device.test_device() - self.assertEquals(device.state, 'ready') + self.assertEquals(device.state, "ready") diff --git a/stock_cubiscan/tests/test_cubiscan_wizard.py b/stock_cubiscan/tests/test_cubiscan_wizard.py index 6fafc3fc0..5bf5c24d4 100644 --- a/stock_cubiscan/tests/test_cubiscan_wizard.py +++ b/stock_cubiscan/tests/test_cubiscan_wizard.py @@ -9,17 +9,17 @@ class TestCubiscanWizard(SavepointCase): @staticmethod def get_measure_result(length, width, height, weight): return { - 'origin': '1', - 'location': 'dev001', - 'length': (length, None), - 'width': (width, None), - 'height': (height, None), - 'space_metric': True, - 'weight': (weight, None), - 'dim_weight': (weight, None), - 'weight_metric': True, - 'factor': 1, - 'intl_unit': True, + "origin": "1", + "location": "dev001", + "length": (length, None), + "width": (width, None), + "height": (height, None), + "space_metric": True, + "weight": (weight, None), + "dim_weight": (weight, None), + "weight_metric": True, + "factor": 1, + "intl_unit": True, } @classmethod @@ -27,42 +27,42 @@ class TestCubiscanWizard(SavepointCase): super().setUpClass() cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) - cls.device_obj = cls.env['cubiscan.device'] - cls.cs_wizard = cls.env['cubiscan.wizard'] - PackType = cls.env['product.packaging.type'] + cls.device_obj = cls.env["cubiscan.device"] + cls.cs_wizard = cls.env["cubiscan.wizard"] + PackType = cls.env["product.packaging.type"] pack_type_data = [ - ('unit', 1, 0, 0), - ('internal', 2, 1, 0), - ('retail', 10, 1, 1), - ('transport', 20, 1, 1), - ('pallet', 30, 1, 1), + ("unit", 1, 0, 0), + ("internal", 2, 1, 0), + ("retail", 10, 1, 1), + ("transport", 20, 1, 1), + ("pallet", 30, 1, 1), ] for name, seq, gtin, req in pack_type_data: PackType.create( { - 'name': name, - 'code': name.upper(), - 'sequence': seq, - 'has_gtin': gtin, - 'required': req, + "name": name, + "code": name.upper(), + "sequence": seq, + "has_gtin": gtin, + "required": req, } ) cls.device = cls.device_obj.create( { - 'name': 'Test Device', - 'device_address': '192.168.21.42', - 'port': 4242, - 'state': 'ready', + "name": "Test Device", + "device_address": "192.168.21.42", + "port": 4242, + "state": "ready", } ) - cls.wizard = cls.cs_wizard.create({'device_id': cls.device.id}) + cls.wizard = cls.cs_wizard.create({"device_id": cls.device.id}) - cls.product_1 = cls.env.ref('product.product_product_6') - cls.product_2 = cls.env.ref('product.product_product_7') + cls.product_1 = cls.env.ref("product.product_product_6") + cls.product_2 = cls.env.ref("product.product_product_7") - cls.product_1.barcode = '424242' + cls.product_1.barcode = "424242" PackType.cron_check_create_required_packaging() def test_product_onchange(self): diff --git a/stock_cubiscan/wizard/cubiscan_wizard.py b/stock_cubiscan/wizard/cubiscan_wizard.py index 9b6648947..2f3115aa1 100644 --- a/stock_cubiscan/wizard/cubiscan_wizard.py +++ b/stock_cubiscan/wizard/cubiscan_wizard.py @@ -5,57 +5,55 @@ from odoo import _, api, fields, models class CubiscanWizard(models.TransientModel): - _name = 'cubiscan.wizard' - _inherit = 'barcodes.barcode_events_mixin' - _description = 'Cubiscan Wizard' - _rec_name = 'device_id' + _name = "cubiscan.wizard" + _inherit = "barcodes.barcode_events_mixin" + _description = "Cubiscan Wizard" + _rec_name = "device_id" - device_id = fields.Many2one('cubiscan.device', readonly=True) - product_id = fields.Many2one( - 'product.product', domain=[('type', '=', 'product')] - ) - line_ids = fields.One2many('cubiscan.wizard.line', 'wizard_id') + device_id = fields.Many2one("cubiscan.device", readonly=True) + product_id = fields.Many2one("product.product", domain=[("type", "=", "product")]) + line_ids = fields.One2many("cubiscan.wizard.line", "wizard_id") - @api.onchange('product_id') + @api.onchange("product_id") def onchange_product_id(self): if self.product_id: to_create = [] - packaging_types = self.env['product.packaging.type'].search([]) + packaging_types = self.env["product.packaging.type"].search([]) for seq, pack_type in enumerate(packaging_types): - pack = self.env['product.packaging'].search( + pack = self.env["product.packaging"].search( [ - ('product_id', '=', self.product_id.id), - ('packaging_type_id', '=', pack_type.id), + ("product_id", "=", self.product_id.id), + ("packaging_type_id", "=", pack_type.id), ], limit=1, ) vals = { - 'wizard_id': self.id, - 'sequence': seq + 1, - 'name': pack_type.name, - 'qty': 0, - 'max_weight': 0, - 'length': 0, - 'width': 0, - 'height': 0, - 'barcode': False, - 'packaging_type_id': pack_type.id, + "wizard_id": self.id, + "sequence": seq + 1, + "name": pack_type.name, + "qty": 0, + "max_weight": 0, + "length": 0, + "width": 0, + "height": 0, + "barcode": False, + "packaging_type_id": pack_type.id, } if pack: vals.update( { - 'qty': pack.qty, - 'max_weight': pack.max_weight, - 'length': pack.length, - 'width': pack.width, - 'height': pack.height, - 'barcode': pack.barcode, - 'packaging_id': pack.id, - 'packaging_type_id': pack_type.id, + "qty": pack.qty, + "max_weight": pack.max_weight, + "length": pack.length, + "width": pack.width, + "height": pack.height, + "barcode": pack.barcode, + "packaging_id": pack.id, + "packaging_type_id": pack_type.id, } ) to_create.append(vals) - recs = self.env['cubiscan.wizard.line'].create(to_create) + recs = self.env["cubiscan.wizard.line"].create(to_create) self.line_ids = recs else: self.line_ids = [(5, 0, 0)] @@ -65,7 +63,7 @@ class CubiscanWizard(models.TransientModel): # Action to reopen wizard in fullscreen (e.g. after page refresh) self.ensure_one() res = self.device_id.open_wizard() - res['res_id'] = self.id + res["res_id"] = self.id return res def action_search_barcode(self): @@ -80,7 +78,7 @@ class CubiscanWizard(models.TransientModel): @api.multi def on_barcode_scanned(self, barcode): self.ensure_one() - prod = self.env['product.product'].search([('barcode', '=', barcode)]) + prod = self.env["product.product"].search([("barcode", "=", barcode)]) self.product_id = prod self.onchange_product_id() @@ -90,14 +88,14 @@ class CubiscanWizard(models.TransientModel): actions = [] for line in self.line_ids: vals = { - 'name': line.name, - 'qty': line.qty, - 'max_weight': line.max_weight, - 'length': line.length, - 'width': line.width, - 'height': line.height, - 'barcode': line.barcode, - 'packaging_type_id': line.packaging_type_id.id, + "name": line.name, + "qty": line.qty, + "max_weight": line.max_weight, + "length": line.length, + "width": line.width, + "height": line.height, + "barcode": line.barcode, + "packaging_type_id": line.packaging_type_id.id, } pack = line.packaging_id if pack: @@ -109,33 +107,29 @@ class CubiscanWizard(models.TransientModel): @api.multi def action_close(self): self.ensure_one() - action = self.env.ref( - 'stock_cubiscan.action_cubiscan_device_form' - ).read()[0] + action = self.env.ref("stock_cubiscan.action_cubiscan_device_form").read()[0] action.update( { - 'res_id': self.device_id.id, - 'target': 'main', - 'views': [ + "res_id": self.device_id.id, + "target": "main", + "views": [ ( - self.env.ref( - 'stock_cubiscan.view_cubiscan_device_form' - ).id, - 'form', + self.env.ref("stock_cubiscan.view_cubiscan_device_form").id, + "form", ) ], - 'flags': {'headless': False, 'clear_breadcrumbs': True}, + "flags": {"headless": False, "clear_breadcrumbs": True}, } ) return action class CubiscanWizardLine(models.TransientModel): - _name = 'cubiscan.wizard.line' - _description = 'Cubiscan Wizard Line' - _order = 'sequence' + _name = "cubiscan.wizard.line" + _description = "Cubiscan Wizard Line" + _order = "sequence" - wizard_id = fields.Many2one('cubiscan.wizard') + wizard_id = fields.Many2one("cubiscan.wizard") sequence = fields.Integer() name = fields.Char("Packaging", readonly=True) qty = fields.Float("Quantity") @@ -146,37 +140,31 @@ class CubiscanWizardLine(models.TransientModel): volume = fields.Float( "Volume (m³)", digits=(8, 4), - compute='_compute_volume', + compute="_compute_volume", readonly=True, store=False, ) barcode = fields.Char("GTIN") - packaging_id = fields.Many2one('product.packaging', readonly=True) + packaging_id = fields.Many2one("product.packaging", readonly=True) packaging_type_id = fields.Many2one( - 'product.packaging.type', readonly=True, required=True - ) - required = fields.Boolean( - related='packaging_type_id.required', readonly=True + "product.packaging.type", readonly=True, required=True ) + required = fields.Boolean(related="packaging_type_id.required", readonly=True) - @api.depends('length', 'width', 'height') + @api.depends("length", "width", "height") def _compute_volume(self): for line in self: - line.volume = ( - line.length * line.width * line.height - ) / 1000.0 ** 3 + line.volume = (line.length * line.width * line.height) / 1000.0 ** 3 @api.multi def cubiscan_measure(self): self.ensure_one() measures = self.wizard_id.device_id.get_measure() measures = { - k: ( - v[0] if k in ['length', 'width', 'height', 'weight'] else False - ) + k: (v[0] if k in ["length", "width", "height", "weight"] else False) for k, v in measures.items() } - weight = measures.pop('weight') + weight = measures.pop("weight") measures = {k: int(v * 1000) for k, v in measures.items()} - measures['max_weight'] = weight + measures["max_weight"] = weight self.write(measures)