Release v4928.1.4.2 stable

This commit is contained in:
2026-06-09 22:55:58 +01:00
commit 6445044ac6
280 changed files with 41775 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
"""Dashboard and landing 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("/", response_class=HTMLResponse)
async def admin_home():
"""v4.5 clean Dashboard: visibility, not daily execution."""
metrics = get_admin_dashboard_metrics()
ops = get_operations_summary(limit=6)
counts = ops.get("counts") or {}
comms = get_communications_summary()
def n(key: str) -> int:
return int(metrics.get(key) or counts.get(key) or 0)
dashboard_cards = [
("Oportunidades abertas", counts.get("open_opportunities", 0), "/opportunities?status=open", "Negócio em acompanhamento"),
("Valor / documentos", counts.get("open_quotations", 0), "/finance", "Orçamentos abertos"),
("Tasks pendentes", n("pending_total"), "/operations", "Trabalho humano por resolver"),
("Mensagens a rever", comms.get("open", 0), "/operations", "Ações vindas do Chatwoot"),
("Erros de integração", counts.get("outbox_failed", 0), "/outbox?status=failed", "Jasmin/Packlink/outbox"),
("Pagamentos por confirmar", n("pending_financeiro"), "/operations", "Fila financeira"),
("Envios pendentes", counts.get("shipments_pending", 0), "/orders", "Logística/Packlink"),
("Clientes incompletos", counts.get("customers_incomplete", 0), "/customers", "Dados fiscais/morada"),
]
cards_html = ""
for label, value, href, hint in dashboard_cards:
cards_html += kpi_card(label, value, href, hint)
alert_items = []
if int(counts.get("outbox_failed") or 0):
alert_items.append(("Erro de integração", f"{counts.get('outbox_failed')} ação(ões) falhadas na outbox", "/outbox?status=failed", "cf-chip-red"))
if int(comms.get("needs_review") or 0):
alert_items.append(("Rever comunicação", f"{comms.get('needs_review')} mensagem(ns) com baixa confiança", "/operations", "cf-chip-orange"))
if int(counts.get("customers_incomplete") or 0):
alert_items.append(("Dados incompletos", f"{counts.get('customers_incomplete')} cliente(s) sem dados fiscais/morada completos", "/customers", "cf-chip-orange"))
if int(counts.get("products_missing_jasmin") or 0):
alert_items.append(("Produto bloqueante", f"{counts.get('products_missing_jasmin')} produto(s) ativos sem Artigo Jasmin", "/products?active=missing_jasmin", "cf-chip-red"))
alert_html = ""
for title, detail, href, chip in alert_items[:5]:
alert_html += f"""
<a class="d-flex justify-content-between align-items-start gap-3 py-3 border-bottom text-reset" href="{esc(href)}">
<div><span class="cf-chip {esc(chip)} mb-2">{esc(title)}</span><div class="fw-bold">{esc(detail)}</div></div>
<span class="text-primary fw-bold">Abrir →</span>
</a>
"""
if not alert_html:
alert_html = '<div class="text-secondary py-3">Sem alertas críticos neste momento.</div>'
body = f"""
<section class="alert alert-primary border-0 shadow-sm d-flex flex-wrap justify-content-between align-items-center gap-3">
<div>
<strong>Dashboard = visibilidade.</strong>
<span class="ms-1">O Chatwoot é a inbox. O ClientFlow mostra o trabalho, bloqueios e próximas ações.</span>
</div>
<a class="btn btn-primary" href="/operations">Abrir Centro de trabalho</a>
</section>
<section class="cf-kpi-grid">
{cards_html}
</section>
<div class="row g-3">
<div class="col-xl-7">
<section class="card cf-card h-100">
<div class="card-body p-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<div><h2 class="cf-section-title">Alertas</h2><div class="small text-secondary">Sinais globais que merecem atenção.</div></div>
<a class="btn btn-sm btn-outline-primary" href="/operations">Resolver no Centro de trabalho</a>
</div>
{alert_html}
</div>
</section>
</div>
<div class="col-xl-5">
<section class="card cf-card h-100">
<div class="card-body p-4">
<h2 class="cf-section-title mb-3">Modelo operacional v4.5</h2>
<div class="d-grid gap-3">
<div class="cf-soft-box"><strong>Dashboard</strong><div class="small text-secondary">Mostra o estado e gargalos.</div></div>
<div class="cf-soft-box"><strong>Centro de trabalho</strong><div class="small text-secondary">Organiza o que precisa de ação agora.</div></div>
<div class="cf-soft-box"><strong>Chatwoot → ClientFlow</strong><div class="small text-secondary">O Chatwoot continua a ser a inbox; o ClientFlow transforma mensagens em ações, tasks e timeline.</div></div>
<div class="cf-soft-box"><strong>Oportunidade</strong><div class="small text-secondary">Mantém contexto, documentos, tasks, outbox e timeline.</div></div>
</div>
</div>
</section>
</div>
</div>
"""
return layout("Dashboard", "Visão geral do negócio e do sistema", body, "overview")