Ajusta estrutura de parâmetros, move arquivos e corrige referências
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-07-29 14:14:04 -03:00
parent e9ed45ba21
commit e7c2a64714
3 changed files with 304 additions and 108 deletions

View File

@@ -4,15 +4,21 @@ from sqlalchemy.dialects.postgresql import UUID
import uuid import uuid
from datetime import datetime from datetime import datetime
from app.database import Base from app.database import Base
from sqlalchemy import Boolean
class ParametrosFormula(Base): class ParametrosFormula(Base):
__tablename__ = 'parametros_formula' __tablename__ = "parametros_formula"
__table_args__ = {'schema': 'faturas', 'extend_existing': True} __table_args__ = {"schema": "faturas"}
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, index=True)
nome = Column(String) tipo = Column(String(20))
formula = Column(Text) formula = Column(Text)
ativo = Column(Boolean)
aliquota_icms = Column(Float)
incluir_icms = Column(Integer)
incluir_pis = Column(Integer)
incluir_cofins = Column(Integer)
class Fatura(Base): class Fatura(Base):
__tablename__ = "faturas" __tablename__ = "faturas"

182
app/routes/parametros.py → app/parametros.py Executable file → Normal file
View File

@@ -1,83 +1,99 @@
# parametros.py # parametros.py
from fastapi import APIRouter, HTTPException, Depends from fastapi import APIRouter, Request, HTTPException, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from database import get_session from app.database import get_session
from models import AliquotaUF, ParametrosFormula, SelicMensal from app.models import AliquotaUF, ParametrosFormula, SelicMensal
from typing import List from typing import List
from pydantic import BaseModel from pydantic import BaseModel
import datetime import datetime
from fastapi.templating import Jinja2Templates
router = APIRouter() from sqlalchemy.future import select
from app.database import AsyncSessionLocal
# === Schemas ===
class AliquotaUFSchema(BaseModel): router = APIRouter()
uf: str
exercicio: int # === Schemas ===
aliquota: float class AliquotaUFSchema(BaseModel):
uf: str
class Config: exercicio: int
orm_mode = True aliquota: float
class ParametrosFormulaSchema(BaseModel): class Config:
nome: str orm_mode = True
formula: str
campos: str class ParametrosFormulaSchema(BaseModel):
nome: str
class Config: formula: str
orm_mode = True campos: str
class SelicMensalSchema(BaseModel): class Config:
mes: str # 'YYYY-MM' orm_mode = True
fator: float
class SelicMensalSchema(BaseModel):
class Config: mes: str # 'YYYY-MM'
orm_mode = True fator: float
# === Rotas === class Config:
orm_mode = True
@router.get("/parametros/aliquotas", response_model=List[AliquotaUFSchema])
def listar_aliquotas(db: AsyncSession = Depends(get_session)): # === Rotas ===
return db.query(AliquotaUF).all()
router = APIRouter()
@router.post("/parametros/aliquotas") templates = Jinja2Templates(directory="app/templates")
def adicionar_aliquota(aliq: AliquotaUFSchema, db: AsyncSession = Depends(get_session)):
existente = db.query(AliquotaUF).filter_by(uf=aliq.uf, exercicio=aliq.exercicio).first() @router.get("/parametros")
if existente: async def parametros_page(request: Request):
existente.aliquota = aliq.aliquota async with AsyncSessionLocal() as session:
else: result = await session.execute(select(ParametrosFormula).where(ParametrosFormula.ativo == True))
novo = AliquotaUF(**aliq.dict()) parametros = result.scalars().first()
db.add(novo) return templates.TemplateResponse("parametros.html", {
db.commit() "request": request,
return {"status": "ok"} "parametros": parametros or {}
})
@router.get("/parametros/formulas", response_model=List[ParametrosFormulaSchema])
def listar_formulas(db: AsyncSession = Depends(get_session)): @router.get("/parametros/aliquotas", response_model=List[AliquotaUFSchema])
return db.query(ParametrosFormula).all() def listar_aliquotas(db: AsyncSession = Depends(get_session)):
return db.query(AliquotaUF).all()
@router.post("/parametros/formulas")
def salvar_formula(form: ParametrosFormulaSchema, db: AsyncSession = Depends(get_session)): @router.post("/parametros/aliquotas")
existente = db.query(ParametrosFormula).filter_by(nome=form.nome).first() def adicionar_aliquota(aliq: AliquotaUFSchema, db: AsyncSession = Depends(get_session)):
if existente: existente = db.query(AliquotaUF).filter_by(uf=aliq.uf, exercicio=aliq.exercicio).first()
existente.formula = form.formula if existente:
existente.campos = form.campos existente.aliquota = aliq.aliquota
else: else:
novo = ParametrosFormula(**form.dict()) novo = AliquotaUF(**aliq.dict())
db.add(novo) db.add(novo)
db.commit() db.commit()
return {"status": "ok"} return {"status": "ok"}
@router.get("/parametros/selic", response_model=List[SelicMensalSchema]) @router.get("/parametros/formulas", response_model=List[ParametrosFormulaSchema])
def listar_selic(db: AsyncSession = Depends(get_session)): def listar_formulas(db: AsyncSession = Depends(get_session)):
return db.query(SelicMensal).order_by(SelicMensal.mes.desc()).all() return db.query(ParametrosFormula).all()
@router.post("/parametros/selic") @router.post("/parametros/formulas")
def salvar_selic(selic: SelicMensalSchema, db: AsyncSession = Depends(get_session)): def salvar_formula(form: ParametrosFormulaSchema, db: AsyncSession = Depends(get_session)):
existente = db.query(SelicMensal).filter_by(mes=selic.mes).first() existente = db.query(ParametrosFormula).filter_by(nome=form.nome).first()
if existente: if existente:
existente.fator = selic.fator existente.formula = form.formula
else: existente.campos = form.campos
novo = SelicMensal(**selic.dict()) else:
db.add(novo) novo = ParametrosFormula(**form.dict())
db.commit() db.add(novo)
return {"status": "ok"} db.commit()
return {"status": "ok"}
@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()
@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"}

