Files
SKEEN-Proyecto/odoo-addons/skeen_whatsapp/permissions.py

382 lines
15 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'),
]),
('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'),
]),
('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'),
]),
('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',
'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',
'membresias.ver',
'compras.ver',
'proveedores.ver',
'comisiones.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,
# 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'),
# 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,
# 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