fase 3.5: licenciamiento por plan (clave firmada, planes basico/profesional/enterprise/custom, modulos incluidos y limites de usuarios/sucursales/citas/almacenamiento, modo aviso o bloqueo, panel de licencia) + API keys gestionables con alcances y revocacion (arregla la API publica que estaba rota) + webhooks salientes firmados con outbox, reintentos y bitacora; herramientas de vendedor y docs
This commit is contained in:
5
odoo-addons/skeen_plataforma/controllers/__init__.py
Normal file
5
odoo-addons/skeen_plataforma/controllers/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import licencia
|
||||
from . import api_keys
|
||||
from . import webhooks
|
||||
125
odoo-addons/skeen_plataforma/controllers/api_keys.py
Normal file
125
odoo-addons/skeen_plataforma/controllers/api_keys.py
Normal file
@@ -0,0 +1,125 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""CRUD de claves de API de terceros (Configuración -> API)."""
|
||||
|
||||
import json
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request, Response
|
||||
|
||||
from odoo.addons.skeen_whatsapp.controllers.auth import require_role, require_perm
|
||||
from odoo.addons.skeen_plataforma.models.api_key import SCOPES
|
||||
|
||||
BASE = '/skeen/frontend/v1'
|
||||
|
||||
|
||||
def _json(data, status=200):
|
||||
return Response(
|
||||
json.dumps(data, default=str),
|
||||
status=status,
|
||||
mimetype='application/json',
|
||||
headers={
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _body():
|
||||
try:
|
||||
raw = request.httprequest.get_data(as_text=True)
|
||||
return json.loads(raw) if raw else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _login():
|
||||
return getattr(request.frontend_user, 'login', '') if hasattr(request, 'frontend_user') else ''
|
||||
|
||||
|
||||
class SkeenApiKeysController(http.Controller):
|
||||
|
||||
@http.route(BASE + '/api_keys', type='http', auth='none',
|
||||
methods=['GET', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('api.claves')
|
||||
def list_api_keys(self, **kw):
|
||||
try:
|
||||
keys = request.env['skeen.api.key'].sudo().search([])
|
||||
return _json({
|
||||
'status': 'success',
|
||||
'api_keys': [k.to_public_dict() for k in keys],
|
||||
'scopes': [{'clave': c, 'label': l} for c, l in SCOPES],
|
||||
})
|
||||
except Exception as e:
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/api_keys', type='http', auth='none',
|
||||
methods=['POST', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('api.claves')
|
||||
def create_api_key(self, **kw):
|
||||
try:
|
||||
data = _body()
|
||||
nombre = (data.get('name') or '').strip()
|
||||
if not nombre:
|
||||
return _json({'status': 'error', 'message': 'El nombre es obligatorio'}, 400)
|
||||
key, raw = request.env['skeen.api.key'].sudo().generar({
|
||||
'name': nombre,
|
||||
'scopes': data.get('scopes') or [],
|
||||
'vence': data.get('vence') or False,
|
||||
'notas': data.get('notas') or '',
|
||||
}, login=_login())
|
||||
return _json({
|
||||
'status': 'success',
|
||||
'clave': raw, # se muestra UNA sola vez
|
||||
'api_key': key.to_public_dict(),
|
||||
'message': 'Guarda esta clave: no se volverá a mostrar.',
|
||||
})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/api_keys/<int:key_id>', type='http', auth='none',
|
||||
methods=['PUT', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('api.claves')
|
||||
def update_api_key(self, key_id, **kw):
|
||||
try:
|
||||
key = request.env['skeen.api.key'].sudo().browse(key_id)
|
||||
if not key.exists():
|
||||
return _json({'status': 'error', 'message': 'Clave no encontrada'}, 404)
|
||||
data = _body()
|
||||
vals = {}
|
||||
if 'name' in data and (data.get('name') or '').strip():
|
||||
vals['name'] = data['name'].strip()
|
||||
if 'scopes' in data:
|
||||
vals['scopes'] = json.dumps(key._normalizar_scopes(data.get('scopes') or []))
|
||||
if 'activo' in data:
|
||||
vals['activo'] = bool(data['activo'])
|
||||
if 'vence' in data:
|
||||
vals['vence'] = data.get('vence') or False
|
||||
if 'notas' in data:
|
||||
vals['notas'] = data.get('notas') or ''
|
||||
if vals:
|
||||
key.write(vals)
|
||||
return _json({'status': 'success', 'api_key': key.to_public_dict()})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/api_keys/<int:key_id>/revocar', type='http', auth='none',
|
||||
methods=['POST', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('api.claves')
|
||||
def revocar_api_key(self, key_id, **kw):
|
||||
try:
|
||||
key = request.env['skeen.api.key'].sudo().browse(key_id)
|
||||
if not key.exists():
|
||||
return _json({'status': 'error', 'message': 'Clave no encontrada'}, 404)
|
||||
key.action_revocar(login=_login())
|
||||
return _json({'status': 'success', 'message': 'Clave revocada.',
|
||||
'api_key': key.to_public_dict()})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
219
odoo-addons/skeen_plataforma/controllers/licencia.py
Normal file
219
odoo-addons/skeen_plataforma/controllers/licencia.py
Normal file
@@ -0,0 +1,219 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""API de licencia por plan (Configuración -> Licencia).
|
||||
|
||||
Solo administración del sistema (PUT) y administración/gerencia (GET/validar).
|
||||
Nunca bloquea el login, la agenda ni la configuración.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from odoo import fields, http
|
||||
from odoo.http import request, Response
|
||||
|
||||
from odoo.addons.skeen_whatsapp.controllers.auth import require_role, current_user
|
||||
from odoo.addons.skeen_citas.clinic_settings import (
|
||||
PLANES, MODULOS_BASE, MODULOS_GATEABLES, MODULO_LABEL, LIMITE_KEYS,
|
||||
get_settings, set_settings, licencia_de, modulos_licencia,
|
||||
licencia_bloqueo_activo, verificar_clave_licencia, licencia_desde_payload,
|
||||
dias_para_vencer, LICENCIA_DEFAULT,
|
||||
)
|
||||
|
||||
BASE = '/skeen/frontend/v1'
|
||||
LIMITE_LABEL = {
|
||||
'usuarios_max': 'Usuarios',
|
||||
'sucursales_max': 'Sucursales',
|
||||
'citas_mes_max': 'Citas del mes',
|
||||
'almacenamiento_mb': 'Almacenamiento (MB)',
|
||||
}
|
||||
AVISO_DIAS = 15
|
||||
|
||||
|
||||
def _json(data, status=200):
|
||||
return Response(
|
||||
json.dumps(data, default=str),
|
||||
status=status,
|
||||
mimetype='application/json',
|
||||
headers={
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _body():
|
||||
try:
|
||||
raw = request.httprequest.get_data(as_text=True)
|
||||
return json.loads(raw) if raw else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _puede_ver():
|
||||
u = current_user(request.env)
|
||||
return bool(u and u.role in ('admin', 'gerencia'))
|
||||
|
||||
|
||||
def _puede_editar():
|
||||
u = current_user(request.env)
|
||||
return bool(u and u.role == 'admin')
|
||||
|
||||
|
||||
def _uso(env):
|
||||
"""Uso actual de cada límite frente a su tope."""
|
||||
from datetime import date
|
||||
hoy = date.today()
|
||||
inicio = hoy.replace(day=1)
|
||||
fin = hoy.replace(year=hoy.year + 1, month=1, day=1) if hoy.month == 12 \
|
||||
else hoy.replace(month=hoy.month + 1, day=1)
|
||||
env.cr.execute('SELECT COALESCE(SUM(file_size), 0) FROM ir_attachment')
|
||||
bytes_almacen = env.cr.fetchone()[0] or 0
|
||||
return {
|
||||
'usuarios_max': env['skeen.frontend.user'].sudo().search_count([('active', '=', True)]),
|
||||
'sucursales_max': env['skeen.sucursal'].sudo().search_count([('active', '=', True)]),
|
||||
'citas_mes_max': env['skeen.cita'].sudo().search_count(
|
||||
[('date', '>=', inicio), ('date', '<', fin)]),
|
||||
'almacenamiento_mb': round(bytes_almacen / (1024.0 * 1024.0), 2),
|
||||
}
|
||||
|
||||
|
||||
def _estado(env):
|
||||
settings = get_settings(env)
|
||||
licencia = settings.get('licencia') or dict(LICENCIA_DEFAULT)
|
||||
limites = licencia.get('limites') or {}
|
||||
uso = _uso(env)
|
||||
filas = []
|
||||
for clave in LIMITE_KEYS:
|
||||
tope = int(limites.get(clave) or 0)
|
||||
actual = uso.get(clave) or 0
|
||||
if tope <= 0:
|
||||
pct = 0
|
||||
estado = 'sin_limite'
|
||||
else:
|
||||
pct = round(min(100.0, (actual / float(tope)) * 100.0), 1)
|
||||
estado = 'excedido' if actual > tope else (
|
||||
'cerca' if pct >= 80 else 'ok')
|
||||
filas.append({
|
||||
'clave': clave,
|
||||
'label': LIMITE_LABEL.get(clave, clave),
|
||||
'uso': actual,
|
||||
'limite': tope,
|
||||
'porcentaje': pct,
|
||||
'estado': estado,
|
||||
})
|
||||
permitidos = modulos_licencia(licencia)
|
||||
modulos = []
|
||||
for clave in list(MODULOS_BASE) + list(MODULOS_GATEABLES):
|
||||
incluido = permitidos is None or clave in permitidos
|
||||
modulos.append({
|
||||
'clave': clave,
|
||||
'label': MODULO_LABEL.get(clave, clave),
|
||||
'base': clave in MODULOS_BASE,
|
||||
'incluido': incluido,
|
||||
})
|
||||
dias = dias_para_vencer(licencia)
|
||||
aplicada = bool(licencia.get('clave'))
|
||||
return {
|
||||
'licencia': {
|
||||
'plan': licencia.get('plan') or '',
|
||||
'plan_label': (PLANES.get(licencia.get('plan')) or {}).get('label', ''),
|
||||
'modo': licencia.get('modo') or 'aviso',
|
||||
'cliente': licencia.get('cliente') or '',
|
||||
'vence': licencia.get('vence') or '',
|
||||
'dias_restantes': dias,
|
||||
'aplicada': aplicada,
|
||||
'bloqueo_activo': licencia_bloqueo_activo(licencia),
|
||||
'aviso_vence': bool(dias is not None and 0 <= dias <= AVISO_DIAS),
|
||||
},
|
||||
'limites': filas,
|
||||
'modulos': modulos,
|
||||
'planes': [{'clave': k, 'label': v['label'], 'modulos': v['modulos']}
|
||||
for k, v in PLANES.items()],
|
||||
'aviso': ('Sin licencia aplicada — todas las funciones habilitadas.'
|
||||
if not aplicada else None),
|
||||
}
|
||||
|
||||
|
||||
class SkeenLicenciaController(http.Controller):
|
||||
|
||||
@http.route(BASE + '/licencia', type='http', auth='none',
|
||||
methods=['GET', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
def get_licencia(self, **kw):
|
||||
try:
|
||||
if str(request.httprequest.method).upper() == 'OPTIONS':
|
||||
return _json({}, 200)
|
||||
if not _puede_ver():
|
||||
return _json({'status': 'error', 'message': 'Sin permiso'}, 403)
|
||||
return _json({'status': 'success', **_estado(request.env)})
|
||||
except Exception as e:
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/licencia', type='http', auth='none',
|
||||
methods=['PUT', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
def put_licencia(self, **kw):
|
||||
try:
|
||||
if str(request.httprequest.method).upper() == 'OPTIONS':
|
||||
return _json({}, 200)
|
||||
if not _puede_editar():
|
||||
return _json({'status': 'error',
|
||||
'message': 'Solo administración puede aplicar licencias'}, 403)
|
||||
data = _body()
|
||||
env = request.env
|
||||
if 'clave' in data:
|
||||
clave = (data.get('clave') or '').strip()
|
||||
if not clave:
|
||||
# Quitar la licencia: todo vuelve a estar habilitado.
|
||||
vacio = dict(LICENCIA_DEFAULT)
|
||||
vacio['limites'] = {k: 0 for k in LIMITE_KEYS}
|
||||
set_settings(env, {'licencia': vacio})
|
||||
return _json({'status': 'success',
|
||||
'message': 'Licencia retirada. Todo habilitado.',
|
||||
**_estado(env)})
|
||||
payload, error = verificar_clave_licencia(env, clave)
|
||||
if error:
|
||||
return _json({'status': 'error',
|
||||
'message': '%s No se guardó ningún cambio.' % error}, 400)
|
||||
modo = data.get('modo') or 'bloqueo'
|
||||
lic = licencia_desde_payload(payload, clave, modo=modo)
|
||||
set_settings(env, {'licencia': lic})
|
||||
return _json({'status': 'success', 'message': 'Licencia aplicada.',
|
||||
**_estado(env)})
|
||||
# Cambios sin re-firmar (p. ej. pasar de aviso a bloqueo).
|
||||
vals = {k: data[k] for k in ('modo', 'plan', 'cliente', 'vence',
|
||||
'modulos', 'limites') if k in data}
|
||||
if not vals:
|
||||
return _json({'status': 'error', 'message': 'Nada que actualizar'}, 400)
|
||||
set_settings(env, {'licencia': vals})
|
||||
return _json({'status': 'success', 'message': 'Licencia actualizada.',
|
||||
**_estado(env)})
|
||||
except ValueError as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 400)
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/licencia/validar', type='http', auth='none',
|
||||
methods=['POST', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
def validar_licencia(self, **kw):
|
||||
try:
|
||||
if str(request.httprequest.method).upper() == 'OPTIONS':
|
||||
return _json({}, 200)
|
||||
if not _puede_ver():
|
||||
return _json({'status': 'error', 'message': 'Sin permiso'}, 403)
|
||||
data = _body()
|
||||
payload, error = verificar_clave_licencia(request.env, data.get('clave'))
|
||||
dias = dias_para_vencer({'vence': (payload or {}).get('vence')})
|
||||
return _json({
|
||||
'status': 'success' if not error else 'error',
|
||||
'valida': error is None,
|
||||
'error': error,
|
||||
'payload': payload,
|
||||
'dias_restantes': dias,
|
||||
'aviso_vence': bool(dias is not None and 0 <= dias <= AVISO_DIAS),
|
||||
}, 200 if not error else 400)
|
||||
except Exception as e:
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
225
odoo-addons/skeen_plataforma/controllers/webhooks.py
Normal file
225
odoo-addons/skeen_plataforma/controllers/webhooks.py
Normal file
@@ -0,0 +1,225 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""CRUD de webhooks salientes y log de entregas (Configuración -> Webhooks)."""
|
||||
|
||||
import json
|
||||
import secrets
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request, Response
|
||||
|
||||
from odoo.addons.skeen_whatsapp.controllers.auth import require_role, require_perm
|
||||
from odoo.addons.skeen_plataforma.models.webhook import EVENTOS, ESTADOS
|
||||
|
||||
BASE = '/skeen/frontend/v1'
|
||||
|
||||
|
||||
def _json(data, status=200):
|
||||
return Response(
|
||||
json.dumps(data, default=str),
|
||||
status=status,
|
||||
mimetype='application/json',
|
||||
headers={
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _body():
|
||||
try:
|
||||
raw = request.httprequest.get_data(as_text=True)
|
||||
return json.loads(raw) if raw else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _login():
|
||||
return getattr(request.frontend_user, 'login', '') if hasattr(request, 'frontend_user') else ''
|
||||
|
||||
|
||||
def _eventos_list(data):
|
||||
if isinstance(data.get('eventos'), list):
|
||||
return [str(e).strip() for e in data['eventos'] if str(e).strip()]
|
||||
if isinstance(data.get('eventos'), str):
|
||||
return [e.strip() for e in data['eventos'].split(',') if e.strip()]
|
||||
return []
|
||||
|
||||
|
||||
class SkeenWebhooksController(http.Controller):
|
||||
|
||||
@http.route(BASE + '/webhooks', type='http', auth='none',
|
||||
methods=['GET', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('webhooks.editar')
|
||||
def list_webhooks(self, **kw):
|
||||
try:
|
||||
service = request.env['skeen.webhook.service'].sudo()
|
||||
hooks = request.env['skeen.webhook'].sudo().search([])
|
||||
catalogo = [{'clave': c, 'label': l,
|
||||
'ejemplo': service.payload_ejemplo(c)} for c, l in EVENTOS]
|
||||
return _json({
|
||||
'status': 'success',
|
||||
'webhooks': [w.to_public_dict() for w in hooks],
|
||||
'eventos': catalogo,
|
||||
'estados': [{'clave': c, 'label': l} for c, l in ESTADOS],
|
||||
'formato_firma': 'X-Skeen-Signature: sha256=<HMAC-SHA256 del cuerpo con el secreto>',
|
||||
})
|
||||
except Exception as e:
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/webhooks', type='http', auth='none',
|
||||
methods=['POST', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('webhooks.editar')
|
||||
def create_webhook(self, **kw):
|
||||
try:
|
||||
data = _body()
|
||||
url = (data.get('url') or '').strip()
|
||||
if not (url.startswith('http://') or url.startswith('https://')):
|
||||
return _json({'status': 'error',
|
||||
'message': 'La URL debe empezar por http:// o https://'}, 400)
|
||||
secreto = (data.get('secreto') or '').strip() or secrets.token_urlsafe(32)
|
||||
headers = data.get('headers')
|
||||
if isinstance(headers, dict):
|
||||
headers = json.dumps(headers, ensure_ascii=False)
|
||||
else:
|
||||
headers = (headers or '').strip()
|
||||
webhook = request.env['skeen.webhook'].sudo().create({
|
||||
'name': (data.get('name') or '').strip() or 'Webhook',
|
||||
'url': url,
|
||||
'eventos': ','.join(_eventos_list(data)),
|
||||
'activo': bool(data.get('activo', True)),
|
||||
'secreto': secreto,
|
||||
'headers': headers,
|
||||
'notas': data.get('notas') or '',
|
||||
'creado_por_login': _login(),
|
||||
})
|
||||
return _json({'status': 'success', 'webhook': webhook.to_public_dict(),
|
||||
'secreto': secreto,
|
||||
'message': 'Webhook creado.'})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/webhooks/<int:webhook_id>', type='http', auth='none',
|
||||
methods=['PUT', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('webhooks.editar')
|
||||
def update_webhook(self, webhook_id, **kw):
|
||||
try:
|
||||
webhook = request.env['skeen.webhook'].sudo().browse(webhook_id)
|
||||
if not webhook.exists():
|
||||
return _json({'status': 'error', 'message': 'Webhook no encontrado'}, 404)
|
||||
data = _body()
|
||||
vals = {}
|
||||
if 'name' in data and (data.get('name') or '').strip():
|
||||
vals['name'] = data['name'].strip()
|
||||
if 'url' in data and (data.get('url') or '').strip():
|
||||
url = data['url'].strip()
|
||||
if not (url.startswith('http://') or url.startswith('https://')):
|
||||
return _json({'status': 'error',
|
||||
'message': 'La URL debe empezar por http:// o https://'}, 400)
|
||||
vals['url'] = url
|
||||
if 'eventos' in data:
|
||||
vals['eventos'] = ','.join(_eventos_list(data))
|
||||
if 'activo' in data:
|
||||
vals['activo'] = bool(data['activo'])
|
||||
if vals['activo']:
|
||||
vals['fallos_consecutivos'] = 0
|
||||
if 'secreto' in data and (data.get('secreto') or '').strip():
|
||||
vals['secreto'] = data['secreto'].strip()
|
||||
if 'headers' in data:
|
||||
headers = data['headers']
|
||||
vals['headers'] = json.dumps(headers, ensure_ascii=False) \
|
||||
if isinstance(headers, dict) else (headers or '')
|
||||
if 'notas' in data:
|
||||
vals['notas'] = data.get('notas') or ''
|
||||
if vals:
|
||||
webhook.write(vals)
|
||||
return _json({'status': 'success', 'webhook': webhook.to_public_dict()})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/webhooks/<int:webhook_id>', type='http', auth='none',
|
||||
methods=['DELETE'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('webhooks.editar')
|
||||
def delete_webhook(self, webhook_id, **kw):
|
||||
try:
|
||||
webhook = request.env['skeen.webhook'].sudo().browse(webhook_id)
|
||||
if not webhook.exists():
|
||||
return _json({'status': 'error', 'message': 'Webhook no encontrado'}, 404)
|
||||
webhook.unlink()
|
||||
return _json({'status': 'success', 'message': 'Webhook eliminado.'})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/webhooks/<int:webhook_id>/eventos', type='http', auth='none',
|
||||
methods=['GET', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('webhooks.editar')
|
||||
def list_webhook_eventos(self, webhook_id, **kw):
|
||||
try:
|
||||
webhook = request.env['skeen.webhook'].sudo().browse(webhook_id)
|
||||
if not webhook.exists():
|
||||
return _json({'status': 'error', 'message': 'Webhook no encontrado'}, 404)
|
||||
domain = [('webhook_id', '=', webhook.id)]
|
||||
if kw.get('estado'):
|
||||
domain.append(('estado', '=', kw['estado']))
|
||||
if kw.get('evento'):
|
||||
domain.append(('evento', '=', kw['evento']))
|
||||
try:
|
||||
limite = min(max(int(kw.get('limit') or 100), 1), 500)
|
||||
except (TypeError, ValueError):
|
||||
limite = 100
|
||||
Evento = request.env['skeen.webhook.evento'].sudo()
|
||||
registros = Evento.search(domain, order='create_date desc, id desc',
|
||||
limit=limite)
|
||||
resumen = {c: 0 for c, _l in ESTADOS}
|
||||
for grupo in Evento.read_group(domain, ['estado'], ['estado'], lazy=False):
|
||||
if grupo.get('estado'):
|
||||
resumen[grupo['estado']] = grupo.get('__count', 0)
|
||||
return _json({'status': 'success',
|
||||
'eventos': [e.to_public_dict() for e in registros],
|
||||
'total': Evento.search_count(domain),
|
||||
'resumen': resumen})
|
||||
except Exception as e:
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/webhooks/<int:webhook_id>/probar', type='http', auth='none',
|
||||
methods=['POST', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('webhooks.editar')
|
||||
def probar_webhook(self, webhook_id, **kw):
|
||||
try:
|
||||
webhook = request.env['skeen.webhook'].sudo().browse(webhook_id)
|
||||
if not webhook.exists():
|
||||
return _json({'status': 'error', 'message': 'Webhook no encontrado'}, 404)
|
||||
ok, mensaje, evento = request.env['skeen.webhook.service'].sudo().probar(
|
||||
webhook, login=_login())
|
||||
return _json({'status': 'success' if ok else 'error', 'ok': ok,
|
||||
'message': mensaje,
|
||||
'evento': evento.to_public_dict() if evento else None},
|
||||
200 if ok else 502)
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
|
||||
@http.route(BASE + '/webhook_eventos/<int:evento_id>/reintentar', type='http',
|
||||
auth='none', methods=['POST', 'OPTIONS'], csrf=False)
|
||||
@require_role('gerencia')
|
||||
@require_perm('webhooks.editar')
|
||||
def reintentar_evento(self, evento_id, **kw):
|
||||
try:
|
||||
evento = request.env['skeen.webhook.evento'].sudo().browse(evento_id)
|
||||
if not evento.exists():
|
||||
return _json({'status': 'error', 'message': 'Evento no encontrado'}, 404)
|
||||
estado = request.env['skeen.webhook.service'].sudo().reintentar(evento)
|
||||
return _json({'status': 'success', 'estado': estado,
|
||||
'evento': evento.to_public_dict()})
|
||||
except Exception as e:
|
||||
request.env.cr.rollback()
|
||||
return _json({'status': 'error', 'message': str(e)}, 500)
|
||||
Reference in New Issue
Block a user