Atualização: template Excel de alíquotas e layout da aba
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-07-30 09:48:44 -03:00
parent b51eeac014
commit d8db2a60e5
8 changed files with 976 additions and 198 deletions

View File

@@ -1,6 +1,5 @@
# parametros.py
from fastapi import APIRouter, Request, HTTPException, Depends
from sqlalchemy.orm import Session
from fastapi import APIRouter, Request, Depends, Form
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_session
from app.models import AliquotaUF, ParametrosFormula, SelicMensal
@@ -10,6 +9,19 @@ import datetime
from fastapi.templating import Jinja2Templates
from sqlalchemy.future import select
from app.database import AsyncSessionLocal
from fastapi.responses import RedirectResponse
from app.models import Fatura
from fastapi import Body
from app.database import engine
import httpx
from app.models import SelicMensal
from sqlalchemy.dialects.postgresql import insert as pg_insert
import io
import csv
from fastapi.responses import StreamingResponse
import pandas as pd
from io import BytesIO
router = APIRouter()
@@ -17,83 +29,202 @@ router = APIRouter()
class AliquotaUFSchema(BaseModel):
uf: str
exercicio: int
aliquota: float
aliq_icms: float
class Config:
orm_mode = True
from_attributes = True
class ParametrosFormulaSchema(BaseModel):
nome: str
formula: str
campos: str
ativo: bool = True
class Config:
orm_mode = True
from_attributes = True
class SelicMensalSchema(BaseModel):
mes: str # 'YYYY-MM'
fator: float
class Config:
orm_mode = True
from_attributes = True
# === Rotas ===
router = APIRouter()
templates = Jinja2Templates(directory="app/templates")
@router.get("/parametros")
async def parametros_page(request: Request):
async with AsyncSessionLocal() as session:
result = await session.execute(select(ParametrosFormula).where(ParametrosFormula.ativo == True))
parametros = result.scalars().first()
# Consulta das fórmulas
result = await session.execute(select(ParametrosFormula))
parametros = result.scalars().all()
# Consulta da tabela selic_mensal
selic_result = await session.execute(
select(SelicMensal).order_by(SelicMensal.ano.desc(), SelicMensal.mes.desc())
)
selic_dados = selic_result.scalars().all()
# Pega última data
ultima_data_selic = "-"
if selic_dados:
ultima = selic_dados[0]
ultima_data_selic = f"{ultima.mes:02d}/{ultima.ano}"
# Campos numéricos da fatura
campos = [
col.name for col in Fatura.__table__.columns
if col.type.__class__.__name__ in ['Integer', 'Float', 'Numeric']
]
return templates.TemplateResponse("parametros.html", {
"request": request,
"parametros": parametros or {}
"lista_parametros": parametros,
"parametros": {},
"campos_fatura": campos,
"selic_dados": selic_dados,
"ultima_data_selic": ultima_data_selic
})
@router.post("/parametros/editar/{param_id}")
async def editar_parametro(param_id: int, request: Request):
data = await request.json()
async with AsyncSessionLocal() as session:
param = await session.get(ParametrosFormula, param_id)
if param:
param.tipo = data.get("tipo", param.tipo)
param.formula = data.get("formula", param.formula)
await session.commit()
return {"success": True}
return {"success": False, "error": "Não encontrado"}
@router.post("/parametros/testar")
async def testar_formula(db: AsyncSession = Depends(get_session), data: dict = Body(...)):
formula = data.get("formula")
exemplo = await db.execute(select(Fatura).limit(1))
fatura = exemplo.scalar_one_or_none()
if not fatura:
return {"success": False, "error": "Sem dados para teste."}
try:
contexto = {col.name: getattr(fatura, col.name) for col in Fatura.__table__.columns}
resultado = eval(formula, {}, contexto)
return {"success": True, "resultado": resultado}
except Exception as e:
return {"success": False, "error": str(e)}
@router.get("/parametros/aliquotas", response_model=List[AliquotaUFSchema])
def listar_aliquotas(db: AsyncSession = Depends(get_session)):
return db.query(AliquotaUF).all()
async def listar_aliquotas(db: AsyncSession = Depends(get_session)):
result = await db.execute(select(AliquotaUF).order_by(AliquotaUF.uf, AliquotaUF.exercicio))
return result.scalars().all()
@router.post("/parametros/aliquotas")
def adicionar_aliquota(aliq: AliquotaUFSchema, db: AsyncSession = Depends(get_session)):
existente = db.query(AliquotaUF).filter_by(uf=aliq.uf, exercicio=aliq.exercicio).first()
async def adicionar_aliquota(aliq: AliquotaUFSchema, db: AsyncSession = Depends(get_session)):
result = await db.execute(
select(AliquotaUF).filter_by(uf=aliq.uf, exercicio=aliq.exercicio)
)
existente = result.scalar_one_or_none()
if existente:
existente.aliquota = aliq.aliquota
existente.aliq_icms = aliq.aliq_icms # atualizado
else:
novo = AliquotaUF(**aliq.dict())
db.add(novo)
db.commit()
return {"status": "ok"}
await db.commit()
return RedirectResponse(url="/parametros?ok=true&msg=Alíquota salva com sucesso", status_code=303)
@router.get("/parametros/formulas", response_model=List[ParametrosFormulaSchema])
def listar_formulas(db: AsyncSession = Depends(get_session)):
return db.query(ParametrosFormula).all()
async def listar_formulas(db: AsyncSession = Depends(get_session)):
result = await db.execute(select(ParametrosFormula).order_by(ParametrosFormula.tipo))
return result.scalars().all()
@router.post("/parametros/formulas")
def salvar_formula(form: ParametrosFormulaSchema, db: AsyncSession = Depends(get_session)):
existente = db.query(ParametrosFormula).filter_by(nome=form.nome).first()
async def salvar_formula(form: ParametrosFormulaSchema, db: AsyncSession = Depends(get_session)):
result = await db.execute(
select(ParametrosFormula).filter_by(tipo=form.tipo)
)
existente = result.scalar_one_or_none()
if existente:
existente.formula = form.formula
existente.campos = form.campos
else:
novo = ParametrosFormula(**form.dict())
db.add(novo)
db.commit()
return {"status": "ok"}
await db.commit()
return RedirectResponse(url="/parametros?ok=true&msg=Parâmetro salvo com sucesso", status_code=303)
@router.get("/parametros/selic", response_model=List[SelicMensalSchema])
def listar_selic(db: AsyncSession = Depends(get_session)):
return db.query(SelicMensal).order_by(SelicMensal.mes.desc()).all()
async def listar_selic(db: AsyncSession = Depends(get_session)):
result = await db.execute(select(SelicMensal).order_by(SelicMensal.mes.desc()))
return result.scalars().all()
@router.post("/parametros/selic/importar")
async def importar_selic(request: Request, data_maxima: str = Form(None)):
try:
hoje = datetime.date.today()
inicio = datetime.date(hoje.year - 5, 1, 1)
fim = datetime.datetime.strptime(data_maxima, "%Y-%m-%d").date() if data_maxima else hoje
url = (
f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.4390/dados?"
f"formato=json&dataInicial={inicio.strftime('%d/%m/%Y')}&dataFinal={fim.strftime('%d/%m/%Y')}"
)
async with httpx.AsyncClient() as client:
response = await client.get(url)
response.raise_for_status()
dados = response.json()
registros = []
for item in dados:
data = datetime.datetime.strptime(item['data'], "%d/%m/%Y")
ano, mes = data.year, data.month
percentual = float(item['valor'].replace(',', '.'))
registros.append({"ano": ano, "mes": mes, "percentual": percentual})
async with engine.begin() as conn:
stmt = pg_insert(SelicMensal.__table__).values(registros)
upsert_stmt = stmt.on_conflict_do_update(
index_elements=['ano', 'mes'],
set_={'percentual': stmt.excluded.percentual}
)
await conn.execute(upsert_stmt)
return RedirectResponse("/parametros?aba=selic", status_code=303)
except Exception as e:
return RedirectResponse(f"/parametros?erro=1&msg={str(e)}", status_code=303)
@router.get("/parametros/aliquotas/template")
def baixar_template_excel():
df = pd.DataFrame(columns=["UF", "Exercício", "Alíquota"])
df.loc[0] = ["SP", "2025", "18"] # exemplo opcional
df.loc[1] = ["MG", "2025", "12"] # exemplo opcional
output = BytesIO()
with pd.ExcelWriter(output, engine='openpyxl') as writer:
df.to_excel(writer, sheet_name='Template', index=False)
# Adiciona instrução como observação na célula A5 (linha 5)
sheet = writer.sheets['Template']
sheet.cell(row=5, column=1).value = (
"⚠️ Após preencher, salve como CSV (.csv separado por vírgulas) para importar no sistema."
)
output.seek(0)
return StreamingResponse(
output,
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={"Content-Disposition": "attachment; filename=template_aliquotas.xlsx"}
)
@router.post("/parametros/selic")
def salvar_selic(selic: SelicMensalSchema, db: AsyncSession = Depends(get_session)):
existente = db.query(SelicMensal).filter_by(mes=selic.mes).first()
if existente:
existente.fator = selic.fator
else:
novo = SelicMensal(**selic.dict())
db.add(novo)
db.commit()
return {"status": "ok"}