From f0c3ce483e9108408e54dec58af82f5aa706c57f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 18:45:22 +0000 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20implementa=20SOE=20Rule=20Engine=20?= =?UTF-8?q?+=20Apache=20Superset=20(Arc=C3=A1dia=20Insights)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## SOE — Sistema Operacional Empresarial - Adiciona 3 tabelas ao schema: soe_regras, soe_eventos, soe_lancamentos - Cria server/soe/rule-engine/ com motor de regras configuráveis - Regras fiscais padrão: CFOP, tributação por regime, validações NCM - Regras de negócio: aprovação auto, reserva estoque, alertas - Cria server/soe/event-bus.ts — dispara automações pós-execução - Cria handlers: contabil-handler.ts (lançamentos automáticos) e financial-handler.ts (contas a P/R automáticas) - Plugar Rule Engine em 3 rotas críticas do server/erp/routes.ts: POST /sales-orders, POST /confirm, POST /generate-nfe - Redireciona /plus → /soe no frontend (Plus fica invisível) ## Apache Superset (Arcádia Insights) - Expande docker-compose.yml: volumes, init script, config file - Cria docker/superset/superset_config.py (embedding + CORS + feature flags) - Cria docker/superset/init.sh (migração DB + admin + registro banco Arcádia) - Substitui server/metabase/proxy.ts por server/superset/proxy.ts - Substitui server/metaset/routes.ts por server/superset/routes.ts com Guest Token JWT para embedding nativo em qualquer tela - Instala @superset-ui/embedded-sdk - Cria client/src/components/SupersetDashboard.tsx — componente reutilizável via Guest Token, fallback para iframe - Atualiza BiWorkspace.tsx: tab MetaSet → Insights (com seletor de dashboards) - Atualiza .env.example com variáveis Superset completas https://claude.ai/code/session_01DinH3VcgbAv1d9MqnNxzdb --- .env.example | 10 +- client/src/App.tsx | 6 +- client/src/components/SupersetDashboard.tsx | 160 +++++++++++++ client/src/pages/BiWorkspace.tsx | 99 +++++--- docker-compose.yml | 27 ++- docker/superset/init.sh | 82 +++++++ docker/superset/superset_config.py | 62 +++++ package.json | 1 + server/erp/routes.ts | 62 ++++- server/routes.ts | 12 +- server/soe/event-bus.ts | 112 +++++++++ server/soe/handlers/contabil-handler.ts | 182 +++++++++++++++ server/soe/handlers/financial-handler.ts | 128 +++++++++++ server/soe/rule-engine/business-rules.ts | 72 ++++++ server/soe/rule-engine/fiscal-rules.ts | 150 ++++++++++++ server/soe/rule-engine/index.ts | 240 ++++++++++++++++++++ server/soe/rule-engine/types.ts | 47 ++++ server/superset/proxy.ts | 39 ++++ server/superset/routes.ts | 108 +++++++++ shared/schema.ts | 67 ++++++ 20 files changed, 1622 insertions(+), 44 deletions(-) create mode 100644 client/src/components/SupersetDashboard.tsx create mode 100755 docker/superset/init.sh create mode 100644 docker/superset/superset_config.py create mode 100644 server/soe/event-bus.ts create mode 100644 server/soe/handlers/contabil-handler.ts create mode 100644 server/soe/handlers/financial-handler.ts create mode 100644 server/soe/rule-engine/business-rules.ts create mode 100644 server/soe/rule-engine/fiscal-rules.ts create mode 100644 server/soe/rule-engine/index.ts create mode 100644 server/soe/rule-engine/types.ts create mode 100644 server/superset/proxy.ts create mode 100644 server/superset/routes.ts diff --git a/.env.example b/.env.example index 656b051..bcc19a1 100644 --- a/.env.example +++ b/.env.example @@ -68,9 +68,15 @@ PLUS_URL=http://localhost:8080 PLUS_PORT=8080 PLUS_API_TOKEN= -# ── Superset (BI avançado) ──────────────────────────────────────────────────── -SUPERSET_SECRET_KEY=troque-por-string-aleatoria-segura +# ── Superset (BI avançado — Arcádia Insights) ──────────────────────────────── +SUPERSET_HOST=superset SUPERSET_PORT=8088 +SUPERSET_SECRET_KEY=troque-por-string-aleatoria-segura # openssl rand -hex 32 +SUPERSET_ADMIN_USER=admin +SUPERSET_ADMIN_EMAIL=admin@arcadia.app +SUPERSET_ADMIN_PASSWORD=troque-senha-forte-em-prod +# URL do banco Arcádia para o Superset acessar os dados (idealmente read-only) +ARCADIA_DATABASE_URL=postgresql://arcadia:arcadia123@db:5432/arcadia # ── Redis ───────────────────────────────────────────────────────────────────── REDIS_URL=redis://localhost:6379 diff --git a/client/src/App.tsx b/client/src/App.tsx index ea75205..f4a16aa 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,5 +1,5 @@ -import { Switch, Route } from "wouter"; -import { lazy, Suspense } from "react"; +import { Switch, Route, useLocation } from "wouter"; +import { lazy, Suspense, useEffect } from "react"; import { queryClient } from "./lib/queryClient"; import { QueryClientProvider } from "@tanstack/react-query"; import { Toaster } from "@/components/ui/toaster"; @@ -121,7 +121,7 @@ function Router() { - + { const [, nav] = useLocation(); useEffect(() => nav("/soe"), []); return null; }} /> diff --git a/client/src/components/SupersetDashboard.tsx b/client/src/components/SupersetDashboard.tsx new file mode 100644 index 0000000..c465bce --- /dev/null +++ b/client/src/components/SupersetDashboard.tsx @@ -0,0 +1,160 @@ +import { useEffect, useRef, useState, useCallback } from "react"; +import { Loader2, AlertTriangle, ExternalLink, RefreshCw } from "lucide-react"; + +interface SupersetDashboardProps { + dashboardId: string; + height?: string | number; + className?: string; + showOpenLink?: boolean; +} + +/** + * Componente reutilizável para embedar dashboards do Apache Superset (Arcádia Insights) + * em QUALQUER tela do Arcádia Suite via Guest Token JWT. + * + * Uso: + * + * + */ +export function SupersetDashboard({ + dashboardId, + height = "calc(100vh - 320px)", + className = "", + showOpenLink = true, +}: SupersetDashboardProps) { + const containerRef = useRef(null); + const sdkRef = useRef(null); + const [status, setStatus] = useState<"loading" | "ready" | "error" | "unavailable">("loading"); + const [errorMsg, setErrorMsg] = useState(""); + + const fetchGuestToken = useCallback(async (): Promise => { + const resp = await fetch("/api/superset/guest-token", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ dashboardId }), + credentials: "include", + }); + if (!resp.ok) { + const err = await resp.json().catch(() => ({ error: "Erro desconhecido" })); + throw new Error(err.error || `HTTP ${resp.status}`); + } + const { token } = await resp.json(); + return token; + }, [dashboardId]); + + const loadDashboard = useCallback(async () => { + if (!containerRef.current) return; + setStatus("loading"); + setErrorMsg(""); + + try { + // Verifica se Superset está disponível + const health = await fetch("/api/superset/health", { credentials: "include" }); + const healthData = await health.json(); + if (!healthData.online) { + setStatus("unavailable"); + return; + } + + // Importa SDK dinamicamente (só quando necessário) + const { embedDashboard } = await import("@superset-ui/embedded-sdk"); + + // Limpa embedding anterior se houver + if (sdkRef.current) { + containerRef.current.innerHTML = ""; + } + + sdkRef.current = await embedDashboard({ + id: dashboardId, + supersetDomain: window.location.origin + "/superset", + mountPoint: containerRef.current, + fetchGuestToken, + dashboardUiConfig: { + hideTitle: true, + hideChartControls: false, + filters: { visible: true, expanded: false }, + }, + }); + + setStatus("ready"); + } catch (err: any) { + console.error("[SupersetDashboard] Erro:", err.message); + // Se SDK não estiver instalado, usa iframe como fallback + if (err.message?.includes("Cannot find module") || err.message?.includes("embedDashboard")) { + setStatus("unavailable"); + } else { + setErrorMsg(err.message); + setStatus("error"); + } + } + }, [dashboardId, fetchGuestToken]); + + useEffect(() => { + loadDashboard(); + return () => { + if (containerRef.current) containerRef.current.innerHTML = ""; + }; + }, [loadDashboard]); + + const heightStyle = typeof height === "number" ? `${height}px` : height; + + if (status === "unavailable") { + return ( +
+