459 lines
19 KiB
Python
459 lines
19 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'),
|
|
]),
|
|
('recursos', 'Recursos de la agenda', [
|
|
('recursos.ver', 'Ver recursos (cabinas, salas, equipos)'),
|
|
('recursos.editar', 'Administrar recursos y su configuración'),
|
|
]),
|
|
('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'),
|
|
('portal.invitar', 'Invitar al portal del cliente y cerrar sus sesiones'),
|
|
('portal.sesiones', 'Ver quién tiene acceso al portal del cliente'),
|
|
]),
|
|
('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'),
|
|
]),
|
|
('campanas', 'Campañas y segmentos', [
|
|
('campanas.ver', 'Ver campañas y segmentos'),
|
|
('campanas.gestionar', 'Crear y editar campañas y segmentos'),
|
|
('campanas.enviar', 'Lanzar campañas y enviar pruebas'),
|
|
('campanas.medir', 'Ver la medición de las campañas'),
|
|
]),
|
|
('encuestas', 'Encuestas de salida (NPS)', [
|
|
('encuestas.ver', 'Ver la configuración de encuestas'),
|
|
('encuestas.gestionar', 'Configurar y enviar las encuestas de salida'),
|
|
('encuestas.medir', 'Ver el reporte de satisfacción'),
|
|
]),
|
|
('membresias', 'Membresías y suscripciones', [
|
|
('membresias.ver', 'Ver membresías, planes y adeudos'),
|
|
('membresias.crear', 'Dar de alta membresías'),
|
|
('membresias.cobrar', 'Cobrar y renovar membresías'),
|
|
('membresias.editar', 'Editar planes y membresías'),
|
|
('membresias.cancelar', 'Congelar y cancelar membresías'),
|
|
]),
|
|
('compras', 'Compras y proveedores', [
|
|
('compras.ver', 'Ver compras, cuentas por pagar y margen'),
|
|
('compras.crear', 'Crear órdenes de compra'),
|
|
('compras.recibir', 'Recibir mercancía (afecta stock y costo)'),
|
|
('compras.pagar', 'Registrar pagos a proveedores'),
|
|
('compras.editar', 'Editar órdenes de compra'),
|
|
('compras.cancelar', 'Cancelar órdenes de compra'),
|
|
('proveedores.ver', 'Ver proveedores'),
|
|
('proveedores.editar', 'Administrar proveedores'),
|
|
]),
|
|
('comisiones', 'Comisiones y liquidaciones', [
|
|
('comisiones.ver', 'Ver comisiones y liquidaciones'),
|
|
('comisiones.editar', 'Administrar reglas y metas de comisiones'),
|
|
('comisiones.liquidar', 'Generar, ajustar y pagar liquidaciones'),
|
|
]),
|
|
('lealtad', 'Lealtad y puntos', [
|
|
('lealtad.ver', 'Ver saldo, nivel y reportes de lealtad'),
|
|
('lealtad.ajustar', 'Ajustar puntos a mano y recalcular el nivel'),
|
|
('lealtad.nivel', 'Asignar el nivel VIP a mano'),
|
|
('lealtad.configurar', 'Configurar el programa de lealtad y sus niveles'),
|
|
]),
|
|
('propinas', 'Propinas', [
|
|
('propinas.ver', 'Ver el reporte de propinas'),
|
|
('propinas.aplicar', 'Aplicar propinas en el punto de venta'),
|
|
('propinas.configurar', 'Configurar propinas y su reparto'),
|
|
]),
|
|
('cobros', 'Cobros y enlaces de pago', [
|
|
('cobros.ver', 'Ver enlaces de cobro pendientes y pagados'),
|
|
('cobros.administrar', 'Administrar enlaces de cobro'),
|
|
('cobros.forzar', 'Forzar el cobro de un enlace'),
|
|
('cobros.cancelar', 'Cancelar enlaces y liberar retenciones'),
|
|
]),
|
|
('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'),
|
|
]),
|
|
('plataforma', 'Licencia, API y Webhooks', [
|
|
('api.claves', 'Administrar claves de API'),
|
|
('webhooks.editar', 'Administrar webhooks'),
|
|
]),
|
|
]
|
|
|
|
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',
|
|
'recursos.ver',
|
|
'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',
|
|
'campanas.ver', 'campanas.medir',
|
|
'encuestas.ver', 'encuestas.medir',
|
|
'membresias.ver',
|
|
'compras.ver',
|
|
'proveedores.ver',
|
|
'comisiones.ver',
|
|
'cobros.ver',
|
|
'lealtad.ver',
|
|
'propinas.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,
|
|
# Recursos de la agenda: ver es de todos; administrarlos es de admin/gerencia.
|
|
'recursos.ver': ALL,
|
|
'recursos.editar': AG,
|
|
# Pacientes
|
|
'pacientes.ver': ALL,
|
|
'pacientes.expediente': ALL,
|
|
'pacientes.crear': W,
|
|
'pacientes.editar': W,
|
|
'pacientes.eliminar': W,
|
|
'pacientes.exportar': ALL,
|
|
'pacientes.saldos': ALL,
|
|
# Portal del cliente: invitar y cerrar sesiones, y ver el listado de
|
|
# accesos, es de recepción/gerencia/admin (dato de contacto del paciente).
|
|
'portal.invitar': ('admin', 'recepcion', 'gerencia'),
|
|
'portal.sesiones': ('admin', 'recepcion', 'gerencia'),
|
|
# 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'),
|
|
# Campañas por segmentos: ver y medir es de todos; gestionar y lanzar es de
|
|
# administración/gerencia y del rol de marketing (su dueño natural).
|
|
'campanas.ver': ALL,
|
|
'campanas.gestionar': ('admin', 'gerencia', 'marketing'),
|
|
'campanas.enviar': ('admin', 'gerencia', 'marketing'),
|
|
'campanas.medir': ALL,
|
|
# Encuestas de salida (NPS): ver y medir es de todos; configurar y enviar
|
|
# es de admin/gerencia.
|
|
'encuestas.ver': ALL,
|
|
'encuestas.gestionar': AG,
|
|
'encuestas.medir': ALL,
|
|
# Membresías y suscripciones. Ver es de todos; dar de alta y cobrar es de
|
|
# recepción/gerencia/admin; editar planes y cancelar/congelar es de
|
|
# gerencia/admin (contra el dinero recurrente de la clínica).
|
|
'membresias.ver': ALL,
|
|
'membresias.crear': ('admin', 'recepcion', 'gerencia'),
|
|
'membresias.cobrar': ('admin', 'recepcion', 'gerencia'),
|
|
'membresias.editar': AG,
|
|
'membresias.cancelar': AG,
|
|
# Compras y proveedores. Ver es de todos; crear/recibir/pagar es de
|
|
# recepción/gerencia/admin (igual que inventario y membresías); editar,
|
|
# cancelar y administrar proveedores es de admin/gerencia (tocan dinero).
|
|
'compras.ver': ALL,
|
|
'compras.crear': ('admin', 'recepcion', 'gerencia'),
|
|
'compras.recibir': ('admin', 'recepcion', 'gerencia'),
|
|
'compras.pagar': ('admin', 'recepcion', 'gerencia'),
|
|
'compras.editar': AG,
|
|
'compras.cancelar': AG,
|
|
'proveedores.ver': ALL,
|
|
'proveedores.editar': AG,
|
|
# Comisiones y liquidaciones. Ver es de todos (el rol médico ve SOLO las
|
|
# suyas: el backend filtra por su empleado); administrar el motor de reglas y
|
|
# metas, y liquidar/pagar, es de admin/gerencia (tocan dinero del personal).
|
|
'comisiones.ver': ALL,
|
|
'comisiones.editar': AG,
|
|
'comisiones.liquidar': AG,
|
|
# Cobros y enlaces de pago. Ver es de todos; administrar (crear/cancelar) es
|
|
# de admin/gerencia; forzar el cobro (registrar un pago sin pasarela) lo
|
|
# hace recepción/gerencia/admin; cancelar retenciones, admin/gerencia.
|
|
# Lealtad y puntos. Ver es de todos; ajustar puntos (equivalen a dinero) y
|
|
# asignar el nivel a mano es de admin/gerencia; configurar el programa
|
|
# (reglas, niveles) también.
|
|
'lealtad.ver': ALL,
|
|
'lealtad.ajustar': AG,
|
|
'lealtad.nivel': AG,
|
|
'lealtad.configurar': AG,
|
|
# Propinas. Ver el reporte es de todos; aplicar la propina al cobrar lo
|
|
# hace quien cobra (recepción); configurar el módulo y el reparto es de
|
|
# admin/gerencia.
|
|
'propinas.ver': ALL,
|
|
'propinas.aplicar': MONEY,
|
|
'propinas.configurar': AG,
|
|
'cobros.ver': ALL,
|
|
'cobros.administrar': AG,
|
|
'cobros.forzar': ('admin', 'recepcion', 'gerencia'),
|
|
'cobros.cancelar': AG,
|
|
# Usuarios y configuración
|
|
'config.usuarios': AG,
|
|
'config.roles': AG,
|
|
'config.editar': AG,
|
|
'config.auditoria': AD,
|
|
# Plataforma: claves de API solo admin; webhooks admin/gerencia.
|
|
'api.claves': AD,
|
|
'webhooks.editar': AG,
|
|
}
|
|
|
|
# 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
|