Files
clientflow_backend/app/admin_ui/pages/integrations.py

199 lines
7.4 KiB
Python

"""Integration configuration and action routes.
Moved from app.admin_dashboard in v4.7.2. The handlers still reuse
legacy helpers to keep this refactor behavior-preserving.
"""
from fastapi import APIRouter
import app.admin_dashboard as legacy
from app.admin_dashboard import * # noqa: F401,F403
router = APIRouter()
@router.get("/integrations", response_class=HTMLResponse)
@router.get("/integracoes", response_class=HTMLResponse)
async def integrations_page():
odoo_stats = {}
odoo_last_sync = ""
odoo_error = ""
try:
snapshot = get_odoo_product_snapshot(limit=1)
odoo_stats = snapshot.get("stats") or {}
odoo_last_sync = odoo_stats.get("last_synced_at") or ""
except Exception as exc:
odoo_error = str(exc)
def badge(enabled: bool) -> str:
if enabled:
return '<span class="badge bg-success-subtle text-success">Ativo</span>'
return '<span class="badge bg-secondary-subtle text-secondary">Inativo</span>'
odoo_enabled = bool(getattr(settings, "odoo_enabled", False))
jasmin_enabled = bool(getattr(settings, "jasmin_enabled", False))
packlink_enabled = bool(getattr(settings, "packlink_enabled", False))
chatwoot_enabled = bool(getattr(settings, "chatwoot_base_url", ""))
odoo_error_html = ""
if odoo_error:
odoo_error_html = f"""
<div class="alert alert-warning mt-3 mb-0">
<strong>Erro Odoo:</strong> {esc(odoo_error)}
</div>
"""
body = f"""
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
<div>
<div class="cf-eyebrow">Configuração</div>
<h1 class="h3 mb-1">Integrações</h1>
<p class="text-secondary mb-0">Ligações externas usadas pelo cockpit operacional.</p>
</div>
</div>
<div class="row g-3">
<div class="col-lg-6">
<section class="card cf-card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h2 class="cf-section-title mb-1">Odoo</h2>
<div class="small text-secondary">Produtos vendáveis, stock e estado operacional.</div>
</div>
{badge(odoo_enabled)}
</div>
<dl class="row small mb-3">
<dt class="col-5 text-secondary">Base URL</dt>
<dd class="col-7">{esc(getattr(settings, "odoo_base_url", "") or "")}</dd>
<dt class="col-5 text-secondary">Base de dados</dt>
<dd class="col-7">{esc(getattr(settings, "odoo_db", "") or "")}</dd>
<dt class="col-5 text-secondary">Produtos sincronizados</dt>
<dd class="col-7">{esc(odoo_stats.get("synced_products") or 0)}</dd>
<dt class="col-5 text-secondary">Com BOM</dt>
<dd class="col-7">{esc(odoo_stats.get("products_with_bom") or 0)}</dd>
<dt class="col-5 text-secondary">Com stock disponível</dt>
<dd class="col-7">{esc(odoo_stats.get("products_with_available_stock") or 0)}</dd>
<dt class="col-5 text-secondary">Último sync</dt>
<dd class="col-7">{esc(odoo_last_sync)}</dd>
</dl>
<div class="d-flex flex-wrap gap-2">
<form method="post" action="/integrations/odoo/test">
<button class="btn btn-outline-primary btn-sm" type="submit">Testar ligação</button>
</form>
<form method="post" action="/integrations/odoo/sync-products" hx-confirm="Sincronizar produtos vendáveis do Odoo agora? Esta ação pode atualizar o catálogo e deve ser usada conscientemente.">
<button class="btn btn-primary btn-sm" type="submit">Sincronizar produtos vendáveis</button>
</form>
</div>
{odoo_error_html}
</div>
</section>
</div>
<div class="col-lg-6">
<section class="card cf-card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h2 class="cf-section-title mb-1">Jasmin</h2>
<div class="small text-secondary">Orçamentos, faturas e documentos fiscais.</div>
</div>
{badge(jasmin_enabled)}
</div>
<dl class="row small mb-0">
<dt class="col-5 text-secondary">Base URL</dt>
<dd class="col-7">{esc(getattr(settings, "jasmin_base_url", "") or "")}</dd>
<dt class="col-5 text-secondary">URL público</dt>
<dd class="col-7">{esc(getattr(settings, "jasmin_public_url", "") or "")}</dd>
</dl>
</div>
</section>
</div>
<div class="col-lg-6">
<section class="card cf-card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h2 class="cf-section-title mb-1">Packlink PRO</h2>
<div class="small text-secondary">Recolhas, envios, etiquetas e tracking.</div>
</div>
{badge(packlink_enabled)}
</div>
<dl class="row small mb-0">
<dt class="col-5 text-secondary">Base URL</dt>
<dd class="col-7">{esc(getattr(settings, "packlink_base_url", "") or "")}</dd>
<dt class="col-5 text-secondary">URL público</dt>
<dd class="col-7">{esc(getattr(settings, "packlink_public_url", "") or "")}</dd>
</dl>
</div>
</section>
</div>
<div class="col-lg-6">
<section class="card cf-card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h2 class="cf-section-title mb-1">Chatwoot</h2>
<div class="small text-secondary">Comunicação com cliente.</div>
</div>
{badge(chatwoot_enabled)}
</div>
<dl class="row small mb-0">
<dt class="col-5 text-secondary">Base URL</dt>
<dd class="col-7">{esc(getattr(settings, "chatwoot_base_url", "") or "")}</dd>
<dt class="col-5 text-secondary">URL público</dt>
<dd class="col-7">{esc(getattr(settings, "chatwoot_public_url", "") or "")}</dd>
</dl>
</div>
</section>
</div>
</div>
<section class="card cf-card mt-4">
<div class="card-body">
<h2 class="cf-section-title mb-2">Regra operacional</h2>
<p class="text-secondary mb-0">
O ClientFlow não replica Odoo, Jasmin ou Packlink. Apenas consulta e guarda o estado necessário
para decidir a próxima ação com o cliente.
</p>
</div>
</section>
"""
return layout("Integrações", "Configuração das ligações externas", body, "integrations")
@router.post("/integrations/odoo/test")
async def integrations_odoo_test_action(request: Request):
try:
test_odoo_connection()
except Exception:
pass
return RedirectResponse(url="/integrations", status_code=303)
@router.post("/integrations/odoo/sync-products")
async def integrations_odoo_sync_products_action(request: Request):
try:
sync_odoo_products(limit=1000, include_inactive=True)
except Exception:
pass
return RedirectResponse(url="/integrations", status_code=303)