From 0f70ada02d3e8e3b7df49cd539c782c537f2b668 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Mon, 2 Mar 2020 13:33:24 +0100 Subject: [PATCH 1/7] Create module stock_location_children --- stock_location_children/__init__.py | 1 + stock_location_children/__manifest__.py | 15 ++++ stock_location_children/models/__init__.py | 1 + .../models/stock_location.py | 27 +++++++ .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 3 + stock_location_children/tests/__init__.py | 1 + .../tests/test_stock_location_children.py | 77 +++++++++++++++++++ 8 files changed, 126 insertions(+) create mode 100644 stock_location_children/__init__.py create mode 100644 stock_location_children/__manifest__.py create mode 100644 stock_location_children/models/__init__.py create mode 100644 stock_location_children/models/stock_location.py create mode 100644 stock_location_children/readme/CONTRIBUTORS.rst create mode 100644 stock_location_children/readme/DESCRIPTION.rst create mode 100644 stock_location_children/tests/__init__.py create mode 100644 stock_location_children/tests/test_stock_location_children.py diff --git a/stock_location_children/__init__.py b/stock_location_children/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/stock_location_children/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_location_children/__manifest__.py b/stock_location_children/__manifest__.py new file mode 100644 index 000000000..614b259b3 --- /dev/null +++ b/stock_location_children/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +{ + "name": "Stock location children", + "summary": "Add relation between stock location and all its children", + "version": "13.0.1.0.0", + "development_status": "Alpha", + "category": "Warehouse Management", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "author": "Camptocamp, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": ["stock"], +} diff --git a/stock_location_children/models/__init__.py b/stock_location_children/models/__init__.py new file mode 100644 index 000000000..88493e35d --- /dev/null +++ b/stock_location_children/models/__init__.py @@ -0,0 +1 @@ +from . import stock_location diff --git a/stock_location_children/models/stock_location.py b/stock_location_children/models/stock_location.py new file mode 100644 index 000000000..2821105c8 --- /dev/null +++ b/stock_location_children/models/stock_location.py @@ -0,0 +1,27 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import api, fields, models + + +class StockLocation(models.Model): + + _inherit = 'stock.location' + + children_ids = fields.Many2many( + 'stock.location', + 'stock_location_children_ids', + 'parent_id', + 'children_id', + compute='_compute_children_ids', + store=True, + help='All the children (multi-level) stock location of this location', + ) + + @api.depends('child_ids', 'child_ids.children_ids') + def _compute_children_ids(self): + for loc in self: + if not loc.child_ids.mapped('child_ids'): + all_children = loc.child_ids + else: + all_children = loc.child_ids | loc.child_ids.children_ids + loc.children_ids = all_children diff --git a/stock_location_children/readme/CONTRIBUTORS.rst b/stock_location_children/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..e31e2f0c4 --- /dev/null +++ b/stock_location_children/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Akim Juillerat diff --git a/stock_location_children/readme/DESCRIPTION.rst b/stock_location_children/readme/DESCRIPTION.rst new file mode 100644 index 000000000..6dd2b549b --- /dev/null +++ b/stock_location_children/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module adds a `children_ids` field on `stock.location` in order to compute +and store all the children for a `stock.location` and not only its first level +children as is the case for `child_ids`. diff --git a/stock_location_children/tests/__init__.py b/stock_location_children/tests/__init__.py new file mode 100644 index 000000000..0bd71126f --- /dev/null +++ b/stock_location_children/tests/__init__.py @@ -0,0 +1 @@ +from . import test_stock_location_children diff --git a/stock_location_children/tests/test_stock_location_children.py b/stock_location_children/tests/test_stock_location_children.py new file mode 100644 index 000000000..12d8af11c --- /dev/null +++ b/stock_location_children/tests/test_stock_location_children.py @@ -0,0 +1,77 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo.tests import SavepointCase + + +class TestStockLocationChildren(SavepointCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + ref = cls.env.ref + cls.stock_input = ref("stock.stock_location_company") + cls.stock_location = ref("stock.stock_location_stock") + cls.stock_shelf_1 = ref("stock.stock_location_components") + cls.stock_shelf_2 = ref("stock.stock_location_14") + cls.stock_shelf_2_refrigerator = ref( + "stock.location_refrigerator_small" + ) + + def test_location_children(self): + self.assertFalse(self.stock_shelf_2_refrigerator.child_ids) + self.assertEqual( + self.stock_shelf_2.child_ids, + self.stock_shelf_2_refrigerator + ) + self.assertEqual( + self.stock_shelf_2.child_ids, + self.stock_shelf_2.children_ids + ) + self.assertFalse(self.stock_shelf_1.child_ids) + self.assertFalse(self.stock_shelf_1.children_ids) + self.assertEqual( + self.stock_location.child_ids, + self.stock_shelf_1 | self.stock_shelf_2 + ) + self.assertEqual( + self.stock_location.children_ids, + self.stock_shelf_1 | self.stock_shelf_2 | self.stock_shelf_2_refrigerator + ) + + def test_create_write_location(self): + refrigerator_drawer = self.env['stock.location'].create({ + 'name': 'Refrigerator drawer', + 'location_id': self.stock_shelf_2_refrigerator.id + }) + self.assertEqual( + self.stock_shelf_2_refrigerator.child_ids, + refrigerator_drawer + ) + self.assertEqual( + self.stock_shelf_2_refrigerator.children_ids, + refrigerator_drawer + ) + self.assertEqual( + self.stock_shelf_2.children_ids, + self.stock_shelf_2_refrigerator | refrigerator_drawer + ) + self.assertEqual( + self.stock_location.children_ids, + self.stock_shelf_1 | self.stock_shelf_2 | + self.stock_shelf_2_refrigerator | refrigerator_drawer + ) + refrigerator_drawer.location_id = self.stock_input + self.assertFalse(self.stock_shelf_2_refrigerator.child_ids) + self.assertEqual( + self.stock_shelf_2.child_ids, + self.stock_shelf_2_refrigerator + ) + self.assertEqual( + self.stock_shelf_2.child_ids, + self.stock_shelf_2.children_ids + ) + self.assertEqual( + self.stock_location.children_ids, + self.stock_shelf_1 | self.stock_shelf_2 | self.stock_shelf_2_refrigerator + ) From caa63a2aa081e64b79d310fbb24f96f6af5ee72b Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Tue, 3 Mar 2020 14:51:32 +0100 Subject: [PATCH 2/7] Use SQL to improve performance --- .../models/stock_location.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/stock_location_children/models/stock_location.py b/stock_location_children/models/stock_location.py index 2821105c8..2ffd89050 100644 --- a/stock_location_children/models/stock_location.py +++ b/stock_location_children/models/stock_location.py @@ -19,9 +19,26 @@ class StockLocation(models.Model): @api.depends('child_ids', 'child_ids.children_ids') def _compute_children_ids(self): + query = """SELECT sub.id, ARRAY_AGG(sl2.id) AS children + FROM stock_location sl2, + ( + SELECT id, parent_path + FROM stock_location sl + ) sub + WHERE sl2.parent_path LIKE sub.parent_path || '%%' + AND sl2.id != sub.id + AND sub.id IN %s + GROUP BY sub.id; + """ + self.env.cr.execute(query, (tuple(self.ids),)) + rows = self.env.cr.dictfetchall() for loc in self: - if not loc.child_ids.mapped('child_ids'): - all_children = loc.child_ids + all_ids = [] + for row in rows: + if row.get('id') == loc.id: + all_ids = row.get('children') + break + if all_ids: + loc.children_ids = [(6, 0, all_ids)] else: - all_children = loc.child_ids | loc.child_ids.children_ids - loc.children_ids = all_children + loc.children_ids = [(5, 0, 0)] From 37bb4d1eef59e2f37f8ce2305a1e4c05ac155540 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Tue, 3 Mar 2020 14:53:47 +0100 Subject: [PATCH 3/7] Run pre-commit --- .../models/stock_location.py | 20 +++---- .../tests/test_stock_location_children.py | 56 +++++++------------ 2 files changed, 30 insertions(+), 46 deletions(-) diff --git a/stock_location_children/models/stock_location.py b/stock_location_children/models/stock_location.py index 2ffd89050..6f2b9e60b 100644 --- a/stock_location_children/models/stock_location.py +++ b/stock_location_children/models/stock_location.py @@ -5,19 +5,19 @@ from odoo import api, fields, models class StockLocation(models.Model): - _inherit = 'stock.location' + _inherit = "stock.location" children_ids = fields.Many2many( - 'stock.location', - 'stock_location_children_ids', - 'parent_id', - 'children_id', - compute='_compute_children_ids', + "stock.location", + "stock_location_children_ids", + "parent_id", + "children_id", + compute="_compute_children_ids", store=True, - help='All the children (multi-level) stock location of this location', + help="All the children (multi-level) stock location of this location", ) - @api.depends('child_ids', 'child_ids.children_ids') + @api.depends("child_ids", "child_ids.children_ids") def _compute_children_ids(self): query = """SELECT sub.id, ARRAY_AGG(sl2.id) AS children FROM stock_location sl2, @@ -35,8 +35,8 @@ class StockLocation(models.Model): for loc in self: all_ids = [] for row in rows: - if row.get('id') == loc.id: - all_ids = row.get('children') + if row.get("id") == loc.id: + all_ids = row.get("children") break if all_ids: loc.children_ids = [(6, 0, all_ids)] diff --git a/stock_location_children/tests/test_stock_location_children.py b/stock_location_children/tests/test_stock_location_children.py index 12d8af11c..f28bbe0cf 100644 --- a/stock_location_children/tests/test_stock_location_children.py +++ b/stock_location_children/tests/test_stock_location_children.py @@ -4,7 +4,6 @@ from odoo.tests import SavepointCase class TestStockLocationChildren(SavepointCase): - @classmethod def setUpClass(cls): super().setUpClass() @@ -14,64 +13,49 @@ class TestStockLocationChildren(SavepointCase): cls.stock_location = ref("stock.stock_location_stock") cls.stock_shelf_1 = ref("stock.stock_location_components") cls.stock_shelf_2 = ref("stock.stock_location_14") - cls.stock_shelf_2_refrigerator = ref( - "stock.location_refrigerator_small" - ) + cls.stock_shelf_2_refrigerator = ref("stock.location_refrigerator_small") def test_location_children(self): self.assertFalse(self.stock_shelf_2_refrigerator.child_ids) - self.assertEqual( - self.stock_shelf_2.child_ids, - self.stock_shelf_2_refrigerator - ) - self.assertEqual( - self.stock_shelf_2.child_ids, - self.stock_shelf_2.children_ids - ) + self.assertEqual(self.stock_shelf_2.child_ids, self.stock_shelf_2_refrigerator) + self.assertEqual(self.stock_shelf_2.child_ids, self.stock_shelf_2.children_ids) self.assertFalse(self.stock_shelf_1.child_ids) self.assertFalse(self.stock_shelf_1.children_ids) self.assertEqual( - self.stock_location.child_ids, - self.stock_shelf_1 | self.stock_shelf_2 + self.stock_location.child_ids, self.stock_shelf_1 | self.stock_shelf_2 ) self.assertEqual( self.stock_location.children_ids, - self.stock_shelf_1 | self.stock_shelf_2 | self.stock_shelf_2_refrigerator + self.stock_shelf_1 | self.stock_shelf_2 | self.stock_shelf_2_refrigerator, ) def test_create_write_location(self): - refrigerator_drawer = self.env['stock.location'].create({ - 'name': 'Refrigerator drawer', - 'location_id': self.stock_shelf_2_refrigerator.id - }) - self.assertEqual( - self.stock_shelf_2_refrigerator.child_ids, - refrigerator_drawer + refrigerator_drawer = self.env["stock.location"].create( + { + "name": "Refrigerator drawer", + "location_id": self.stock_shelf_2_refrigerator.id, + } ) + self.assertEqual(self.stock_shelf_2_refrigerator.child_ids, refrigerator_drawer) self.assertEqual( - self.stock_shelf_2_refrigerator.children_ids, - refrigerator_drawer + self.stock_shelf_2_refrigerator.children_ids, refrigerator_drawer ) self.assertEqual( self.stock_shelf_2.children_ids, - self.stock_shelf_2_refrigerator | refrigerator_drawer + self.stock_shelf_2_refrigerator | refrigerator_drawer, ) self.assertEqual( self.stock_location.children_ids, - self.stock_shelf_1 | self.stock_shelf_2 | - self.stock_shelf_2_refrigerator | refrigerator_drawer + self.stock_shelf_1 + | self.stock_shelf_2 + | self.stock_shelf_2_refrigerator + | refrigerator_drawer, ) refrigerator_drawer.location_id = self.stock_input self.assertFalse(self.stock_shelf_2_refrigerator.child_ids) - self.assertEqual( - self.stock_shelf_2.child_ids, - self.stock_shelf_2_refrigerator - ) - self.assertEqual( - self.stock_shelf_2.child_ids, - self.stock_shelf_2.children_ids - ) + self.assertEqual(self.stock_shelf_2.child_ids, self.stock_shelf_2_refrigerator) + self.assertEqual(self.stock_shelf_2.child_ids, self.stock_shelf_2.children_ids) self.assertEqual( self.stock_location.children_ids, - self.stock_shelf_1 | self.stock_shelf_2 | self.stock_shelf_2_refrigerator + self.stock_shelf_1 | self.stock_shelf_2 | self.stock_shelf_2_refrigerator, ) From 19adebd80ce087409af5db1f9fdfe2c380fe79d7 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Fri, 15 May 2020 10:30:22 +0200 Subject: [PATCH 4/7] Update stock_location_children/models/stock_location.py --- stock_location_children/models/stock_location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_location_children/models/stock_location.py b/stock_location_children/models/stock_location.py index 6f2b9e60b..f872641f4 100644 --- a/stock_location_children/models/stock_location.py +++ b/stock_location_children/models/stock_location.py @@ -17,7 +17,7 @@ class StockLocation(models.Model): help="All the children (multi-level) stock location of this location", ) - @api.depends("child_ids", "child_ids.children_ids") + @api.depends("child_ids", "child_ids.child_ids") def _compute_children_ids(self): query = """SELECT sub.id, ARRAY_AGG(sl2.id) AS children FROM stock_location sl2, From 6567e9183a60e5297cb4bc2aaeac8132ff63e640 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 15 May 2020 13:07:48 +0200 Subject: [PATCH 5/7] stock_location_children: generate setup folder --- .../odoo/addons/stock_location_children | 1 + setup/stock_location_children/setup.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 120000 setup/stock_location_children/odoo/addons/stock_location_children create mode 100644 setup/stock_location_children/setup.py diff --git a/setup/stock_location_children/odoo/addons/stock_location_children b/setup/stock_location_children/odoo/addons/stock_location_children new file mode 120000 index 000000000..094bfef70 --- /dev/null +++ b/setup/stock_location_children/odoo/addons/stock_location_children @@ -0,0 +1 @@ +../../../../stock_location_children \ No newline at end of file diff --git a/setup/stock_location_children/setup.py b/setup/stock_location_children/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/stock_location_children/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From 935af5f18ed066c6051884e3c4ea56a776e214a4 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 18 Jan 2021 10:40:31 +0100 Subject: [PATCH 6/7] Ensure children_ids is computed with proper triggers * Any child_ids changed on a children_ids should recompute locations: otherwise only 2 levels are updated. * Flush any change in the hierarchy before executing the SQL --- stock_location_children/models/stock_location.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stock_location_children/models/stock_location.py b/stock_location_children/models/stock_location.py index f872641f4..efe3d14b1 100644 --- a/stock_location_children/models/stock_location.py +++ b/stock_location_children/models/stock_location.py @@ -17,7 +17,7 @@ class StockLocation(models.Model): help="All the children (multi-level) stock location of this location", ) - @api.depends("child_ids", "child_ids.child_ids") + @api.depends("child_ids", "children_ids.child_ids") def _compute_children_ids(self): query = """SELECT sub.id, ARRAY_AGG(sl2.id) AS children FROM stock_location sl2, @@ -30,6 +30,7 @@ class StockLocation(models.Model): AND sub.id IN %s GROUP BY sub.id; """ + self.flush(["location_id", "child_ids"]) self.env.cr.execute(query, (tuple(self.ids),)) rows = self.env.cr.dictfetchall() for loc in self: From f59687a88c45271c5ed551db618b74e8db005e06 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Mon, 18 Jan 2021 12:19:56 +0100 Subject: [PATCH 7/7] Fix test compatibility with other modules When other modules add locations in "stock.stock_location_stock", the assertions miss them. Create a new tree of locations to run the tests. --- .../tests/test_stock_location_children.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/stock_location_children/tests/test_stock_location_children.py b/stock_location_children/tests/test_stock_location_children.py index f28bbe0cf..773f64b8b 100644 --- a/stock_location_children/tests/test_stock_location_children.py +++ b/stock_location_children/tests/test_stock_location_children.py @@ -11,9 +11,18 @@ class TestStockLocationChildren(SavepointCase): ref = cls.env.ref cls.stock_input = ref("stock.stock_location_company") cls.stock_location = ref("stock.stock_location_stock") - cls.stock_shelf_1 = ref("stock.stock_location_components") - cls.stock_shelf_2 = ref("stock.stock_location_14") - cls.stock_shelf_2_refrigerator = ref("stock.location_refrigerator_small") + cls.test_location = cls.env["stock.location"].create( + {"name": "Test Location", "location_id": cls.stock_location.id} + ) + cls.stock_shelf_1 = cls.env["stock.location"].create( + {"name": "Test Shelf 1", "location_id": cls.test_location.id} + ) + cls.stock_shelf_2 = cls.env["stock.location"].create( + {"name": "Test Shelf 2", "location_id": cls.test_location.id} + ) + cls.stock_shelf_2_refrigerator = cls.env["stock.location"].create( + {"name": "Test Shelf Refrigerator", "location_id": cls.stock_shelf_2.id} + ) def test_location_children(self): self.assertFalse(self.stock_shelf_2_refrigerator.child_ids) @@ -22,10 +31,10 @@ class TestStockLocationChildren(SavepointCase): self.assertFalse(self.stock_shelf_1.child_ids) self.assertFalse(self.stock_shelf_1.children_ids) self.assertEqual( - self.stock_location.child_ids, self.stock_shelf_1 | self.stock_shelf_2 + self.test_location.child_ids, self.stock_shelf_1 | self.stock_shelf_2 ) self.assertEqual( - self.stock_location.children_ids, + self.test_location.children_ids, self.stock_shelf_1 | self.stock_shelf_2 | self.stock_shelf_2_refrigerator, ) @@ -45,7 +54,7 @@ class TestStockLocationChildren(SavepointCase): self.stock_shelf_2_refrigerator | refrigerator_drawer, ) self.assertEqual( - self.stock_location.children_ids, + self.test_location.children_ids, self.stock_shelf_1 | self.stock_shelf_2 | self.stock_shelf_2_refrigerator @@ -56,6 +65,6 @@ class TestStockLocationChildren(SavepointCase): self.assertEqual(self.stock_shelf_2.child_ids, self.stock_shelf_2_refrigerator) self.assertEqual(self.stock_shelf_2.child_ids, self.stock_shelf_2.children_ids) self.assertEqual( - self.stock_location.children_ids, + self.test_location.children_ids, self.stock_shelf_1 | self.stock_shelf_2 | self.stock_shelf_2_refrigerator, )