2025-07-28 13:29:45 -03:00
{% extends "index.html" %}
{% block title %}Parâmetros de Cálculo{% endblock %}
{% block content %}
2025-07-29 14:14:04 -03:00
< h1 style = "font-size: 1.6rem; margin-bottom: 1rem; display:flex; align-items:center; gap:0.5rem;" >
⚙️ Parâmetros de Cálculo
< / h1 >
2025-07-28 13:29:45 -03:00
2025-07-29 14:14:04 -03:00
< div class = "tabs" >
< button class = "tab active" onclick = "switchTab('formulas')" > 📄 Fórmulas< / button >
< button class = "tab" onclick = "switchTab('selic')" > 📊 Gestão SELIC< / button >
2025-07-30 09:48:44 -03:00
< button class = "tab" onclick = "switchTab('aliquotas')" > 🧾 Cadastro de Alíquotas por Estado< / button >
2025-07-29 14:14:04 -03:00
< / div >
2025-07-28 13:29:45 -03:00
2025-07-29 14:14:04 -03:00
<!-- ABA FÓRMULAS -->
< div id = "formulas" class = "tab-content active" >
< form method = "post" class = "formulario-box" >
2025-07-30 09:48:44 -03:00
< div class = "grid" style = "grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));" >
2025-07-29 14:14:04 -03:00
< div class = "form-group" >
2025-07-30 09:48:44 -03:00
< label for = "nome" > Nome:< / label >
< input type = "text" name = "nome" id = "nome" value = "{{ parametros.nome or '' }}" required / >
2025-07-29 14:14:04 -03:00
< / 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 >
2025-07-28 13:29:45 -03:00
2025-07-29 14:14:04 -03:00
< div class = "form-group" >
< label for = "formula" > Fórmula:< / label >
< div class = "editor-box" >
2025-07-30 09:48:44 -03:00
< div style = "margin-bottom: 0.5rem;" >
< strong > Campos disponíveis:< / strong >
< div class = "campo-badges" >
{% for campo in campos_fatura %}
< span class = "badge-campo" onclick = "inserirNoEditor('{{ campo }}')" > {{ campo }}< / span >
{% endfor %}
< / div >
< / div >
< div style = "margin-bottom: 0.5rem;" >
< strong > Operadores:< / strong >
< div class = "campo-badges" >
{% for op in ['+', '-', '*', '/', '(', ')'] %}
< span class = "badge-operador" onclick = "inserirNoEditor('{{ op }}')" > {{ op }}< / span >
{% endfor %}
< / div >
< / div >
2025-07-29 14:14:04 -03:00
< 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 >
2025-07-28 13:29:45 -03:00
2025-07-29 14:14:04 -03:00
< button type = "submit" class = "btn btn-primary" > 💾 Salvar Parâmetro< / button >
2025-07-30 09:48:44 -03:00
< button type = "button" class = "btn btn-primary pulse" onclick = "limparFormulario()" > 🔁 Novo Parâmetro< / button >
2025-07-28 13:29:45 -03:00
2025-07-30 09:48:44 -03:00
< / form >
< hr style = "margin-top: 2rem; margin-bottom: 1rem;" >
< h3 style = "margin-top: 2rem;" > 📋 Fórmulas Salvas< / h3 >
2025-07-29 14:14:04 -03:00
< div class = "card-list" >
{% for param in lista_parametros %}
2025-07-30 09:48:44 -03:00
< 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;" >
< input type = "text" class = "edit-nome" value = "{{ param.nome }}" data-id = "{{ param.id }}"
onkeydown="if(event.key==='Enter'){ event.preventDefault(); salvarInline('{{ param.id }}') }" />
< span class = "badge-status" > {{ 'Ativo ✅' if param.ativo else 'Inativo ❌' }}< / span >
< / div >
< textarea class = "edit-formula" data-id = "{{ param.id }}" title = "{{ param.formula }}" > {{ param.formula }}< / textarea >
<!-- botão de testar e salvar -->
< div style = "display: flex; justify-content: space-between; align-items:center;" >
< label >
< input type = "checkbox" class = "toggle-ativo" data-id = "{{ param.id }}" { % if param . ativo % } checked { % endif % } >
Ativo
< / label >
< div class = "actions" >
< button type = "button" class = "btn btn-sm btn-secondary btn-testar" data-id = "{{ param.id }}" > 🧪 Testar< / button >
< button class = "btn btn-sm btn-primary" onclick = "salvarInline('{{ param.id }}')" > 💾 Salvar< / button >
< a href = "/parametros/delete/{{ param.id }}" class = "btn btn-sm btn-danger" onclick = "return confirm('Deseja excluir?')" > 🗑️ Excluir< / a >
< / div >
< / div >
< div class = "mensagem-info" id = "resultado-inline-{{ param.id }}" style = "margin-top: 0.5rem; display:none;" > < / div >
2025-07-29 14:14:04 -03:00
< / div >
2025-07-30 09:48:44 -03:00
{% else %}
< p style = "color:gray;" > Nenhuma fórmula cadastrada.< / p >
{% endfor %}
2025-07-28 13:29:45 -03:00
< / div >
2025-07-29 14:14:04 -03:00
< / div >
2025-07-30 09:48:44 -03:00
2025-07-29 14:14:04 -03:00
<!-- ABA SELIC -->
2025-07-30 09:48:44 -03:00
< div id = "selic" class = "tab-content" >
2025-07-29 14:14:04 -03:00
< div class = "formulario-box" >
< p > Utilize o botão abaixo para importar os fatores SELIC automaticamente a partir da API do Banco Central.< / p >
2025-07-30 09:48:44 -03:00
< form method = "post" action = "/parametros/selic/importar" onsubmit = "mostrarLoadingSelic()" >
2025-07-29 14:14:04 -03:00
< 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 >
2025-07-30 09:48:44 -03:00
< div style = "display: flex; gap: 1rem; flex-wrap: wrap;" >
< button type = "submit" class = "btn btn-primary" > ⬇️ Atualizar Fatores SELIC< / button >
< button type = "button" class = "btn btn-secondary" onclick = "mostrarFeedback('🔁 Atualização', 'Função de recarga futura')" > 🔄 Recarregar< / button >
< / div >
< div class = "mensagem-info" style = "margin-top:1rem;" > Última data coletada da SELIC: < strong > {{ ultima_data_selic }}< / strong > < / div >
2025-07-29 14:14:04 -03:00
< / form >
< table class = "selic-table" >
< thead > < tr > < th > Competência< / th > < th > Fator< / th > < / tr > < / thead >
< tbody >
{% for item in selic_dados %}
2025-07-30 09:48:44 -03:00
< tr >
< td > {{ "%02d"|format(item.mes) }}/{{ item.ano }}< / td >
< td > {{ "%.4f"|format(item.percentual) }}< / td >
< / tr >
2025-07-29 14:14:04 -03:00
{% endfor %}
< / tbody >
< / table >
< / div >
< / div >
2025-07-30 09:48:44 -03:00
<!-- ABA ALÍQUOTAS -->
< div id = "aliquotas" class = "tab-content" >
< div class = "formulario-box" >
< form onsubmit = "return salvarAliquota(this)" >
< div class = "grid" style = "grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));" >
< div class = "form-group" >
< label > UF:< / label >
< select name = "uf" required >
< option value = "" > Selecione o Estado< / 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 >
< / div >
< div class = "form-group" >
< label > Exercício:< / label >
< input name = "exercicio" type = "number" required / >
< / div >
< div class = "form-group" >
< label > Alíquota ICMS (%):< / label >
< input name = "aliquota" type = "number" step = "0.0001" required / >
< / div >
< / div >
<!-- Bloco com espaçamento e alinhamento central -->
< div class = "grupo-botoes" >
<!-- Botão salvar -->
< button class = "btn btn-primary" type = "submit" > 💾 Salvar Alíquota< / button >
<!-- Importação e template -->
< div style = "display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem;" >
< label for = "arquivo_aliquotas" class = "btn btn-secondary" style = "cursor: pointer;" >
📎 Importar CSV
< input type = "file" name = "arquivo_aliquotas" accept = ".csv" onchange = "enviarArquivoAliquotas(this)" style = "display: none;" / >
< / label >
< a href = "/parametros/aliquotas/template" class = "btn btn-secondary" > 📥 Baixar Template CSV< / a >
< / div >
< / div >
< / form >
< table class = "selic-table" >
< thead > < tr > < th > UF< / th > < th > Exercício< / th > < th > Alíquota< / th > < / tr > < / thead >
< tbody id = "tabela-aliquotas" > < / tbody >
< / table >
< / div >
< / div >
2025-07-29 14:14:04 -03:00
<!-- ESTILOS -->
< style >
2025-07-30 09:48:44 -03:00
/* Abas */
.tabs {
display: flex;
justify-content: center;
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;
}
2025-07-29 14:14:04 -03:00
2025-07-30 09:48:44 -03:00
/* Formulário principal */
2025-07-29 14:14:04 -03:00
.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;
}
2025-07-30 09:48:44 -03:00
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 0.5rem;
}
2025-07-29 14:14:04 -03:00
.form-group input[type="text"],
.form-group input[type="number"],
.form-group input[type="date"],
2025-07-30 09:48:44 -03:00
.form-group textarea {
width: 100%;
padding: 0.6rem;
border-radius: 6px;
border: 1px solid #ccc;
}
2025-07-29 14:14:04 -03:00
2025-07-30 09:48:44 -03:00
.form-group.check-group {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.check-group label {
display: flex;
align-items: center;
gap: 0.5rem;
}
2025-07-29 14:14:04 -03:00
2025-07-30 09:48:44 -03:00
/* Grade do formulário */
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
2025-07-29 14:14:04 -03:00
2025-07-30 09:48:44 -03:00
/* Botões */
.btn {
padding: 0.5rem 1rem;
border: none;
2025-07-29 14:14:04 -03:00
border-radius: 6px;
2025-07-30 09:48:44 -03:00
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;
2025-07-29 14:14:04 -03:00
}
2025-07-30 09:48:44 -03:00
/* Cards de fórmulas salvas */
2025-07-29 14:14:04 -03:00
.card-list {
display: grid;
2025-07-30 09:48:44 -03:00
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
align-items: start;
2025-07-29 14:14:04 -03:00
}
2025-07-30 09:48:44 -03:00
2025-07-29 14:14:04 -03:00
.param-card {
2025-07-30 09:48:44 -03:00
display: flex;
flex-direction: column;
justify-content: flex-start;
2025-07-29 14:14:04 -03:00
background: #f9f9f9;
padding: 1rem;
border-radius: 8px;
border-left: 4px solid #2563eb;
2025-07-30 09:48:44 -03:00
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
box-sizing: border-box;
min-height: 200px;
}
.param-card.ativo {
border-left-color: #198754;
background: #f6fff9;
}
.param-card.inativo {
border-left-color: #adb5bd;
background: #f9f9f9;
}
.param-card input.edit-nome {
font-weight: bold;
font-size: 1rem;
border: none;
background: transparent;
outline: none;
flex: 1;
width: 100%;
padding: 0.25rem 0;
}
.edit-formula {
width: 100%;
font-family: monospace;
border: 1px solid #ccc;
border-radius: 6px;
padding: 0.5rem;
margin: 0.5rem 0;
resize: vertical;
min-height: 60px;
}
.param-card .actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 0.5rem;
}
.badge-status {
font-size: 0.75rem;
font-weight: bold;
padding: 0.2rem 0.6rem;
border-radius: 12px;
color: white;
background: #198754;
}
.param-card.inativo .badge-status {
background: #adb5bd;
}
/* Badge de campos e operadores */
.campo-badges {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0.5rem 0;
}
.badge-campo, .badge-operador {
background: #e0e7ff;
color: #1e3a8a;
padding: 0.3rem 0.6rem;
border-radius: 8px;
cursor: pointer;
font-family: monospace;
transition: background 0.2s ease;
}
.badge-campo:hover, .badge-operador:hover {
background: #c7d2fe;
}
/* Mensagens */
.mensagem-info {
background: #e0f7fa;
padding: 1rem;
border-left: 4px solid #2563eb;
border-radius: 6px;
color: #007b83;
font-size: 0.85rem;
}
/* Tabela SELIC */
.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;
}
/* Popup de feedback */
.feedback-popup {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 99999;
}
.feedback-content {
background-color: #fff;
padding: 2rem;
border-radius: 12px;
text-align: center;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
}
.feedback-content h3 {
margin-bottom: 1rem;
}
.feedback-content p {
margin: 0.25rem 0;
font-weight: 500;
2025-07-29 14:14:04 -03:00
}
2025-07-30 09:48:44 -03:00
.hidden {
display: none !important;
}
.form-group select {
width: 100%;
padding: 0.6rem;
border-radius: 6px;
border: 1px solid #ccc;
font-family: inherit;
}
.btn {
text-decoration: none;
}
.grupo-botoes {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 1.5rem;
gap: 1rem;
}
.btn {
padding: 0.5rem 1rem;
font-weight: 600;
font-size: 1rem;
border-radius: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
text-align: center;
text-decoration: none !important;
}
2025-07-29 14:14:04 -03:00
< / style >
2025-07-30 09:48:44 -03:00
{% block scripts %}
2025-07-29 14:14:04 -03:00
< script >
2025-07-30 09:48:44 -03:00
// 🟡 Alterna entre abas
2025-07-29 14:14:04 -03:00
function switchTab(tabId) {
2025-07-30 09:48:44 -03:00
// Remove classe 'active' de todos os botões e abas
2025-07-29 14:14:04 -03:00
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
2025-07-30 09:48:44 -03:00
// Ativa a aba correspondente
2025-07-29 14:14:04 -03:00
document.getElementById(tabId).classList.add('active');
2025-07-30 09:48:44 -03:00
// Ativa o botão correspondente
const button = document.querySelector(`.tab[onclick="switchTab('${tabId}')"]`);
if (button) button.classList.add('active');
2025-07-29 14:14:04 -03:00
}
2025-07-30 09:48:44 -03:00
// ✅ Insere valores no editor de fórmulas
function inserirNoEditor(valor) {
const formula = document.getElementById("formula");
const start = formula.selectionStart;
const end = formula.selectionEnd;
formula.value = formula.value.slice(0, start) + valor + formula.value.slice(end);
formula.focus();
formula.setSelectionRange(start + valor.length, start + valor.length);
}
// ✅ Feedback visual em popup
function mostrarFeedback(titulo, mensagem) {
document.getElementById("feedback-titulo").innerText = titulo;
document.getElementById("feedback-mensagem").innerText = mensagem;
document.getElementById("parametros-feedback").classList.remove("hidden");
}
function fecharFeedbackParametros() {
document.getElementById("parametros-feedback").classList.add("hidden");
}
// ✅ Testa fórmula principal (formulário superior)
2025-07-29 14:14:04 -03:00
function testarFormula() {
2025-07-30 09:48:44 -03:00
const nome = document.getElementById("nome").value.trim();
const formula = document.getElementById("formula").value.trim();
const output = document.getElementById("resultado-teste");
if (!nome || !formula) {
output.innerText = "❌ Preencha o nome e a fórmula para testar.";
output.style.display = "block";
return;
2025-07-29 14:14:04 -03:00
}
2025-07-30 09:48:44 -03:00
fetch("/parametros/testar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ nome, formula })
})
.then(res => res.json())
.then(data => {
output.style.display = "block";
if (data.success) {
output.innerText = `✅ Fórmula válida. Resultado: ${data.resultado}`;
} else {
output.innerText = `❌ Erro: ${data.error}`;
}
});
2025-07-29 14:14:04 -03:00
}
2025-07-30 09:48:44 -03:00
// ✅ Testa fórmula inline nos cards salvos
function testarFormulaInline(id) {
const nome = document.querySelector(`.edit-nome[data-id='${id}']`)?.value;
const formula = document.querySelector(`.edit-formula[data-id='${id}']`)?.value;
const output = document.getElementById(`resultado-inline-${id}`);
if (!formula) {
output.innerText = "❌ Fórmula não preenchida.";
output.style.display = 'block';
return;
}
fetch("/parametros/testar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ nome, formula })
})
.then(res => res.json())
.then(data => {
output.style.display = 'block';
if (data.success) {
output.innerText = `✅ Fórmula válida. Resultado: ${data.resultado}`;
} else {
output.innerText = `❌ Erro: ${data.error}`;
}
});
}
// ✅ Salva edição inline nos cards
async function salvarInline(id) {
const inputNome = document.querySelector(`.edit-nome[data-id='${id}']`);
const textareaFormula = document.querySelector(`.edit-formula[data-id='${id}']`);
const nome = inputNome.value.trim();
const formula = textareaFormula.value.trim();
if (!nome || !formula) {
alert("Preencha todos os campos.");
return;
}
const response = await fetch(`/parametros/editar/${id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nome, formula })
});
if (response.ok) {
mostrarFeedback("✅ Atualizado", "Parâmetro salvo com sucesso.");
} else {
mostrarFeedback("❌ Erro", "Erro ao salvar.");
}
}
// ✅ Carrega tabela de alíquotas
async function carregarAliquotas() {
const res = await fetch("/parametros/aliquotas");
const dados = await res.json();
const tbody = document.getElementById("tabela-aliquotas");
tbody.innerHTML = dados.map(a => `
< tr > < td > ${a.uf}< / td > < td > ${a.exercicio}< / td > < td > ${a.aliquota.toFixed(4)}%< / td > < / tr >
`).join('');
}
// ✅ Eventos após carregar DOM
document.addEventListener('DOMContentLoaded', () => {
carregarAliquotas();
// Ativar/desativar checkbox
document.querySelectorAll('.toggle-ativo').forEach(input => {
input.addEventListener('change', async function () {
const id = this.dataset.id;
const ativo = this.checked;
const response = await fetch(`/parametros/ativar/${id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ativo })
});
if (response.ok) {
location.reload();
} else {
alert('Erro ao atualizar status.');
}
});
});
// Botões de teste inline
document.querySelectorAll('.btn-testar').forEach(button => {
button.addEventListener('click', function () {
const id = this.dataset.id;
testarFormulaInline(id);
});
});
});
function mostrarLoadingSelic() {
document.getElementById("selic-loading").classList.remove("hidden");
}
window.addEventListener("DOMContentLoaded", () => {
const abaUrl = new URLSearchParams(window.location.search).get("aba");
if (abaUrl === "selic") {
document.querySelector(".tab.active")?.classList.remove("active");
document.querySelector(".tab-content.active")?.classList.remove("active");
document.querySelector(".tab:nth-child(2)").classList.add("active"); // Ativa o botão da aba
document.getElementById("selic").classList.add("active"); // Ativa o conteúdo da aba
}
});
function enviarArquivoAliquotas(input) {
const file = input.files[0];
if (!file) return;
const formData = new FormData();
formData.append("arquivo", file);
fetch("/parametros/aliquotas/importar", {
method: "POST",
body: formData
})
.then(res => res.json())
.then(data => {
if (data.success) {
mostrarFeedback("✅ Importado", `${data.qtd} alíquotas foram importadas.`);
carregarAliquotas();
} else {
mostrarFeedback("❌ Erro", data.error || "Falha na importação.");
}
});
}
2025-07-29 14:14:04 -03:00
< / script >
2025-07-30 09:48:44 -03:00
<!-- Feedback estilo popup -->
< div id = "parametros-feedback" class = "feedback-popup hidden" >
< div class = "feedback-content" >
< h3 id = "feedback-titulo" > ✅ Ação Concluída< / h3 >
< p id = "feedback-mensagem" > Parâmetro salvo com sucesso.< / p >
< button onclick = "fecharFeedbackParametros()" class = "btn btn-primary" style = "margin-top: 1rem;" > OK< / button >
< / div >
< / div >
< div id = "selic-loading" class = "feedback-popup hidden" >
< div class = "feedback-content" >
< h3 > ⏳ Atualizando SELIC< / h3 >
< p > Aguarde enquanto os fatores SELIC estão sendo carregados...< / p >
< / div >
< / div >
2025-07-28 13:29:45 -03:00
{% endblock %}
2025-07-30 09:48:44 -03:00
{% endblock %}