Cadastro da alíquota do ICMS correta. Inclusão da nova alíquota e comparação em todos os relatórios.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-08-11 18:45:57 -03:00
parent 950eb2a826
commit 4d2fcff4a8
5 changed files with 368 additions and 68 deletions

View File

@@ -29,6 +29,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_session
from fastapi import Query
from sqlalchemy import select as sqla_select
from app.models import AliquotaUF
import pandas as pd
app = FastAPI()
@@ -40,29 +42,25 @@ UPLOAD_DIR = os.path.join("app", "uploads", "temp")
os.makedirs(UPLOAD_DIR, exist_ok=True)
def _parse_referencia(ref: str):
"""Aceita 'JAN/2024', 'JAN/24', '01/2024', '01/24', '202401'. Retorna (ano, mes)."""
"""Aceita 'JAN/2024', '01/2024', '202401' etc. 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 = [p.strip() for p in ref.split("/", 1)]
# mês pode vir 'JAN' ou '01'
mes = meses.get(a, None)
if mes is None:
mes = int(re.sub(r"\D", "", a) or 1)
ano = int(re.sub(r"\D", "", b) or 0)
# ano 2 dígitos -> 2000+
if ano < 100:
ano += 2000
else:
# '202401' ou '2024-01'
num = re.sub(r"\D", "", ref)
if len(num) >= 6:
ano, mes = int(num[:4]), int(num[4:6])
elif len(num) == 4: # '2024'
elif len(num) == 4:
ano, mes = int(num), 1
else:
ano, mes = date.today().year, 1
ano, mes = 0, 0
return ano, mes
async def _carregar_selic_map(session):
@@ -373,46 +371,59 @@ async def clear_all():
@app.get("/export-excel")
async def export_excel(
tipo: str = Query("geral", regex="^(geral|exclusao_icms|aliquota_icms)$"),
tipo: str = Query("geral", pattern="^(geral|exclusao_icms|aliquota_icms)$"),
cliente: str | None = Query(None)
):
import pandas as pd
# 1) Carregar faturas (com filtro por cliente, se houver)
async with AsyncSessionLocal() as session:
# 1) Faturas
stmt = select(Fatura)
if cliente:
# filtra por cliente_id (UUID em string)
stmt = stmt.where(Fatura.cliente_id == cliente)
faturas_result = await session.execute(stmt)
faturas = faturas_result.scalars().all()
faturas = (await session.execute(stmt)).scalars().all()
# 2) Mapa de alíquotas cadastradas (UF/ano)
aliq_rows = (await session.execute(select(AliquotaUF))).scalars().all()
aliq_map = {(r.uf.upper(), int(r.exercicio)): float(r.aliq_icms) for r in aliq_rows}
# 2) Montar dados conforme 'tipo'
dados = []
if tipo == "aliquota_icms":
# Campos: (Cliente, UC, Referência, Valor Total, ICMS (%), ICMS (R$),
# Base ICMS (R$), Consumo (kWh), Tarifa, Nota Fiscal)
for f in faturas:
uf = (f.estado or "").strip().upper()
ano, _ = _parse_referencia(f.referencia or "")
aliq_nf = float(f.icms_aliq or 0.0)
aliq_cad = aliq_map.get((uf, ano))
diff_pp = (aliq_nf - aliq_cad) if aliq_cad is not None else None
confere = (abs(diff_pp) < 1e-6) if diff_pp is not None else None
dados.append({
"Cliente": f.nome,
"UC": f.unidade_consumidora,
"UF (fatura)": uf,
"Exercício (ref)": ano,
"Referência": f.referencia,
"Valor Total": f.valor_total,
"ICMS (%)": f.icms_aliq,
"ICMS (R$)": f.icms_valor,
"Base ICMS (R$)": f.icms_base,
"Consumo (kWh)": f.consumo,
"Tarifa": f.tarifa,
"Nota Fiscal": f.nota_fiscal,
"ICMS (%) NF": aliq_nf,
# novas colunas padronizadas
"ICMS (%) (UF/Ref)": aliq_cad,
"Dif. ICMS (pp)": diff_pp,
"ICMS confere?": "SIM" if confere else ("N/D" if confere is None else "NÃO"),
"Valor Total": f.valor_total,
"Distribuidora": f.distribuidora,
"Data Processamento": f.data_processamento,
})
filename = "relatorio_aliquota_icms.xlsx"
elif tipo == "exclusao_icms":
# Campos: (Cliente, UC, Referência, Valor Total, PIS (%), ICMS (%), COFINS (%),
# PIS (R$), ICMS (R$), COFINS (R$), Base PIS (R$), Base ICMS (R$),
# Base COFINS (R$), Consumo (kWh), Tarifa, Nota Fiscal)
for f in faturas:
uf = (f.estado or "").strip().upper()
ano, _ = _parse_referencia(f.referencia or "")
aliq_nf = float(f.icms_aliq or 0.0)
aliq_cad = aliq_map.get((uf, ano))
diff_pp = (aliq_nf - aliq_cad) if aliq_cad is not None else None
confere = (abs(diff_pp) < 1e-6) if diff_pp is not None else None
dados.append({
"Cliente": f.nome,
"UC": f.unidade_consumidora,
@@ -427,17 +438,27 @@ async def export_excel(
"Base PIS (R$)": f.pis_base,
"Base ICMS (R$)": f.icms_base,
"Base COFINS (R$)": f.cofins_base,
# novas colunas
"ICMS (%) (UF/Ref)": aliq_cad,
"Dif. ICMS (pp)": diff_pp,
"ICMS confere?": "SIM" if confere else ("N/D" if confere is None else "NÃO"),
"Consumo (kWh)": f.consumo,
"Tarifa": f.tarifa,
"Nota Fiscal": f.nota_fiscal,
})
filename = "relatorio_exclusao_icms.xlsx"
else: # "geral" (mantém seu relatório atual — sem fórmulas SELIC)
# Se quiser manter exatamente o que já tinha com SELIC e fórmulas,
# você pode copiar sua lógica anterior aqui. Abaixo deixo um "geral"
# simplificado com as colunas principais.
else: # geral
for f in faturas:
uf = (f.estado or "").strip().upper()
ano, _ = _parse_referencia(f.referencia or "")
aliq_nf = float(f.icms_aliq or 0.0)
aliq_cad = aliq_map.get((uf, ano))
diff_pp = (aliq_nf - aliq_cad) if aliq_cad is not None else None
confere = (abs(diff_pp) < 1e-6) if diff_pp is not None else None
dados.append({
"Cliente": f.nome,
"UC": f.unidade_consumidora,
@@ -446,6 +467,12 @@ async def export_excel(
"Valor Total": f.valor_total,
"ICMS (%)": f.icms_aliq,
"ICMS (R$)": f.icms_valor,
# novas colunas
"ICMS (%) (UF/Ref)": aliq_cad,
"Dif. ICMS (pp)": diff_pp,
"ICMS confere?": "SIM" if confere else ("N/D" if confere is None else "NÃO"),
"Base ICMS (R$)": f.icms_base,
"PIS (%)": f.pis_aliq,
"PIS (R$)": f.pis_valor,
@@ -460,15 +487,13 @@ async def export_excel(
})
filename = "relatorio_geral.xlsx"
# 3) Gerar excel em memória
from io import BytesIO
# 3) Excel em memória
output = BytesIO()
df = pd.DataFrame(dados)
with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
df.to_excel(writer, index=False, sheet_name="Relatório")
output.seek(0)
# 4) Responder
return StreamingResponse(
output,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
@@ -566,10 +591,12 @@ async def api_relatorios(
if cliente:
params["cliente"] = cliente
# ❗ Inclua 'estado' no SELECT
sql = text(f"""
SELECT id, nome, unidade_consumidora, referencia, nota_fiscal,
valor_total, icms_aliq, icms_valor, pis_aliq, pis_valor,
cofins_aliq, cofins_valor, distribuidora, data_processamento
cofins_aliq, cofins_valor, distribuidora, data_processamento,
estado
FROM faturas.faturas
{where}
ORDER BY data_processamento DESC
@@ -580,21 +607,47 @@ async def api_relatorios(
rows = (await db.execute(sql, params)).mappings().all()
total = (await db.execute(count_sql, params)).scalar_one()
items = [{
"id": str(r["id"]),
"nome": r["nome"],
"unidade_consumidora": r["unidade_consumidora"],
"referencia": r["referencia"],
"nota_fiscal": r["nota_fiscal"],
"valor_total": float(r["valor_total"]) if r["valor_total"] is not None else None,
"icms_aliq": r["icms_aliq"],
"icms_valor": r["icms_valor"],
"pis_aliq": r["pis_aliq"],
"pis_valor": r["pis_valor"],
"cofins_aliq": r["cofins_aliq"],
"cofins_valor": r["cofins_valor"],
"distribuidora": r["distribuidora"],
"data_processamento": r["data_processamento"].isoformat() if r["data_processamento"] else None,
} for r in rows]
# 🔹 Carrega mapa de alíquotas UF/ano
aliq_rows = (await db.execute(select(AliquotaUF))).scalars().all()
aliq_map = {(r.uf.upper(), int(r.exercicio)): float(r.aliq_icms) for r in aliq_rows}
return {"items": items, "total": total, "page": page, "page_size": page_size}
items = []
for r in rows:
uf = (r["estado"] or "").strip().upper()
ano, _mes = _parse_referencia(r["referencia"] or "")
aliq_nf = float(r["icms_aliq"] or 0.0)
aliq_cad = aliq_map.get((uf, ano))
diff_pp = (aliq_nf - aliq_cad) if aliq_cad is not None else None
ok = (abs(diff_pp) < 1e-6) if diff_pp is not None else None
items.append({
"id": str(r["id"]),
"nome": r["nome"],
"unidade_consumidora": r["unidade_consumidora"],
"referencia": r["referencia"],
"nota_fiscal": r["nota_fiscal"],
"valor_total": float(r["valor_total"]) if r["valor_total"] is not None else None,
"icms_aliq": aliq_nf,
"icms_valor": r["icms_valor"],
"pis_aliq": r["pis_aliq"],
"pis_valor": r["pis_valor"],
"cofins_aliq": r["cofins_aliq"],
"cofins_valor": r["cofins_valor"],
"distribuidora": r["distribuidora"],
"data_processamento": r["data_processamento"].isoformat() if r["data_processamento"] else None,
# novos
"estado": uf,
"exercicio": ano,
"aliq_cadastral": aliq_cad,
"aliq_diff_pp": round(diff_pp, 4) if diff_pp is not None else None,
"aliq_ok": ok,
})
return {"items": items, "total": total, "page": page, "page_size": page_size}
async def _carregar_aliquota_map(session):
rows = (await session.execute(
text("SELECT uf, exercicio, aliq_icms FROM faturas.aliquotas_uf")
)).mappings().all()
# (UF, ANO) -> float
return {(r["uf"].upper(), int(r["exercicio"])): float(r["aliq_icms"]) for r in rows}