From d38155dd4a26a18f7d3a3e042f99000f4b66cd18 Mon Sep 17 00:00:00 2001 From: solomonprabu Date: Thu, 30 Apr 2026 15:03:58 +0530 Subject: [PATCH 1/7] initial commit --- web_easy_switch_operating_unit/README.rst | 10 +++ web_easy_switch_operating_unit/__init__.py | 2 + .../__manifest__.py | 20 ++++++ .../controllers/__init__.py | 1 + .../controllers/main.py | 15 ++++ .../models/__init__.py | 1 + .../models/ir_http.py | 31 ++++++++ web_easy_switch_operating_unit/pyproject.toml | 3 + .../static/description/icon.png | Bin 0 -> 6125 bytes .../static/description/selection-off.png | Bin 0 -> 112 bytes .../static/description/selection-on.png | Bin 0 -> 445 bytes .../static/src/js/switch_operating_unit.js | 68 ++++++++++++++++++ .../static/src/xml/switch_operating_unit.xml | 45 ++++++++++++ .../views/res_users_views.xml | 30 ++++++++ 14 files changed, 226 insertions(+) create mode 100644 web_easy_switch_operating_unit/README.rst create mode 100644 web_easy_switch_operating_unit/__init__.py create mode 100644 web_easy_switch_operating_unit/__manifest__.py create mode 100644 web_easy_switch_operating_unit/controllers/__init__.py create mode 100644 web_easy_switch_operating_unit/controllers/main.py create mode 100644 web_easy_switch_operating_unit/models/__init__.py create mode 100644 web_easy_switch_operating_unit/models/ir_http.py create mode 100644 web_easy_switch_operating_unit/pyproject.toml create mode 100644 web_easy_switch_operating_unit/static/description/icon.png create mode 100644 web_easy_switch_operating_unit/static/description/selection-off.png create mode 100644 web_easy_switch_operating_unit/static/description/selection-on.png create mode 100644 web_easy_switch_operating_unit/static/src/js/switch_operating_unit.js create mode 100644 web_easy_switch_operating_unit/static/src/xml/switch_operating_unit.xml create mode 100644 web_easy_switch_operating_unit/views/res_users_views.xml diff --git a/web_easy_switch_operating_unit/README.rst b/web_easy_switch_operating_unit/README.rst new file mode 100644 index 0000000000..3a998eb265 --- /dev/null +++ b/web_easy_switch_operating_unit/README.rst @@ -0,0 +1,10 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +========================================================== +Add menu to allow user to switch to another Operating Unit +========================================================== + +This module adds a new menu in the top bar to switch to another Operating Unit. + diff --git a/web_easy_switch_operating_unit/__init__.py b/web_easy_switch_operating_unit/__init__.py new file mode 100644 index 0000000000..91c5580fed --- /dev/null +++ b/web_easy_switch_operating_unit/__init__.py @@ -0,0 +1,2 @@ +from . import controllers +from . import models diff --git a/web_easy_switch_operating_unit/__manifest__.py b/web_easy_switch_operating_unit/__manifest__.py new file mode 100644 index 0000000000..69f7ab6515 --- /dev/null +++ b/web_easy_switch_operating_unit/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2016 ICTSTUDIO (). +# Copyright (C) Startx 2021 +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Easy Switch Operating Unit", + "version": "18.0.1.0.0", + "category": "web", + "author": "Startx, ICTSTUDIO, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/operating-unit", + "depends": ["web", "operating_unit"], + "data": ["views/res_users_views.xml"], + 'assets': { + 'web.assets_backend': [ + 'web_easy_switch_operating_unit/static/src/js/*', + 'web_easy_switch_operating_unit/static/src/xml/switch_operating_unit.xml', + ], + } +} diff --git a/web_easy_switch_operating_unit/controllers/__init__.py b/web_easy_switch_operating_unit/controllers/__init__.py new file mode 100644 index 0000000000..12a7e529b6 --- /dev/null +++ b/web_easy_switch_operating_unit/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/web_easy_switch_operating_unit/controllers/main.py b/web_easy_switch_operating_unit/controllers/main.py new file mode 100644 index 0000000000..cfbcb09d75 --- /dev/null +++ b/web_easy_switch_operating_unit/controllers/main.py @@ -0,0 +1,15 @@ +# Copyright (C) 2016 ICTSTUDIO (). +# Copyright (C) Startx 2021 +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.http import Controller, request, route + + +class WebEasySwitchOperatingUnitController(Controller): + @route( + "/web_easy_switch_operating_unit/switch/change_current_operating_unit", + type="json", + auth="user", + ) + def change_current_operating_unit(self, operating_unit_id=False): + request.env.user.write({"default_operating_unit_id": operating_unit_id}) diff --git a/web_easy_switch_operating_unit/models/__init__.py b/web_easy_switch_operating_unit/models/__init__.py new file mode 100644 index 0000000000..9a5eb71871 --- /dev/null +++ b/web_easy_switch_operating_unit/models/__init__.py @@ -0,0 +1 @@ +from . import ir_http diff --git a/web_easy_switch_operating_unit/models/ir_http.py b/web_easy_switch_operating_unit/models/ir_http.py new file mode 100644 index 0000000000..6ba2c70db7 --- /dev/null +++ b/web_easy_switch_operating_unit/models/ir_http.py @@ -0,0 +1,31 @@ +# Copyright (C) 2016 ICTSTUDIO (). +# Copyright (C) Startx 2021 +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models +from odoo.http import request + + +class IrHttp(models.AbstractModel): + _inherit = "ir.http" + + def session_info(self): + info = super().session_info() + + if self.env.user.has_group("base.group_user"): + user = request.env.user + info.update( + { + "user_operating_units": { + "current_operating_unit": ( + user.default_operating_unit_id.id, + user.default_operating_unit_id.code, + ), + "allowed_operating_units": [ + (ou.id, ou.code) for ou in user.operating_unit_ids + ], + } + } + ) + + return info diff --git a/web_easy_switch_operating_unit/pyproject.toml b/web_easy_switch_operating_unit/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/web_easy_switch_operating_unit/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/web_easy_switch_operating_unit/static/description/icon.png b/web_easy_switch_operating_unit/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ee270eb32c8a2a81c134da15e544c50f69d07315 GIT binary patch literal 6125 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 literal 0 HcmV?d00001 diff --git a/web_easy_switch_operating_unit/static/description/selection-off.png b/web_easy_switch_operating_unit/static/description/selection-off.png new file mode 100644 index 0000000000000000000000000000000000000000..c8e922cc52b4e47aeb67397084070164ae6db15a GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ z@ErkR#;MwT(m+8WPZ!4!i{9h}3D(64B0WqTKz#xXjP<@1eLz_TPgg&ebxsLQ09bVu A-T(jq literal 0 HcmV?d00001 diff --git a/web_easy_switch_operating_unit/static/description/selection-on.png b/web_easy_switch_operating_unit/static/description/selection-on.png new file mode 100644 index 0000000000000000000000000000000000000000..8faf7d4def9060c05b2bd335adc56a42a13834e4 GIT binary patch literal 445 zcmV;u0Yd(XP)F4y14e=>LK&E2J>|n? zM3tMqe1E_4{`>1Qx1|4g1e0bWq~F(`Pf=Y~KUqghTTw+?JC=t@Knq!(paDwaYN?V^ z5|+RIePwv@{^_x|zh9g~mS;ic7|<^RGU_VE+L`?HM<-@N)+4HRY6)0sH`a(sCC z_0fZ;H>T{)Vt9qq04-K|S0!!jqDL=p9pq`hvshVLFJD|zjPLuekALqyyuI%IkLRb+ zU5FBh6`SqP*&4X&{r>ZZ@yhLUuM}jIc)zp1 + diff --git a/web_easy_switch_operating_unit/views/res_users_views.xml b/web_easy_switch_operating_unit/views/res_users_views.xml new file mode 100644 index 0000000000..b73b9a5e3d --- /dev/null +++ b/web_easy_switch_operating_unit/views/res_users_views.xml @@ -0,0 +1,30 @@ + + + + + res.users.form + res.users + + + + 1 + + + + + res.users.preferences.operating.unit + res.users + + + + + + + + + + From f6a8db576434ec351ad12c36753828ef6b974194 Mon Sep 17 00:00:00 2001 From: solomonprabu Date: Thu, 30 Apr 2026 18:44:43 +0530 Subject: [PATCH 2/7] [MIG] Adapted module to Odoo 18, OWL 2.0 adaptations in js and [DEL] removal of Controller as OWL adaptation makes it redundant --- web_easy_switch_operating_unit/__init__.py | 1 - .../__manifest__.py | 10 +- .../controllers/__init__.py | 1 - .../controllers/main.py | 15 --- .../src/js/switch_operating_unit.esm.js | 55 +++++++++++ .../static/src/js/switch_operating_unit.js | 68 -------------- .../static/src/xml/switch_operating_unit.xml | 94 ++++++++++++------- .../views/res_users_views.xml | 47 +++++----- 8 files changed, 142 insertions(+), 149 deletions(-) delete mode 100644 web_easy_switch_operating_unit/controllers/__init__.py delete mode 100644 web_easy_switch_operating_unit/controllers/main.py create mode 100644 web_easy_switch_operating_unit/static/src/js/switch_operating_unit.esm.js delete mode 100644 web_easy_switch_operating_unit/static/src/js/switch_operating_unit.js diff --git a/web_easy_switch_operating_unit/__init__.py b/web_easy_switch_operating_unit/__init__.py index 91c5580fed..0650744f6b 100644 --- a/web_easy_switch_operating_unit/__init__.py +++ b/web_easy_switch_operating_unit/__init__.py @@ -1,2 +1 @@ -from . import controllers from . import models diff --git a/web_easy_switch_operating_unit/__manifest__.py b/web_easy_switch_operating_unit/__manifest__.py index 69f7ab6515..1cdb273af3 100644 --- a/web_easy_switch_operating_unit/__manifest__.py +++ b/web_easy_switch_operating_unit/__manifest__.py @@ -11,10 +11,10 @@ "website": "https://github.com/OCA/operating-unit", "depends": ["web", "operating_unit"], "data": ["views/res_users_views.xml"], - 'assets': { - 'web.assets_backend': [ - 'web_easy_switch_operating_unit/static/src/js/*', - 'web_easy_switch_operating_unit/static/src/xml/switch_operating_unit.xml', + "assets": { + "web.assets_backend": [ + "web_easy_switch_operating_unit/static/src/js/*", + "web_easy_switch_operating_unit/static/src/xml/switch_operating_unit.xml", ], - } + }, } diff --git a/web_easy_switch_operating_unit/controllers/__init__.py b/web_easy_switch_operating_unit/controllers/__init__.py deleted file mode 100644 index 12a7e529b6..0000000000 --- a/web_easy_switch_operating_unit/controllers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import main diff --git a/web_easy_switch_operating_unit/controllers/main.py b/web_easy_switch_operating_unit/controllers/main.py deleted file mode 100644 index cfbcb09d75..0000000000 --- a/web_easy_switch_operating_unit/controllers/main.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (C) 2016 ICTSTUDIO (). -# Copyright (C) Startx 2021 -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). - -from odoo.http import Controller, request, route - - -class WebEasySwitchOperatingUnitController(Controller): - @route( - "/web_easy_switch_operating_unit/switch/change_current_operating_unit", - type="json", - auth="user", - ) - def change_current_operating_unit(self, operating_unit_id=False): - request.env.user.write({"default_operating_unit_id": operating_unit_id}) diff --git a/web_easy_switch_operating_unit/static/src/js/switch_operating_unit.esm.js b/web_easy_switch_operating_unit/static/src/js/switch_operating_unit.esm.js new file mode 100644 index 0000000000..2b7831ef6d --- /dev/null +++ b/web_easy_switch_operating_unit/static/src/js/switch_operating_unit.esm.js @@ -0,0 +1,55 @@ +/** @odoo-module **/ +/* eslint parserOptions: { sourceType: "module" } */ + +import {Component} from "@odoo/owl"; +import {registry} from "@web/core/registry"; +import {session} from "@web/session"; +import {useService} from "@web/core/utils/hooks"; +import {user} from "@web/core/user"; + +export class SwitchOperatingUnitMenu extends Component { + static template = "web_easy_switch_operating_unit.SwitchOperatingUnitMenu"; + static props = {}; + + setup() { + this.orm = useService("orm"); + if (!session.user_operating_units) { + this.user_operating_units = []; + this.allowed_operating_unit_ids = []; + this.current_operating_unit_id = false; + this.current_operating_unit_name = "No OU"; + return; + } + + this.user_operating_units = + session.user_operating_units.allowed_operating_units; + this.allowed_operating_unit_ids = this.user_operating_units.map((ou) => + parseInt(ou[0], 10) + ); + this.current_operating_unit_id = + session.user_operating_units.current_operating_unit[0]; + this.current_operating_unit_name = + session.user_operating_units.current_operating_unit[1]; + } + + async onSwitchOperatingUnitClick(operatingUnitId) { + await this.orm.write("res.users", [user.userId], { + default_operating_unit_id: operatingUnitId || false, + }); + window.location.reload(); + } +} + +const systrayItem = { + Component: SwitchOperatingUnitMenu, + isDisplayed(env) { + return ( + session.user_operating_units && + session.user_operating_units.allowed_operating_units.length > 0 + ); + }, +}; + +registry + .category("systray") + .add("SwitchOperatingUnitMenu", systrayItem, {sequence: 10}); diff --git a/web_easy_switch_operating_unit/static/src/js/switch_operating_unit.js b/web_easy_switch_operating_unit/static/src/js/switch_operating_unit.js deleted file mode 100644 index f3b933b8ef..0000000000 --- a/web_easy_switch_operating_unit/static/src/js/switch_operating_unit.js +++ /dev/null @@ -1,68 +0,0 @@ -odoo.define('web_easy_switch_operating_unit.SwitchOperatingUnitMenu', function(require) { -"use strict"; - -var config = require('web.config'); -var session = require('web.session'); -var SystrayMenu = require('web.SystrayMenu'); -var Widget = require('web.Widget'); - -var SwitchOperatingUnitMenu = Widget.extend({ - template: 'SwitchOperatingUnitMenu', - events: { - 'click .dropdown-item[data-menu] div.log_into': '_onSwitchOperatingUnitClick', - 'keydown .dropdown-item[data-menu] div.log_into': '_onSwitchOperatingUnitClick', - }, - /** - * @override - */ - init: function () { - this._super.apply(this, arguments); - this.isMobile = config.device.isMobile; - this._onSwitchOperatingUnitClick = _.debounce(this._onSwitchOperatingUnitClick, 1500, true); - }, - - /** - * @override - */ - willStart: function () { - this.user_operating_units = session.user_operating_units.allowed_operating_units; - this.allowed_operating_unit_ids = this.user_operating_units - .map(function (ou) {return parseInt(ou[0], 10);}); - this.current_operating_unit_id = session.user_operating_units.current_operating_unit[0]; - this.current_operating_unit_name = session.user_operating_units.current_operating_unit[1]; - - return this._super.apply(this, arguments); - }, - - /** - * @private - * @param {MouseEvent|KeyEvent} ev - */ - _onSwitchOperatingUnitClick: function (ev) { - if (ev.type === 'keydown' && ev.which !== $.ui.keyCode.ENTER && ev.which !== $.ui.keyCode.SPACE) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - var dropdownItem = $(ev.currentTarget).parent(); - var operating_unit_id = dropdownItem.data('operating-unit-id'); - $(ev.currentTarget).attr('aria-pressed', 'true'); - - this._rpc({ - 'route': '/web_easy_switch_operating_unit/switch/change_current_operating_unit', - 'params': { - 'operating_unit_id': operating_unit_id, - } - }).then( - function () { - window.location.reload(); - } - ); - } -}); - - -SystrayMenu.Items.push(SwitchOperatingUnitMenu); - -return SwitchOperatingUnitMenu; -}); diff --git a/web_easy_switch_operating_unit/static/src/xml/switch_operating_unit.xml b/web_easy_switch_operating_unit/static/src/xml/switch_operating_unit.xml index 6e3d6c8b80..e3cb21f150 100644 --- a/web_easy_switch_operating_unit/static/src/xml/switch_operating_unit.xml +++ b/web_easy_switch_operating_unit/static/src/xml/switch_operating_unit.xml @@ -1,45 +1,71 @@ - - + diff --git a/web_easy_switch_operating_unit/views/res_users_views.xml b/web_easy_switch_operating_unit/views/res_users_views.xml index b73b9a5e3d..61971093ae 100644 --- a/web_easy_switch_operating_unit/views/res_users_views.xml +++ b/web_easy_switch_operating_unit/views/res_users_views.xml @@ -1,30 +1,27 @@ - - - res.users.form - res.users - - - - 1 - + + res.users.form + res.users + + + + 1 - - - res.users.preferences.operating.unit - res.users - - - - - + + + + res.users.preferences.operating.unit + res.users + + + + - - - + + From b6b4a76e947ea7465ab3e4da1dd521d3fb63c32c Mon Sep 17 00:00:00 2001 From: solomonprabu Date: Mon, 4 May 2026 16:35:07 +0530 Subject: [PATCH 3/7] [UPDATE] Updated the Licenses, Author in __manifest__.py and [FIX] Fixed pre-commit erros in .js file --- web_easy_switch_operating_unit/__manifest__.py | 5 ++--- web_easy_switch_operating_unit/models/ir_http.py | 3 +-- .../static/src/js/switch_operating_unit.esm.js | 5 +++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/web_easy_switch_operating_unit/__manifest__.py b/web_easy_switch_operating_unit/__manifest__.py index 1cdb273af3..d9b7e8dea9 100644 --- a/web_easy_switch_operating_unit/__manifest__.py +++ b/web_easy_switch_operating_unit/__manifest__.py @@ -1,12 +1,11 @@ -# Copyright (C) 2016 ICTSTUDIO (). -# Copyright (C) Startx 2021 +# Copyright (C) 2026 CIT-Services # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "Easy Switch Operating Unit", "version": "18.0.1.0.0", "category": "web", - "author": "Startx, ICTSTUDIO, Odoo Community Association (OCA)", + "author": "CIT-Services, Odoo Community Association (OCA)", "license": "AGPL-3", "website": "https://github.com/OCA/operating-unit", "depends": ["web", "operating_unit"], diff --git a/web_easy_switch_operating_unit/models/ir_http.py b/web_easy_switch_operating_unit/models/ir_http.py index 6ba2c70db7..b5300d5670 100644 --- a/web_easy_switch_operating_unit/models/ir_http.py +++ b/web_easy_switch_operating_unit/models/ir_http.py @@ -1,5 +1,4 @@ -# Copyright (C) 2016 ICTSTUDIO (). -# Copyright (C) Startx 2021 +# Copyright (C) 2026 CIT-Services # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import models diff --git a/web_easy_switch_operating_unit/static/src/js/switch_operating_unit.esm.js b/web_easy_switch_operating_unit/static/src/js/switch_operating_unit.esm.js index 2b7831ef6d..7c41cf8df5 100644 --- a/web_easy_switch_operating_unit/static/src/js/switch_operating_unit.esm.js +++ b/web_easy_switch_operating_unit/static/src/js/switch_operating_unit.esm.js @@ -1,5 +1,6 @@ +/* eslint-disable jsdoc/check-tag-names */ /** @odoo-module **/ -/* eslint parserOptions: { sourceType: "module" } */ +/* global window */ import {Component} from "@odoo/owl"; import {registry} from "@web/core/registry"; @@ -42,7 +43,7 @@ export class SwitchOperatingUnitMenu extends Component { const systrayItem = { Component: SwitchOperatingUnitMenu, - isDisplayed(env) { + isDisplayed() { return ( session.user_operating_units && session.user_operating_units.allowed_operating_units.length > 0 From ae8856423f10d0907ab27d2367cccf235c3a88ae Mon Sep 17 00:00:00 2001 From: solomonprabu Date: Tue, 5 May 2026 12:43:25 +0530 Subject: [PATCH 4/7] [ADD] Added tests to test ir_http to verify adding of OU to session_info --- .../tests/__init__.py | 1 + .../tests/test_ir_http.py | 92 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 web_easy_switch_operating_unit/tests/__init__.py create mode 100644 web_easy_switch_operating_unit/tests/test_ir_http.py diff --git a/web_easy_switch_operating_unit/tests/__init__.py b/web_easy_switch_operating_unit/tests/__init__.py new file mode 100644 index 0000000000..e2983aa2a4 --- /dev/null +++ b/web_easy_switch_operating_unit/tests/__init__.py @@ -0,0 +1 @@ +from . import test_ir_http diff --git a/web_easy_switch_operating_unit/tests/test_ir_http.py b/web_easy_switch_operating_unit/tests/test_ir_http.py new file mode 100644 index 0000000000..b80fdd8b31 --- /dev/null +++ b/web_easy_switch_operating_unit/tests/test_ir_http.py @@ -0,0 +1,92 @@ +# Copyright (C) 2026 CIT-Services +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from unittest.mock import MagicMock, patch +from odoo.tests import HttpCase, tagged +from odoo.tests.common import TransactionCase + +@tagged('-at_install', 'post_install') +class TestIrHttp(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.operating_unit_1 = cls.env["operating.unit"].create( + { + "name": "Operating Unit 1", + "code": "TEST_OU1", + "partner_id": cls.env.company.partner_id.id, + } + ) + cls.operating_unit_2 = cls.env["operating.unit"].create( + { + "name": "Operating Unit 2", + "code": "TEST_OU2", + "partner_id": cls.env.company.partner_id.id, + } + ) + + cls.user = cls.env["res.users"].create( + { + "name": "Test User", + "login": "test_ou_user", + "groups_id": [(6, 0, [cls.env.ref("base.group_user").id])], + "operating_unit_ids": [ + (6, 0, [cls.operating_unit_1.id, cls.operating_unit_2.id]) + ], + "default_operating_unit_id": cls.operating_unit_1.id, + } + ) + + cls.portal_user = cls.env["res.users"].create( + { + "name": "Portal User", + "login": "test_ou_portal", + "groups_id": [(6, 0, [cls.env.ref("base.group_portal").id])], + } + ) + + def test_session_info_internal_user(self): + """Test session_info for internal user includes operating unit data.""" + mock_request = MagicMock() + mock_request.env.user = self.user + mock_request.session.uid = self.user.id + + with patch( + "odoo.addons.web_easy_switch_operating_unit.models.ir_http.request", + mock_request, + ), patch( + "odoo.addons.web.models.ir_http.request", + mock_request, + ): + info = self.env["ir.http"].with_user(self.user).session_info() + self.assertIn("user_operating_units", info) + ou_data = info["user_operating_units"] + self.assertEqual( + ou_data["current_operating_unit"], + (self.operating_unit_1.id, self.operating_unit_1.code), + ) + + allowed_ous = ou_data["allowed_operating_units"] + self.assertEqual(len(allowed_ous), 2) + self.assertIn( + (self.operating_unit_1.id, self.operating_unit_1.code), allowed_ous + ) + self.assertIn( + (self.operating_unit_2.id, self.operating_unit_2.code), allowed_ous + ) + + def test_session_info_portal_user(self): + """Test session_info for portal user does not include operating unit data.""" + mock_request = MagicMock() + mock_request.env.user = self.portal_user + mock_request.session.uid = self.portal_user.id + + with patch( + "odoo.addons.web_easy_switch_operating_unit.models.ir_http.request", + mock_request, + ), patch( + "odoo.addons.web.models.ir_http.request", + mock_request, + ): + info = self.env["ir.http"].with_user(self.portal_user).session_info() + self.assertNotIn("user_operating_units", info) From a9e2d504caa86e7a2843af56604c4028fdc8f66f Mon Sep 17 00:00:00 2001 From: solomonprabu Date: Tue, 5 May 2026 12:47:15 +0530 Subject: [PATCH 5/7] [UPDATE] Pre-commit fix --- .../tests/test_ir_http.py | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/web_easy_switch_operating_unit/tests/test_ir_http.py b/web_easy_switch_operating_unit/tests/test_ir_http.py index b80fdd8b31..22d470fe20 100644 --- a/web_easy_switch_operating_unit/tests/test_ir_http.py +++ b/web_easy_switch_operating_unit/tests/test_ir_http.py @@ -2,10 +2,12 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from unittest.mock import MagicMock, patch -from odoo.tests import HttpCase, tagged + +from odoo.tests import tagged from odoo.tests.common import TransactionCase -@tagged('-at_install', 'post_install') + +@tagged("-at_install", "post_install") class TestIrHttp(TransactionCase): @classmethod def setUpClass(cls): @@ -51,12 +53,15 @@ def test_session_info_internal_user(self): mock_request.env.user = self.user mock_request.session.uid = self.user.id - with patch( - "odoo.addons.web_easy_switch_operating_unit.models.ir_http.request", - mock_request, - ), patch( - "odoo.addons.web.models.ir_http.request", - mock_request, + with ( + patch( + "odoo.addons.web_easy_switch_operating_unit.models.ir_http.request", + mock_request, + ), + patch( + "odoo.addons.web.models.ir_http.request", + mock_request, + ), ): info = self.env["ir.http"].with_user(self.user).session_info() self.assertIn("user_operating_units", info) @@ -81,12 +86,15 @@ def test_session_info_portal_user(self): mock_request.env.user = self.portal_user mock_request.session.uid = self.portal_user.id - with patch( - "odoo.addons.web_easy_switch_operating_unit.models.ir_http.request", - mock_request, - ), patch( - "odoo.addons.web.models.ir_http.request", - mock_request, + with ( + patch( + "odoo.addons.web_easy_switch_operating_unit.models.ir_http.request", + mock_request, + ), + patch( + "odoo.addons.web.models.ir_http.request", + mock_request, + ), ): info = self.env["ir.http"].with_user(self.portal_user).session_info() self.assertNotIn("user_operating_units", info) From f23e56cf484098c2877dc39a8d895ba736b34a53 Mon Sep 17 00:00:00 2001 From: solomonprabu Date: Tue, 5 May 2026 17:15:42 +0530 Subject: [PATCH 6/7] [FIX] Fixed test case object unbound error with odoo.http implementation and added try-catch block --- .../tests/test_ir_http.py | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/web_easy_switch_operating_unit/tests/test_ir_http.py b/web_easy_switch_operating_unit/tests/test_ir_http.py index 22d470fe20..ba1792989d 100644 --- a/web_easy_switch_operating_unit/tests/test_ir_http.py +++ b/web_easy_switch_operating_unit/tests/test_ir_http.py @@ -1,7 +1,7 @@ # Copyright (C) 2026 CIT-Services # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock from odoo.tests import tagged from odoo.tests.common import TransactionCase @@ -52,17 +52,12 @@ def test_session_info_internal_user(self): mock_request = MagicMock() mock_request.env.user = self.user mock_request.session.uid = self.user.id + mock_request.cookies = {} - with ( - patch( - "odoo.addons.web_easy_switch_operating_unit.models.ir_http.request", - mock_request, - ), - patch( - "odoo.addons.web.models.ir_http.request", - mock_request, - ), - ): + import odoo.http + + odoo.http._request_stack.push(mock_request) + try: info = self.env["ir.http"].with_user(self.user).session_info() self.assertIn("user_operating_units", info) ou_data = info["user_operating_units"] @@ -79,22 +74,21 @@ def test_session_info_internal_user(self): self.assertIn( (self.operating_unit_2.id, self.operating_unit_2.code), allowed_ous ) + finally: + odoo.http._request_stack.pop() def test_session_info_portal_user(self): """Test session_info for portal user does not include operating unit data.""" mock_request = MagicMock() mock_request.env.user = self.portal_user mock_request.session.uid = self.portal_user.id + mock_request.cookies = {} + + import odoo.http - with ( - patch( - "odoo.addons.web_easy_switch_operating_unit.models.ir_http.request", - mock_request, - ), - patch( - "odoo.addons.web.models.ir_http.request", - mock_request, - ), - ): + odoo.http._request_stack.push(mock_request) + try: info = self.env["ir.http"].with_user(self.portal_user).session_info() self.assertNotIn("user_operating_units", info) + finally: + odoo.http._request_stack.pop() From 055675f88b4371411953db1c788fb931c676584f Mon Sep 17 00:00:00 2001 From: solomonprabu Date: Wed, 6 May 2026 14:30:12 +0530 Subject: [PATCH 7/7] [FIX] Removed Invisible attribute for field , allows test cases to pass --- .../views/res_users_views.xml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/web_easy_switch_operating_unit/views/res_users_views.xml b/web_easy_switch_operating_unit/views/res_users_views.xml index 61971093ae..0ade040863 100644 --- a/web_easy_switch_operating_unit/views/res_users_views.xml +++ b/web_easy_switch_operating_unit/views/res_users_views.xml @@ -1,15 +1,5 @@ - - res.users.form - res.users - - - - 1 - - - res.users.preferences.operating.unit res.users