Roles ampliados (10) y matriz de permisos por rol

- Roles: admin, médico, recepción, enfermería, cosmetología, psicóloga,
  farmacia, entrenador, marketing + lectura (compatibilidad)
- Matriz de permisos por rol persistida en Odoo (ir.config_parameter)
  y editable en Configuración → Roles (secciones × roles)
- Prioridad: permisos personales del usuario > matriz por rol > defaults
- Protección: admin siempre conserva /configuracion
This commit is contained in:
2026-08-14 08:47:27 +00:00
parent be42974030
commit 281fb34588
9 changed files with 346 additions and 29 deletions

View File

@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
"""Roles del frontend React y matriz de permisos de menú por rol.
Módulo sin dependencias de Odoo para poder importarse tanto desde modelos
como desde controladores sin problemas de orden de carga.
La matriz se persiste en ir.config_parameter 'skeen.menu_roles' como JSON
{href: [roles...]}. Si no existe, se usan los defaults de DEFAULT_MENU_ROLES
(mismos que MENU_ROLES hardcodeado del frontend) y los 6 roles nuevos heredan
el mismo set de secciones que 'lectura'.
"""
import json
ROLE_SELECTION = [
('admin', 'Administrador'),
('medico', 'Médico'),
('recepcion', 'Recepción'),
('enfermeria', 'Enfermería'),
('cosmetologia', 'Cosmetología'),
('psicologa', 'Psicóloga'),
('farmacia', 'Farmacia'),
('entrenador', 'Entrenador'),
('marketing', 'Marketing'),
('lectura', 'Lectura'),
]
ROLE_KEYS = tuple(k for k, _ in ROLE_SELECTION)
ROLE_RANKS = {
'admin': 100,
'medico': 50,
'recepcion': 50,
'enfermeria': 50,
'cosmetologia': 50,
'psicologa': 50,
'farmacia': 50,
'entrenador': 10,
'marketing': 10,
'lectura': 10,
}
MENU_ROLES_KEY = 'skeen.menu_roles'
# Defaults = MENU_ROLES de src/lib/auth.tsx (4 roles originales).
DEFAULT_MENU_ROLES = {
'/': ['admin', 'recepcion', 'medico', 'lectura'],
'/agenda': ['admin', 'recepcion', 'medico'],
'/pacientes': ['admin', 'recepcion', 'medico', 'lectura'],
'/pos': ['admin', 'recepcion'],
'/visitas': ['admin', 'recepcion', 'medico'],
'/consultas': ['admin', 'recepcion', 'medico'],
'/expedientes': ['admin', 'recepcion', 'medico'],
'/medicos': ['admin', 'recepcion'],
'/servicios': ['admin', 'recepcion', 'medico', 'lectura'],
'/productos': ['admin', 'recepcion', 'lectura'],
'/ventas': ['admin', 'recepcion'],
'/pagos': ['admin', 'recepcion'],
'/monedero': ['admin', 'recepcion', 'lectura'],
'/inventario': ['admin', 'recepcion', 'lectura'],
'/cortes': ['admin', 'recepcion'],
'/reportes': ['admin', 'recepcion', 'lectura'],
'/reportes?tab=adeudos': ['admin', 'recepcion', 'lectura'],
'/reportes?tab=comisiones': ['admin', 'recepcion', 'lectura'],
'/reportes?tab=horas-agenda': ['admin', 'recepcion', 'lectura'],
'/reportes?tab=paquetes': ['admin', 'recepcion', 'lectura'],
'/reportes?tab=vendedores': ['admin', 'recepcion', 'lectura'],
'/reportes?tab=concentrado': ['admin', 'recepcion', 'lectura'],
'/reportes?tab=recomendaciones': ['admin', 'recepcion', 'lectura'],
'/reportes?tab=exportar': ['admin', 'recepcion', 'lectura'],
'/configuracion': ['admin'],
'/cumpleanos': ['admin', 'recepcion'],
'/wacrm/messages': ['admin', 'recepcion'],
'/wacrm/leads': ['admin', 'recepcion'],
}
# Roles nuevos que por default ven lo mismo que 'lectura'.
READ_ONLY_NEW_ROLES = ('enfermeria', 'cosmetologia', 'psicologa', 'farmacia', 'entrenador', 'marketing')
def default_menu_roles():
"""Matriz por default: MENU_ROLES + roles nuevos donde esté 'lectura'."""
matrix = {}
for href, roles in DEFAULT_MENU_ROLES.items():
r = list(roles)
if 'lectura' in roles:
r.extend(nr for nr in READ_ONLY_NEW_ROLES if nr not in r)
matrix[href] = r
return matrix
def get_menu_roles(env):
"""Matriz vigente: la guardada en ir.config_parameter o los defaults."""
raw = env['ir.config_parameter'].sudo().get_param(MENU_ROLES_KEY)
if raw:
try:
val = json.loads(raw)
if isinstance(val, dict):
return val
except Exception:
pass
return default_menu_roles()