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

@@ -6,6 +6,7 @@ import base64
import json
from datetime import date, datetime, timedelta
from .auth import sign_token, current_user, require_role, verify_token
from ..roles import ROLE_SELECTION, ROLE_KEYS, MENU_ROLES_KEY, get_menu_roles
def json_response(data, status=200):
@@ -100,7 +101,7 @@ class SkeenFrontendController(http.Controller):
return json_response({'status': 'error', 'message': 'login y name son obligatorios'}, 400)
if len(password) < 8:
return json_response({'status': 'error', 'message': 'La contraseña debe tener al menos 8 caracteres'}, 400)
if role not in ('admin', 'recepcion', 'medico', 'lectura'):
if role not in ROLE_KEYS:
return json_response({'status': 'error', 'message': 'Rol inválido'}, 400)
User = request.env['skeen.frontend.user'].sudo()
if User.search([('login', '=', login)], limit=1):
@@ -123,7 +124,7 @@ class SkeenFrontendController(http.Controller):
if 'name' in data:
vals['name'] = data['name']
if 'role' in data:
if data['role'] not in ('admin', 'recepcion', 'medico', 'lectura'):
if data['role'] not in ROLE_KEYS:
return json_response({'status': 'error', 'message': 'Rol inválido'}, 400)
vals['role'] = data['role']
if 'active' in data:
@@ -159,6 +160,43 @@ class SkeenFrontendController(http.Controller):
except Exception as e:
return json_response({'status': 'error', 'message': str(e)}, 500)
# ============================================================
# Permisos de menú por rol (matriz dinámica)
# ============================================================
@http.route('/skeen/frontend/v1/roles/permissions', type='http', auth='none', methods=['GET', 'OPTIONS'], csrf=False)
@require_role('lectura')
def roles_permissions_get(self, **kw):
try:
return json_response({
'status': 'success',
'matrix': get_menu_roles(request.env),
'roles': [{'key': k, 'label': l} for k, l in ROLE_SELECTION],
})
except Exception as e:
return json_response({'status': 'error', 'message': str(e)}, 500)
@http.route('/skeen/frontend/v1/roles/permissions', type='http', auth='none', methods=['PUT', 'OPTIONS'], csrf=False)
@require_role('admin')
def roles_permissions_put(self, **kw):
try:
data = _parse_json_body()
matrix = data.get('matrix')
if not isinstance(matrix, dict):
return json_response({'status': 'error', 'message': 'matrix debe ser un objeto {href: [roles]}'}, 400)
clean = {}
for href, roles in matrix.items():
if not isinstance(href, str):
return json_response({'status': 'error', 'message': 'Las llaves de matrix deben ser strings (href)'}, 400)
if not isinstance(roles, list) or any(not isinstance(r, str) or r not in ROLE_KEYS for r in roles):
return json_response({'status': 'error', 'message': f'Roles inválidos para {href}'}, 400)
clean[href] = list(dict.fromkeys(roles))
if 'admin' not in clean.get('/configuracion', []):
return json_response({'status': 'error', 'message': "No se puede quitar '/configuracion' al rol admin"}, 400)
request.env['ir.config_parameter'].sudo().set_param(MENU_ROLES_KEY, json.dumps(clean))
return json_response({'status': 'success', 'matrix': clean})
except Exception as e:
return json_response({'status': 'error', 'message': str(e)}, 500)
# ============================================================
# Dashboard
# ============================================================