323 lines
12 KiB
Python
323 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Catálogo de permisos por ACCIÓN y defaults por rol.
|
|
|
|
A diferencia de la matriz de menús (qué secciones ve un rol) y de los permisos
|
|
de agenda por usuario, esto responde a "qué puede HACER" cada rol dentro de las
|
|
pantallas: cancelar un ticket, aplicar descuentos, cerrar caja, exportar,
|
|
administrar usuarios, etc.
|
|
|
|
Sin dependencias de Odoo para poder importarse desde modelos y controladores.
|
|
|
|
Persistencia: ir.config_parameter 'skeen.role_perms' = JSON
|
|
{role_code: {perm_key: true/false}}. Lo guardado manda sobre los defaults; no
|
|
hay que persistir los defaults (se calculan).
|
|
|
|
REGLA DE ORO: con la configuración por defecto TODO se comporta como antes.
|
|
Los defaults solo son restrictivos donde ya lo era el guard viejo
|
|
(@require_role) o donde el rol es claramente de solo lectura. Todo lo demás
|
|
queda permitido para los roles de escritura y son los guards existentes
|
|
(veto de 'medico' a dinero, forbid_roles('marketing'), _puede_gestionar_*)
|
|
los que siguen mandando. Así ningún usuario pierde acceso al desplegar.
|
|
"""
|
|
|
|
import json
|
|
|
|
ROLE_PERMS_KEY = 'skeen.role_perms'
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Catálogo: grupos -> [(clave, etiqueta)]. El orden es el de la UI.
|
|
# ---------------------------------------------------------------------------
|
|
PERMISSION_GROUPS = [
|
|
('agenda', 'Agenda', [
|
|
('agenda.ver', 'Consultar la agenda'),
|
|
('agenda.agendar', 'Agendar citas'),
|
|
('agenda.editar', 'Editar / reprogramar citas'),
|
|
('agenda.eliminar', 'Eliminar o cancelar citas'),
|
|
('agenda.mover', 'Mover citas de horario'),
|
|
('agenda.visitas', 'Generar visitas (check-in)'),
|
|
('agenda.todas', 'Ver la agenda de todos (no solo la propia)'),
|
|
('agenda.bloquear', 'Bloquear / cerrar agenda'),
|
|
]),
|
|
('pacientes', 'Pacientes', [
|
|
('pacientes.ver', 'Ver pacientes'),
|
|
('pacientes.expediente', 'Ver expediente completo'),
|
|
('pacientes.crear', 'Crear pacientes'),
|
|
('pacientes.editar', 'Editar pacientes'),
|
|
('pacientes.eliminar', 'Eliminar pacientes'),
|
|
('pacientes.exportar', 'Exportar pacientes'),
|
|
('pacientes.saldos', 'Ver saldos y adeudos'),
|
|
]),
|
|
('visitas', 'Visitas y expedientes', [
|
|
('visitas.ver', 'Ver visitas'),
|
|
('visitas.crear', 'Crear visitas / consultas'),
|
|
('visitas.editar', 'Editar visitas'),
|
|
('visitas.eliminar', 'Eliminar visitas'),
|
|
('visitas.archivos', 'Subir archivos al expediente'),
|
|
]),
|
|
('ventas', 'Ventas y cobros', [
|
|
('ventas.vender', 'Vender (punto de venta)'),
|
|
('ventas.cobrar', 'Cobrar y registrar pagos'),
|
|
('ventas.descuentos', 'Aplicar descuentos'),
|
|
('ventas.impuestos', 'Ajustar el impuesto por línea en la venta'),
|
|
('ventas.cancelar', 'Cancelar tickets / ventas'),
|
|
('ventas.devolver', 'Hacer devoluciones'),
|
|
('ventas.reimprimir', 'Reimprimir tickets'),
|
|
]),
|
|
('inventario', 'Inventario', [
|
|
('inventario.ver', 'Ver inventario'),
|
|
('inventario.ajustar', 'Ajustar existencias'),
|
|
('inventario.mover', 'Registrar movimientos / compras / bajas'),
|
|
('inventario.costos', 'Editar costos'),
|
|
]),
|
|
('servicios', 'Servicios y catálogo', [
|
|
('servicios.ver', 'Ver servicios y catálogo'),
|
|
('servicios.editar', 'Editar servicios y precios'),
|
|
]),
|
|
('reportes', 'Reportes y caja', [
|
|
('reportes.ver', 'Ver reportes'),
|
|
('reportes.exportar', 'Exportar reportes'),
|
|
('caja.ver', 'Ver cortes de caja'),
|
|
('caja.cerrar', 'Cerrar caja'),
|
|
]),
|
|
('whatsapp', 'WhatsApp y leads', [
|
|
('whatsapp.ver', 'Ver conversaciones y leads'),
|
|
('whatsapp.responder', 'Responder conversaciones'),
|
|
('whatsapp.asignar', 'Asignar conversaciones y leads'),
|
|
]),
|
|
('notificaciones', 'Notificaciones', [
|
|
('notificaciones.ver', 'Ver el historial de notificaciones'),
|
|
('notificaciones.enviar', 'Enviar y reintentar notificaciones'),
|
|
('notificaciones.editar', 'Configurar recordatorios y plantillas'),
|
|
]),
|
|
('formularios', 'Formularios y consentimientos', [
|
|
('formularios.ver', 'Ver formularios y respuestas'),
|
|
('formularios.crear', 'Llenar formularios (recepción)'),
|
|
('formularios.editar', 'Administrar plantillas de formularios'),
|
|
('formularios.enviar', 'Enviar formularios a pacientes'),
|
|
]),
|
|
('config', 'Usuarios y configuración', [
|
|
('config.usuarios', 'Administrar usuarios'),
|
|
('config.roles', 'Administrar roles y menús'),
|
|
('config.editar', 'Editar la configuración de la clínica'),
|
|
('config.auditoria', 'Ver la auditoría'),
|
|
]),
|
|
]
|
|
|
|
PERM_KEYS = tuple(
|
|
key for _g, _label, perms in PERMISSION_GROUPS for key, _l in perms
|
|
)
|
|
|
|
PERM_LABELS = {
|
|
key: label for _g, _label, perms in PERMISSION_GROUPS for key, label in perms
|
|
}
|
|
|
|
# Permisos que sólo son lectura: son los que conserva el rol 'lectura'.
|
|
READ_PERM_KEYS = (
|
|
'agenda.ver', 'agenda.todas',
|
|
'pacientes.ver', 'pacientes.expediente', 'pacientes.exportar', 'pacientes.saldos',
|
|
'visitas.ver',
|
|
'inventario.ver',
|
|
'servicios.ver',
|
|
'reportes.ver', 'reportes.exportar', 'caja.ver',
|
|
'ventas.reimprimir',
|
|
'whatsapp.ver',
|
|
'notificaciones.ver',
|
|
'formularios.ver',
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Conjuntos de roles para los defaults.
|
|
# W = roles con rango de escritura (todo menos 'lectura').
|
|
# WN = W sin marketing (a quien forbid_roles cierra los módulos de dinero).
|
|
# MONEY = W sin medico ni marketing (veto de medico a las pantallas de dinero).
|
|
# AG = admin + gerencia (los que hoy administran usuarios/roles/catálogos).
|
|
# AD = admin (lo que hoy exige @require_role('admin')).
|
|
# ALL = W + lectura.
|
|
# ---------------------------------------------------------------------------
|
|
W = ('admin', 'medico', 'recepcion', 'enfermeria', 'cosmetologia',
|
|
'psicologa', 'farmacia', 'gerencia', 'marketing')
|
|
WN = tuple(r for r in W if r != 'marketing')
|
|
MONEY = tuple(r for r in W if r not in ('medico', 'marketing'))
|
|
AG = ('admin', 'gerencia')
|
|
AD = ('admin',)
|
|
ALL = W + ('lectura',)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Defaults por permiso. Tabla explicable:
|
|
# - lectura -> solo los READ_PERM_KEYS.
|
|
# - W -> escritura general (agenda, pacientes, visitas, visitas...).
|
|
# - WN / MONEY -> bloques de dinero: coinciden con require_role('recepcion')
|
|
# + forbid_roles('marketing') y con el veto de 'medico'.
|
|
# - AG / AD -> administración (hoy restringida a admin/gerencia o admin).
|
|
# ---------------------------------------------------------------------------
|
|
DEFAULT_PERM_ROLES = {
|
|
# Agenda (hoy: require_role('recepcion') para escribir)
|
|
'agenda.ver': ALL,
|
|
'agenda.agendar': W,
|
|
'agenda.editar': W,
|
|
'agenda.eliminar': W,
|
|
'agenda.mover': W,
|
|
'agenda.visitas': W,
|
|
'agenda.todas': W,
|
|
'agenda.bloquear': W,
|
|
# Pacientes
|
|
'pacientes.ver': ALL,
|
|
'pacientes.expediente': ALL,
|
|
'pacientes.crear': W,
|
|
'pacientes.editar': W,
|
|
'pacientes.eliminar': W,
|
|
'pacientes.exportar': ALL,
|
|
'pacientes.saldos': ALL,
|
|
# Visitas
|
|
'visitas.ver': ALL,
|
|
'visitas.crear': W,
|
|
'visitas.editar': W,
|
|
'visitas.eliminar': W,
|
|
'visitas.archivos': W,
|
|
# Ventas y cobros
|
|
'ventas.vender': MONEY,
|
|
'ventas.cobrar': WN, # medico también cobra depósitos de paciente hoy
|
|
'ventas.descuentos': MONEY,
|
|
'ventas.impuestos': MONEY,
|
|
'ventas.cancelar': AG, # hoy: admin/gerencia (chequeo explícito en el endpoint)
|
|
'ventas.devolver': MONEY,
|
|
'ventas.reimprimir': ALL,
|
|
# Inventario (hoy: require_role('recepcion') + forbid_roles('marketing'))
|
|
'inventario.ver': ALL,
|
|
'inventario.ajustar': WN,
|
|
'inventario.mover': WN,
|
|
'inventario.costos': WN,
|
|
# Servicios y catálogo (hoy services POST/PUT/DELETE exigen admin)
|
|
'servicios.ver': ALL,
|
|
'servicios.editar': AD,
|
|
# Reportes y caja
|
|
'reportes.ver': ALL,
|
|
'reportes.exportar': ALL,
|
|
'caja.ver': ALL,
|
|
'caja.cerrar': MONEY,
|
|
# WhatsApp y leads (menú WACRM: admin, recepcion, gerencia, marketing)
|
|
'whatsapp.ver': W,
|
|
'whatsapp.responder': W,
|
|
'whatsapp.asignar': W,
|
|
# Notificaciones de citas: ver es de todos; enviar lo hace recepción y
|
|
# quien agenda; configurar (plantillas y conexión) es de admin/gerencia.
|
|
'notificaciones.ver': ALL,
|
|
'notificaciones.enviar': W,
|
|
'notificaciones.editar': AG,
|
|
# Formularios digitales y consentimientos: ver es de todos; llenar/enviar lo
|
|
# hace quien atiende al paciente; administrar plantillas es de admin/gerencia.
|
|
'formularios.ver': ALL,
|
|
'formularios.crear': ('admin', 'medico', 'recepcion', 'enfermeria', 'gerencia'),
|
|
'formularios.editar': AG,
|
|
'formularios.enviar': ('admin', 'medico', 'recepcion', 'enfermeria', 'gerencia'),
|
|
# Usuarios y configuración
|
|
'config.usuarios': AG,
|
|
'config.roles': AG,
|
|
'config.editar': AG,
|
|
'config.auditoria': AD,
|
|
}
|
|
|
|
# Los roles nuevos (creados desde la UI) arrancan como 'lectura': el admin
|
|
# decide después qué permisos les da. Si no, cualquier rol nuevo tendría acceso
|
|
# de escritura sin que nadie lo pidiera.
|
|
NEW_ROLE_BASE = 'lectura'
|
|
|
|
|
|
def default_perms_for(role_code):
|
|
"""Dict {perm_key: bool} por defecto para un rol (base por rango)."""
|
|
base_role = role_code if role_code in W or role_code == 'lectura' else NEW_ROLE_BASE
|
|
perms = {}
|
|
for key in PERM_KEYS:
|
|
if base_role == 'lectura':
|
|
perms[key] = key in READ_PERM_KEYS
|
|
else:
|
|
perms[key] = base_role in (DEFAULT_PERM_ROLES.get(key) or ())
|
|
return perms
|
|
|
|
|
|
def default_role_perms():
|
|
"""Defaults completos {role_code: {perm_key: bool}} para los 10 roles base.
|
|
|
|
No incluye roles personalizados (no se conocen sin consultar Odoo); el
|
|
frontend usa su propio espejo para eso.
|
|
"""
|
|
return {code: default_perms_for(code) for code in
|
|
('admin', 'medico', 'recepcion', 'enfermeria', 'cosmetologia',
|
|
'psicologa', 'farmacia', 'gerencia', 'marketing', 'lectura')}
|
|
|
|
|
|
def _stored(env):
|
|
"""Overrides guardados {role_code: {perm_key: bool}} (o {})."""
|
|
raw = env['ir.config_parameter'].sudo().get_param(ROLE_PERMS_KEY)
|
|
if not raw:
|
|
return {}
|
|
try:
|
|
val = json.loads(raw)
|
|
except Exception:
|
|
return {}
|
|
if not isinstance(val, dict):
|
|
return {}
|
|
clean = {}
|
|
for role, perms in val.items():
|
|
if isinstance(perms, dict):
|
|
clean[role] = {k: bool(v) for k, v in perms.items() if k in PERM_KEYS}
|
|
return clean
|
|
|
|
|
|
def resolve_role_perms(env, role_code):
|
|
"""Permisos vigentes de un rol: defaults + overrides guardados."""
|
|
perms = default_perms_for(role_code)
|
|
perms.update(_stored(env).get(role_code) or {})
|
|
return perms
|
|
|
|
|
|
def resolve_role_perms_for_user(user):
|
|
"""Permisos vigentes del usuario (por su rol). None -> todo False."""
|
|
if user is None:
|
|
return {k: False for k in PERM_KEYS}
|
|
return resolve_role_perms(user.env, user.role)
|
|
|
|
|
|
def has_perm(user, perm_key):
|
|
"""True si el usuario puede ejecutar la acción. Admin siempre puede."""
|
|
if user is None:
|
|
return False
|
|
if user.role == 'admin':
|
|
return True
|
|
return bool(resolve_role_perms_for_user(user).get(perm_key))
|
|
|
|
|
|
def all_roles_perms(env):
|
|
"""{role_code: {perm_key: bool}} para todos los roles en skeen.role."""
|
|
roles = env['skeen.role'].sudo().with_context(active_test=False).search([])
|
|
codes = [r.code for r in roles if r.code]
|
|
if not codes:
|
|
return default_role_perms()
|
|
return {code: resolve_role_perms(env, code) for code in codes}
|
|
|
|
|
|
def set_role_perms(env, role_code, perms):
|
|
"""Guarda permisos de un rol (merge con lo ya guardado). False si no aplica."""
|
|
rol = env['skeen.role'].sudo().with_context(active_test=False).search(
|
|
[('code', '=', role_code)], limit=1)
|
|
if not rol:
|
|
return False
|
|
clean = {k: bool(v) for k, v in (perms or {}).items() if k in PERM_KEYS}
|
|
if not clean:
|
|
return False
|
|
stored = _stored(env)
|
|
current = dict(stored.get(role_code) or {})
|
|
current.update(clean)
|
|
stored[role_code] = current
|
|
env['ir.config_parameter'].sudo().set_param(ROLE_PERMS_KEY, json.dumps(stored))
|
|
return True
|
|
|
|
|
|
def reset_role_perms(env, role_code):
|
|
"""Quita los overrides de un rol (vuelve a los defaults)."""
|
|
stored = _stored(env)
|
|
if role_code in stored:
|
|
stored.pop(role_code)
|
|
env['ir.config_parameter'].sudo().set_param(ROLE_PERMS_KEY, json.dumps(stored))
|
|
return True
|