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 app.database import get_session
from fastapi import Query from fastapi import Query
from sqlalchemy import select as sqla_select from sqlalchemy import select as sqla_select
from app.models import AliquotaUF
import pandas as pd
app = FastAPI() app = FastAPI()
@@ -40,29 +42,25 @@ UPLOAD_DIR = os.path.join("app", "uploads", "temp")
os.makedirs(UPLOAD_DIR, exist_ok=True) os.makedirs(UPLOAD_DIR, exist_ok=True)
def _parse_referencia(ref: str): 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} 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() ref = (ref or "").strip().upper()
if "/" in ref: if "/" in ref:
a, b = [p.strip() for p in ref.split("/", 1)] a, b = [p.strip() for p in ref.split("/", 1)]
# mês pode vir 'JAN' ou '01'
mes = meses.get(a, None) mes = meses.get(a, None)
if mes is None: if mes is None:
mes = int(re.sub(r"\D", "", a) or 1) mes = int(re.sub(r"\D", "", a) or 1)
ano = int(re.sub(r"\D", "", b) or 0) ano = int(re.sub(r"\D", "", b) or 0)
# ano 2 dígitos -> 2000+
if ano < 100: if ano < 100:
ano += 2000 ano += 2000
else: else:
# '202401' ou '2024-01'
num = re.sub(r"\D", "", ref) num = re.sub(r"\D", "", ref)
if len(num) >= 6: if len(num) >= 6:
ano, mes = int(num[:4]), int(num[4:6]) ano, mes = int(num[:4]), int(num[4:6])
elif len(num) == 4: # '2024' elif len(num) == 4:
ano, mes = int(num), 1 ano, mes = int(num), 1
else: else:
ano, mes = date.today().year, 1 ano, mes = 0, 0
return ano, mes return ano, mes
async def _carregar_selic_map(session): async def _carregar_selic_map(session):
@@ -373,46 +371,59 @@ async def clear_all():
@app.get("/export-excel") @app.get("/export-excel")
async def 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) cliente: str | None = Query(None)
): ):
import pandas as pd
# 1) Carregar faturas (com filtro por cliente, se houver)
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:
# 1) Faturas
stmt = select(Fatura) stmt = select(Fatura)
if cliente: if cliente:
# filtra por cliente_id (UUID em string)
stmt = stmt.where(Fatura.cliente_id == cliente) stmt = stmt.where(Fatura.cliente_id == cliente)
faturas_result = await session.execute(stmt) faturas = (await session.execute(stmt)).scalars().all()
faturas = faturas_result.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 = [] dados = []
if tipo == "aliquota_icms": 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: 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({ dados.append({
"Cliente": f.nome, "Cliente": f.nome,
"UC": f.unidade_consumidora, "UF (fatura)": uf,
"Exercício (ref)": ano,
"Referência": f.referencia, "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, "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" filename = "relatorio_aliquota_icms.xlsx"
elif tipo == "exclusao_icms": 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: 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({ dados.append({
"Cliente": f.nome, "Cliente": f.nome,
"UC": f.unidade_consumidora, "UC": f.unidade_consumidora,
@@ -427,17 +438,27 @@ async def export_excel(
"Base PIS (R$)": f.pis_base, "Base PIS (R$)": f.pis_base,
"Base ICMS (R$)": f.icms_base, "Base ICMS (R$)": f.icms_base,
"Base COFINS (R$)": f.cofins_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, "Consumo (kWh)": f.consumo,
"Tarifa": f.tarifa, "Tarifa": f.tarifa,
"Nota Fiscal": f.nota_fiscal, "Nota Fiscal": f.nota_fiscal,
}) })
filename = "relatorio_exclusao_icms.xlsx" filename = "relatorio_exclusao_icms.xlsx"
else: # "geral" (mantém seu relatório atual — sem fórmulas SELIC) else: # geral
# 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.
for f in faturas: 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({ dados.append({
"Cliente": f.nome, "Cliente": f.nome,
"UC": f.unidade_consumidora, "UC": f.unidade_consumidora,
@@ -446,6 +467,12 @@ async def export_excel(
"Valor Total": f.valor_total, "Valor Total": f.valor_total,
"ICMS (%)": f.icms_aliq, "ICMS (%)": f.icms_aliq,
"ICMS (R$)": f.icms_valor, "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, "Base ICMS (R$)": f.icms_base,
"PIS (%)": f.pis_aliq, "PIS (%)": f.pis_aliq,
"PIS (R$)": f.pis_valor, "PIS (R$)": f.pis_valor,
@@ -460,15 +487,13 @@ async def export_excel(
}) })
filename = "relatorio_geral.xlsx" filename = "relatorio_geral.xlsx"
# 3) Gerar excel em memória # 3) Excel em memória
from io import BytesIO
output = BytesIO() output = BytesIO()
df = pd.DataFrame(dados) df = pd.DataFrame(dados)
with pd.ExcelWriter(output, engine="xlsxwriter") as writer: with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
df.to_excel(writer, index=False, sheet_name="Relatório") df.to_excel(writer, index=False, sheet_name="Relatório")
output.seek(0) output.seek(0)
# 4) Responder
return StreamingResponse( return StreamingResponse(
output, output,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
@@ -566,10 +591,12 @@ async def api_relatorios(
if cliente: if cliente:
params["cliente"] = cliente params["cliente"] = cliente
# ❗ Inclua 'estado' no SELECT
sql = text(f""" sql = text(f"""
SELECT id, nome, unidade_consumidora, referencia, nota_fiscal, SELECT id, nome, unidade_consumidora, referencia, nota_fiscal,
valor_total, icms_aliq, icms_valor, pis_aliq, pis_valor, 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 FROM faturas.faturas
{where} {where}
ORDER BY data_processamento DESC ORDER BY data_processamento DESC
@@ -580,14 +607,27 @@ async def api_relatorios(
rows = (await db.execute(sql, params)).mappings().all() rows = (await db.execute(sql, params)).mappings().all()
total = (await db.execute(count_sql, params)).scalar_one() total = (await db.execute(count_sql, params)).scalar_one()
items = [{ # 🔹 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}
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"]), "id": str(r["id"]),
"nome": r["nome"], "nome": r["nome"],
"unidade_consumidora": r["unidade_consumidora"], "unidade_consumidora": r["unidade_consumidora"],
"referencia": r["referencia"], "referencia": r["referencia"],
"nota_fiscal": r["nota_fiscal"], "nota_fiscal": r["nota_fiscal"],
"valor_total": float(r["valor_total"]) if r["valor_total"] is not None else None, "valor_total": float(r["valor_total"]) if r["valor_total"] is not None else None,
"icms_aliq": r["icms_aliq"], "icms_aliq": aliq_nf,
"icms_valor": r["icms_valor"], "icms_valor": r["icms_valor"],
"pis_aliq": r["pis_aliq"], "pis_aliq": r["pis_aliq"],
"pis_valor": r["pis_valor"], "pis_valor": r["pis_valor"],
@@ -595,6 +635,19 @@ async def api_relatorios(
"cofins_valor": r["cofins_valor"], "cofins_valor": r["cofins_valor"],
"distribuidora": r["distribuidora"], "distribuidora": r["distribuidora"],
"data_processamento": r["data_processamento"].isoformat() if r["data_processamento"] else None, "data_processamento": r["data_processamento"].isoformat() if r["data_processamento"] else None,
} for r in rows] # 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} 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}

View File

@@ -72,7 +72,7 @@ class AliquotaUF(Base):
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
uf = Column(String) uf = Column(String)
exercicio = Column(String) exercicio = Column(Integer)
aliq_icms = Column(Numeric(6, 4)) aliq_icms = Column(Numeric(6, 4))
class SelicMensal(Base): class SelicMensal(Base):

View File

@@ -1,5 +1,5 @@
# parametros.py # parametros.py
from fastapi import APIRouter, Request, Depends, Form from fastapi import APIRouter, Request, Depends, Form, UploadFile, File
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_session from app.database import get_session
from app.models import AliquotaUF, ParametrosFormula, SelicMensal from app.models import AliquotaUF, ParametrosFormula, SelicMensal
@@ -9,7 +9,7 @@ import datetime
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlalchemy.future import select from sqlalchemy.future import select
from app.database import AsyncSessionLocal from app.database import AsyncSessionLocal
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse, JSONResponse
from app.models import Fatura from app.models import Fatura
from fastapi import Body from fastapi import Body
from app.database import engine from app.database import engine
@@ -21,6 +21,8 @@ import csv
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
import pandas as pd import pandas as pd
from io import BytesIO from io import BytesIO
from sqlalchemy import select
from decimal import Decimal
router = APIRouter() router = APIRouter()
@@ -100,6 +102,28 @@ async def editar_parametro(param_id: int, request: Request):
return {"success": True} return {"success": True}
return {"success": False, "error": "Não encontrado"} return {"success": False, "error": "Não encontrado"}
@router.post("/parametros/ativar/{param_id}")
async def ativar_parametro(param_id: int, request: Request):
data = await request.json()
ativo = bool(data.get("ativo", True))
async with AsyncSessionLocal() as session:
param = await session.get(ParametrosFormula, param_id)
if not param:
return JSONResponse(status_code=404, content={"error": "Parâmetro não encontrado"})
param.ativo = ativo
await session.commit()
return {"success": True}
@router.get("/parametros/delete/{param_id}")
async def deletar_parametro(param_id: int):
async with AsyncSessionLocal() as session:
param = await session.get(ParametrosFormula, param_id)
if not param:
return RedirectResponse("/parametros?erro=1&msg=Parâmetro não encontrado", status_code=303)
await session.delete(param)
await session.commit()
return RedirectResponse("/parametros?ok=1&msg=Parâmetro removido", status_code=303)
@router.post("/parametros/testar") @router.post("/parametros/testar")
async def testar_formula(db: AsyncSession = Depends(get_session), data: dict = Body(...)): async def testar_formula(db: AsyncSession = Depends(get_session), data: dict = Body(...)):
formula = data.get("formula") formula = data.get("formula")
@@ -117,10 +141,18 @@ async def testar_formula(db: AsyncSession = Depends(get_session), data: dict = B
return {"success": False, "error": str(e)} return {"success": False, "error": str(e)}
@router.get("/parametros/aliquotas", response_model=List[AliquotaUFSchema]) @router.get("/parametros/aliquotas")
async def listar_aliquotas(db: AsyncSession = Depends(get_session)): async def listar_aliquotas(uf: str | None = None, db: AsyncSession = Depends(get_session)):
result = await db.execute(select(AliquotaUF).order_by(AliquotaUF.uf, AliquotaUF.exercicio)) stmt = select(AliquotaUF).order_by(AliquotaUF.uf, AliquotaUF.exercicio.desc())
return result.scalars().all() if uf:
stmt = stmt.where(AliquotaUF.uf == uf)
rows = (await db.execute(stmt)).scalars().all()
return [
{"uf": r.uf, "exercicio": int(r.exercicio), "aliquota": float(r.aliq_icms)}
for r in rows
]
@router.post("/parametros/aliquotas") @router.post("/parametros/aliquotas")
async def adicionar_aliquota(aliq: AliquotaUFSchema, db: AsyncSession = Depends(get_session)): async def adicionar_aliquota(aliq: AliquotaUFSchema, db: AsyncSession = Depends(get_session)):
@@ -227,5 +259,88 @@ def baixar_template_excel():
headers={"Content-Disposition": "attachment; filename=template_aliquotas.xlsx"} headers={"Content-Disposition": "attachment; filename=template_aliquotas.xlsx"}
) )
@router.post("/parametros/aliquotas/salvar")
async def salvar_aliquota(payload: dict, db: AsyncSession = Depends(get_session)):
uf = (payload.get("uf") or "").strip().upper()
exercicio = int(payload.get("exercicio") or 0)
aliquota = Decimal(str(payload.get("aliquota") or "0"))
orig_uf = (payload.get("original_uf") or "").strip().upper() or uf
orig_ex = int(payload.get("original_exercicio") or 0) or exercicio
if not uf or not exercicio or aliquota <= 0:
return JSONResponse(status_code=400, content={"error": "UF, exercício e alíquota são obrigatórios."})
# busca pelo registro original (antes da edição)
stmt = select(AliquotaUF).where(
AliquotaUF.uf == orig_uf,
AliquotaUF.exercicio == orig_ex
)
existente = (await db.execute(stmt)).scalar_one_or_none()
if existente:
# atualiza (inclusive a chave, se mudou)
existente.uf = uf
existente.exercicio = exercicio
existente.aliq_icms = aliquota
else:
# não existia o original -> upsert padrão
db.add(AliquotaUF(uf=uf, exercicio=exercicio, aliq_icms=aliquota))
await db.commit()
return {"success": True}
@router.post("/parametros/aliquotas/importar")
async def importar_aliquotas_csv(arquivo: UploadFile = File(...), db: AsyncSession = Depends(get_session)):
content = await arquivo.read()
text = content.decode("utf-8", errors="ignore")
# tenta ; depois ,
sniffer = csv.Sniffer()
dialect = sniffer.sniff(text.splitlines()[0] if text else "uf;exercicio;aliquota")
reader = csv.DictReader(io.StringIO(text), dialect=dialect)
count = 0
for row in reader:
uf = (row.get("uf") or row.get("UF") or "").strip().upper()
exercicio_str = (row.get("exercicio") or row.get("ano") or "").strip()
try:
exercicio = int(exercicio_str)
except Exception:
continue
aliquota_str = (row.get("aliquota") or row.get("aliq_icms") or "").replace(",", ".").strip()
if not uf or not exercicio or not aliquota_str:
continue
try:
aliquota = Decimal(aliquota_str)
except Exception:
continue
stmt = select(AliquotaUF).where(AliquotaUF.uf == uf, AliquotaUF.exercicio == exercicio)
existente = (await db.execute(stmt)).scalar_one_or_none()
if existente:
existente.aliq_icms = aliquota
else:
db.add(AliquotaUF(uf=uf, exercicio=exercicio, aliq_icms=aliquota))
count += 1
await db.commit()
return {"success": True, "qtd": count}
@router.delete("/parametros/aliquotas/{uf}/{exercicio}")
async def excluir_aliquota(uf: str, exercicio: int, db: AsyncSession = Depends(get_session)):
stmt = select(AliquotaUF).where(
AliquotaUF.uf == uf.upper(),
AliquotaUF.exercicio == exercicio
)
row = (await db.execute(stmt)).scalar_one_or_none()
if not row:
return JSONResponse(status_code=404, content={"error": "Registro não encontrado."})
await db.delete(row)
await db.commit()
return {"success": True}

View File

@@ -22,7 +22,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="aliquota_icms">Alíquota de ICMS (%):</label> <label for="aliquota_icms">Alíquota de ICMS (%):</label>
<input type="number" step="0.01" name="aliquota_icms" id="aliquota_icms" value="{{ parametros.aliquota_icms or '' }}" /> <input type="text" id="aliquota" name="aliquota" inputmode="decimal" pattern="[0-9]+([,][0-9]+)?" placeholder="Ex: 20,7487">
</div> </div>
</div> </div>
@@ -62,6 +62,7 @@
<hr style="margin-top: 2rem; margin-bottom: 1rem;"> <hr style="margin-top: 2rem; margin-bottom: 1rem;">
<h3 style="margin-top: 2rem;">📋 Fórmulas Salvas</h3> <h3 style="margin-top: 2rem;">📋 Fórmulas Salvas</h3>
<div class="card-list"> <div class="card-list">
{% if
{% for param in lista_parametros %} {% for param in lista_parametros %}
<div class="param-card {{ 'ativo' if param.ativo else 'inativo' }}" id="card-{{ param.id }}"> <div class="param-card {{ 'ativo' if param.ativo else 'inativo' }}" id="card-{{ param.id }}">
<div style="display:flex; justify-content:space-between; align-items:center;"> <div style="display:flex; justify-content:space-between; align-items:center;">
@@ -124,7 +125,7 @@
<!-- ABA ALÍQUOTAS --> <!-- ABA ALÍQUOTAS -->
<div id="aliquotas" class="tab-content"> <div id="aliquotas" class="tab-content">
<div class="formulario-box"> <div class="formulario-box">
<form onsubmit="return salvarAliquota(this)"> <form onsubmit="return salvarAliquota(this, event)">
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));"> <div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));">
<div class="form-group"> <div class="form-group">
<label>UF:</label> <label>UF:</label>
@@ -141,7 +142,10 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Alíquota ICMS (%):</label> <label>Alíquota ICMS (%):</label>
<input name="aliquota" type="number" step="0.0001" required /> <input id="aliquota-uf" name="aliquota"
type="text" inputmode="decimal"
pattern="[0-9]+([,][0-9]+)?"
placeholder="Ex: 20,7487" required />
</div> </div>
</div> </div>
<!-- Bloco com espaçamento e alinhamento central --> <!-- Bloco com espaçamento e alinhamento central -->
@@ -160,12 +164,35 @@
</div> </div>
</div> </div>
<input type="hidden" id="orig-uf" name="original_uf">
<input type="hidden" id="orig-exercicio" name="original_exercicio">
</form> </form>
<table class="selic-table"> <!-- Filtro de UF para a tabela -->
<thead><tr><th>UF</th><th>Exercício</th><th>Alíquota</th></tr></thead> <div style="display:flex; align-items:center; gap:12px; margin:14px 0;">
<label for="filtro-uf" style="font-weight:600;">Filtrar por UF:</label>
<select id="filtro-uf" style="min-width:220px; padding:.5rem .75rem; border:1px solid #ddd; border-radius:8px;">
<option value="">Todas</option>
{% for uf in ['AC','AL','AP','AM','BA','CE','DF','ES','GO','MA','MT','MS','MG','PA','PB','PR','PE','PI','RJ','RN','RS','RO','RR','SC','SP','SE','TO'] %}
<option value="{{ uf }}">{{ uf }}</option>
{% endfor %}
</select>
<span id="total-aliquotas" class="muted"></span>
</div>
<table class="selic-table">
<thead>
<tr>
<th>UF</th>
<th>Exercício</th>
<th>Alíquota</th>
<th style="width:140px;">Ações</th>
</tr>
</thead>
<tbody id="tabela-aliquotas"></tbody> <tbody id="tabela-aliquotas"></tbody>
</table> </table>
</div> </div>
</div> </div>
@@ -581,16 +608,39 @@
// ✅ Carrega tabela de alíquotas // ✅ Carrega tabela de alíquotas
async function carregarAliquotas() { async function carregarAliquotas() {
const res = await fetch("/parametros/aliquotas"); const uf = document.getElementById("filtro-uf")?.value || "";
const url = new URL("/parametros/aliquotas", window.location.origin);
if (uf) url.searchParams.set("uf", uf);
const res = await fetch(url);
const dados = await res.json(); const dados = await res.json();
const tbody = document.getElementById("tabela-aliquotas"); const tbody = document.getElementById("tabela-aliquotas");
if (!dados.length) {
tbody.innerHTML = `<tr><td colspan="3" style="padding:.6rem;">Nenhum registro.</td></tr>`;
} else {
tbody.innerHTML = dados.map(a => ` tbody.innerHTML = dados.map(a => `
<tr><td>${a.uf}</td><td>${a.exercicio}</td><td>${a.aliquota.toFixed(4)}%</td></tr> <tr>
<td>${a.uf}</td>
<td>${a.exercicio}</td>
<td>${Number(a.aliquota).toLocaleString('pt-BR', {minimumFractionDigits:4, maximumFractionDigits:4})}%</td>
<td style="display:flex; gap:8px;">
<button class="btn btn-sm btn-secondary"
onclick="editarAliquota('${a.uf}', ${a.exercicio}, ${Number(a.aliquota)})">✏️ Editar</button>
<button class="btn btn-sm btn-danger"
onclick="excluirAliquota('${a.uf}', ${a.exercicio})">🗑️ Excluir</button>
</td>
</tr>
`).join(''); `).join('');
} }
document.getElementById("total-aliquotas").textContent = `Registros: ${dados.length}`;
}
// ✅ Eventos após carregar DOM // ✅ Eventos após carregar DOM
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
document.getElementById("filtro-uf")?.addEventListener("change", carregarAliquotas);
carregarAliquotas(); carregarAliquotas();
// Ativar/desativar checkbox // Ativar/desativar checkbox
@@ -657,6 +707,86 @@
}); });
} }
async function salvarAliquota(form, ev) {
ev?.preventDefault();
const uf = form.uf.value?.trim();
const exercicio = Number(form.exercicio.value?.trim());
const aliquotaStr = form.aliquota.value?.trim();
const aliquota = parseFloat(aliquotaStr.replace(',', '.')); // vírgula -> ponto
// 👇 LE OS ORIGINAIS
const original_uf = document.getElementById('orig-uf').value || null;
const original_exercicio = document.getElementById('orig-exercicio').value
? Number(document.getElementById('orig-exercicio').value)
: null;
if (!uf || !exercicio || isNaN(exercicio) || !aliquotaStr || isNaN(aliquota)) {
mostrarFeedback("❌ Erro", "Preencha UF, exercício e alíquota válidos.");
return false;
}
const res = await fetch("/parametros/aliquotas/salvar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ uf, exercicio, aliquota, original_uf, original_exercicio })
});
if (!res.ok) {
const msg = await res.text();
mostrarFeedback("❌ Erro ao salvar", msg || "Falha na operação.");
return false;
}
mostrarFeedback("✅ Salvo", "Alíquota registrada/atualizada com sucesso.");
cancelarEdicao(); // limpa modo edição
carregarAliquotas();
return false;
}
function editarAliquota(uf, exercicio, aliquota) {
const form = document.querySelector('#aliquotas form');
form.uf.value = uf;
form.exercicio.value = String(exercicio);
// Mostrar no input com vírgula e 4 casas
const valorBR = Number(aliquota).toLocaleString('pt-BR', {
minimumFractionDigits: 4, maximumFractionDigits: 4
});
form.querySelector('[name="aliquota"]').value = valorBR;
// 👇 GUARDA A CHAVE ORIGINAL
document.getElementById('orig-uf').value = uf;
document.getElementById('orig-exercicio').value = String(exercicio);
document.getElementById('aliquotas')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function cancelarEdicao(){
const form = document.querySelector('#aliquotas form');
form.reset();
document.getElementById('orig-uf').value = '';
document.getElementById('orig-exercicio').value = '';
}
async function excluirAliquota(uf, exercicio){
if(!confirm(`Excluir a alíquota de ${uf}/${exercicio}?`)) return;
const res = await fetch(`/parametros/aliquotas/${encodeURIComponent(uf)}/${exercicio}`, {
method: 'DELETE'
});
if(!res.ok){
const msg = await res.text();
mostrarFeedback("❌ Erro", msg || "Falha ao excluir.");
return;
}
mostrarFeedback("🗑️ Excluída", "Alíquota removida com sucesso.");
carregarAliquotas();
}
</script> </script>
<!-- Feedback estilo popup --> <!-- Feedback estilo popup -->

View File

@@ -32,8 +32,10 @@
<a class="btn btn-secondary" href="/erros/log">📄 Ver Log de Erros (.txt)</a> <a class="btn btn-secondary" href="/erros/log">📄 Ver Log de Erros (.txt)</a>
</div> </div>
{% endif %} {% endif %}
<!--
<button class="btn btn-success" onclick="baixarPlanilha()">📅 Abrir Planilha</button> <button class="btn btn-success" onclick="baixarPlanilha()">📅 Abrir Planilha</button>
<button class="btn btn-success" onclick="gerarRelatorio()">📊 Gerar Relatório</button> <button class="btn btn-success" onclick="gerarRelatorio()">📊 Gerar Relatório</button>
-->
{% if app_env != "producao" %} {% if app_env != "producao" %}
<button class="btn btn-warning" onclick="limparFaturas()">🧹 Limpar Faturas (Teste)</button> <button class="btn btn-warning" onclick="limparFaturas()">🧹 Limpar Faturas (Teste)</button>
{% endif %} {% endif %}