From fe4e921e4d0671a8f76ff9ef2a8980470863da3c Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Thu, 9 Jan 2020 14:34:27 +0100 Subject: [PATCH] Migrate stock_cubiscan to 13.0 --- stock_cubiscan/__manifest__.py | 9 +- stock_cubiscan/models/cubiscan.py | 30 +++--- stock_cubiscan/models/stock.py | 3 +- stock_cubiscan/tests/test_cubiscan_wizard.py | 98 +++++++++----------- stock_cubiscan/views/cubiscan_view.xml | 1 - stock_cubiscan/wizard/cubiscan_wizard.py | 37 ++++---- stock_cubiscan/wizard/cubiscan_wizard.xml | 7 +- 7 files changed, 92 insertions(+), 93 deletions(-) diff --git a/stock_cubiscan/__manifest__.py b/stock_cubiscan/__manifest__.py index e3eb53685..6afdff588 100644 --- a/stock_cubiscan/__manifest__.py +++ b/stock_cubiscan/__manifest__.py @@ -3,9 +3,9 @@ { "name": "Stock Cubiscan", "summary": "Implement inteface with Cubiscan devices for packaging", - "version": "12.0.1.0.0", - "category": "Stock", - "author": "Camptocamp", + "version": "13.0.1.0.0", + "category": "Warehouse", + "author": "Camptocamp, Odoo Community Association (OCA)", "license": "AGPL-3", "depends": [ "barcodes", @@ -14,7 +14,8 @@ "product_packaging_dimension", "product_packaging_type_required", ], - "website": "http://www.camptocamp.com", + "external_dependencies": {"python": ["cubiscan"]}, + "website": "https://github.com/OCA/stock-logistics-warehouse", "data": [ "views/assets.xml", "views/cubiscan_view.xml", diff --git a/stock_cubiscan/models/cubiscan.py b/stock_cubiscan/models/cubiscan.py index 802892bd1..8e0b48e69 100644 --- a/stock_cubiscan/models/cubiscan.py +++ b/stock_cubiscan/models/cubiscan.py @@ -25,23 +25,15 @@ class CubiscanDevice(models.Model): [("not_ready", "Not Ready"), ("ready", "Ready")], default="not_ready", readonly=True, + copy=False, ) - @api.multi @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" - return super().copy(default) - - @api.multi def open_wizard(self): self.ensure_one() return { @@ -50,18 +42,20 @@ class CubiscanDevice(models.Model): "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, + "withControlPanel": False, "form_view_initial_mode": "edit", "no_breadcrumbs": True, }, } - @api.multi def _get_interface(self): + """Return the CubiScan client + + Can be overrided to customize the way it is instanciated + """ self.ensure_one() ctx = SSL.create_default_context() ctx.load_cert_chain("/usr/lib/ssl/certs/camptocamp.pem") @@ -69,8 +63,8 @@ class CubiscanDevice(models.Model): ctx.verify_mode = SSL.CERT_NONE return CubiScan(self.device_address, self.port, self.timeout, ssl=ctx) - @api.multi def test_device(self): + """Check connection with the Cubiscan device""" for device in self: res = device._get_interface().test() if res and "error" not in res and device.state == "not_ready": @@ -78,12 +72,14 @@ class CubiscanDevice(models.Model): elif res and "error" in res and device.state == "ready": device.state = "not_ready" - @api.multi def get_measure(self): + """Return a measure from the Cubiscan device""" self.ensure_one() 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 e28248e4c..e0ce1e610 100644 --- a/stock_cubiscan/models/stock.py +++ b/stock_cubiscan/models/stock.py @@ -14,7 +14,8 @@ class StockWarehouse(models.Model): class ProductPackaging(models.Model): _inherit = "product.packaging" - # FIXME: Not sure this is still the best place for this constraint + # FIXME: move this constraint in product_packaging_type + # https://github.com/OCA/product-attribute/tree/13.0/product_packaging_type _sql_constraints = [ ( "product_packaging_type_unique", diff --git a/stock_cubiscan/tests/test_cubiscan_wizard.py b/stock_cubiscan/tests/test_cubiscan_wizard.py index 5bf5c24d4..ad4e3d941 100644 --- a/stock_cubiscan/tests/test_cubiscan_wizard.py +++ b/stock_cubiscan/tests/test_cubiscan_wizard.py @@ -1,7 +1,8 @@ # Copyright 2019 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -# from cubiscan.cubiscan import CubiScan -# from mock import patch +from cubiscan.cubiscan import CubiScan +from mock import patch + from odoo.tests.common import SavepointCase @@ -66,63 +67,56 @@ class TestCubiscanWizard(SavepointCase): PackType.cron_check_create_required_packaging() def test_product_onchange(self): - return - # self.wizard.product_id = self.product_1.id + self.wizard.product_id = self.product_1.id - # self.assertEqual(len(self.wizard.line_ids), 0) - # self.wizard.onchange_product_id() - # self.assertEqual(len(self.wizard.line_ids), 5) + self.assertEqual(len(self.wizard.line_ids), 0) + self.wizard.onchange_product_id() + self.assertEqual(len(self.wizard.line_ids), 6) def test_product_onchange_barcode(self): - return - # self.assertFalse(self.wizard.product_id) - # self.assertFalse(self.wizard.line_ids) + self.assertFalse(self.wizard.product_id) + self.assertFalse(self.wizard.line_ids) - # self.wizard.on_barcode_scanned('424242') + self.wizard.on_barcode_scanned("424242") - # self.assertEqual(self.wizard.product_id, self.product_1) - # self.assertEqual(len(self.wizard.line_ids), 5) + self.assertEqual(self.wizard.product_id, self.product_1) + self.assertEqual(len(self.wizard.line_ids), 6) def test_cubiscan_measures(self): - return - # self.wizard.product_id = self.product_1.id - # self.wizard.onchange_product_id() + self.wizard.product_id = self.product_1.id + self.wizard.onchange_product_id() - # with patch.object(CubiScan, '_make_request') as request: - # for idx, line in enumerate(self.wizard.line_ids): - # request.return_value = TestCubiscanWizard.get_measure_result( - # 2 ** idx, 1, 1, 2 ** idx - # ) - # line.cubiscan_measure() - # self.assertEqual( - # line.read( - # ['length', 'width', 'height', 'max_weight', 'volume'] - # )[0], - # { - # 'id': line.id, - # 'length': (2 ** idx) * 1000, - # 'width': 1000, - # 'height': 1000, - # 'max_weight': 2.0 ** idx, - # 'volume': 2.0 ** idx, - # }, - # ) + with patch.object(CubiScan, "_make_request") as request: + for idx, line in enumerate(self.wizard.line_ids): + request.return_value = TestCubiscanWizard.get_measure_result( + 2 ** idx, 1, 1, 2 ** idx + ) + line.cubiscan_measure() + self.assertEqual( + line.read(["lngth", "width", "height", "max_weight", "volume"])[0], + { + "id": line.id, + "lngth": (2 ** idx) * 1000, + "width": 1000, + "height": 1000, + "max_weight": 2.0 ** idx, + "volume": 2.0 ** idx, + }, + ) - # self.wizard.action_save() + self.wizard.action_save() - # packagings = self.product_1.packaging_ids - # self.assertEqual(len(packagings), 5) - # for idx, packaging in enumerate(packagings): - # self.assertEqual( - # packaging.read( - # ['length', 'width', 'height', 'max_weight', 'volume'] - # )[0], - # { - # 'id': packaging.id, - # 'length': (2 ** idx) * 1000, - # 'width': 1000, - # 'height': 1000, - # 'max_weight': 2.0 ** idx, - # 'volume': 2.0 ** idx, - # }, - # ) + packagings = self.product_1.packaging_ids.sorted() + self.assertEqual(len(packagings), 6) + for idx, packaging in enumerate(packagings): + self.assertEqual( + packaging.read(["lngth", "width", "height", "max_weight", "volume"])[0], + { + "id": packaging.id, + "lngth": (2 ** idx) * 1000, + "width": 1000, + "height": 1000, + "max_weight": 2.0 ** idx, + "volume": 2.0 ** idx, + }, + ) diff --git a/stock_cubiscan/views/cubiscan_view.xml b/stock_cubiscan/views/cubiscan_view.xml index 93e9131b2..20f7b9b71 100644 --- a/stock_cubiscan/views/cubiscan_view.xml +++ b/stock_cubiscan/views/cubiscan_view.xml @@ -46,7 +46,6 @@ CubiScan Devices cubiscan.device - form tree,form diff --git a/stock_cubiscan/wizard/cubiscan_wizard.py b/stock_cubiscan/wizard/cubiscan_wizard.py index 2f3115aa1..b007cdbdd 100644 --- a/stock_cubiscan/wizard/cubiscan_wizard.py +++ b/stock_cubiscan/wizard/cubiscan_wizard.py @@ -5,6 +5,11 @@ from odoo import _, api, fields, models class CubiscanWizard(models.TransientModel): + """This wizard is used to show a screen showing Cubiscan information + + It is opened in a headless view (no breadcrumb, no menus, fullscreen). + """ + _name = "cubiscan.wizard" _inherit = "barcodes.barcode_events_mixin" _description = "Cubiscan Wizard" @@ -33,7 +38,7 @@ class CubiscanWizard(models.TransientModel): "name": pack_type.name, "qty": 0, "max_weight": 0, - "length": 0, + "lngth": 0, "width": 0, "height": 0, "barcode": False, @@ -44,7 +49,7 @@ class CubiscanWizard(models.TransientModel): { "qty": pack.qty, "max_weight": pack.max_weight, - "length": pack.length, + "lngth": pack.lngth, "width": pack.width, "height": pack.height, "barcode": pack.barcode, @@ -58,7 +63,6 @@ class CubiscanWizard(models.TransientModel): else: self.line_ids = [(5, 0, 0)] - @api.multi def action_reopen_fullscreen(self): # Action to reopen wizard in fullscreen (e.g. after page refresh) self.ensure_one() @@ -75,14 +79,12 @@ class CubiscanWizard(models.TransientModel): "target": "new", } - @api.multi def on_barcode_scanned(self, barcode): self.ensure_one() prod = self.env["product.product"].search([("barcode", "=", barcode)]) self.product_id = prod self.onchange_product_id() - @api.multi def action_save(self): self.ensure_one() actions = [] @@ -91,7 +93,7 @@ class CubiscanWizard(models.TransientModel): "name": line.name, "qty": line.qty, "max_weight": line.max_weight, - "length": line.length, + "lngth": line.lngth, "width": line.width, "height": line.height, "barcode": line.barcode, @@ -103,8 +105,9 @@ class CubiscanWizard(models.TransientModel): else: actions.append((0, 0, vals)) self.product_id.packaging_ids = actions + # reload lines + self.onchange_product_id() - @api.multi def action_close(self): self.ensure_one() action = self.env.ref("stock_cubiscan.action_cubiscan_device_form").read()[0] @@ -134,7 +137,9 @@ class CubiscanWizardLine(models.TransientModel): name = fields.Char("Packaging", readonly=True) qty = fields.Float("Quantity") max_weight = fields.Float("Weight (kg)", readonly=True) - length = fields.Integer("Length (mm)", readonly=True) + # this is not a typo: + # https://github.com/odoo/odoo/issues/41353#issuecomment-568037415 + lngth = fields.Integer("Length (mm)", readonly=True) width = fields.Integer("Width (mm)", readonly=True) height = fields.Integer("Height (mm)", readonly=True) volume = fields.Float( @@ -151,20 +156,20 @@ class CubiscanWizardLine(models.TransientModel): ) required = fields.Boolean(related="packaging_type_id.required", readonly=True) - @api.depends("length", "width", "height") + @api.depends("lngth", "width", "height") def _compute_volume(self): for line in self: - line.volume = (line.length * line.width * line.height) / 1000.0 ** 3 + line.volume = (line.lngth * 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 are a tuple of 2 slots (measure, precision error), + # we only care about the measure for now measures = { - k: (v[0] if k in ["length", "width", "height", "weight"] else False) - for k, v in measures.items() + "lngth": int(measures["length"][0] * 1000), + "width": int(measures["width"][0] * 1000), + "height": int(measures["height"][0] * 1000), + "max_weight": measures["weight"][0], } - weight = measures.pop("weight") - measures = {k: int(v * 1000) for k, v in measures.items()} - measures["max_weight"] = weight self.write(measures) diff --git a/stock_cubiscan/wizard/cubiscan_wizard.xml b/stock_cubiscan/wizard/cubiscan_wizard.xml index a6595d408..9f0e09a27 100644 --- a/stock_cubiscan/wizard/cubiscan_wizard.xml +++ b/stock_cubiscan/wizard/cubiscan_wizard.xml @@ -10,7 +10,10 @@ - + @@ -23,7 +26,7 @@ - +