feat: implementa sistema de comprovantes com centralizações e PIX

This commit is contained in:
LS
2025-04-16 13:54:31 -03:00
parent 813c968efd
commit 8ff58cc51e
15 changed files with 581 additions and 480 deletions

323
app.py
View File

@@ -24,14 +24,14 @@ from functions.database import (
Endereco,
TipoComprovante,
Comprovante,
VendaJornal,
AssinaturaJornal,
CampanhaFinanceira,
TransacaoPIX,
Permission,
Role,
RolePermission,
UserRole
Atividade,
MaterialAtividade,
Relatorio,
CentralizacaoComprovante
)
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, joinedload
@@ -73,10 +73,6 @@ def create_app():
csrf = CSRFProtect()
csrf.init_app(app)
# Configurar cabeçalhos CSRF personalizados
app.config['WTF_CSRF_CHECK_DEFAULT'] = False
app.config['WTF_CSRF_HEADERS'] = ['X-CSRFToken']
# Configurar Flask-Login
login_manager = LoginManager()
login_manager.init_app(app)
@@ -88,6 +84,13 @@ def create_app():
"""Filtro para operação bit a bit AND"""
return value1 & value2
@app.before_request
def before_request():
"""Configurações antes de cada requisição"""
session.permanent = True
app.permanent_session_lifetime = timedelta(minutes=30)
session.modified = True
@login_manager.user_loader
def load_user(user_id):
"""Carrega o usuário pelo ID"""
@@ -214,14 +217,15 @@ def create_app():
flash("Email/usuário ou senha incorretos.", "danger")
return redirect(url_for("login"))
# Verificar OTP se o usuário tiver configurado
if user.otp_secret and not otp:
flash("Código OTP é obrigatório para sua conta.", "danger")
return redirect(url_for("login"))
if user.otp_secret and not user.verify_otp(otp):
flash("Código OTP inválido.", "danger")
return redirect(url_for("login"))
# Verificar OTP apenas se o usuário tiver configurado
if user.otp_secret:
if not otp:
flash("Código OTP é obrigatório para sua conta.", "danger")
return redirect(url_for("login"))
if not user.verify_otp(otp):
flash("Código OTP inválido.", "danger")
return redirect(url_for("login"))
# Atualizar último login
user.ultimo_login = datetime.utcnow()
@@ -281,28 +285,23 @@ def create_app():
.order_by(Militante.id.desc())\
.limit(5)\
.all()
# Buscar últimos comprovantes
ultimos_comprovantes = db.query(Comprovante)\
.join(Militante)\
.order_by(Comprovante.data_comprovante.desc())\
# Buscar últimos pagamentos
ultimos_pagamentos = db.query(Pagamento)\
.order_by(Pagamento.data_pagamento.desc())\
.limit(5)\
.all()
# Buscar tipos de comprovante
tipos_comprovante = db.query(TipoComprovante).all()
return render_template('home.html',
nome_usuario=nome_usuario,
data_atual=data_atual,
total_militantes=total_militantes,
total_cotas="{:.2f}".format(total_cotas),
total_materiais=total_materiais,
total_assinaturas=total_assinaturas,
ultimos_militantes=ultimos_militantes,
ultimos_comprovantes=ultimos_comprovantes,
tipos_comprovante=tipos_comprovante,
user=current_user)
nome_usuario=nome_usuario,
data_atual=data_atual,
total_militantes=total_militantes,
total_cotas=total_cotas,
total_materiais=total_materiais,
total_assinaturas=total_assinaturas,
ultimos_militantes=ultimos_militantes,
ultimos_pagamentos=ultimos_pagamentos,
Militante=Militante)
except Exception as e:
print(f"Erro na página inicial: {e}")
import traceback
@@ -316,7 +315,7 @@ def create_app():
total_materiais=0,
total_assinaturas=0,
ultimos_militantes=[],
ultimos_comprovantes=[],
ultimos_pagamentos=[],
Militante=Militante)
finally:
db.close()
@@ -819,7 +818,7 @@ def create_app():
return redirect(url_for("nova_venda_jornal"))
db = get_db_connection()
venda_jornal = VendaJornal(
venda_jornal = VendaJornalAvulso(
militante_id=militante_id,
quantidade=quantidade,
data_venda=data_venda
@@ -842,7 +841,7 @@ def create_app():
@require_permission(Permission.MANAGE_MATERIALS)
def listar_vendas_jornal():
db = get_db_connection()
vendas_jornal = db.query(VendaJornal).all()
vendas_jornal = db.query(VendaJornalAvulso).all()
return render_template("listar_vendas_jornal.html", vendas_jornal=vendas_jornal)
# Rota para criar um novo relatório de cotas
@@ -888,8 +887,8 @@ def create_app():
return redirect(url_for("novo_relatorio_jornais"))
db = get_db_connection()
jornais = db.query(VendaJornal).filter(
VendaJornal.data_venda.between(data_inicio, data_fim)
jornais = db.query(VendaJornalAvulso).filter(
VendaJornalAvulso.data_venda.between(data_inicio, data_fim)
).all()
return render_template("relatorio_jornais.html",
@@ -916,8 +915,8 @@ def create_app():
return redirect(url_for("novo_relatorio_assinaturas"))
db = get_db_connection()
assinaturas = db.query(AssinaturaJornal).filter(
AssinaturaJornal.data_assinatura.between(data_inicio, data_fim)
assinaturas = db.query(AssinaturaAnual).filter(
AssinaturaAnual.data_assinatura.between(data_inicio, data_fim)
).all()
return render_template("relatorio_assinaturas.html",
@@ -1406,227 +1405,41 @@ def create_app():
finally:
session.close()
@app.route('/novo_comprovante', methods=['GET', 'POST'])
@login_required
def novo_comprovante():
"""Rota para criar um novo comprovante"""
try:
session = get_db_connection()
if request.method == 'POST':
militante_id = request.form.get('militante_id')
tipo_comprovante = request.form.get('tipo_comprovante')
valor = request.form.get('valor')
data_comprovante = request.form.get('data_comprovante')
if not all([militante_id, tipo_comprovante, valor, data_comprovante]):
flash('Todos os campos são obrigatórios', 'error')
return redirect(url_for('novo_comprovante'))
comprovante = Comprovante(
militante_id=militante_id,
tipo_comprovante=tipo_comprovante,
valor=float(valor.replace('R$', '').replace('.', '').replace(',', '.')),
data_comprovante=datetime.strptime(data_comprovante, '%Y-%m-%d')
)
session.add(comprovante)
session.commit()
flash('Comprovante criado com sucesso!', 'success')
return redirect(url_for('listar_comprovantes'))
militantes = session.query(Militante).all()
tipos_comprovante = session.query(TipoComprovante).all()
return render_template(
'novo_comprovante.html',
militantes=militantes,
tipos_comprovante=tipos_comprovante
)
except Exception as e:
flash(f'Erro ao criar comprovante: {str(e)}', 'error')
return redirect(url_for('listar_comprovantes'))
finally:
session.close()
@app.route('/listar_comprovantes')
@app.route('/comprovantes')
@login_required
@require_permission(Permission.MANAGE_MATERIALS)
def listar_comprovantes():
"""Rota para listar todos os comprovantes"""
try:
session = get_db_connection()
comprovantes = session.query(Comprovante).all()
return render_template('listar_comprovantes.html', comprovantes=comprovantes)
db = get_db_connection()
comprovantes = db.query(Comprovante)\
.options(joinedload(Comprovante.centralizacoes))\
.options(joinedload(Comprovante.militante))\
.options(joinedload(Comprovante.campanha))\
.all()
militantes = db.query(Militante).order_by(Militante.nome).all()
campanhas = db.query(CampanhaFinanceira).all()
return render_template('listar_comprovantes.html',
comprovantes=comprovantes,
militantes=militantes,
campanhas=campanhas)
except Exception as e:
flash(f'Erro ao listar comprovantes: {str(e)}', 'error')
return redirect(url_for('dashboard'))
finally:
session.close()
db.close()
@app.route('/adicionar_comprovante', methods=['POST'])
@app.route('/comprovantes/<int:id>', methods=['DELETE'])
@login_required
def adicionar_comprovante():
"""Rota para adicionar um novo comprovante via AJAX"""
@require_permission(Permission.MANAGE_MATERIALS)
def excluir_comprovante(id):
try:
session = get_db_connection()
data = request.get_json()
comprovante = Comprovante(
militante_id=data['militante_id'],
tipo_comprovante=data['tipo_comprovante'],
valor=float(data['valor'].replace('R$', '').replace('.', '').replace(',', '.')),
data_comprovante=datetime.strptime(data['data_comprovante'], '%Y-%m-%d')
)
session.add(comprovante)
session.commit()
return jsonify({
'status': 'success',
'message': 'Comprovante adicionado com sucesso!'
})
comprovante = Comprovante.query.get_or_404(id)
db.session.delete(comprovante)
db.session.commit()
return jsonify({'success': True})
except Exception as e:
return jsonify({
'status': 'error',
'message': f'Erro ao adicionar comprovante: {str(e)}'
})
finally:
session.close()
@app.route("/relatorios/comprovantes/novo", methods=["GET", "POST"])
@require_login
@require_permission(Permission.MANAGE_CELL_REPORTS)
def novo_relatorio_comprovantes():
if request.method == "POST":
try:
data_inicio = request.form.get("data_inicio")
data_fim = request.form.get("data_fim")
if not all([data_inicio, data_fim]):
flash("Todos os campos são obrigatórios", "danger")
return redirect(url_for("novo_relatorio_comprovantes"))
db = get_db_connection()
comprovantes = db.query(Comprovante).filter(
Comprovante.data_comprovante.between(data_inicio, data_fim)
).all()
return render_template("relatorio_comprovantes.html",
comprovantes=comprovantes,
data_inicio=data_inicio,
data_fim=data_fim)
except Exception as e:
flash(f"Erro ao gerar relatório: {str(e)}", "danger")
return redirect(url_for("novo_relatorio_comprovantes"))
return render_template("novo_relatorio_comprovantes.html")
@app.route("/relatorios/comprovantes/celula/<int:celula_id>", methods=["GET", "POST"])
@require_login
@require_permission(Permission.MANAGE_CELL_REPORTS)
def relatorio_comprovantes_celula(celula_id):
if request.method == "POST":
try:
data_inicio = request.form.get("data_inicio")
data_fim = request.form.get("data_fim")
if not all([data_inicio, data_fim]):
flash("Todos os campos são obrigatórios", "danger")
return redirect(url_for("relatorio_comprovantes_celula", celula_id=celula_id))
db = get_db_connection()
comprovantes = db.query(Comprovante).join(Militante).filter(
Militante.celula_id == celula_id,
Comprovante.data_comprovante.between(data_inicio, data_fim)
).all()
return render_template("relatorio_comprovantes.html",
comprovantes=comprovantes,
data_inicio=data_inicio,
data_fim=data_fim)
except Exception as e:
flash(f"Erro ao gerar relatório: {str(e)}", "danger")
return redirect(url_for("relatorio_comprovantes_celula", celula_id=celula_id))
return render_template("novo_relatorio_comprovantes.html", celula_id=celula_id)
@app.route("/assinaturas/jornal/novo", methods=["GET", "POST"])
@require_login
@require_permission(Permission.MANAGE_MATERIALS)
def nova_assinatura_jornal():
if request.method == "POST":
try:
militante_id = request.form.get("militante_id")
data_inicio = request.form.get("data_inicio")
data_fim = request.form.get("data_fim")
if not all([militante_id, data_inicio, data_fim]):
flash("Todos os campos são obrigatórios", "danger")
return redirect(url_for("nova_assinatura_jornal"))
db = get_db_connection()
assinatura = AssinaturaJornal(
militante_id=militante_id,
data_inicio=data_inicio,
data_fim=data_fim
)
db.add(assinatura)
db.commit()
flash("Assinatura de jornal registrada com sucesso", "success")
return redirect(url_for("listar_assinaturas_jornal"))
except Exception as e:
flash(f"Erro ao registrar assinatura de jornal: {str(e)}", "danger")
return redirect(url_for("nova_assinatura_jornal"))
db = get_db_connection()
militantes = db.query(Militante).all()
return render_template("nova_assinatura_jornal.html", militantes=militantes)
@app.route("/assinaturas/jornal")
@require_login
@require_permission(Permission.MANAGE_MATERIALS)
def listar_assinaturas_jornal():
db = get_db_connection()
assinaturas = db.query(AssinaturaJornal).all()
return render_template("listar_assinaturas_jornal.html", assinaturas=assinaturas)
@app.route("/campanhas/financeira/novo", methods=["GET", "POST"])
@require_login
@require_permission(Permission.MANAGE_MATERIALS)
def nova_campanha_financeira():
if request.method == "POST":
try:
militante_id = request.form.get("militante_id")
valor = request.form.get("valor")
data_campanha = request.form.get("data_campanha")
if not all([militante_id, valor, data_campanha]):
flash("Todos os campos são obrigatórios", "danger")
return redirect(url_for("nova_campanha_financeira"))
db = get_db_connection()
campanha = CampanhaFinanceira(
militante_id=militante_id,
valor=valor,
data_campanha=data_campanha
)
db.add(campanha)
db.commit()
flash("Campanha financeira registrada com sucesso", "success")
return redirect(url_for("listar_campanhas_financeira"))
except Exception as e:
flash(f"Erro ao registrar campanha financeira: {str(e)}", "danger")
return redirect(url_for("nova_campanha_financeira"))
db = get_db_connection()
militantes = db.query(Militante).all()
return render_template("nova_campanha_financeira.html", militantes=militantes)
@app.route("/campanhas/financeira")
@require_login
@require_permission(Permission.MANAGE_MATERIALS)
def listar_campanhas_financeira():
db = get_db_connection()
campanhas = db.query(CampanhaFinanceira).all()
return render_template("listar_campanhas_financeira.html", campanhas=campanhas)
db.session.rollback()
return jsonify({'success': False, 'message': str(e)})
return app
@@ -1696,6 +1509,10 @@ def main():
# Inicializar o sistema
init_system()
# Configurar modo debug
app.debug = True
app.config['DEBUG'] = True
return app
# Criar a aplicação usando a função main
@@ -1704,6 +1521,6 @@ app = main()
if __name__ == '__main__':
app.run(
host='0.0.0.0',
port=5000,
debug=os.getenv('FLASK_ENV') == 'development'
port=int(os.getenv('FLASK_PORT', 5000)),
debug=True
)