Ajustes gerais: overlay visual, validação por banco e limpeza segura em homologação
This commit is contained in:
45
app/main.py
45
app/main.py
@@ -4,9 +4,12 @@ from fastapi.templating import Jinja2Templates
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import os, shutil
|
||||
from sqlalchemy import text
|
||||
from fastapi import Depends
|
||||
from fastapi.responses import StreamingResponse
|
||||
from io import BytesIO
|
||||
import pandas as pd
|
||||
from app.models import ParametrosFormula
|
||||
from sqlalchemy.future import select
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.models import Fatura
|
||||
@@ -16,6 +19,7 @@ from app.processor import (
|
||||
status_arquivos,
|
||||
limpar_arquivos_processados
|
||||
)
|
||||
from app.parametros import router as parametros_router
|
||||
|
||||
app = FastAPI()
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
@@ -47,15 +51,25 @@ def dashboard(request: Request):
|
||||
|
||||
@app.get("/upload", response_class=HTMLResponse)
|
||||
def upload_page(request: Request):
|
||||
return templates.TemplateResponse("upload.html", {"request": request})
|
||||
app_env = os.getenv("APP_ENV", "dev") # Captura variável de ambiente
|
||||
return templates.TemplateResponse("upload.html", {
|
||||
"request": request,
|
||||
"app_env": app_env # Passa para o template
|
||||
})
|
||||
|
||||
@app.get("/relatorios", response_class=HTMLResponse)
|
||||
def relatorios_page(request: Request):
|
||||
return templates.TemplateResponse("relatorios.html", {"request": request})
|
||||
|
||||
@app.get("/parametros", response_class=HTMLResponse)
|
||||
def parametros_page(request: Request):
|
||||
return templates.TemplateResponse("parametros.html", {"request": request})
|
||||
async def parametros_page(request: Request):
|
||||
async with AsyncSessionLocal() as session:
|
||||
result = await session.execute(select(ParametrosFormula))
|
||||
parametros = result.scalars().first()
|
||||
return templates.TemplateResponse("parametros.html", {
|
||||
"request": request,
|
||||
"parametros": parametros or {}
|
||||
})
|
||||
|
||||
@app.post("/upload-files")
|
||||
async def upload_files(files: list[UploadFile] = File(...)):
|
||||
@@ -144,3 +158,28 @@ async def export_excel():
|
||||
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
headers={"Content-Disposition": "attachment; filename=relatorio_faturas.xlsx"}
|
||||
)
|
||||
|
||||
from app.parametros import router as parametros_router
|
||||
app.include_router(parametros_router)
|
||||
|
||||
def is_homolog():
|
||||
return os.getenv("APP_ENV", "dev") == "homolog"
|
||||
|
||||
@app.post("/limpar-faturas")
|
||||
async def limpar_faturas():
|
||||
app_env = os.getenv("APP_ENV", "dev")
|
||||
if app_env not in ["homolog", "dev", "local"]:
|
||||
return JSONResponse(status_code=403, content={"message": "Operação não permitida neste ambiente."})
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
print("🧪 Limpando faturas do banco...")
|
||||
await session.execute(text("DELETE FROM faturas.faturas"))
|
||||
await session.commit()
|
||||
|
||||
upload_path = os.path.join("app", "uploads")
|
||||
for nome in os.listdir(upload_path):
|
||||
caminho = os.path.join(upload_path, nome)
|
||||
if os.path.isfile(caminho):
|
||||
os.remove(caminho)
|
||||
|
||||
return {"message": "Faturas e arquivos apagados com sucesso."}
|
||||
@@ -8,6 +8,7 @@ from app.database import AsyncSessionLocal
|
||||
from app.models import Fatura, LogProcessamento
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -29,7 +30,7 @@ def remover_arquivo_temp(caminho_pdf):
|
||||
def salvar_em_uploads(caminho_pdf_temp, nome_original, nota_fiscal):
|
||||
try:
|
||||
extensao = os.path.splitext(nome_original)[1].lower()
|
||||
nome_destino = f"{nota_fiscal}{extensao}"
|
||||
nome_destino = f"{nota_fiscal}_{uuid.uuid4().hex[:6]}{extensao}"
|
||||
destino_final = os.path.join(UPLOADS_DIR, nome_destino)
|
||||
shutil.copy2(caminho_pdf_temp, destino_final)
|
||||
return destino_final
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
<button class="btn btn-danger" onclick="limpar()">Limpar Tudo</button>
|
||||
<button class="btn btn-success" onclick="baixarPlanilha()">📅 Abrir Planilha</button>
|
||||
<button class="btn btn-success" onclick="gerarRelatorio()">📊 Gerar Relatório</button>
|
||||
{% if app_env != "producao" %}
|
||||
<button class="btn btn-warning" onclick="limparFaturas()">🧹 Limpar Faturas (Teste)</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -124,38 +128,54 @@ function renderTable(statusList = []) {
|
||||
window.open('/generate-report', '_blank');
|
||||
}
|
||||
|
||||
const dropZone = document.body;
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('dragover');
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
updateStatus();
|
||||
|
||||
const dragOverlay = document.getElementById("drag-overlay");
|
||||
let dragCounter = 0;
|
||||
|
||||
window.addEventListener("dragenter", e => {
|
||||
dragCounter++;
|
||||
dragOverlay.classList.add("active");
|
||||
});
|
||||
dropZone.addEventListener('dragleave', () => {
|
||||
dropZone.classList.remove('dragover');
|
||||
|
||||
window.addEventListener("dragleave", e => {
|
||||
dragCounter--;
|
||||
if (dragCounter <= 0) {
|
||||
dragOverlay.classList.remove("active");
|
||||
dragCounter = 0;
|
||||
}
|
||||
});
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
|
||||
window.addEventListener("dragover", e => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('dragover');
|
||||
});
|
||||
|
||||
window.addEventListener("drop", e => {
|
||||
e.preventDefault();
|
||||
dragOverlay.classList.remove("active");
|
||||
dragCounter = 0;
|
||||
handleFiles(e.dataTransfer.files);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('DOMContentLoaded', updateStatus);
|
||||
|
||||
// Adiciona destaque visual à caixa ao arrastar arquivos na tela toda
|
||||
window.addEventListener('dragover', (e) => {
|
||||
async function limparFaturas() {
|
||||
if (!confirm("Deseja realmente limpar todas as faturas e arquivos (somente homologação)?")) return;
|
||||
|
||||
const res = await fetch("/limpar-faturas", { method: "POST" });
|
||||
const data = await res.json();
|
||||
alert(data.message || "Concluído");
|
||||
updateStatus(); // atualiza visual
|
||||
}
|
||||
|
||||
window.addEventListener("dragover", e => {
|
||||
e.preventDefault();
|
||||
document.getElementById('upload-box').classList.add('dragover');
|
||||
});
|
||||
|
||||
window.addEventListener('dragleave', () => {
|
||||
document.getElementById('upload-box').classList.remove('dragover');
|
||||
});
|
||||
|
||||
window.addEventListener('drop', (e) => {
|
||||
window.addEventListener("drop", e => {
|
||||
e.preventDefault();
|
||||
document.getElementById('upload-box').classList.remove('dragover');
|
||||
handleFiles(e.dataTransfer.files);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -224,6 +244,62 @@ function renderTable(statusList = []) {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.drag-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-color: rgba(67, 97, 238, 0.7); /* institucional */
|
||||
z-index: 9999;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.drag-overlay.active {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.drag-overlay-content {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
animation: fadeInUp 0.3s ease;
|
||||
text-shadow: 1px 1px 6px rgba(0, 0, 0, 0.4);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.drag-overlay-content svg {
|
||||
margin-bottom: 1rem;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
fill: white;
|
||||
filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.4));
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
transform: translateY(10px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div class="drag-overlay" id="drag-overlay">
|
||||
<div class="drag-overlay-content">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="#fff" viewBox="0 0 24 24">
|
||||
<path d="M12 2C6.477 2 2 6.477 2 12c0 5.522 4.477 10 10 10s10-4.478 10-10c0-5.523-4.477-10-10-10Zm0 13a1 1 0 0 1-1-1v-2.586l-.293.293a1 1 0 0 1-1.414-1.414l2.707-2.707a1 1 0 0 1 1.414 0l2.707 2.707a1 1 0 0 1-1.414 1.414L13 11.414V14a1 1 0 0 1-1 1Z"/>
|
||||
</svg>
|
||||
<p>Solte os arquivos para enviar</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user