From bfc138251afe23a0fde5d0ca785b06d68b95a8fa Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 24 May 2026 20:00:15 -0400 Subject: [PATCH] feat(fusion_plating): hide back-office menus from Plating Technicians MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per user request: technicians on the tablet should only see Discuss, To-do, Plating, AI, Maintenance, Time Off. Every other top-level app menu (Calendar, Contacts, CRM, Sales, Dashboards, RC, Faxes, Field Service, Fusion Clock, Invoicing, Accounting, Project, Timesheets, Planning, Shipping, Website, Purchase, Inventory, Sign, HR, Payroll, Attendances, Recruitment, Expenses, IoT, Link Tracker, Apps) is now restricted to a new group_fp_office_user. Architecture: - New group_fp_office_user (security/fp_menu_visibility.xml) — a marker group that controls back-office menu visibility. - Owner / Manager / Quality Manager / Shop Manager / Sales Rep all imply office_user via implied_ids — they see everything they did before. - Pure Technicians do NOT imply office_user — they see only the tablet-friendly menus. - A "!technician" filter would have hit managers too (because Manager → ... → Technician via implication), so office_user is the inverse pattern that gets the right scoping. Implementation: - post_init_hook + migrations/19.0.21.4.0/post-migrate.py both call _fp_apply_office_user_menu_visibility(env) which iterates a curated list of menu xmlids and sets group_ids = [office_user] on each. - Uses env.ref(..., raise_if_not_found=False) so menus from uninstalled modules silently skip — no hard depends added. - ir.ui.menu uses `group_ids` in Odoo 19 (was groups_id pre-18 — same rename pattern as res.users; CLAUDE.md Rule 13c). - Settings / Apps / Tests left untouched (already admin-restricted). - Some menus (Field Service) end up with office_user OR their original group — that's correct behavior: Plating Techs have neither so still don't see them; explicit Field Technicians keep access. Co-Authored-By: Claude Opus 4.7 (1M context) --- fusion_plating/fusion_plating/__init__.py | 80 +++++++++++++++++++ fusion_plating/fusion_plating/__manifest__.py | 9 ++- .../migrations/19.0.21.4.0/post-migrate.py | 22 +++++ .../security/fp_menu_visibility.xml | 73 +++++++++++++++++ 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 fusion_plating/fusion_plating/migrations/19.0.21.4.0/post-migrate.py create mode 100644 fusion_plating/fusion_plating/security/fp_menu_visibility.xml diff --git a/fusion_plating/fusion_plating/__init__.py b/fusion_plating/fusion_plating/__init__.py index db9673ad..ff872e85 100644 --- a/fusion_plating/fusion_plating/__init__.py +++ b/fusion_plating/fusion_plating/__init__.py @@ -35,6 +35,86 @@ def post_init_hook(env): _migrate_legacy_uom_columns(env) _seed_starter_recipes_once(env) _fp_post_init_role_migration(env) + _fp_apply_office_user_menu_visibility(env) + + +# Top-level app menus that technicians should NOT see. Each entry is an +# xmlid; env.ref(..., raise_if_not_found=False) silently skips menus +# from uninstalled modules so this is safe across configurations. +# Kept visible to technicians (NOT in this list): Discuss, To-do, +# Plating, AI, Maintenance, Time Off. Settings/Apps/Tests are admin- +# restricted upstream — also not in this list. +# See security/fp_menu_visibility.xml for the design rationale. +MENU_HIDE_FROM_TECHNICIANS = [ + 'calendar.mail_menu_calendar', + 'contacts.menu_contacts', + 'crm.crm_menu_root', + 'sale.sale_menu_root', + 'spreadsheet_dashboard.spreadsheet_dashboard_menu_root', + 'fusion_ringcentral.menu_rc_root', + 'fusion_faxes.menu_fusion_faxes_root', + 'fusion_tasks.menu_field_service_root', + 'fusion_clock.menu_fusion_clock_root', + 'account.menu_finance', + 'accountant.menu_accounting', + 'project.menu_main_pm', + 'hr_timesheet.timesheet_menu_root', + 'planning.planning_menu_root', + 'fusion_shipping.menu_fusion_shipping_root', + 'website.menu_website_configuration', + 'purchase.menu_purchase_root', + 'stock.menu_stock_root', + 'sign.menu_document', + 'hr.menu_hr_root', + 'hr_work_entry_enterprise.menu_hr_payroll_root', + 'hr_attendance.menu_hr_attendance_root', + 'hr_recruitment.menu_hr_recruitment_root', + 'hr_expense.menu_hr_expense_root', + 'iot.iot_menu_root', + 'utm.menu_link_tracker_root', + 'base.menu_management', +] + + +def _fp_apply_office_user_menu_visibility(env): + """Set group_ids = [group_fp_office_user] on every menu in + MENU_HIDE_FROM_TECHNICIANS that exists in this DB. + + Field is `group_ids` on ir.ui.menu in Odoo 19 (was `groups_id` in + earlier versions — Odoo 18 renamed it). Same naming-rename pattern + as res.users (CLAUDE.md Critical Rule 13c). + + Idempotent: if a menu already has only the office_user group, no + change is made. If it has additional groups (e.g. a previous custom + restriction), they're REPLACED — the design accepts this trade-off + because office_user is implied by every fp role above Technician, + so non-fp users keep their access on entech. + + Cross-module xmlids: env.ref(..., raise_if_not_found=False) returns + None for menus from uninstalled modules, which we silently skip. + """ + office = env.ref( + 'fusion_plating.group_fp_office_user', raise_if_not_found=False, + ) + if not office: + _logger.warning( + '[menu-visibility] group_fp_office_user not found; skipping' + ) + return + touched = 0 + for xmlid in MENU_HIDE_FROM_TECHNICIANS: + menu = env.ref(xmlid, raise_if_not_found=False) + if not menu: + continue + current_ids = set(menu.group_ids.ids) + if current_ids == {office.id}: + continue # already locked-down, nothing to do + menu.sudo().group_ids = [(6, 0, [office.id])] + touched += 1 + _logger.info( + '[menu-visibility] restricted %s menu(s) to group_fp_office_user', + touched, + ) def _fp_post_init_role_migration(env): diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index 98947f2e..30bef953 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.3.0', + 'version': '19.0.21.4.0', 'category': 'Manufacturing/Plating', 'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.', 'description': """ @@ -82,6 +82,13 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved. 'security/fp_security.xml', 'security/fp_security_v2.xml', 'security/ir.model.access.csv', + # Menu visibility — loads after fp_security_v2.xml so the role + # group xmlids exist when we add office_user to their + # implied_ids. Loads after fp_menu.xml in spirit BUT references + # cross-module menus (calendar, sale, hr, etc.) which exist by + # the time fusion_plating loads, so safe to load here at + # security-config time. + 'security/fp_menu_visibility.xml', 'data/fp_landing_data.xml', 'data/fp_sequence_data.xml', 'data/fp_job_sequences.xml', diff --git a/fusion_plating/fusion_plating/migrations/19.0.21.4.0/post-migrate.py b/fusion_plating/fusion_plating/migrations/19.0.21.4.0/post-migrate.py new file mode 100644 index 00000000..56b9b8a9 --- /dev/null +++ b/fusion_plating/fusion_plating/migrations/19.0.21.4.0/post-migrate.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +"""19.0.21.4.0 — Apply office-user menu visibility on -u. + +post_init_hook only fires on FIRST install (CLAUDE.md Rule 13d). +This script runs the same helper on every -u so existing installs +get the menu restrictions applied without needing to uninstall + +reinstall. Idempotent — the helper checks current state and skips +already-restricted menus. +""" +import logging + +from odoo.api import Environment, SUPERUSER_ID + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + from odoo.addons.fusion_plating import _fp_apply_office_user_menu_visibility + env = Environment(cr, SUPERUSER_ID, {}) + _fp_apply_office_user_menu_visibility(env) diff --git a/fusion_plating/fusion_plating/security/fp_menu_visibility.xml b/fusion_plating/fusion_plating/security/fp_menu_visibility.xml new file mode 100644 index 00000000..ee86d673 --- /dev/null +++ b/fusion_plating/fusion_plating/security/fp_menu_visibility.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Plating: Office User (sees back-office menus) + + 90 + Marker group that controls visibility of + non-tablet app menus (Calendar, Sales, Inventory, etc.). + Implied by every fp role above Technician (Owner, Manager, + Quality Manager, Shop Manager, Sales Rep, Estimator). + Pure Technicians don't have it, so they only see the + tablet apps (Plating, Discuss, To-do, AI, Maintenance, + Time Off). + + + + + + + + + + + + + + + + + + + + + + + +