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
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
157
app/main.py
157
app/main.py
@@ -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}
|
||||
Reference in New Issue
Block a user