From f3f68afb157b18a0704e8b6caeacb1f4314f6bc2 Mon Sep 17 00:00:00 2001 From: mav-adhoc Date: Fri, 3 Jul 2026 16:21:09 +0000 Subject: [PATCH] [ADD] stock_ux: auto-activate multi-warehouse group for branch warehouses Native stock._check_multiwarehouse_group counts active warehouses grouped by company_id, so it only activates the "Manage Several Warehouses" group when a single company has more than one warehouse. When a company uses branches (child companies), each branch is a separate company_id with its own warehouse, so the count stays at 1 per company and the group is never activated. Override the method in stock_ux to aggregate the warehouse count by company_id.root_id (the top of the company tree), so a parent company and its branches are counted together. For a company without branches root_id is the company itself, so behaviour is identical to the native one. Includes tests covering the tree aggregation and the effective group activation. --- stock_ux/README.rst | 1 + stock_ux/models/__init__.py | 1 + stock_ux/models/stock_warehouse.py | 63 +++++++++++++++++++++ stock_ux/tests/__init__.py | 5 ++ stock_ux/tests/test_multiwarehouse_group.py | 58 +++++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 stock_ux/models/stock_warehouse.py create mode 100644 stock_ux/tests/__init__.py create mode 100644 stock_ux/tests/test_multiwarehouse_group.py diff --git a/stock_ux/README.rst b/stock_ux/README.rst index c3f8a0bab..e27314c07 100644 --- a/stock_ux/README.rst +++ b/stock_ux/README.rst @@ -40,6 +40,7 @@ Stock UX #. Add a "All transfers" view form in Menu: Operations #. Add a restriction to edit operation type for users with the "Restrict editing Operation Type in Pickings" check. #. Add number of packages on pickings +#. Auto-activate the "Manage Several Warehouses" permission when a company has branches (child companies) with warehouses: warehouses are counted across the whole company tree (parent + branches) instead of per single company, so a setup with one warehouse per branch still enables the multi-warehouse UI. Installation ============ diff --git a/stock_ux/models/__init__.py b/stock_ux/models/__init__.py index fce31c39b..219789b09 100644 --- a/stock_ux/models/__init__.py +++ b/stock_ux/models/__init__.py @@ -16,3 +16,4 @@ from . import stock_location from . import stock_forecasted from . import stock_quant +from . import stock_warehouse diff --git a/stock_ux/models/stock_warehouse.py b/stock_ux/models/stock_warehouse.py new file mode 100644 index 000000000..0d072578f --- /dev/null +++ b/stock_ux/models/stock_warehouse.py @@ -0,0 +1,63 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from collections import defaultdict + +from odoo import models + + +class StockWarehouse(models.Model): + _inherit = "stock.warehouse" + + def _multiwarehouse_max_count(self): + """Máximo de almacenes activos dentro de un mismo árbol de compañías + (compañía raíz + sus sucursales). + + El método nativo ``_check_multiwarehouse_group`` cuenta los almacenes + agrupando por ``company_id``, así que solo activa "Gestionar varios + almacenes" cuando UNA compañía tiene más de un almacén. Como cada + sucursal es una compañía hija con su propio ``company_id``, un almacén + por sucursal deja el conteo en 1 por compañía y el grupo nunca se + activa. Acá agregamos el conteo por ``company_id.root_id`` para que las + sucursales cuenten junto con su compañía madre. + + Para una compañía sin sucursales ``root_id`` es la propia compañía, con + lo cual el comportamiento es idéntico al nativo. + """ + cnt_by_company = ( + self.env["stock.warehouse"] + .sudo() + ._read_group([("active", "=", True)], ["company_id"], aggregates=["__count"]) + ) + count_by_root = defaultdict(int) + for company, count in cnt_by_company: + count_by_root[company.root_id] += count + return max(count_by_root.values(), default=0) + + def _check_multiwarehouse_group(self): + """Réplica del método nativo pero contando los almacenes por árbol de + compañías (ver ``_multiwarehouse_max_count``) en lugar de por + ``company_id``. Mantener sincronizado con ``stock`` en cada upgrade de + versión. + """ + max_count = self._multiwarehouse_max_count() + if not max_count: + return + group_user = self.env.ref("base.group_user") + group_stock_multi_warehouses = self.env.ref("stock.group_stock_multi_warehouses") + group_stock_multi_locations = self.env.ref("stock.group_stock_multi_locations") + if max_count <= 1 and group_stock_multi_warehouses in group_user.implied_ids: + group_user.write({"implied_ids": [(3, group_stock_multi_warehouses.id)]}) + group_stock_multi_warehouses.write({"user_ids": [(3, user.id) for user in group_user.all_user_ids]}) + if max_count > 1 and group_stock_multi_warehouses not in group_user.implied_ids: + if group_stock_multi_locations not in group_user.implied_ids: + self.env["res.config.settings"].create({"group_stock_multi_locations": True}).execute() + group_user.write( + { + "implied_ids": [ + (4, group_stock_multi_warehouses.id), + (4, group_stock_multi_locations.id), + ] + } + ) diff --git a/stock_ux/tests/__init__.py b/stock_ux/tests/__init__.py new file mode 100644 index 000000000..2bcaae530 --- /dev/null +++ b/stock_ux/tests/__init__.py @@ -0,0 +1,5 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from . import test_multiwarehouse_group diff --git a/stock_ux/tests/test_multiwarehouse_group.py b/stock_ux/tests/test_multiwarehouse_group.py new file mode 100644 index 000000000..822def6b4 --- /dev/null +++ b/stock_ux/tests/test_multiwarehouse_group.py @@ -0,0 +1,58 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from odoo.tests.common import TransactionCase + + +class TestMultiwarehouseGroupBranch(TransactionCase): + """El grupo "Gestionar varios almacenes" debe activarse cuando una compañía + tiene sucursales (compañías hijas) con almacenes, aunque cada sucursal tenga + un solo almacén. En modo test, crear una ``res.company`` crea un almacén + para ella automáticamente (ver ``stock/models/res_company.py``), así que + crear madre + sucursal genera dos almacenes bajo la misma raíz. + """ + + def _create_company(self, name, parent=None): + return self.env["res.company"].create({"name": name, "parent_id": parent.id if parent else False}) + + def test_branch_warehouses_share_root_and_aggregate(self): + """Cada compañía tiene 1 almacén (conteo nativo por company_id = 1), + pero madre y sucursal comparten ``root_id``, así que el conteo por árbol + da 2.""" + Warehouse = self.env["stock.warehouse"] + parent = self._create_company("UX Madre") + branch = self._create_company("UX Sucursal", parent=parent) + + # La sucursal es una compañía hija cuya raíz es la madre. + self.assertEqual(parent.root_id, parent) + self.assertEqual(branch.root_id, parent) + + # Conteo nativo (por company_id): un almacén por compañía. + native = Warehouse.sudo()._read_group( + [("active", "=", True), ("company_id", "in", (parent + branch).ids)], + ["company_id"], + aggregates=["__count"], + ) + self.assertEqual(sorted(count for _company, count in native), [1, 1]) + + # Conteo por árbol de compañías: la madre + sucursal suman 2. + self.assertGreaterEqual(Warehouse._multiwarehouse_max_count(), 2) + + def test_branch_warehouse_activates_multi_wh_group(self): + """Al crear una sucursal con almacén, el grupo multi-almacén queda + activo (implicado en el grupo de usuario interno).""" + group_user = self.env.ref("base.group_user") + group_multi_wh = self.env.ref("stock.group_stock_multi_warehouses") + + parent = self._create_company("UX Madre Grupo") + # Crear la sucursal dispara la creación de su almacén y, con ello, + # _check_multiwarehouse_group. + self._create_company("UX Sucursal Grupo", parent=parent) + + self.assertIn( + group_multi_wh, + group_user.implied_ids, + "Con una madre y una sucursal, cada una con un almacén, el permiso " + "'Gestionar varios almacenes' debe activarse automáticamente.", + )