diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index 8708c613..deeab6a5 100644 --- a/fusion_plating/fusion_plating/__manifest__.py +++ b/fusion_plating/fusion_plating/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating', - 'version': '19.0.21.0.2', + 'version': '19.0.21.0.3', 'category': 'Manufacturing/Plating', 'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.', 'description': """ diff --git a/fusion_plating/fusion_plating/tests/__init__.py b/fusion_plating/fusion_plating/tests/__init__.py index ae483a5d..be2bf5fd 100644 --- a/fusion_plating/fusion_plating/tests/__init__.py +++ b/fusion_plating/fusion_plating/tests/__init__.py @@ -6,3 +6,4 @@ from . import test_simple_recipe_flatten from . import test_role_groups from . import test_acl_migration from . import test_quality_split +from . import test_menu_visibility diff --git a/fusion_plating/fusion_plating/tests/test_menu_visibility.py b/fusion_plating/fusion_plating/tests/test_menu_visibility.py new file mode 100644 index 00000000..dc0699df --- /dev/null +++ b/fusion_plating/fusion_plating/tests/test_menu_visibility.py @@ -0,0 +1,85 @@ +from odoo.tests.common import TransactionCase, tagged + + +@tagged('-at_install', 'post_install', 'fp_perms') +class TestMenuVisibility(TransactionCase): + """Section 2.F of spec: per-role menu render matrix.""" + + def setUp(self): + super().setUp() + Users = self.env['res.users'].with_context(no_reset_password=True) + def mk(name, xmlid): + return Users.create({ + 'login': f'menu_{name}', 'name': f'Menu Test {name}', + 'email': f'menu_{name}@example.com', + 'groups_id': [(6, 0, [self.env.ref(xmlid).id])] if xmlid else [(6, 0, [])], + }) + # "No" user has only base.group_user — no plating group + no_user = Users.create({ + 'login': 'menu_no', 'name': 'Menu Test no', + 'email': 'menu_no@example.com', + }) + no_user.write({'groups_id': [(6, 0, [self.env.ref('base.group_user').id])]}) + self.u_no = no_user + self.u_tech = mk('tech', 'fusion_plating.group_fp_technician') + self.u_sr = mk('sr', 'fusion_plating.group_fp_sales_rep') + self.u_sm = mk('sm', 'fusion_plating.group_fp_shop_manager_v2') + self.u_smg = mk('smg', 'fusion_plating.group_fp_sales_manager') + self.u_mgr = mk('mgr', 'fusion_plating.group_fp_manager') + self.u_qm = mk('qm', 'fusion_plating.group_fp_quality_manager') + self.u_owner = mk('owner', 'fusion_plating.group_fp_owner') + + def _visible(self, user, menu_xmlid): + menu = self.env.ref(menu_xmlid, raise_if_not_found=False) + if not menu: + return None # menu not installed + # An "invisible" menu is one the user can't read + return bool(self.env['ir.ui.menu'].with_user(user).search_count([('id', '=', menu.id)])) + + def test_no_sees_no_plating_root(self): + result = self._visible(self.u_no, 'fusion_plating.menu_fp_root') + if result is None: + self.skipTest('Plating root menu not found') + self.assertFalse(result, '"No" role must not see Plating root') + + def test_technician_sees_shop_floor(self): + result = self._visible(self.u_tech, 'fusion_plating_shopfloor.menu_fp_shopfloor_root') + if result is None: + self.skipTest('Shop Floor menu not found') + self.assertTrue(result) + + def test_technician_does_not_see_sales(self): + result = self._visible(self.u_tech, 'fusion_plating_configurator.menu_fp_sales_root') + if result is None: + self.skipTest('Sales menu not found') + self.assertFalse(result, 'Technician must not see Sales & Quoting') + + def test_sales_rep_sees_sales(self): + result = self._visible(self.u_sr, 'fusion_plating_configurator.menu_fp_sales_root') + if result is None: + self.skipTest('Sales menu not found') + self.assertTrue(result) + + def test_sales_rep_does_not_see_shop_floor(self): + result = self._visible(self.u_sr, 'fusion_plating_shopfloor.menu_fp_shopfloor_root') + if result is None: + self.skipTest('Shop Floor menu not found') + self.assertFalse(result, 'Sales Rep must not see Shop Floor') + + def test_manager_sees_quality(self): + result = self._visible(self.u_mgr, 'fusion_plating_quality.menu_fp_quality') + if result is None: + self.skipTest('Quality menu not found') + self.assertTrue(result) + + def test_manager_does_not_see_compliance(self): + result = self._visible(self.u_mgr, 'fusion_plating_compliance.menu_fp_compliance_hub') + if result is None: + self.skipTest('Compliance hub not found') + self.assertFalse(result, 'Manager must not see Compliance hub') + + def test_qm_sees_compliance(self): + result = self._visible(self.u_qm, 'fusion_plating_compliance.menu_fp_compliance_hub') + if result is None: + self.skipTest('Compliance hub not found') + self.assertTrue(result) diff --git a/fusion_plating/fusion_plating/views/fp_job_step_move_views.xml b/fusion_plating/fusion_plating/views/fp_job_step_move_views.xml index f1d06e39..5da4533b 100644 --- a/fusion_plating/fusion_plating/views/fp_job_step_move_views.xml +++ b/fusion_plating/fusion_plating/views/fp_job_step_move_views.xml @@ -116,13 +116,13 @@ + Phase D (perms v2) — Shop Manager+ only. Operators see their + own moves on the tablet; this is an audit view of every move. --> + groups="fusion_plating.group_fp_shop_manager_v2"/> diff --git a/fusion_plating/fusion_plating/views/fp_job_step_timelog_views.xml b/fusion_plating/fusion_plating/views/fp_job_step_timelog_views.xml index 49c263dc..dbc559ac 100644 --- a/fusion_plating/fusion_plating/views/fp_job_step_timelog_views.xml +++ b/fusion_plating/fusion_plating/views/fp_job_step_timelog_views.xml @@ -133,10 +133,12 @@ + + sequence="95" + groups="fusion_plating.group_fp_shop_manager_v2"/> diff --git a/fusion_plating/fusion_plating/views/fp_menu.xml b/fusion_plating/fusion_plating/views/fp_menu.xml index 14b6190c..796903c1 100644 --- a/fusion_plating/fusion_plating/views/fp_menu.xml +++ b/fusion_plating/fusion_plating/views/fp_menu.xml @@ -22,14 +22,14 @@ sequence="46" web_icon="fusion_plating,static/description/icon.png" action="action_fp_resolve_plating_landing" - groups="group_fusion_plating_operator"/> + groups="fusion_plating.group_fp_technician,fusion_plating.group_fp_sales_rep"/> + groups="fusion_plating.group_fp_manager"/> + groups="fusion_plating.group_fp_quality_manager"/> + sequence="18" + groups="fusion_plating.group_fp_technician"/> @@ -112,13 +113,13 @@ action="action_fp_rack" sequence="35"/> - + + groups="fusion_plating.group_fp_manager"/> + + groups="fusion_plating.group_fp_quality_manager"/> + + groups="fusion_plating.group_fp_shop_manager_v2"/> + + groups="fusion_plating.group_fp_quality_manager"/> + parent="fusion_plating.menu_fp_compliance_hub" sequence="10" + groups="fusion_plating.group_fp_quality_manager"/> diff --git a/fusion_plating/fusion_plating_configurator/__manifest__.py b/fusion_plating/fusion_plating_configurator/__manifest__.py index e349e207..eacb52af 100644 --- a/fusion_plating/fusion_plating_configurator/__manifest__.py +++ b/fusion_plating/fusion_plating_configurator/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Configurator', - 'version': '19.0.21.8.1', + 'version': '19.0.21.8.2', 'category': 'Manufacturing/Plating', 'summary': 'Quotation configurator with part catalog, coating configs, and formula-based pricing engine.', 'description': """ diff --git a/fusion_plating/fusion_plating_configurator/views/fp_configurator_menu.xml b/fusion_plating/fusion_plating_configurator/views/fp_configurator_menu.xml index 6daaf472..815174e0 100644 --- a/fusion_plating/fusion_plating_configurator/views/fp_configurator_menu.xml +++ b/fusion_plating/fusion_plating_configurator/views/fp_configurator_menu.xml @@ -29,7 +29,7 @@ name="Sales" parent="fusion_plating.menu_fp_root" sequence="5" - groups="group_fp_estimator,fusion_plating.group_fusion_plating_supervisor"/> + groups="fusion_plating.group_fp_sales_rep"/> - + + groups="fusion_plating.group_fp_manager"/> + + groups="fusion_plating.group_fp_quality_manager"/> + groups="fusion_plating.group_fp_manager"/> + sequence="40" + groups="fusion_plating.group_fp_quality_manager"/> + sequence="70" + groups="fusion_plating.group_fp_quality_manager"/> + sequence="20" + groups="fusion_plating.group_fp_quality_manager"/> diff --git a/fusion_plating/fusion_plating_receiving/__manifest__.py b/fusion_plating/fusion_plating_receiving/__manifest__.py index 8b84adff..c6807bde 100644 --- a/fusion_plating/fusion_plating_receiving/__manifest__.py +++ b/fusion_plating/fusion_plating_receiving/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Receiving & Inspection', - 'version': '19.0.3.28.1', + 'version': '19.0.3.28.2', 'category': 'Manufacturing/Plating', 'summary': 'Parts receiving, inspection, damage logging, and manufacturing gate.', 'description': """ diff --git a/fusion_plating/fusion_plating_receiving/views/fp_receiving_menu.xml b/fusion_plating/fusion_plating_receiving/views/fp_receiving_menu.xml index 64868c43..5de3d72f 100644 --- a/fusion_plating/fusion_plating_receiving/views/fp_receiving_menu.xml +++ b/fusion_plating/fusion_plating_receiving/views/fp_receiving_menu.xml @@ -29,7 +29,7 @@ name="Shipping & Receiving" parent="fusion_plating.menu_fp_root" sequence="15" - groups="group_fp_receiving"/> + groups="fusion_plating.group_fp_shop_manager_v2"/> + + groups="fusion_plating.group_fp_quality_manager"/> + groups="fusion_plating.group_fp_technician"/>