feat(fusion_plating): hide back-office menus from Plating Technicians

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) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-24 20:00:15 -04:00
parent 7dab5fb9c6
commit bfc138251a
4 changed files with 183 additions and 1 deletions

View File

@@ -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):