View File

@@ -1,32 +1,206 @@
{% extends "index.html" %} {% extends "index.html" %}
{% block title %}Parâmetros de Cálculo{% endblock %} {% block title %}Parâmetros de Cálculo{% endblock %}
{% block content %} {% block content %}
<h1>⚙️ Parâmetros</h1>
<form method="post"> <h1 style="font-size: 1.6rem; margin-bottom: 1rem; display:flex; align-items:center; gap:0.5rem;">
<label for="tipo">Tipo:</label><br> ⚙️ Parâmetros de Cálculo
<input type="text" name="tipo" id="tipo" value="{{ parametros.tipo or '' }}" required/><br><br> </h1>
<label for="formula">Fórmula:</label><br> <div class="tabs">
<input type="text" name="formula" id="formula" value="{{ parametros.formula or '' }}" required/><br><br> <button class="tab active" onclick="switchTab('formulas')">📄 Fórmulas</button>
<button class="tab" onclick="switchTab('selic')">📊 Gestão SELIC</button>
</div>
<label for="aliquota_icms">Alíquota de ICMS (%):</label><br> <!-- ABA FÓRMULAS -->
<input type="number" step="0.01" name="aliquota_icms" id="aliquota_icms" value="{{ parametros.aliquota_icms or '' }}" /><br><br> <div id="formulas" class="tab-content active">
<form method="post" class="formulario-box">
<div class="grid">
<div class="form-group">
<label for="tipo">Tipo:</label>
<input type="text" name="tipo" id="tipo" value="{{ parametros.tipo or '' }}" required />
</div>
<div class="form-group">
<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 '' }}" />
</div>
</div>
<label for="incluir_icms">Incluir ICMS:</label><br> <div class="form-group">
<input type="checkbox" name="incluir_icms" id="incluir_icms" value="1" {% if parametros.incluir_icms %}checked{% endif %}><br><br> <label for="formula">Fórmula:</label>
<div class="editor-box">
<select onchange="inserirCampo(this)">
<option value="">Inserir campo...</option>
<option value="pis_base">pis_base</option>
<option value="cofins_base">cofins_base</option>
<option value="icms_valor">icms_valor</option>
<option value="pis_aliq">pis_aliq</option>
<option value="cofins_aliq">cofins_aliq</option>
</select>
<textarea name="formula" id="formula" rows="3" required>{{ parametros.formula or '' }}</textarea>
<div class="actions-inline">
<button type="button" class="btn btn-secondary" onclick="testarFormula()">🧪 Testar Fórmula</button>
<span class="exemplo">Ex: (pis_base - (pis_base - icms_valor)) * pis_aliq</span>
</div>
<div id="resultado-teste" class="mensagem-info" style="display:none;"></div>
</div>
</div>
<label for="ativo">Ativo:</label><br> <div class="form-group check-group">
<input type="checkbox" name="ativo" id="ativo" value="1" {% if parametros.ativo %}checked{% endif %}><br><br> <label><input type="checkbox" name="incluir_icms" value="1" {% if parametros.incluir_icms %}checked{% endif %}> Incluir ICMS</label>
<label><input type="checkbox" name="incluir_pis" value="1" {% if parametros.incluir_pis %}checked{% endif %}> Incluir PIS</label>
<label><input type="checkbox" name="incluir_cofins" value="1" {% if parametros.incluir_cofins %}checked{% endif %}> Incluir COFINS</label>
<label><input type="checkbox" name="ativo" value="1" {% if parametros.ativo %}checked{% endif %}> Ativo</label>
</div>
<button type="submit" style="padding: 10px 20px; background: #2563eb; color: white; border: none; border-radius: 4px; cursor: pointer;"> <button type="submit" class="btn btn-primary">💾 Salvar Parâmetro</button>
Salvar Parâmetros </form>
</button>
</form>
{% if mensagem %} <h3 style="margin-top: 2rem;">📋 Fórmulas Salvas</h3>
<div style="margin-top: 20px; background: #e0f7fa; padding: 10px; border-left: 4px solid #2563eb;"> <div class="card-list">
{{ mensagem }} {% for param in lista_parametros %}
<div class="param-card">
<h4>{{ param.tipo }}</h4>
<code>{{ param.formula }}</code>
<div class="actions">
<a href="?editar={{ param.id }}" class="btn btn-sm">✏️ Editar</a>
<a href="?testar={{ param.id }}" class="btn btn-sm btn-secondary">🧪 Testar</a>
<a href="/parametros/delete/{{ param.id }}" class="btn btn-sm btn-danger">🗑️ Excluir</a>
</div>
</div>
{% else %}<p style="color:gray;">Nenhuma fórmula cadastrada.</p>{% endfor %}
</div> </div>
{% endif %} </div>
<!-- ABA SELIC -->
<div id="selic" class="tab-content" style="display:none;">
<div class="formulario-box">
<h3>📈 Gestão da SELIC</h3>
<p>Utilize o botão abaixo para importar os fatores SELIC automaticamente a partir da API do Banco Central.</p>
<form method="post" action="/parametros/selic/importar">
<div class="form-group">
<label for="data_maxima">Data máxima para cálculo da SELIC:</label>
<input type="date" id="data_maxima" name="data_maxima" value="{{ data_maxima or '' }}" />
</div>
<button type="submit" class="btn btn-primary">⬇️ Atualizar Fatores SELIC</button>
</form>
<div class="mensagem-info" style="margin-top:1rem;">Última data coletada da SELIC: <strong>{{ ultima_data_selic }}</strong></div>
<table class="selic-table">
<thead><tr><th>Competência</th><th>Fator</th></tr></thead>
<tbody>
{% for item in selic_dados %}
<tr><td>{{ item.competencia }}</td><td>{{ item.fator }}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- ESTILOS -->
<style>
.tabs { display: flex; gap: 1rem; margin-bottom: 1rem; }
.tab { background: none; border: none; font-weight: bold; cursor: pointer; padding: 0.5rem 1rem; border-bottom: 2px solid transparent; }
.tab.active { border-bottom: 2px solid #2563eb; color: #2563eb; }
.tab-content { display: none; }
.tab-content.active { display: block; }
.formulario-box {
background: #fff;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.05);
max-width: 850px;
margin: 0 auto;
}
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; font-weight: bold; margin-bottom: 0.5rem; }
.form-group input[type="text"],
.form-group input[type="number"],
.form-group input[type="date"],
.form-group textarea { width: 100%; padding: 0.6rem; border-radius: 6px; border: 1px solid #ccc; }
.form-group.check-group { display: flex; gap: 1rem; flex-wrap: wrap; }
.check-group label { display: flex; align-items: center; gap: 0.5rem; }
.editor-box select { margin-bottom: 0.5rem; }
.editor-box textarea { font-family: monospace; }
.actions-inline { display: flex; gap: 1rem; align-items: center; margin-top: 0.5rem; }
.btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; }
.btn-primary { background-color: #2563eb; color: white; }
.btn-secondary { background-color: #6c757d; color: white; }
.btn-danger { background-color: #dc3545; color: white; }
.btn-sm { font-size: 0.85rem; padding: 0.3rem 0.6rem; }
.mensagem-info {
background: #e0f7fa;
padding: 1rem;
border-left: 4px solid #2563eb;
border-radius: 6px;
color: #007b83;
}
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.param-card {
background: #f9f9f9;
padding: 1rem;
border-radius: 8px;
border-left: 4px solid #2563eb;
}
.param-card code { display: block; margin: 0.5rem 0; color: #333; }
.actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.selic-table { width: 100%; margin-top: 1rem; border-collapse: collapse; }
.selic-table th, .selic-table td { padding: 0.6rem; border-bottom: 1px solid #ccc; }
.selic-table th { text-align: left; background: #f1f1f1; }
</style>
<script>
function switchTab(tabId) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
document.getElementById(tabId).classList.add('active');
event.target.classList.add('active');
}
function inserirCampo(select) {
const campo = select.value;
if (campo) {
const formula = document.getElementById("formula");
const start = formula.selectionStart;
const end = formula.selectionEnd;
formula.value = formula.value.slice(0, start) + campo + formula.value.slice(end);
formula.focus();
formula.setSelectionRange(start + campo.length, start + campo.length);
select.selectedIndex = 0;
}
}
function testarFormula() {
const formula = document.getElementById("formula").value;
const vars = {
pis_base: 84.38,
cofins_base: 84.38,
icms_valor: 24.47,
pis_aliq: 0.012872,
cofins_aliq: 0.059287
};
try {
const resultado = eval(formula.replace(/\b(\w+)\b/g, match => vars.hasOwnProperty(match) ? vars[match] : match));
document.getElementById("resultado-teste").innerText = "Resultado: R$ " + resultado.toFixed(5);
document.getElementById("resultado-teste").style.display = "block";
} catch (e) {
document.getElementById("resultado-teste").innerText = "Erro: " + e.message;
document.getElementById("resultado-teste").style.display = "block";
}
}
</script>
{% endblock %} {% endblock %}