rebase usando pagamentos

This commit is contained in:
LS
2025-04-22 18:11:32 -03:00
parent 63ebf09fb6
commit bb6e5c887b
15 changed files with 571 additions and 601 deletions

625
app.py
View File

@@ -22,6 +22,16 @@ from functions.database import (
init_database, init_database,
EstadoMilitante, EstadoMilitante,
Endereco, Endereco,
TipoComprovante,
Comprovante,
VendaJornal,
AssinaturaJornal,
CampanhaFinanceira,
TransacaoPIX,
Permission,
Role,
RolePermission,
UserRole
) )
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, joinedload from sqlalchemy.orm import sessionmaker, joinedload
@@ -34,7 +44,7 @@ from time import time
from flask_mail import Mail, Message from flask_mail import Mail, Message
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
from functions.rbac import Role, Permission, init_rbac from functions.rbac import init_rbac
from functions.decorators import require_permission, require_role, require_minimum_role, require_login, require_instance_permission, require_instance_access from functions.decorators import require_permission, require_role, require_minimum_role, require_login, require_instance_permission, require_instance_access
import re import re
import secrets import secrets
@@ -301,15 +311,15 @@ def create_app():
.limit(5)\ .limit(5)\
.all() .all()
# Buscar últimos pagamentos # Buscar últimos comprovantes
ultimos_pagamentos = db.query(Pagamento)\ ultimos_comprovantes = db.query(Comprovante)\
.join(Militante)\ .join(Militante)\
.order_by(Pagamento.data_pagamento.desc())\ .order_by(Comprovante.data_comprovante.desc())\
.limit(5)\ .limit(5)\
.all() .all()
# Buscar tipos de pagamento # Buscar tipos de comprovante
tipos_pagamento = db.query(TipoPagamento).all() tipos_comprovante = db.query(TipoComprovante).all()
return render_template('home.html', return render_template('home.html',
nome_usuario=nome_usuario, nome_usuario=nome_usuario,
@@ -319,9 +329,9 @@ def create_app():
total_materiais=total_materiais, total_materiais=total_materiais,
total_assinaturas=total_assinaturas, total_assinaturas=total_assinaturas,
ultimos_militantes=ultimos_militantes, ultimos_militantes=ultimos_militantes,
ultimos_pagamentos=ultimos_pagamentos, ultimos_comprovantes=ultimos_comprovantes,
tipos_pagamento=tipos_pagamento, tipos_comprovante=tipos_comprovante,
Militante=Militante) user=current_user)
except Exception as e: except Exception as e:
print(f"Erro na página inicial: {e}") print(f"Erro na página inicial: {e}")
import traceback import traceback
@@ -335,7 +345,7 @@ def create_app():
total_materiais=0, total_materiais=0,
total_assinaturas=0, total_assinaturas=0,
ultimos_militantes=[], ultimos_militantes=[],
ultimos_pagamentos=[], ultimos_comprovantes=[],
Militante=Militante) Militante=Militante)
finally: finally:
db.close() db.close()
@@ -742,190 +752,240 @@ def create_app():
# Rota para criar um novo material vendido # Rota para criar um novo material vendido
@app.route("/materiais/novo", methods=["GET", "POST"]) @app.route("/materiais/novo", methods=["GET", "POST"])
@require_login @require_login
@require_permission(Permission.VIEW_CELL_REPORTS) @require_permission(Permission.MANAGE_MATERIALS)
def novo_material(): def novo_material():
db = get_db_connection() if request.method == "POST":
try: try:
militante_id = request.form.get('militante_id') nome = request.form.get("nome")
tipo_material_id = request.form.get('tipo_material_id') descricao = request.form.get("descricao")
descricao = request.form.get('descricao') preco = float(request.form.get("preco"))
valor = float(request.form.get('valor')) quantidade = int(request.form.get("quantidade"))
data_venda = converter_data(request.form.get('data_venda'))
if not all([nome, descricao, preco, quantidade]):
flash("Todos os campos são obrigatórios", "danger")
return redirect(url_for("novo_material"))
db = get_db_connection()
material = MaterialVendido(nome=nome, valor=valor)
db.add(material)
db.commit()
flash("Material cadastrado com sucesso", "success")
return redirect(url_for("listar_materiais"))
except Exception as e:
flash(f"Erro ao cadastrar material: {str(e)}", "danger")
return redirect(url_for("novo_material"))
return render_template("novo_material.html")
material = MaterialVendido(
militante_id=militante_id,
tipo_material_id=tipo_material_id,
descricao=descricao,
valor=valor,
data_venda=data_venda
)
db.add(material)
db.commit()
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({
'status': 'success',
'message': 'Material cadastrado com sucesso!'
})
flash('Material cadastrado com sucesso!', 'success')
return redirect(url_for('listar_materiais'))
except Exception as e:
db.rollback()
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({
'status': 'error',
'message': 'Erro ao cadastrar material. Por favor, tente novamente.'
}), 400
flash('Erro ao cadastrar material. Por favor, tente novamente.', 'error')
return redirect(url_for('listar_materiais'))
finally:
db.close()
# Rota para listar materiais vendidos
@app.route("/materiais") @app.route("/materiais")
@require_login @require_login
@require_permission(Permission.VIEW_CELL_REPORTS) @require_permission(Permission.MANAGE_MATERIALS)
def listar_materiais(): def listar_materiais():
db = get_db_connection() db = get_db_connection()
try: materiais = db.query(MaterialVendido).all()
materiais = db.query(MaterialVendido).join(Militante).join(TipoMaterial).all() return render_template("listar_materiais.html", materiais=materiais)
militantes = db.query(Militante).all()
tipos_material = db.query(TipoMaterial).all() @app.route("/materiais/vendidos/novo", methods=["GET", "POST"])
return render_template('listar_materiais.html', @require_login
materiais=materiais, @require_permission(Permission.MANAGE_MATERIALS)
militantes=militantes, def novo_material_vendido():
tipos_material=tipos_material) if request.method == "POST":
finally: try:
db.close() militante_id = request.form.get("militante_id")
material_id = request.form.get("material_id")
quantidade = request.form.get("quantidade")
data_venda = request.form.get("data_venda")
if not all([militante_id, material_id, quantidade, data_venda]):
flash("Todos os campos são obrigatórios", "danger")
return redirect(url_for("novo_material_vendido"))
db = get_db_connection()
material_vendido = MaterialVendido(
militante_id=militante_id,
material_id=material_id,
quantidade=quantidade,
data_venda=data_venda
)
db.add(material_vendido)
db.commit()
flash("Material vendido registrado com sucesso", "success")
return redirect(url_for("listar_materiais_vendidos"))
except Exception as e:
flash(f"Erro ao registrar material vendido: {str(e)}", "danger")
return redirect(url_for("novo_material_vendido"))
db = get_db_connection()
militantes = db.query(Militante).all()
materiais = db.query(MaterialVendido).all()
return render_template("novo_material_vendido.html",
militantes=militantes,
materiais=materiais)
@app.route("/materiais/vendidos")
@require_login
@require_permission(Permission.MANAGE_MATERIALS)
def listar_materiais_vendidos():
db = get_db_connection()
materiais_vendidos = db.query(MaterialVendido).all()
return render_template("listar_materiais_vendidos.html",
materiais_vendidos=materiais_vendidos)
# Rota para criar uma nova venda de jornal # Rota para criar uma nova venda de jornal
@app.route("/jornais/novo", methods=["GET", "POST"]) @app.route("/vendas/jornal/novo", methods=["GET", "POST"])
@require_login @require_login
@require_permission(Permission.VIEW_CELL_REPORTS) @require_permission(Permission.MANAGE_MATERIALS)
def nova_venda_jornal(): def nova_venda_jornal():
if request.method == "POST": if request.method == "POST":
try: try:
militante_id = request.form.get('militante_id') militante_id = request.form.get("militante_id")
quantidade = int(request.form.get('quantidade')) quantidade = request.form.get("quantidade")
valor_total = float(request.form.get('valor_total')) data_venda = request.form.get("data_venda")
data_venda = converter_data(request.form.get('data_venda'))
if not validar_data(data_venda): if not all([militante_id, quantidade, data_venda]):
if request.headers.get('X-Requested-With') == 'XMLHttpRequest': flash("Todos os campos são obrigatórios", "danger")
return jsonify({ return redirect(url_for("nova_venda_jornal"))
'status': 'error',
'message': 'Data de venda inválida ou futura'
}), 400
flash('Data de venda inválida ou futura', 'danger')
return redirect(url_for('nova_venda_jornal'))
db = get_db_connection() db = get_db_connection()
venda = VendaJornalAvulso( venda_jornal = VendaJornal(
militante_id=militante_id, militante_id=militante_id,
quantidade=quantidade, quantidade=quantidade,
valor_total=valor_total,
data_venda=data_venda data_venda=data_venda
) )
db.add(venda) db.add(venda_jornal)
db.commit() db.commit()
if request.headers.get('X-Requested-With') == 'XMLHttpRequest': flash("Venda de jornal registrada com sucesso", "success")
return jsonify({ return redirect(url_for("listar_vendas_jornal"))
'status': 'success',
'message': 'Venda cadastrada com sucesso!'
})
flash('Venda cadastrada com sucesso!', 'success')
return redirect(url_for('listar_vendas_jornal'))
except Exception as e: except Exception as e:
db.rollback() flash(f"Erro ao registrar venda de jornal: {str(e)}", "danger")
app.logger.error(f"Erro ao cadastrar venda: {e}") return redirect(url_for("nova_venda_jornal"))
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({ db = get_db_connection()
'status': 'error', militantes = db.query(Militante).all()
'message': 'Erro ao cadastrar venda' return render_template("nova_venda_jornal.html", militantes=militantes)
}), 400
flash('Erro ao cadastrar venda', 'danger')
return redirect(url_for('nova_venda_jornal'))
finally:
db.close()
# Rota para listar vendas de jornal @app.route("/vendas/jornal")
@app.route("/jornais")
@require_login @require_login
@require_permission(Permission.VIEW_CELL_REPORTS) @require_permission(Permission.MANAGE_MATERIALS)
def listar_vendas_jornal(): def listar_vendas_jornal():
db = get_db_connection() db = get_db_connection()
try: vendas_jornal = db.query(VendaJornal).all()
vendas = db.query(VendaJornalAvulso).join(Militante).all() return render_template("listar_vendas_jornal.html", vendas_jornal=vendas_jornal)
militantes = db.query(Militante).all()
return render_template('listar_vendas_jornal.html',
vendas=vendas,
militantes=militantes)
finally:
db.close()
# Rota para criar um novo relatório de cotas # Rota para criar um novo relatório de cotas
@app.route("/relatorios/cotas/novo", methods=["GET", "POST"]) @app.route("/relatorios/cotas/novo", methods=["GET", "POST"])
@require_login @require_login
@require_permission(Permission.VIEW_CELL_REPORTS) @require_permission(Permission.MANAGE_REPORTS)
def novo_relatorio_cotas(): def novo_relatorio_cotas():
if request.method == "POST": if request.method == "POST":
try: try:
setor_id = request.form.get("setor_id") data_inicio = request.form.get("data_inicio")
comite_id = request.form.get("comite_id") data_fim = request.form.get("data_fim")
total_cotas = float(request.form.get("total_cotas"))
data_relatorio = request.form.get("data_relatorio")
# Validar data if not all([data_inicio, data_fim]):
if not validar_data(data_relatorio): flash("Todos os campos são obrigatórios", "danger")
flash('Data do relatório inválida', 'danger') return redirect(url_for("novo_relatorio_cotas"))
return render_template("novo_relatorio_cotas.html")
# Converter data
data_relatorio = converter_data(data_relatorio)
# Validar data futura
if data_relatorio > date.today():
flash('A data do relatório não pode ser futura', 'danger')
return render_template("novo_relatorio_cotas.html")
db = get_db_connection() db = get_db_connection()
try: cotas = db.query(CotaMensal).filter(
relatorio_cotas_mensais = RelatorioCotasMensais( CotaMensal.data_cota.between(data_inicio, data_fim)
setor_id=setor_id, ).all()
comite_id=comite_id,
total_cotas=total_cotas, return render_template("relatorio_cotas.html",
data_relatorio=data_relatorio cotas=cotas,
) data_inicio=data_inicio,
db.add(relatorio_cotas_mensais) data_fim=data_fim)
db.commit() except Exception as e:
flash('Relatório de cotas cadastrado com sucesso!', 'success') flash(f"Erro ao gerar relatório: {str(e)}", "danger")
return redirect(url_for('listar_relatorios_cotas')) return redirect(url_for("novo_relatorio_cotas"))
except Exception as e:
db.rollback() return render_template("novo_relatorio_cotas.html")
app.logger.error(f"Erro ao cadastrar relatório de cotas: {e}")
flash('Erro ao cadastrar relatório de cotas', 'danger')
return render_template("novo_relatorio_cotas.html")
finally:
db.close()
except ValueError as e:
flash(str(e), 'danger')
return render_template("novo_relatorio_cotas.html")
db = get_db_connection() @app.route("/relatorios/jornais/novo", methods=["GET", "POST"])
try: @require_login
setores = db.query(Setor).order_by(Setor.nome).all() @require_permission(Permission.MANAGE_REPORTS)
comites = db.query(ComiteCentral).order_by(ComiteCentral.nome).all() def novo_relatorio_jornais():
return render_template("novo_relatorio_cotas.html", if request.method == "POST":
setores=setores, try:
comites=comites, data_inicio = request.form.get("data_inicio")
hoje=date.today().strftime('%Y-%m-%d')) data_fim = request.form.get("data_fim")
finally:
db.close() if not all([data_inicio, data_fim]):
flash("Todos os campos são obrigatórios", "danger")
return redirect(url_for("novo_relatorio_jornais"))
db = get_db_connection()
jornais = db.query(VendaJornal).filter(
VendaJornal.data_venda.between(data_inicio, data_fim)
).all()
return render_template("relatorio_jornais.html",
jornais=jornais,
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_jornais"))
return render_template("novo_relatorio_jornais.html")
@app.route("/relatorios/assinaturas/novo", methods=["GET", "POST"])
@require_login
@require_permission(Permission.MANAGE_REPORTS)
def novo_relatorio_assinaturas():
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_assinaturas"))
db = get_db_connection()
assinaturas = db.query(AssinaturaJornal).filter(
AssinaturaJornal.data_assinatura.between(data_inicio, data_fim)
).all()
return render_template("relatorio_assinaturas.html",
assinaturas=assinaturas,
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_assinaturas"))
return render_template("novo_relatorio_assinaturas.html")
@app.route("/relatorios/campanhas/novo", methods=["GET", "POST"])
@require_login
@require_permission(Permission.MANAGE_REPORTS)
def novo_relatorio_campanhas():
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_campanhas"))
db = get_db_connection()
campanhas = db.query(CampanhaFinanceira).filter(
CampanhaFinanceira.data_campanha.between(data_inicio, data_fim)
).all()
return render_template("relatorio_campanhas.html",
campanhas=campanhas,
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_campanhas"))
return render_template("novo_relatorio_campanhas.html")
# Rota para listar relatórios de cotas # Rota para listar relatórios de cotas
@app.route("/relatorios/cotas") @app.route("/relatorios/cotas")
@@ -1775,7 +1835,7 @@ def create_app():
@app.route('/celulas/<int:celula_id>/comprovantes/novo', methods=['GET', 'POST']) @app.route('/celulas/<int:celula_id>/comprovantes/novo', methods=['GET', 'POST'])
@require_login @require_login
@require_instance_permission('REGISTER_CELL_PAYMENT', 'celula_id') @require_instance_permission('REGISTER_CELL_RECEIPT', 'celula_id')
def novo_comprovante_celula(celula_id): def novo_comprovante_celula(celula_id):
if request.method == 'POST': if request.method == 'POST':
db = get_db_connection() db = get_db_connection()
@@ -1796,7 +1856,7 @@ def create_app():
@app.route('/setores/<int:setor_id>/comprovantes/novo', methods=['GET', 'POST']) @app.route('/setores/<int:setor_id>/comprovantes/novo', methods=['GET', 'POST'])
@require_login @require_login
@require_instance_permission('REGISTER_SECTOR_PAYMENT', 'setor_id') @require_instance_permission('REGISTER_SECTOR_RECEIPT', 'setor_id')
def novo_comprovante_setor(setor_id): def novo_comprovante_setor(setor_id):
if request.method == 'POST': if request.method == 'POST':
db = get_db_connection() db = get_db_connection()
@@ -1817,7 +1877,7 @@ def create_app():
@app.route('/crs/<int:cr_id>/comprovantes/novo', methods=['GET', 'POST']) @app.route('/crs/<int:cr_id>/comprovantes/novo', methods=['GET', 'POST'])
@require_login @require_login
@require_instance_permission('REGISTER_CR_PAYMENT', 'cr_id') @require_instance_permission('REGISTER_CR_RECEIPT', 'cr_id')
def novo_comprovante_cr(cr_id): def novo_comprovante_cr(cr_id):
if request.method == 'POST': if request.method == 'POST':
db = get_db_connection() db = get_db_connection()
@@ -1836,6 +1896,249 @@ def create_app():
db.close() db.close()
return render_template('comprovantes/form.html') return render_template('comprovantes/form.html')
@app.route('/dashboard')
@login_required
def dashboard():
"""Rota para a página inicial do dashboard"""
try:
session = get_db_connection()
# Buscar dados recentes
comprovantes_recentes = session.query(Comprovante).order_by(Comprovante.data_comprovante.desc()).limit(5).all()
tipos_comprovante = session.query(TipoComprovante).all()
return render_template(
'dashboard.html',
comprovantes_recentes=comprovantes_recentes,
tipos_comprovante=tipos_comprovante
)
except Exception as e:
flash(f'Erro ao carregar dashboard: {str(e)}', 'error')
return redirect(url_for('index'))
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')
@login_required
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)
except Exception as e:
flash(f'Erro ao listar comprovantes: {str(e)}', 'error')
return redirect(url_for('dashboard'))
finally:
session.close()
@app.route('/adicionar_comprovante', methods=['POST'])
@login_required
def adicionar_comprovante():
"""Rota para adicionar um novo comprovante via AJAX"""
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!'
})
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)
return app return app
def init_system(): def init_system():

