2025-03-27 14:34:16 -03:00
|
|
|
from flask import Flask, request, render_template, redirect, url_for, flash, session, jsonify
|
2025-01-08 00:19:49 -03:00
|
|
|
from functions.database import (
|
|
|
|
|
Base,
|
|
|
|
|
Militante,
|
|
|
|
|
CotaMensal,
|
|
|
|
|
TipoPagamento,
|
|
|
|
|
Pagamento,
|
|
|
|
|
MaterialVendido,
|
|
|
|
|
TipoMaterial,
|
|
|
|
|
VendaJornalAvulso,
|
|
|
|
|
engine,
|
|
|
|
|
AssinaturaAnual,
|
|
|
|
|
RelatorioCotasMensais,
|
|
|
|
|
RelatorioVendasMateriais,
|
2025-03-18 17:31:59 -03:00
|
|
|
Usuario,
|
|
|
|
|
get_db_connection,
|
2025-04-01 15:27:16 -03:00
|
|
|
ComiteRegional,
|
2025-04-03 11:24:47 -03:00
|
|
|
Setor,
|
|
|
|
|
Celula,
|
2025-01-08 00:19:49 -03:00
|
|
|
)
|
2025-03-18 17:31:59 -03:00
|
|
|
from sqlalchemy import create_engine
|
2025-01-08 00:19:49 -03:00
|
|
|
from sqlalchemy.orm import sessionmaker
|
2025-03-27 14:34:16 -03:00
|
|
|
from datetime import datetime, timedelta
|
2025-02-19 14:27:14 -03:00
|
|
|
from flask_bootstrap import Bootstrap5
|
2025-02-20 10:39:31 -03:00
|
|
|
from routes.cota import cota_bp
|
|
|
|
|
from functions.validations import validar_cpf
|
2025-03-18 17:31:59 -03:00
|
|
|
from functools import wraps
|
|
|
|
|
from pathlib import Path
|
2025-03-27 14:34:16 -03:00
|
|
|
from time import time
|
|
|
|
|
from create_admin import generate_qr_code
|
2025-04-01 15:27:16 -03:00
|
|
|
from flask_mail import Mail, Message
|
2025-04-03 11:24:47 -03:00
|
|
|
import os
|
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
|
|
|
|
load_dotenv()
|
2024-11-26 10:57:25 -03:00
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
2025-03-18 17:31:59 -03:00
|
|
|
app.secret_key = 'sua_chave_secreta_aqui' # Necessário para sessões do Flask
|
2025-02-19 14:27:14 -03:00
|
|
|
bootstrap = Bootstrap5(app)
|
2024-11-26 10:57:25 -03:00
|
|
|
|
2025-04-03 11:24:47 -03:00
|
|
|
# Registrar blueprints
|
|
|
|
|
app.register_blueprint(cota_bp)
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
# Configuração da sessão do SQLAlchemy
|
|
|
|
|
db_session = get_db_connection()
|
2025-01-08 00:19:49 -03:00
|
|
|
|
2025-04-01 15:27:16 -03:00
|
|
|
# Configurar Flask-Mail
|
|
|
|
|
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
|
|
|
|
|
app.config['MAIL_PORT'] = 587
|
|
|
|
|
app.config['MAIL_USE_TLS'] = True
|
2025-04-03 11:24:47 -03:00
|
|
|
app.config['MAIL_USERNAME'] = 'seu-email@gmail.com' # Substituir pelo seu email
|
|
|
|
|
app.config['MAIL_PASSWORD'] = 'sua-senha-de-app' # Substituir pela senha de app
|
|
|
|
|
|
|
|
|
|
# Se estiver usando variáveis de ambiente (recomendado):
|
|
|
|
|
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
|
|
|
|
|
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
|
2025-04-01 15:27:16 -03:00
|
|
|
|
|
|
|
|
mail = Mail(app)
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
# Decorator para verificar se o usuário está logado
|
|
|
|
|
def login_required(f):
|
|
|
|
|
@wraps(f)
|
|
|
|
|
def decorated_function(*args, **kwargs):
|
|
|
|
|
if 'user_id' not in session:
|
|
|
|
|
flash('Por favor, faça login para acessar esta página.', 'warning')
|
2025-03-24 14:50:42 -03:00
|
|
|
return redirect(url_for('login'))
|
2025-03-18 17:31:59 -03:00
|
|
|
return f(*args, **kwargs)
|
|
|
|
|
return decorated_function
|
2025-01-08 00:19:49 -03:00
|
|
|
|
2025-03-27 14:34:16 -03:00
|
|
|
# Decorator para verificar se a sessão expirou
|
|
|
|
|
def session_timeout(f):
|
|
|
|
|
@wraps(f)
|
|
|
|
|
def decorated_function(*args, **kwargs):
|
|
|
|
|
if 'user_id' not in session:
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
# Verificar se existe timestamp do último acesso
|
|
|
|
|
if 'last_activity' in session:
|
|
|
|
|
last_activity = datetime.fromtimestamp(session['last_activity'])
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
|
|
|
|
|
# Se passaram mais de 2 horas
|
|
|
|
|
if now - last_activity > timedelta(hours=2):
|
|
|
|
|
# Registrar o logout por timeout
|
|
|
|
|
try:
|
|
|
|
|
user = db_session.query(Usuario).get(session['user_id'])
|
|
|
|
|
if user:
|
|
|
|
|
user.ultimo_logout = datetime.now()
|
|
|
|
|
user.motivo_logout = "Timeout de sessão"
|
|
|
|
|
db_session.commit()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Erro ao registrar logout por timeout: {e}")
|
|
|
|
|
|
|
|
|
|
session.clear()
|
|
|
|
|
flash('Sua sessão expirou. Por favor, faça login novamente.', 'warning')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
# Atualizar timestamp de último acesso
|
|
|
|
|
session['last_activity'] = time()
|
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
|
return decorated_function
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
# Rota raiz - redireciona para login se não estiver autenticado
|
|
|
|
|
@app.route("/")
|
|
|
|
|
def index():
|
|
|
|
|
if 'user_id' not in session:
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
return redirect(url_for('home'))
|
|
|
|
|
|
|
|
|
|
# Rota de login
|
|
|
|
|
@app.route("/login", methods=["GET", "POST"])
|
|
|
|
|
def login():
|
|
|
|
|
if 'user_id' in session:
|
|
|
|
|
return redirect(url_for('home'))
|
|
|
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
|
username = request.form.get("username")
|
|
|
|
|
password = request.form.get("password")
|
|
|
|
|
otp = request.form.get("otp")
|
|
|
|
|
|
2025-03-24 14:50:42 -03:00
|
|
|
# Log dos dados recebidos (sem a senha)
|
|
|
|
|
print(f"Tentativa de login - Username: {username}, OTP fornecido: {'Sim' if otp else 'Não'}")
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
user = db_session.query(Usuario).filter_by(username=username).first()
|
|
|
|
|
|
2025-03-24 14:50:42 -03:00
|
|
|
if not user:
|
|
|
|
|
print(f"Erro: Usuário '{username}' não encontrado")
|
|
|
|
|
flash('Usuário não encontrado', 'danger')
|
|
|
|
|
return render_template('login.html')
|
|
|
|
|
|
|
|
|
|
if not user.check_password(password):
|
|
|
|
|
print(f"Erro: Senha incorreta para o usuário '{username}'")
|
|
|
|
|
flash('Senha incorreta', 'danger')
|
|
|
|
|
return render_template('login.html')
|
|
|
|
|
|
|
|
|
|
if not user.verify_otp(otp):
|
|
|
|
|
print(f"Erro: Código OTP inválido para o usuário '{username}'")
|
|
|
|
|
print(f"OTP fornecido: {otp}")
|
|
|
|
|
print(f"OTP secret do usuário: {user.otp_secret}")
|
|
|
|
|
flash('Código OTP inválido', 'danger')
|
|
|
|
|
return render_template('login.html')
|
|
|
|
|
|
|
|
|
|
# Se chegou aqui, login bem sucedido
|
|
|
|
|
print(f"Login bem sucedido para o usuário '{username}'")
|
|
|
|
|
session['user_id'] = user.id
|
|
|
|
|
session['is_admin'] = user.is_admin
|
2025-03-27 14:34:16 -03:00
|
|
|
session['last_activity'] = time() # Inicializar timestamp
|
2025-04-03 11:24:47 -03:00
|
|
|
session['username'] = user.username # Armazenar apenas o username
|
2025-03-27 14:34:16 -03:00
|
|
|
|
|
|
|
|
# Registrar horário do login
|
|
|
|
|
user.ultimo_login = datetime.now()
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
2025-03-24 14:50:42 -03:00
|
|
|
next_page = request.args.get('next')
|
|
|
|
|
flash('Login realizado com sucesso!', 'success')
|
|
|
|
|
return redirect(next_page or url_for('home'))
|
2025-03-18 17:31:59 -03:00
|
|
|
|
|
|
|
|
return render_template('login.html')
|
|
|
|
|
|
|
|
|
|
# Rota de logout
|
|
|
|
|
@app.route("/logout")
|
|
|
|
|
def logout():
|
|
|
|
|
session.clear() # Limpa a sessão do Flask
|
|
|
|
|
flash('Você foi desconectado com sucesso.', 'info')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
# Rota home (protegida)
|
|
|
|
|
@app.route("/home")
|
|
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2025-03-18 17:31:59 -03:00
|
|
|
def home():
|
|
|
|
|
"""Página inicial do sistema"""
|
2025-03-24 14:50:42 -03:00
|
|
|
try:
|
|
|
|
|
links = []
|
|
|
|
|
# Filtrar apenas as rotas que queremos mostrar
|
|
|
|
|
allowed_endpoints = {
|
|
|
|
|
'listar_militantes': 'Militantes',
|
|
|
|
|
'listar_cotas': 'Cotas',
|
|
|
|
|
'listar_pagamentos': 'Pagamentos',
|
|
|
|
|
'listar_materiais': 'Materiais',
|
|
|
|
|
'listar_vendas_jornal': 'Vendas de Jornal',
|
|
|
|
|
'listar_assinaturas': 'Assinaturas'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for rule in app.url_map.iter_rules():
|
|
|
|
|
if (rule.endpoint in allowed_endpoints and
|
|
|
|
|
"GET" in rule.methods and
|
|
|
|
|
len(rule.arguments) == 0): # Apenas rotas sem parâmetros
|
|
|
|
|
url = url_for(rule.endpoint)
|
|
|
|
|
nome = allowed_endpoints[rule.endpoint]
|
|
|
|
|
links.append((url, nome))
|
|
|
|
|
|
|
|
|
|
# Ordenar links pelo nome
|
|
|
|
|
links.sort(key=lambda x: x[1])
|
|
|
|
|
|
2025-04-03 11:24:47 -03:00
|
|
|
return render_template('home.html', links=links, current_user={'username': session.get('username')})
|
2025-03-24 14:50:42 -03:00
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Erro na página inicial: {e}")
|
|
|
|
|
import traceback
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
flash('Erro ao carregar a página inicial', 'error')
|
2025-04-03 11:24:47 -03:00
|
|
|
return render_template('home.html', links=[], current_user={'username': session.get('username')})
|
2025-01-08 00:19:49 -03:00
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para criar um novo militante
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/militantes/novo", methods=["GET", "POST"])
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def novo_militante():
|
2025-04-01 15:27:16 -03:00
|
|
|
user = db_session.query(Usuario).get(session['user_id'])
|
|
|
|
|
|
2025-04-03 11:24:47 -03:00
|
|
|
try:
|
|
|
|
|
# Obter CRs e células existentes
|
|
|
|
|
if user.is_admin:
|
|
|
|
|
celulas_por_cr = db_session.query(ComiteRegional).all()
|
|
|
|
|
crs_existentes = celulas_por_cr
|
|
|
|
|
elif user.cr_id:
|
|
|
|
|
celulas_por_cr = [user.cr]
|
|
|
|
|
crs_existentes = [user.cr]
|
|
|
|
|
else:
|
|
|
|
|
celulas_por_cr = []
|
|
|
|
|
crs_existentes = []
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Erro ao obter células: {e}")
|
|
|
|
|
celulas_por_cr = []
|
|
|
|
|
crs_existentes = []
|
2025-04-01 15:27:16 -03:00
|
|
|
|
2025-01-08 00:19:49 -03:00
|
|
|
if request.method == "POST":
|
2025-03-18 17:31:59 -03:00
|
|
|
try:
|
2025-04-03 11:24:47 -03:00
|
|
|
# Validar CPF
|
|
|
|
|
cpf = request.form.get("cpf")
|
|
|
|
|
if not validar_cpf(cpf):
|
|
|
|
|
flash('CPF inválido. Por favor, verifique o número informado.', 'error')
|
|
|
|
|
return render_template("novo_militante.html",
|
|
|
|
|
dados_anteriores=request.form,
|
|
|
|
|
responsabilidades=Militante.get_responsabilidades_list(),
|
|
|
|
|
celulas_por_cr=celulas_por_cr,
|
|
|
|
|
crs_existentes=crs_existentes)
|
|
|
|
|
|
|
|
|
|
celula_id = None
|
|
|
|
|
if request.form.get("nova_celula"):
|
|
|
|
|
# Criar nova estrutura organizacional se necessário
|
|
|
|
|
cr = None
|
|
|
|
|
if request.form.get("nome_cr") == "novo":
|
|
|
|
|
cr = ComiteRegional(nome=request.form.get("novo_cr"))
|
|
|
|
|
db_session.add(cr)
|
|
|
|
|
db_session.flush()
|
|
|
|
|
else:
|
|
|
|
|
cr_id = request.form.get("nome_cr")
|
|
|
|
|
cr = db_session.query(ComiteRegional).get(cr_id)
|
|
|
|
|
|
|
|
|
|
setor = None
|
|
|
|
|
if not request.form.get("sem_setor"):
|
|
|
|
|
if request.form.get("nome_setor") == "novo":
|
|
|
|
|
setor = Setor(nome=request.form.get("novo_setor"), cr_id=cr.id)
|
|
|
|
|
db_session.add(setor)
|
|
|
|
|
db_session.flush()
|
|
|
|
|
elif request.form.get("nome_setor"):
|
|
|
|
|
setor = db_session.query(Setor).get(request.form.get("nome_setor"))
|
|
|
|
|
|
|
|
|
|
# Criar nova célula
|
|
|
|
|
celula = Celula(
|
|
|
|
|
nome=request.form.get("nome_celula"),
|
|
|
|
|
cr_id=cr.id,
|
|
|
|
|
setor_id=setor.id if setor else None
|
|
|
|
|
)
|
|
|
|
|
db_session.add(celula)
|
|
|
|
|
db_session.flush()
|
|
|
|
|
celula_id = celula.id
|
|
|
|
|
else:
|
|
|
|
|
celula_id = request.form.get("celula_id")
|
|
|
|
|
|
|
|
|
|
# Criar militante com todos os campos
|
|
|
|
|
novo_militante = Militante(
|
|
|
|
|
nome=request.form.get("nome"),
|
|
|
|
|
cpf=cpf,
|
|
|
|
|
titulo_eleitoral=request.form.get("titulo_eleitoral"),
|
|
|
|
|
data_nascimento=datetime.strptime(request.form.get("data_nascimento"), "%Y-%m-%d") if request.form.get("data_nascimento") else None,
|
|
|
|
|
data_entrada_oci=datetime.strptime(request.form.get("data_entrada_oci"), "%Y-%m-%d") if request.form.get("data_entrada_oci") else None,
|
|
|
|
|
data_efetivacao_oci=datetime.strptime(request.form.get("data_efetivacao_oci"), "%Y-%m-%d") if request.form.get("data_efetivacao_oci") else None,
|
|
|
|
|
telefone1=request.form.get("telefone1"),
|
|
|
|
|
telefone2=request.form.get("telefone2"),
|
|
|
|
|
profissao=request.form.get("profissao"),
|
|
|
|
|
regime_trabalho=request.form.get("regime_trabalho"),
|
|
|
|
|
empresa=request.form.get("empresa"),
|
|
|
|
|
contratante=request.form.get("contratante"),
|
|
|
|
|
instituicao_ensino=request.form.get("instituicao_ensino"),
|
|
|
|
|
tipo_instituicao=request.form.get("tipo_instituicao"),
|
|
|
|
|
sindicato=request.form.get("sindicato"),
|
|
|
|
|
cargo_sindical=request.form.get("cargo_sindical"),
|
|
|
|
|
dirigente_sindical=bool(request.form.get("dirigente_sindical")),
|
|
|
|
|
central_sindical=request.form.get("central_sindical"),
|
|
|
|
|
celula_id=celula_id
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Definir responsabilidades
|
|
|
|
|
responsabilidades = [
|
|
|
|
|
int(r) for r in request.form.getlist("responsabilidades")
|
|
|
|
|
]
|
|
|
|
|
novo_militante.set_responsabilidades(responsabilidades)
|
|
|
|
|
|
|
|
|
|
db_session.add(novo_militante)
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.commit()
|
2025-04-03 11:24:47 -03:00
|
|
|
|
|
|
|
|
# Tentar enviar email
|
|
|
|
|
try:
|
|
|
|
|
novo_militante.send_otp_email(mail)
|
|
|
|
|
flash('Militante cadastrado com sucesso! Um email foi enviado com as instruções de autenticação.', 'success')
|
|
|
|
|
except Exception as mail_error:
|
|
|
|
|
print(f"Erro ao enviar email: {mail_error}")
|
|
|
|
|
flash('Militante cadastrado com sucesso, mas houve um erro ao enviar o email. Entre em contato com o administrador.', 'warning')
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
return redirect(url_for("listar_militantes"))
|
2025-04-03 11:24:47 -03:00
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
except Exception as e:
|
2025-04-03 11:24:47 -03:00
|
|
|
print(f"Erro ao cadastrar militante: {e}")
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.rollback()
|
2025-04-01 15:27:16 -03:00
|
|
|
flash('Erro ao cadastrar militante.', 'error')
|
2025-03-18 17:31:59 -03:00
|
|
|
return render_template("novo_militante.html",
|
2025-04-01 15:27:16 -03:00
|
|
|
dados_anteriores=request.form,
|
|
|
|
|
responsabilidades=Militante.get_responsabilidades_list(),
|
2025-04-03 11:24:47 -03:00
|
|
|
celulas_por_cr=celulas_por_cr,
|
|
|
|
|
crs_existentes=crs_existentes)
|
2025-01-08 00:19:49 -03:00
|
|
|
|
2025-04-01 15:27:16 -03:00
|
|
|
return render_template("novo_militante.html",
|
|
|
|
|
responsabilidades=Militante.get_responsabilidades_list(),
|
2025-04-03 11:24:47 -03:00
|
|
|
celulas_por_cr=celulas_por_cr,
|
|
|
|
|
crs_existentes=crs_existentes)
|
2025-01-08 00:19:49 -03:00
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para listar militantes
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/militantes")
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def listar_militantes():
|
2025-03-18 17:31:59 -03:00
|
|
|
militantes = db_session.query(Militante).all()
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("listar_militantes.html", militantes=militantes)
|
|
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para criar uma nova cota mensal
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/cotas/novo", methods=["GET", "POST"])
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def nova_cota():
|
2025-01-08 00:19:49 -03:00
|
|
|
if request.method == "POST":
|
|
|
|
|
cotas_mensais = CotaMensal(
|
|
|
|
|
militante_id=request.form["militante_id"],
|
|
|
|
|
valor_antigo=request.form["valor_antigo"],
|
|
|
|
|
valor_novo=request.form["valor_novo"],
|
|
|
|
|
data_alteracao=datetime.strptime(request.form["data_alteracao"], "%Y-%m-%d")
|
|
|
|
|
)
|
2024-11-26 10:57:25 -03:00
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.add(cotas_mensais)
|
|
|
|
|
try:
|
|
|
|
|
db_session.commit()
|
|
|
|
|
return redirect(url_for("listar_cotas"))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(e)
|
|
|
|
|
db_session.rollback()
|
|
|
|
|
flash('Erro ao cadastrar cota mensal. Verifique se os dados fornecidos são válidos.', 'error')
|
|
|
|
|
return render_template("nova_cota.html")
|
2025-01-08 00:19:49 -03:00
|
|
|
|
|
|
|
|
return render_template("nova_cota.html")
|
2024-11-26 10:57:25 -03:00
|
|
|
|
|
|
|
|
# Rota para listar cotas mensais
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/cotas")
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def listar_cotas():
|
2025-03-18 17:31:59 -03:00
|
|
|
cotas = db_session.query(CotaMensal).all()
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("listar_cotas.html", cotas=cotas)
|
|
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para criar um novo pagamento
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/pagamentos/novo", methods=["GET", "POST"])
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def novo_pagamento():
|
2025-01-08 00:19:49 -03:00
|
|
|
if request.method == "POST":
|
|
|
|
|
pagamentos = Pagamento(
|
|
|
|
|
militante_id=request.form["militante_id"],
|
|
|
|
|
tipo_pagamento_id=request.form["tipo_pagamento_id"],
|
|
|
|
|
valor=request.form["valor"],
|
|
|
|
|
data_pagamento=datetime.strptime(request.form["data_pagamento"], "%Y-%m-%d")
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.add(pagamentos)
|
|
|
|
|
try:
|
|
|
|
|
db_session.commit()
|
|
|
|
|
return redirect(url_for("listar_pagamentos"))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(e)
|
|
|
|
|
db_session.rollback()
|
|
|
|
|
flash('Erro ao cadastrar pagamento. Verifique se os dados fornecidos são válidos.', 'error')
|
|
|
|
|
return render_template("novo_pagamento.html")
|
2024-11-26 10:57:25 -03:00
|
|
|
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("novo_pagamento.html")
|
2024-11-26 10:57:25 -03:00
|
|
|
|
|
|
|
|
# Rota para listar pagamentos
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/pagamentos")
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def listar_pagamentos():
|
2025-03-18 17:31:59 -03:00
|
|
|
pagamentos = db_session.query(Pagamento).all()
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("listar_pagamentos.html", pagamentos=pagamentos)
|
|
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para criar um novo material vendido
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/materiais/novo", methods=["GET", "POST"])
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def novo_material():
|
2025-01-08 00:19:49 -03:00
|
|
|
if request.method == "POST":
|
|
|
|
|
materiais_vendidos = MaterialVendido(
|
|
|
|
|
militante_id=request.form["militante_id"],
|
|
|
|
|
tipo_material_id=request.form["tipo_material_id"],
|
|
|
|
|
descricao=request.form["descricao"],
|
|
|
|
|
valor=request.form["valor"],
|
|
|
|
|
data_venda=datetime.strptime(request.form["data_venda"], "%Y-%m-%d"),
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.add(materiais_vendidos)
|
|
|
|
|
try:
|
|
|
|
|
db_session.commit()
|
|
|
|
|
return redirect(url_for("listar_materiais"))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(e)
|
|
|
|
|
db_session.rollback()
|
|
|
|
|
flash('Erro ao cadastrar material vendido. Verifique se os dados fornecidos são válidos.', 'error')
|
|
|
|
|
return render_template("novo_material.html")
|
2025-01-08 00:19:49 -03:00
|
|
|
|
|
|
|
|
return render_template("novo_material.html")
|
|
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para listar materiais vendidos
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/materiais")
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def listar_materiais():
|
2025-03-18 17:31:59 -03:00
|
|
|
materiais = db_session.query(MaterialVendido).all()
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("listar_materiais.html", materiais=materiais)
|
|
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para criar uma nova venda de jornais avulsos
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/jornais/novo", methods=["GET", "POST"])
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def nova_venda_jornal():
|
2025-01-08 00:19:49 -03:00
|
|
|
if request.method == "POST":
|
|
|
|
|
vendas_jornais_avulsos = VendaJornalAvulso(
|
|
|
|
|
militante_id=request.form["militante_id"],
|
|
|
|
|
quantidade=request.form["quantidade"],
|
|
|
|
|
valor_total=request.form["valor_total"],
|
|
|
|
|
data_venda=datetime.strptime(request.form["data_venda"], "%Y-%m-%d"),
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.add(vendas_jornais_avulsos)
|
|
|
|
|
try:
|
|
|
|
|
db_session.commit()
|
|
|
|
|
return redirect(url_for("listar_vendas_jornal"))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(e)
|
|
|
|
|
db_session.rollback()
|
|
|
|
|
flash('Erro ao cadastrar venda de jornal avulso. Verifique se os dados fornecidos são válidos.', 'error')
|
|
|
|
|
return render_template("nova_venda_jornal.html")
|
2024-11-26 10:57:25 -03:00
|
|
|
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("nova_venda_jornal.html")
|
2024-11-26 10:57:25 -03:00
|
|
|
|
|
|
|
|
# Rota para listar vendas de jornais avulsos
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/jornais")
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def listar_vendas_jornal():
|
2025-03-18 17:31:59 -03:00
|
|
|
vendas = db_session.query(VendaJornalAvulso).all()
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("listar_vendas_jornal.html", vendas=vendas)
|
|
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para criar uma nova assinatura anual
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/assinaturas/novo", methods=["GET", "POST"])
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def nova_assinatura():
|
2025-01-08 00:19:49 -03:00
|
|
|
if request.method == "POST":
|
|
|
|
|
assinaturas_anuais = AssinaturaAnual(
|
|
|
|
|
militante_id=request.form["militante_id"],
|
|
|
|
|
tipo_material_id=request.form["tipo_material_id"],
|
|
|
|
|
quantidade=request.form["quantidade"],
|
|
|
|
|
valor_total=request.form["valor_total"],
|
|
|
|
|
data_inicio=datetime.strptime(request.form["data_inicio"], "%Y-%m-%d"),
|
|
|
|
|
data_fim=datetime.strptime(request.form["data_fim"], "%Y-%m-%d")
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.add(assinaturas_anuais)
|
|
|
|
|
try:
|
|
|
|
|
db_session.commit()
|
|
|
|
|
return redirect(url_for("listar_assinaturas"))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(e)
|
|
|
|
|
db_session.rollback()
|
|
|
|
|
flash('Erro ao cadastrar assinatura anual. Verifique se os dados fornecidos são válidos.', 'error')
|
|
|
|
|
return render_template("nova_assinatura.html")
|
2025-01-08 00:19:49 -03:00
|
|
|
|
|
|
|
|
return render_template("nova_assinatura.html")
|
|
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para listar assinaturas anuais
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/assinaturas")
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def listar_assinaturas():
|
2025-03-18 17:31:59 -03:00
|
|
|
assinaturas = db_session.query(AssinaturaAnual).all()
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("listar_assinaturas.html", assinaturas=assinaturas)
|
|
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para criar um novo relatório de cotas mensais
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/relatorios/cotas/novo", methods=["GET", "POST"])
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def novo_relatorio_cotas():
|
2025-01-08 00:19:49 -03:00
|
|
|
if request.method == "POST":
|
|
|
|
|
relatorio_cotas_mensais = RelatorioCotasMensais(
|
|
|
|
|
setor_id=request.form["setor_id"],
|
|
|
|
|
comite_id=request.form["comite_id"],
|
|
|
|
|
total_cotas=request.form["total_cotas"],
|
|
|
|
|
data_relatorio=datetime.strptime(request.form["data_relatorio"], "%Y-%m-%d")
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.add(relatorio_cotas_mensais)
|
|
|
|
|
try:
|
|
|
|
|
db_session.commit()
|
|
|
|
|
return redirect(url_for("listar_relatorios_cotas"))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(e)
|
|
|
|
|
db_session.rollback()
|
|
|
|
|
flash('Erro ao cadastrar relatório de cotas mensais. Verifique se os dados fornecidos são válidos.', 'error')
|
|
|
|
|
return render_template("novo_relatorio_cotas.html")
|
2024-11-26 10:57:25 -03:00
|
|
|
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("novo_relatorio_cotas.html")
|
2024-11-26 10:57:25 -03:00
|
|
|
|
|
|
|
|
# Rota para listar relatórios de cotas mensais
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/relatorios/cotas")
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def listar_relatorios_cotas():
|
2025-03-18 17:31:59 -03:00
|
|
|
relatorios = db_session.query(RelatorioCotasMensais).all()
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("listar_relatorios_cotas.html", relatorios=relatorios)
|
|
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Rota para criar um novo relatório de vendas de materiais
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/relatorios/vendas/novo", methods=["GET", "POST"])
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def novo_relatorio_vendas():
|
2025-01-08 00:19:49 -03:00
|
|
|
if request.method == "POST":
|
|
|
|
|
relatorio_vendas_materiais = RelatorioVendasMateriais(
|
|
|
|
|
setor_id=request.form["setor_id"],
|
|
|
|
|
comite_id=request.form["comite_id"],
|
|
|
|
|
total_vendas=request.form["total_vendas"],
|
|
|
|
|
data_relatorio=datetime.strptime(request.form["data_relatorio"], "%Y-%m-%d")
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.add(relatorio_vendas_materiais)
|
|
|
|
|
try:
|
|
|
|
|
db_session.commit()
|
|
|
|
|
return redirect(url_for("listar_relatorios_vendas"))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(e)
|
|
|
|
|
db_session.rollback()
|
|
|
|
|
flash('Erro ao cadastrar relatório de vendas de materiais. Verifique se os dados fornecidos são válidos.', 'error')
|
|
|
|
|
return render_template("novo_relatorio_vendas.html")
|
2024-11-26 10:57:25 -03:00
|
|
|
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("novo_relatorio_vendas.html")
|
2024-11-26 10:57:25 -03:00
|
|
|
|
|
|
|
|
# Rota para listar relatórios de vendas de materiais
|
2025-01-08 00:19:49 -03:00
|
|
|
@app.route("/relatorios/vendas")
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2024-11-26 10:57:25 -03:00
|
|
|
def listar_relatorios_vendas():
|
2025-03-18 17:31:59 -03:00
|
|
|
relatorios = db_session.query(RelatorioVendasMateriais).all()
|
2025-01-08 00:19:49 -03:00
|
|
|
return render_template("listar_relatorios_vendas.html", relatorios=relatorios)
|
|
|
|
|
|
2025-02-20 10:39:31 -03:00
|
|
|
@app.route("/militantes/editar/<int:id>", methods=["GET", "POST"])
|
2025-03-18 17:31:59 -03:00
|
|
|
@login_required
|
2025-03-27 14:34:16 -03:00
|
|
|
@session_timeout
|
2025-02-20 10:39:31 -03:00
|
|
|
def editar_militante(id):
|
2025-03-18 17:31:59 -03:00
|
|
|
militante = db_session.query(Militante).get(id)
|
2025-02-20 10:39:31 -03:00
|
|
|
if not militante:
|
|
|
|
|
flash('Militante não encontrado.', 'error')
|
|
|
|
|
return redirect(url_for('listar_militantes'))
|
|
|
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
|
cpf = request.form["cpf"]
|
|
|
|
|
if cpf != militante.cpf and not validar_cpf(cpf): # Só valida se o CPF foi alterado
|
|
|
|
|
flash('CPF inválido. Por favor, verifique o número informado.', 'error')
|
|
|
|
|
return render_template("editar_militante.html", militante=militante)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
militante.nome = request.form["nome"]
|
|
|
|
|
militante.cpf = cpf
|
|
|
|
|
militante.email = request.form["email"]
|
|
|
|
|
militante.telefone = request.form["telefone"]
|
|
|
|
|
militante.endereco = request.form["endereco"]
|
|
|
|
|
militante.filiado = bool(request.form.get("filiado", False))
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.commit()
|
2025-02-20 10:39:31 -03:00
|
|
|
flash('Militante atualizado com sucesso!', 'success')
|
|
|
|
|
return redirect(url_for('listar_militantes'))
|
|
|
|
|
except Exception as e:
|
2025-03-18 17:31:59 -03:00
|
|
|
db_session.rollback()
|
2025-02-20 10:39:31 -03:00
|
|
|
flash('Erro ao atualizar militante. Verifique se o CPF ou email já não estão cadastrados.', 'error')
|
|
|
|
|
return render_template("editar_militante.html", militante=militante)
|
|
|
|
|
|
|
|
|
|
return render_template("editar_militante.html", militante=militante)
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
# Rota para criar novo usuário
|
|
|
|
|
@app.route("/usuarios/novo", methods=["GET", "POST"])
|
|
|
|
|
@login_required
|
|
|
|
|
def novo_usuario():
|
|
|
|
|
if not session.get('is_admin'):
|
|
|
|
|
flash('Acesso negado. Apenas administradores podem criar novos usuários.', 'danger')
|
|
|
|
|
return redirect(url_for('home'))
|
|
|
|
|
|
|
|
|
|
if request.method == "POST":
|
|
|
|
|
username = request.form.get("username")
|
|
|
|
|
email = request.form.get("email")
|
|
|
|
|
password = request.form.get("password")
|
|
|
|
|
confirm_password = request.form.get("confirm_password")
|
|
|
|
|
is_admin = bool(request.form.get("is_admin"))
|
|
|
|
|
|
|
|
|
|
# Validações
|
|
|
|
|
if password != confirm_password:
|
|
|
|
|
flash('As senhas não conferem.', 'danger')
|
|
|
|
|
return render_template('novo_usuario.html')
|
|
|
|
|
|
|
|
|
|
# Verificar se usuário já existe
|
|
|
|
|
if db_session.query(Usuario).filter_by(username=username).first():
|
|
|
|
|
flash('Nome de usuário já existe.', 'danger')
|
|
|
|
|
return render_template('novo_usuario.html')
|
|
|
|
|
|
|
|
|
|
if db_session.query(Usuario).filter_by(email=email).first():
|
|
|
|
|
flash('E-mail já cadastrado.', 'danger')
|
|
|
|
|
return render_template('novo_usuario.html')
|
|
|
|
|
|
|
|
|
|
# Criar novo usuário
|
|
|
|
|
try:
|
|
|
|
|
novo_usuario = Usuario(
|
|
|
|
|
username=username,
|
|
|
|
|
password=password,
|
|
|
|
|
is_admin=is_admin
|
|
|
|
|
)
|
|
|
|
|
novo_usuario.email = email
|
|
|
|
|
|
|
|
|
|
db_session.add(novo_usuario)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
2025-03-27 14:34:16 -03:00
|
|
|
# Gerar QR code usando a função do create_admin.py
|
2025-03-18 17:31:59 -03:00
|
|
|
qr_uri = novo_usuario.get_otp_uri()
|
2025-03-27 14:34:16 -03:00
|
|
|
qr_path = generate_qr_code(novo_usuario)
|
|
|
|
|
|
2025-03-18 17:31:59 -03:00
|
|
|
flash('Usuário criado com sucesso!', 'success')
|
|
|
|
|
return render_template('mostrar_qr_code.html', qr_uri=qr_uri)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
db_session.rollback()
|
|
|
|
|
flash('Erro ao criar usuário. Por favor, tente novamente.', 'danger')
|
|
|
|
|
print(f"Erro: {e}")
|
|
|
|
|
return render_template('novo_usuario.html')
|
|
|
|
|
|
|
|
|
|
return render_template('novo_usuario.html')
|
2025-02-20 10:39:31 -03:00
|
|
|
|
2025-03-27 14:34:16 -03:00
|
|
|
@app.route('/check_session')
|
|
|
|
|
def check_session():
|
|
|
|
|
if 'last_activity' not in session:
|
|
|
|
|
return jsonify({'expired': True})
|
|
|
|
|
|
|
|
|
|
last_activity = datetime.fromtimestamp(session['last_activity'])
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
|
|
|
|
|
if now - last_activity > timedelta(hours=2):
|
|
|
|
|
# Registrar o logout por timeout
|
|
|
|
|
try:
|
|
|
|
|
user = db_session.query(Usuario).get(session.get('user_id'))
|
|
|
|
|
if user:
|
|
|
|
|
user.ultimo_logout = datetime.now()
|
|
|
|
|
user.motivo_logout = "Timeout de sessão"
|
|
|
|
|
db_session.commit()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Erro ao registrar logout por timeout: {e}")
|
|
|
|
|
|
|
|
|
|
session.clear()
|
|
|
|
|
return jsonify({'expired': True})
|
|
|
|
|
|
|
|
|
|
return jsonify({'expired': False})
|
|
|
|
|
|
2025-04-01 15:27:16 -03:00
|
|
|
@app.route("/qr/<token>")
|
|
|
|
|
def get_qr_code(token):
|
|
|
|
|
militante = db_session.query(Militante).filter_by(temp_token=token).first()
|
|
|
|
|
|
|
|
|
|
if not militante or militante.temp_token_expiry < datetime.now():
|
|
|
|
|
flash('Link expirado ou inválido', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
qr_path = generate_qr_code(militante)
|
|
|
|
|
return render_template('mostrar_qr_code.html', qr_uri=militante.get_otp_uri())
|
|
|
|
|
|
2025-04-03 11:24:47 -03:00
|
|
|
# Adicionar nova rota para API de setores
|
|
|
|
|
@app.route("/api/setores/<int:cr_id>")
|
|
|
|
|
@login_required
|
|
|
|
|
def get_setores(cr_id):
|
|
|
|
|
setores = db_session.query(Setor).filter_by(cr_id=cr_id).all()
|
|
|
|
|
return jsonify({
|
|
|
|
|
'setores': [{'id': s.id, 'nome': s.nome} for s in setores]
|
|
|
|
|
})
|
|
|
|
|
|
2025-02-20 10:39:31 -03:00
|
|
|
def create_app():
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
# ... existing code ...
|
|
|
|
|
|
|
|
|
|
# ... existing code ...
|
|
|
|
|
return app
|
|
|
|
|
|
2024-11-26 10:57:25 -03:00
|
|
|
# Iniciar o servidor Flask
|
2025-01-08 00:19:49 -03:00
|
|
|
if __name__ == "__main__":
|
2025-03-27 14:34:16 -03:00
|
|
|
# Verificar se existe usuário admin e gerar QR code
|
|
|
|
|
admin = db_session.query(Usuario).filter_by(username="admin").first()
|
|
|
|
|
if admin:
|
|
|
|
|
print("\n=== Gerando QR Code para usuário admin existente ===")
|
|
|
|
|
generate_qr_code(admin)
|
|
|
|
|
|
2025-01-08 00:19:49 -03:00
|
|
|
app.run(debug=True)
|