141 lines
5.4 KiB
Python
141 lines
5.4 KiB
Python
|
|
from fastapi import APIRouter, Request
|
||
|
|
from fastapi.templating import Jinja2Templates
|
||
|
|
from sqlalchemy import create_engine, text
|
||
|
|
import os
|
||
|
|
from datetime import date
|
||
|
|
|
||
|
|
# Usa o avaliador de fórmulas já existente
|
||
|
|
from app.utils import avaliar_formula
|
||
|
|
|
||
|
|
router = APIRouter()
|
||
|
|
templates = Jinja2Templates(directory="app/templates")
|
||
|
|
|
||
|
|
# Conexão com o banco (use a mesma DATABASE_URL do restante do app)
|
||
|
|
DATABASE_URL = os.getenv("DATABASE_URL")
|
||
|
|
engine = create_engine(DATABASE_URL)
|
||
|
|
|
||
|
|
def _parse_referencia(ref: str):
|
||
|
|
"""Aceita 'JAN/2024', '01/2024' ou '202401'. Retorna (ano, mes)."""
|
||
|
|
meses = {'JAN':1,'FEV':2,'MAR':3,'ABR':4,'MAI':5,'JUN':6,'JUL':7,'AGO':8,'SET':9,'OUT':10,'NOV':11,'DEZ':12}
|
||
|
|
ref = (ref or "").strip().upper()
|
||
|
|
if "/" in ref:
|
||
|
|
a, b = ref.split("/")
|
||
|
|
if a.isdigit():
|
||
|
|
mes, ano = int(a), int(b)
|
||
|
|
else:
|
||
|
|
mes, ano = meses.get(a, 1), int(b)
|
||
|
|
else:
|
||
|
|
ano, mes = int(ref[:4]), int(ref[4:]) if len(ref) >= 6 else 1
|
||
|
|
return ano, mes
|
||
|
|
|
||
|
|
def _fator_selic_acumulado(conn, ano_inicio, mes_inicio, hoje):
|
||
|
|
selic = conn.execute(text("""
|
||
|
|
SELECT ano, mes, percentual
|
||
|
|
FROM faturas.selic_mensal
|
||
|
|
""")).mappings().all()
|
||
|
|
selic_map = {(r["ano"], r["mes"]): float(r["percentual"]) for r in selic}
|
||
|
|
|
||
|
|
fator = 1.0
|
||
|
|
ano, mes = int(ano_inicio), int(mes_inicio)
|
||
|
|
while (ano < hoje.year) or (ano == hoje.year and mes <= hoje.month):
|
||
|
|
perc = selic_map.get((ano, mes))
|
||
|
|
if perc is not None:
|
||
|
|
fator *= (1 + perc/100.0)
|
||
|
|
mes += 1
|
||
|
|
if mes > 12:
|
||
|
|
mes = 1; ano += 1
|
||
|
|
return fator
|
||
|
|
|
||
|
|
@router.get("/dashboard")
|
||
|
|
def dashboard(request: Request, cliente: str | None = None):
|
||
|
|
with engine.begin() as conn:
|
||
|
|
# Lista de clientes (distinct nome)
|
||
|
|
clientes = [r[0] for r in conn.execute(text("""
|
||
|
|
SELECT DISTINCT nome FROM faturas.faturas ORDER BY nome
|
||
|
|
""")).fetchall()]
|
||
|
|
|
||
|
|
# Carrega fórmulas (ativas)
|
||
|
|
formula_pis = conn.execute(text("""
|
||
|
|
SELECT formula FROM faturas.parametros_formula
|
||
|
|
WHERE nome = 'Cálculo PIS sobre ICMS' AND ativo = TRUE
|
||
|
|
LIMIT 1
|
||
|
|
""")).scalar_one_or_none()
|
||
|
|
|
||
|
|
formula_cofins = conn.execute(text("""
|
||
|
|
SELECT formula FROM faturas.parametros_formula
|
||
|
|
WHERE nome = 'Cálculo COFINS sobre ICMS' AND ativo = TRUE
|
||
|
|
LIMIT 1
|
||
|
|
""")).scalar_one_or_none()
|
||
|
|
|
||
|
|
# Carrega faturas (com filtro opcional de cliente)
|
||
|
|
params = {}
|
||
|
|
sql = "SELECT * FROM faturas.faturas"
|
||
|
|
if cliente:
|
||
|
|
sql += " WHERE nome = :cliente"
|
||
|
|
params["cliente"] = cliente
|
||
|
|
|
||
|
|
faturas = conn.execute(text(sql), params).mappings().all()
|
||
|
|
|
||
|
|
|
||
|
|
total_faturas = len(faturas)
|
||
|
|
|
||
|
|
# Cálculos de restituição e % ICMS na base
|
||
|
|
hoje = date.today()
|
||
|
|
soma_corrigida = 0.0
|
||
|
|
qtd_icms_na_base = 0
|
||
|
|
|
||
|
|
for f in faturas:
|
||
|
|
contexto = dict(f) # usa colunas como variáveis da fórmula
|
||
|
|
# PIS sobre ICMS
|
||
|
|
v_pis_icms = avaliar_formula(formula_pis, contexto) if formula_pis else None
|
||
|
|
# COFINS sobre ICMS
|
||
|
|
v_cofins_icms = avaliar_formula(formula_cofins, contexto) if formula_cofins else None
|
||
|
|
|
||
|
|
# Contagem para % ICMS na base: considera PIS_sobre_ICMS > 0
|
||
|
|
if v_pis_icms and float(v_pis_icms) > 0:
|
||
|
|
qtd_icms_na_base += 1
|
||
|
|
|
||
|
|
# Corrigir pela SELIC desde a referência da fatura
|
||
|
|
try:
|
||
|
|
ano, mes = _parse_referencia(f.get("referencia"))
|
||
|
|
fator = _fator_selic_acumulado(conn, ano, mes, hoje)
|
||
|
|
except Exception:
|
||
|
|
fator = 1.0
|
||
|
|
|
||
|
|
valor_bruto = (float(v_pis_icms) if v_pis_icms else 0.0) + (float(v_cofins_icms) if v_cofins_icms else 0.0)
|
||
|
|
soma_corrigida += valor_bruto * fator
|
||
|
|
|
||
|
|
percentual_icms_base = (qtd_icms_na_base / total_faturas * 100.0) if total_faturas else 0.0
|
||
|
|
valor_restituicao_corrigida = soma_corrigida
|
||
|
|
|
||
|
|
# --- Análise STF (mantida) ---
|
||
|
|
def media_percentual_icms(inicio: str, fim: str):
|
||
|
|
# Aproximação: base PIS = base ICMS => configurado como proxy “com ICMS na base”
|
||
|
|
q = text(f"""
|
||
|
|
SELECT
|
||
|
|
ROUND(AVG(CASE WHEN icms_base IS NOT NULL AND pis_base = icms_base THEN 100.0 ELSE 0.0 END), 2) AS percentual_com_icms,
|
||
|
|
ROUND(AVG(COALESCE(pis_valor,0) + COALESCE(cofins_valor,0)), 2) AS media_valor
|
||
|
|
FROM faturas.faturas
|
||
|
|
WHERE data_processamento::date BETWEEN :inicio AND :fim
|
||
|
|
{ "AND nome = :cliente" if cliente else "" }
|
||
|
|
""")
|
||
|
|
params = {"inicio": inicio, "fim": fim}
|
||
|
|
if cliente: params["cliente"] = cliente
|
||
|
|
r = conn.execute(q, params).mappings().first() or {}
|
||
|
|
return {"percentual_com_icms": r.get("percentual_com_icms", 0), "media_valor": r.get("media_valor", 0)}
|
||
|
|
|
||
|
|
analise_stf = {
|
||
|
|
"antes": media_percentual_icms("2000-01-01", "2017-03-15"),
|
||
|
|
"depois": media_percentual_icms("2017-03-16", "2099-12-31")
|
||
|
|
}
|
||
|
|
|
||
|
|
return templates.TemplateResponse("dashboard.html", {
|
||
|
|
"request": request,
|
||
|
|
"clientes": clientes,
|
||
|
|
"cliente_atual": cliente or "",
|
||
|
|
"total_faturas": total_faturas,
|
||
|
|
"valor_restituicao_corrigida": valor_restituicao_corrigida,
|
||
|
|
"percentual_icms_base": percentual_icms_base,
|
||
|
|
"analise_stf": analise_stf
|
||
|
|
})
|