View File

@@ -109,22 +109,26 @@ CREATE TABLE user_roles (
- `manage_cell_members`: Gerenciar membros da célula - `manage_cell_members`: Gerenciar membros da célula
- `create_cell_member`: Criar novos membros na célula - `create_cell_member`: Criar novos membros na célula
- `view_cell_reports`: Visualizar relatórios da célula - `view_cell_reports`: Visualizar relatórios da célula
- `REGISTER_CELL_RECEIPT`: Registrar comprovantes da célula
### Permissões de Setor ### Permissões de Setor
- `manage_sector_cells`: Gerenciar células do setor - `manage_sector_cells`: Gerenciar células do setor
- `create_sector_cell`: Criar novas células no setor - `create_sector_cell`: Criar novas células no setor
- `view_sector_reports`: Visualizar relatórios do setor - `view_sector_reports`: Visualizar relatórios do setor
- `REGISTER_SECTOR_RECEIPT`: Registrar comprovantes do setor
### Permissões de CR ### Permissões de CR
- `manage_cr_sectors`: Gerenciar setores do CR - `manage_cr_sectors`: Gerenciar setores do CR
- `create_cr_sector`: Criar novos setores no CR - `create_cr_sector`: Criar novos setores no CR
- `view_cr_reports`: Visualizar relatórios do CR - `view_cr_reports`: Visualizar relatórios do CR
- `REGISTER_CR_RECEIPT`: Registrar comprovantes do CR
### Permissões de CC ### Permissões de CC
- `manage_cc_crs`: Gerenciar CRs - `manage_cc_crs`: Gerenciar CRs
- `create_cc_cr`: Criar novos CRs - `create_cc_cr`: Criar novos CRs
- `view_cc_reports`: Visualizar relatórios nacionais - `view_cc_reports`: Visualizar relatórios nacionais
- `system_config`: Configurar o sistema - `system_config`: Configurar o sistema
- `REGISTER_CC_RECEIPT`: Registrar comprovantes do CC
## Uso no Código ## Uso no Código
@@ -166,12 +170,12 @@ O sistema possui uma estrutura hierárquica com os seguintes níveis:
- `MANAGE_CELL_MEMBERS`: Gerenciar membros da célula - `MANAGE_CELL_MEMBERS`: Gerenciar membros da célula
- `VIEW_CELL_DATA`: Visualizar dados da célula - `VIEW_CELL_DATA`: Visualizar dados da célula
- `VIEW_CELL_REPORTS`: Visualizar relatórios da célula - `VIEW_CELL_REPORTS`: Visualizar relatórios da célula
- `REGISTER_CELL_PAYMENT`: Registrar pagamentos da célula - `REGISTER_CELL_RECEIPT`: Registrar comprovantes da célula
- **Tesoureiro(a)**: - **Tesoureiro(a)**:
- `VIEW_CELL_DATA`: Visualizar dados da célula - `VIEW_CELL_DATA`: Visualizar dados da célula
- `VIEW_CELL_REPORTS`: Visualizar relatórios da célula - `VIEW_CELL_REPORTS`: Visualizar relatórios da célula
- `REGISTER_CELL_PAYMENT`: Registrar pagamentos da célula - `REGISTER_CELL_RECEIPT`: Registrar comprovantes da célula
- **Militante**: - **Militante**:
- `VIEW_OWN_DATA`: Visualizar apenas seus próprios dados - `VIEW_OWN_DATA`: Visualizar apenas seus próprios dados
@@ -180,32 +184,32 @@ O sistema possui uma estrutura hierárquica com os seguintes níveis:
- **Secretário(a)**: - **Secretário(a)**:
- `MANAGE_SECTOR_CELLS`: Gerenciar células do setor - `MANAGE_SECTOR_CELLS`: Gerenciar células do setor
- `VIEW_SECTOR_REPORTS`: Visualizar relatórios do setor - `VIEW_SECTOR_REPORTS`: Visualizar relatórios do setor
- `REGISTER_SECTOR_PAYMENT`: Registrar pagamentos do setor - `REGISTER_SECTOR_RECEIPT`: Registrar comprovantes do setor
- **Tesoureiro(a)**: - **Tesoureiro(a)**:
- `VIEW_SECTOR_REPORTS`: Visualizar relatórios do setor - `VIEW_SECTOR_REPORTS`: Visualizar relatórios do setor
- `REGISTER_SECTOR_PAYMENT`: Registrar pagamentos do setor - `REGISTER_SECTOR_RECEIPT`: Registrar comprovantes do setor
### CR ### CR
- **Secretário(a)**: - **Secretário(a)**:
- `MANAGE_CR_SECTORS`: Gerenciar setores do CR - `MANAGE_CR_SECTORS`: Gerenciar setores do CR
- `VIEW_CR_REPORTS`: Visualizar relatórios do CR - `VIEW_CR_REPORTS`: Visualizar relatórios do CR
- `REGISTER_CR_PAYMENT`: Registrar pagamentos do CR - `REGISTER_CR_RECEIPT`: Registrar comprovantes do CR
- **Tesoureiro(a)**: - **Tesoureiro(a)**:
- `VIEW_CR_REPORTS`: Visualizar relatórios do CR - `VIEW_CR_REPORTS`: Visualizar relatórios do CR
- `REGISTER_CR_PAYMENT`: Registrar pagamentos do CR - `REGISTER_CR_RECEIPT`: Registrar comprovantes do CR
### CC ### CC
- **Secretário(a)**: - **Secretário(a)**:
- `MANAGE_CC_CRS`: Gerenciar CRs - `MANAGE_CC_CRS`: Gerenciar CRs
- `VIEW_CC_REPORTS`: Visualizar relatórios do CC - `VIEW_CC_REPORTS`: Visualizar relatórios do CC
- `REGISTER_CC_PAYMENT`: Registrar pagamentos do CC - `REGISTER_CC_RECEIPT`: Registrar comprovantes do CC
- `SYSTEM_CONFIG`: Configurar o sistema - `SYSTEM_CONFIG`: Configurar o sistema
- **Tesoureiro(a)**: - **Tesoureiro(a)**:
- `VIEW_CC_REPORTS`: Visualizar relatórios do CC - `VIEW_CC_REPORTS`: Visualizar relatórios do CC
- `REGISTER_CC_PAYMENT`: Registrar pagamentos do CC - `REGISTER_CC_RECEIPT`: Registrar comprovantes do CC
## Regras de Acesso a Dados ## Regras de Acesso a Dados
@@ -214,10 +218,10 @@ O sistema possui uma estrutura hierárquica com os seguintes níveis:
- Secretários e tesoureiros podem ver dados de sua instância - Secretários e tesoureiros podem ver dados de sua instância
- O CC tem acesso a todos os dados - O CC tem acesso a todos os dados
2. **Registro de Pagamentos**: 2. **Registro de Comprovantes**:
- Apenas tesoureiros e secretários podem registrar pagamentos - Apenas tesoureiros e secretários podem registrar comprovantes
- O registro é restrito à instância do usuário - O registro é restrito à instância do usuário
- O CC pode registrar pagamentos em qualquer nível - O CC pode registrar comprovantes em qualquer nível
## Implementação Técnica ## Implementação Técnica

View File

@@ -190,6 +190,7 @@ class Militante(Base):
vendas_jornais = relationship("VendaJornalAvulso", back_populates="militante") vendas_jornais = relationship("VendaJornalAvulso", back_populates="militante")
assinaturas = relationship("AssinaturaAnual", back_populates="militante") assinaturas = relationship("AssinaturaAnual", back_populates="militante")
celula = relationship("Celula", back_populates="militantes", foreign_keys=[celula_id]) celula = relationship("Celula", back_populates="militantes", foreign_keys=[celula_id])
comprovantes = relationship("Comprovante", back_populates="militante")
# Constantes para responsabilidades # Constantes para responsabilidades
SECRETARIO = 1 SECRETARIO = 1
@@ -626,6 +627,22 @@ class TransacaoPIX(Base):
pagamento = relationship("Pagamento", back_populates="transacoes_pix") pagamento = relationship("Pagamento", back_populates="transacoes_pix")
class TipoComprovante(Base):
__tablename__ = 'tipos_comprovante'
id = Column(Integer, primary_key=True)
descricao = Column(String(50), nullable=False)
valor = Column(Float, nullable=False)
class Comprovante(Base):
__tablename__ = 'comprovantes'
id = Column(Integer, primary_key=True)
militante_id = Column(Integer, ForeignKey('militantes.id'), nullable=False)
tipo_comprovante = Column(String(50)) # Cota, Jornal, Assinatura, etc.
data_comprovante = Column(Date, nullable=False)
militante = relationship("Militante", back_populates="comprovantes")
transacoes_pix = relationship("TransacaoPIX", back_populates="comprovante")
def init_database(): def init_database():
"""Inicializa o banco de dados com dados básicos""" """Inicializa o banco de dados com dados básicos"""
print("Inicializando banco de dados...") print("Inicializando banco de dados...")

View File

@@ -8,7 +8,7 @@ Werkzeug==3.0.1
python-dotenv==1.0.1 python-dotenv==1.0.1
pyotp==2.9.0 pyotp==2.9.0
qrcode==7.4.2 qrcode==7.4.2
Pillow==10.2.0 Pillow==9.5.0
email-validator==2.1.0.post1 email-validator==2.1.0.post1
cryptography==42.0.2 cryptography==42.0.2
bcrypt==4.1.2 bcrypt==4.1.2

View File

@@ -1,10 +1,11 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functions.database import ( from functions.database import (
Base, Militante, CotaMensal, TipoPagamento, Pagamento, Base, Militante, CotaMensal, TipoComprovante, Comprovante,
MaterialVendido, TipoMaterial, VendaJornalAvulso, AssinaturaAnual, MaterialVendido, TipoMaterial, VendaJornalAvulso, AssinaturaAnual,
RelatorioCotasMensais, RelatorioVendasMateriais, engine, SessionLocal, RelatorioCotasMensais, RelatorioVendasMateriais, engine, SessionLocal,
Setor, ComiteCentral, Usuario, Role, EmailMilitante, Endereco, Setor, ComiteCentral, Usuario, Role, EmailMilitante, Endereco,
ComiteRegional, Celula, EstadoMilitante ComiteRegional, Celula, EstadoMilitante, get_db_connection,
init_database
) )
import random import random
from faker import Faker from faker import Faker
@@ -54,20 +55,28 @@ def criar_estrutura_organizacional(session):
session.commit() session.commit()
return crs, setores return crs, setores
def criar_tipos_pagamento(session): def criar_tipos_comprovante(session):
"""Cria tipos de pagamento padrão""" """Cria tipos de comprovante padrão"""
print("\nCriando tipos de pagamento...") print("\nCriando tipos de comprovante...")
tipos = [ tipos = [
"Dinheiro", "Comprovante Padrão",
"PIX", "Comprovante Especial",
"Cartão de Crédito", "Comprovante Extraordinário",
"Cartão de Débito", "Jornal Avulso",
"Transferência Bancária" "Assinatura de Jornal",
"Campanha Financeira"
] ]
for tipo in tipos: for tipo in tipos:
if not session.query(TipoPagamento).filter_by(descricao=tipo).first(): if not session.query(TipoComprovante).filter_by(descricao=tipo).first():
session.add(TipoPagamento(descricao=tipo)) session.add(TipoComprovante(descricao=tipo))
session.commit()
try:
session.commit()
print("Tipos de comprovante criados com sucesso!")
except Exception as e:
session.rollback()
print(f"Erro ao criar tipos de comprovante: {e}")
def criar_tipos_material(session): def criar_tipos_material(session):
"""Cria tipos de material padrão""" """Cria tipos de material padrão"""
@@ -211,27 +220,27 @@ def criar_cotas(session, militantes):
print(f"Erro ao criar cotas para militante {militante.nome}: {e}") print(f"Erro ao criar cotas para militante {militante.nome}: {e}")
session.rollback() session.rollback()
def criar_pagamentos(session, militantes): def criar_comprovantes(session, militantes):
"""Cria pagamentos para os militantes""" """Cria comprovantes para os militantes"""
print("\nCriando pagamentos...") print("\nCriando comprovantes...")
tipos_pagamento = session.query(TipoPagamento).all() tipos_comprovante = session.query(TipoComprovante).all()
for militante in militantes: for militante in militantes:
try: try:
# Criar entre 3 e 8 pagamentos por militante # Criar entre 3 e 8 comprovantes por militante
for _ in range(random.randint(3, 8)): for _ in range(random.randint(3, 8)):
tipo = random.choice(tipos_pagamento) tipo = random.choice(tipos_comprovante)
pagamento = Pagamento( comprovante = Comprovante(
militante_id=militante.id, militante_id=militante.id,
tipo_pagamento=tipo.descricao, # Usando a descrição do tipo tipo_comprovante=tipo.descricao, # Usando a descrição do tipo
valor=random.uniform(50, 500), valor=random.uniform(10, 1000),
data_pagamento=fake.date_between(start_date='-1y', end_date='today') data_comprovante=fake.date_between(start_date='-1y', end_date='today')
) )
session.add(pagamento) session.add(comprovante)
session.commit() session.commit()
except Exception as e: except Exception as e:
print(f"Erro ao criar pagamentos para militante {militante.nome}: {e}")
session.rollback() session.rollback()
print(f"Erro ao criar comprovantes para militante {militante.nome}: {e}")
def criar_materiais_vendidos(session, militantes): def criar_materiais_vendidos(session, militantes):
"""Cria registros de materiais vendidos""" """Cria registros de materiais vendidos"""
@@ -302,7 +311,7 @@ def criar_assinaturas(session, militantes):
def seed_database(): def seed_database():
"""Função principal para popular o banco de dados""" """Função principal para popular o banco de dados"""
session = SessionLocal() session = get_db_connection()
try: try:
print("Iniciando população do banco de dados...") print("Iniciando população do banco de dados...")
@@ -310,7 +319,7 @@ def seed_database():
crs, setores = criar_estrutura_organizacional(session) crs, setores = criar_estrutura_organizacional(session)
# Criar tipos básicos # Criar tipos básicos
criar_tipos_pagamento(session) criar_tipos_comprovante(session)
criar_tipos_material(session) criar_tipos_material(session)
# Criar militantes (30 militantes para teste) # Criar militantes (30 militantes para teste)
@@ -318,7 +327,7 @@ def seed_database():
# Criar dados financeiros e materiais # Criar dados financeiros e materiais
criar_cotas(session, militantes) criar_cotas(session, militantes)
criar_pagamentos(session, militantes) criar_comprovantes(session, militantes)
criar_materiais_vendidos(session, militantes) criar_materiais_vendidos(session, militantes)
criar_vendas_jornal(session, militantes) criar_vendas_jornal(session, militantes)
criar_assinaturas(session, militantes) criar_assinaturas(session, militantes)

View File

@@ -1,10 +1,10 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Configurar clique nos itens da lista de pagamentos // Configurar clique nos itens da lista de comprovantes
document.querySelectorAll('.list-group-item[onclick*="carregarDadosPagamento"]').forEach(item => { document.querySelectorAll('.list-group-item[onclick*="carregarDadosComprovante"]').forEach(item => {
item.addEventListener('click', function(e) { item.addEventListener('click', function(e) {
const pagamentoId = this.getAttribute('data-pagamento-id'); const comprovanteId = this.getAttribute('data-comprovante-id');
if (pagamentoId) { if (comprovanteId) {
carregarDadosPagamento(pagamentoId); carregarDadosComprovante(comprovanteId);
} }
}); });
}); });

View File

@@ -56,7 +56,7 @@ function configurarOrdenacaoTabela(tabelaId) {
if (column === 'data' || if (column === 'data' ||
column === 'data_vencimento' || column === 'data_vencimento' ||
column === 'data_alteracao' || column === 'data_alteracao' ||
column === 'data_pagamento' || column === 'data_comprovante' ||
column === 'data_venda' || column === 'data_venda' ||
column === 'data_relatorio') { column === 'data_relatorio') {
const aDate = converterDataParaComparacao(aValue); const aDate = converterDataParaComparacao(aValue);
@@ -112,7 +112,7 @@ document.addEventListener('DOMContentLoaded', function() {
'materiaisTable', 'materiaisTable',
'vendasTable', 'vendasTable',
'cotasTable', 'cotasTable',
'pagamentosTable' 'comprovantesTable'
]; ];
tabelas.forEach(tabelaId => { tabelas.forEach(tabelaId => {
@@ -197,4 +197,12 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
}); });
}); });
}); });
function sortTable(table, column, type = 'text') {
// ... existing code ...
if (column === 'data_comprovante') {
// ... existing code ...
}
// ... existing code ...
}

