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"

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

@@ -1,12 +1,15 @@
# 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
from sqlalchemy.future import select
from app.database import AsyncSessionLocal
router = APIRouter() router = APIRouter()
@@ -36,6 +39,19 @@ class SelicMensalSchema(BaseModel):
# === Rotas === # === 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()
return templates.TemplateResponse("parametros.html", {
"request": request,
"parametros": parametros or {}
})
@router.get("/parametros/aliquotas", response_model=List[AliquotaUFSchema]) @router.get("/parametros/aliquotas", response_model=List[AliquotaUFSchema])
def listar_aliquotas(db: AsyncSession = Depends(get_session)): def listar_aliquotas(db: AsyncSession = Depends(get_session)):
return db.query(AliquotaUF).all() return db.query(AliquotaUF).all()

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 %}