View File

@@ -29,8 +29,8 @@
</select> </select>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="data_pagamento" class="form-label">Data do Comprovante:</label> <label for="data_comprovante" class="form-label">Data do Comprovante:</label>
<input type="date" class="form-control" id="data_pagamento" name="data_pagamento" <input type="date" class="form-control" id="data_comprovante" name="data_comprovante"
required max="{{ hoje }}"> required max="{{ hoje }}">
</div> </div>
<button type="submit" class="btn btn-primary">Salvar</button> <button type="submit" class="btn btn-primary">Salvar</button>

View File

@@ -1,37 +0,0 @@
{% extends 'base.html' %}
{% block title %}Editar Comprovante{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="card shadow-sm">
<div class="card-header bg-light">
<h4 class="card-title mb-0">
<i class="fas fa-money-bill-wave me-2"></i>Editar Comprovante
</h4>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="tipo_pagamento_id" class="form-label">Tipo de Comprovante:</label>
<select class="form-select" id="tipo_pagamento_id" name="tipo_pagamento_id" required>
<option value="">Selecione o tipo de comprovante</option>
<!-- Add your options here -->
</select>
</div>
<div class="mb-3">
<label for="data_pagamento" class="form-label">Data do Comprovante:</label>
<input type="date" class="form-control" id="data_pagamento" name="data_pagamento"
required max="{{ hoje }}">
</div>
<button type="submit" class="btn btn-primary">Salvar</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,12 +1,12 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}Editar Relatório de Pagamentos{% endblock %} {% block title %}Editar Relatório de Comprovantes{% endblock %}
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h1 class="mb-4">Editar Relatório de Pagamentos</h1> <h1 class="mb-4">Editar Relatório de Comprovantes</h1>
{% with messages = get_flashed_messages(with_categories=true) %} {% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %} {% if messages %}
@@ -44,10 +44,10 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="total_pagamentos" class="form-label">Total de Pagamentos</label> <label for="total_comprovantes" class="form-label">Total de Comprovantes</label>
<input type="number" class="form-control" id="total_pagamentos" name="total_pagamentos" step="0.01" value="{{ relatorio.total_pagamentos }}" required> <input type="number" class="form-control" id="total_comprovantes" name="total_comprovantes" step="0.01" value="{{ relatorio.total_comprovantes }}" required>
<div class="invalid-feedback"> <div class="invalid-feedback">
Por favor, insira o total de pagamentos. Por favor, insira o total de comprovantes.
</div> </div>
</div> </div>
@@ -61,7 +61,7 @@
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<button type="submit" class="btn btn-success">Salvar</button> <button type="submit" class="btn btn-success">Salvar</button>
<a href="{{ url_for('listar_relatorios_pagamentos') }}" class="btn btn-outline-secondary">Voltar</a> <a href="{{ url_for('listar_relatorios_comprovantes') }}" class="btn btn-outline-secondary">Voltar</a>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,37 +0,0 @@
{% extends 'base.html' %}
{% block title %}Lista de Comprovantes{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="card shadow-sm">
<div class="card-header bg-light">
<h4 class="card-title mb-0">
<i class="fas fa-list me-2"></i>Lista de Comprovantes
</h4>
</div>
<div class="card-body">
<table class="table table-striped">
<thead>
<tr>
<th>Tipo de Comprovante</th>
<th>Data do Comprovante</th>
</tr>
</thead>
<tbody>
{% for comprovante in comprovantes %}
<tr>
<td>{{ comprovante.tipo }}</td>
<td>{{ comprovante.data }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,203 +0,0 @@
{% extends "base.html" %}
{% block title %}Comprovantes{% endblock %}
{% block content %}
<div class="container-fluid mt-3">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2><i class="fas fa-money-bill-wave"></i> Comprovantes</h2>
<div>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalNovoPagamento">
<i class="fas fa-plus"></i> Novo Comprovante
</button>
<button type="button" class="btn btn-outline-primary" id="btnExportar">
<i class="fas fa-file-export"></i> Exportar
</button>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover" id="tabelaPagamentos">
<thead>
<tr>
<th>Militante</th>
<th>Tipo de Comprovante</th>
<th>Valor</th>
<th>Data do Comprovante</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{% for pagamento in pagamentos %}
<tr>
<td data-militante="{{ pagamento.militante_id }}">{{ pagamento.militante.nome if pagamento.militante else 'N/A' }}</td>
<td data-tipo="{{ pagamento.tipo_pagamento }}">
{% if pagamento.tipo_pagamento == 1 %}
Cota
{% elif pagamento.tipo_pagamento == 2 %}
Contribuição Extra
{% elif pagamento.tipo_pagamento == 3 %}
Doação
{% elif pagamento.tipo_pagamento == 4 %}
Taxa de Evento
{% elif pagamento.tipo_pagamento == 5 %}
Outros
{% else %}
Não Definido
{% endif %}
</td>
<td data-valor="{{ pagamento.valor }}">R$ {{ "%.2f"|format(pagamento.valor) }}</td>
<td data-data="{{ pagamento.data_pagamento }}">{{ pagamento.data_pagamento.strftime('%d/%m/%Y') }}</td>
<td>
<button type="button"
class="btn btn-sm btn-outline-primary"
data-bs-toggle="modal"
data-bs-target="#modalEditarPagamento"
data-pagamento-id="{{ pagamento.id }}"
data-militante-id="{{ pagamento.militante_id }}"
data-tipo-pagamento="{{ pagamento.tipo_pagamento }}"
data-valor="{{ pagamento.valor }}"
data-data-pagamento="{{ pagamento.data_pagamento.strftime('%Y-%m-%d') }}"
title="Editar">
<i class="fas fa-edit"></i>
</button>
<button type="button"
class="btn btn-sm btn-outline-danger"
data-bs-toggle="modal"
data-bs-target="#modalExcluirPagamento"
data-pagamento-id="{{ pagamento.id }}"
data-pagamento-info="Comprovante de {{ pagamento.militante.nome if pagamento.militante else 'N/A' }} - R$ {{ "%.2f"|format(pagamento.valor) }}"
title="Excluir">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Modal Novo Pagamento -->
<div class="modal fade" id="modalNovoPagamento" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-plus"></i> Novo Comprovante</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="formNovoPagamento" method="post" action="{{ url_for('adicionar_pagamento') }}">
<div class="mb-3">
<label for="militante" class="form-label">Militante:</label>
<select class="form-select" id="militante" name="militante_id" required>
<option value="">Selecione um militante</option>
{% for militante in militantes %}
<option value="{{ militante.id }}">{{ militante.nome }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="tipoPagamento" class="form-label">Tipo de Comprovante:</label>
<select class="form-select" id="tipoPagamento" name="tipo_pagamento" required>
<option value="">Selecione o tipo</option>
<option value="1">Cota</option>
<option value="2">Contribuição Extra</option>
<option value="3">Doação</option>
<option value="4">Taxa de Evento</option>
<option value="5">Outros</option>
</select>
</div>
<div class="mb-3">
<label for="valor" class="form-label">Valor:</label>
<input type="number" step="0.01" class="form-control" id="valor" name="valor" required>
</div>
<div class="mb-3">
<label for="dataPagamento" class="form-label">Data do Comprovante:</label>
<input type="date" class="form-control" id="dataPagamento" name="data_pagamento" required>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Salvar</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Modal Editar Pagamento -->
<div class="modal fade" id="modalEditarPagamento" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-edit"></i> Editar Comprovante</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="formEditarPagamento" method="post">
<div class="mb-3">
<label for="editMilitante" class="form-label">Militante:</label>
<input type="text" class="form-control bg-light" id="editMilitanteNome" readonly>
<input type="hidden" id="editMilitante" name="militante_id">
</div>
<div class="mb-3">
<label for="editTipoPagamento" class="form-label">Tipo de Comprovante:</label>
<select class="form-select" id="editTipoPagamento" name="tipo_pagamento" required>
<option value="">Selecione o tipo</option>
<option value="1">Cota</option>
<option value="2">Contribuição Extra</option>
<option value="3">Doação</option>
<option value="4">Taxa de Evento</option>
<option value="5">Outros</option>
</select>
</div>
<div class="mb-3">
<label for="editValor" class="form-label">Valor:</label>
<input type="number" step="0.01" class="form-control" id="editValor" name="valor" required>
</div>
<div class="mb-3">
<label for="editDataPagamento" class="form-label">Data do Comprovante:</label>
<input type="date" class="form-control" id="editDataPagamento" name="data_pagamento" required>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Salvar</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Modal Excluir Pagamento -->
<div class="modal fade" id="modalExcluirPagamento" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-trash"></i> Excluir Comprovante</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Tem certeza que deseja excluir este comprovante?</p>
<p id="pagamentoInfo" class="text-muted"></p>
</div>
<div class="modal-footer">
<form id="formExcluirPagamento" method="post">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-danger">Excluir</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/pagamentos.js') }}"></script>
{% endblock %}

View File

@@ -1,12 +1,12 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}Listar Relatórios de Pagamentos{% endblock %} {% block title %}Listar Relatórios de Comprovantes{% endblock %}
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h1 class="mb-4">Lista de Relatórios de Pagamentos</h1> <h1 class="mb-4">Lista de Relatórios de Comprovantes</h1>
{% with messages = get_flashed_messages(with_categories=true) %} {% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %} {% if messages %}
@@ -17,7 +17,7 @@
{% endwith %} {% endwith %}
<div class="d-flex justify-content-between mb-4"> <div class="d-flex justify-content-between mb-4">
<a href="{{ url_for('novo_relatorio_pagamentos') }}" class="btn btn-success">Novo Relatório</a> <a href="{{ url_for('novo_relatorio_comprovantes') }}" class="btn btn-success">Novo Relatório</a>
<a href="{{ url_for('home') }}" class="btn btn-outline-primary">Início</a> <a href="{{ url_for('home') }}" class="btn btn-outline-primary">Início</a>
</div> </div>
@@ -28,7 +28,7 @@
<th>ID</th> <th>ID</th>
<th>Setor</th> <th>Setor</th>
<th>Comitê Central</th> <th>Comitê Central</th>
<th>Total de Pagamentos</th> <th>Total de Comprovantes</th>
<th>Data do Relatório</th> <th>Data do Relatório</th>
<th>Ações</th> <th>Ações</th>
</tr> </tr>
@@ -39,11 +39,11 @@
<td>{{ relatorio.id }}</td> <td>{{ relatorio.id }}</td>
<td>{{ relatorio.setor.nome }}</td> <td>{{ relatorio.setor.nome }}</td>
<td>{{ relatorio.comite.nome }}</td> <td>{{ relatorio.comite.nome }}</td>
<td>R$ {{ "%.2f"|format(relatorio.total_pagamentos) }}</td> <td>R$ {{ "%.2f"|format(relatorio.total_comprovantes) }}</td>
<td>{{ relatorio.data_relatorio.strftime('%d/%m/%Y') }}</td> <td>{{ relatorio.data_relatorio.strftime('%d/%m/%Y') }}</td>
<td> <td>
<a href="{{ url_for('editar_relatorio_pagamentos', id=relatorio.id) }}" class="btn btn-primary btn-sm">Editar</a> <a href="{{ url_for('editar_relatorio_comprovantes', id=relatorio.id) }}" class="btn btn-primary btn-sm">Editar</a>
<a href="{{ url_for('deletar_relatorio_pagamentos', id=relatorio.id) }}" class="btn btn-danger btn-sm" onclick="return confirm('Tem certeza que deseja excluir este relatório?')">Excluir</a> <a href="{{ url_for('deletar_relatorio_comprovantes', id=relatorio.id) }}" class="btn btn-danger btn-sm" onclick="return confirm('Tem certeza que deseja excluir este relatório?')">Excluir</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@@ -1,94 +0,0 @@
{% extends 'base.html' %}
{% block title %}Novo Comprovante{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="card shadow-sm">
<div class="card-header bg-light">
<h4 class="card-title mb-0">
<i class="fas fa-money-bill-wave me-2"></i>Registrar Novo Comprovante
</h4>
</div>
<div class="card-body">
<form method="post" class="needs-validation" novalidate>
<div class="mb-3">
<label for="militante_id" class="form-label">Militante:</label>
<select class="form-select" id="militante_id" name="militante_id" required>
<option value="">Selecione um militante</option>
{% for militante in militantes %}
<option value="{{ militante.id }}">{{ militante.nome }}</option>
{% endfor %}
</select>
<div class="invalid-feedback">
Por favor, selecione um militante.
</div>
</div>
<div class="mb-3">
<label for="tipo_pagamento_id" class="form-label">Tipo de Comprovante:</label>
<select class="form-select" id="tipo_pagamento_id" name="tipo_pagamento_id" required>
<option value="">Selecione o tipo de comprovante</option>
{% for tipo in tipos_pagamento %}
<option value="{{ tipo.id }}">{{ tipo.descricao }}</option>
{% endfor %}
</select>
<div class="invalid-feedback">
Por favor, selecione o tipo de comprovante.
</div>
</div>
<div class="mb-3">
<label for="valor" class="form-label">Valor:</label>
<div class="input-group">
<span class="input-group-text">R$</span>
<input type="text" class="form-control money" id="valor" name="valor" required>
</div>
<div class="invalid-feedback">
Por favor, informe um valor válido.
</div>
</div>
<div class="mb-3">
<label for="data_pagamento" class="form-label">Data do Comprovante:</label>
<input type="date" class="form-control" id="data_pagamento" name="data_pagamento"
required max="{{ hoje }}">
<div class="invalid-feedback">
Por favor, informe uma data válida.
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-1"></i>Registrar
</button>
<a href="{{ url_for('listar_pagamentos') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i>Voltar
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.mask/1.14.16/jquery.mask.min.js"></script>
<script>
$(document).ready(function(){
$('.money').mask('000.000.000.000.000,00', {reverse: true});
// Converter valor para formato aceito pelo backend
$('form').on('submit', function(e) {
e.preventDefault();
const valor = $('#valor').val().replace(/\./g, '').replace(',', '.');
$('#valor').val(valor);
this.submit();
});
});
</script>
{% endblock %}

View File

@@ -1,12 +1,12 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}Novo Relatório de Cotas{% endblock %} {% block title %}Novo Relatório de Comprovantes{% endblock %}
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h1 class="mb-4">Novo Relatório de Cotas</h1> <h1 class="mb-4">Novo Relatório de Comprovantes</h1>
{% with messages = get_flashed_messages(with_categories=true) %} {% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %} {% if messages %}
@@ -44,10 +44,10 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="total_pagamentos" class="form-label">Total de Pagamentos</label> <label for="total_comprovantes" class="form-label">Total de Comprovantes</label>
<input type="number" class="form-control" id="total_pagamentos" name="total_pagamentos" step="0.01" required> <input type="number" class="form-control" id="total_comprovantes" name="total_comprovantes" step="0.01" required>
<div class="invalid-feedback"> <div class="invalid-feedback">
Por favor, insira o total de pagamentos. Por favor, insira o total de comprovantes.
</div> </div>
</div> </div>
@@ -61,7 +61,7 @@
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<button type="submit" class="btn btn-success">Registrar</button> <button type="submit" class="btn btn-success">Registrar</button>
<a href="{{ url_for('listar_relatorios_pagamentos') }}" class="btn btn-outline-secondary">Voltar</a> <a href="{{ url_for('listar_relatorios_comprovantes') }}" class="btn btn-outline-secondary">Voltar</a>
</div> </div>
</form> </form>
</div> </div>