Compare commits
8 Commits
911ead7835
...
pagamentos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ff58cc51e | ||
|
|
813c968efd | ||
|
|
bdab631f0f | ||
|
|
91369f670f | ||
|
|
494b6262bf | ||
|
|
e01764ab40 | ||
|
|
279924a43c | ||
|
|
54191b8dde |
18
Makefile
18
Makefile
@@ -1,7 +1,19 @@
|
|||||||
|
.PHONY: install run test clean refresh
|
||||||
|
|
||||||
install:
|
install:
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
pip install pytest pytest-cov
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
find . -type d -name "__pycache__" -exec rm -r {} +
|
||||||
|
find . -type f -name "*.pyc" -delete
|
||||||
|
find . -type f -name "*.pyo" -delete
|
||||||
|
find . -type f -name "*.pyd" -delete
|
||||||
|
find . -type f -name ".coverage" -delete
|
||||||
|
find . -type d -name "*.egg-info" -exec rm -r {} +
|
||||||
|
find . -type d -name "*.egg" -exec rm -r {} +
|
||||||
|
find . -type d -name ".pytest_cache" -exec rm -r {} +
|
||||||
|
find . -type d -name "htmlcov" -exec rm -r {} +
|
||||||
rm -rf ~/.local/share/controles/database.db*
|
rm -rf ~/.local/share/controles/database.db*
|
||||||
rm -f admin_qr.png
|
rm -f admin_qr.png
|
||||||
|
|
||||||
@@ -18,3 +30,9 @@ run-with-seed: seed run
|
|||||||
|
|
||||||
reset-admin: clean
|
reset-admin: clean
|
||||||
python create_admin.py
|
python create_admin.py
|
||||||
|
|
||||||
|
test:
|
||||||
|
pytest tests/ --cov=app --cov=functions --cov-report=term-missing
|
||||||
|
|
||||||
|
refresh: clean install test
|
||||||
|
python app.py
|
||||||
|
|||||||
766
app.py
766
app.py
@@ -22,6 +22,16 @@ from functions.database import (
|
|||||||
init_database,
|
init_database,
|
||||||
EstadoMilitante,
|
EstadoMilitante,
|
||||||
Endereco,
|
Endereco,
|
||||||
|
TipoComprovante,
|
||||||
|
Comprovante,
|
||||||
|
CampanhaFinanceira,
|
||||||
|
TransacaoPIX,
|
||||||
|
Permission,
|
||||||
|
Role,
|
||||||
|
Atividade,
|
||||||
|
MaterialAtividade,
|
||||||
|
Relatorio,
|
||||||
|
CentralizacaoComprovante
|
||||||
)
|
)
|
||||||
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
|
||||||
@@ -63,10 +73,6 @@ def create_app():
|
|||||||
csrf = CSRFProtect()
|
csrf = CSRFProtect()
|
||||||
csrf.init_app(app)
|
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
|
# Configurar Flask-Login
|
||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
@@ -78,6 +84,13 @@ def create_app():
|
|||||||
"""Filtro para operação bit a bit AND"""
|
"""Filtro para operação bit a bit AND"""
|
||||||
return value1 & value2
|
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
|
@login_manager.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
"""Carrega o usuário pelo ID"""
|
"""Carrega o usuário pelo ID"""
|
||||||
@@ -204,12 +217,13 @@ def create_app():
|
|||||||
flash("Email/usuário ou senha incorretos.", "danger")
|
flash("Email/usuário ou senha incorretos.", "danger")
|
||||||
return redirect(url_for("login"))
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
# Verificar OTP se o usuário tiver configurado
|
# Verificar OTP apenas se o usuário tiver configurado
|
||||||
if user.otp_secret and not otp:
|
if user.otp_secret:
|
||||||
|
if not otp:
|
||||||
flash("Código OTP é obrigatório para sua conta.", "danger")
|
flash("Código OTP é obrigatório para sua conta.", "danger")
|
||||||
return redirect(url_for("login"))
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
if user.otp_secret and not user.verify_otp(otp):
|
if not user.verify_otp(otp):
|
||||||
flash("Código OTP inválido.", "danger")
|
flash("Código OTP inválido.", "danger")
|
||||||
return redirect(url_for("login"))
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
@@ -274,24 +288,19 @@ def create_app():
|
|||||||
|
|
||||||
# Buscar últimos pagamentos
|
# Buscar últimos pagamentos
|
||||||
ultimos_pagamentos = db.query(Pagamento)\
|
ultimos_pagamentos = db.query(Pagamento)\
|
||||||
.join(Militante)\
|
|
||||||
.order_by(Pagamento.data_pagamento.desc())\
|
.order_by(Pagamento.data_pagamento.desc())\
|
||||||
.limit(5)\
|
.limit(5)\
|
||||||
.all()
|
.all()
|
||||||
|
|
||||||
# Buscar tipos de pagamento
|
|
||||||
tipos_pagamento = db.query(TipoPagamento).all()
|
|
||||||
|
|
||||||
return render_template('home.html',
|
return render_template('home.html',
|
||||||
nome_usuario=nome_usuario,
|
nome_usuario=nome_usuario,
|
||||||
data_atual=data_atual,
|
data_atual=data_atual,
|
||||||
total_militantes=total_militantes,
|
total_militantes=total_militantes,
|
||||||
total_cotas="{:.2f}".format(total_cotas),
|
total_cotas=total_cotas,
|
||||||
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_pagamentos=ultimos_pagamentos,
|
||||||
tipos_pagamento=tipos_pagamento,
|
|
||||||
Militante=Militante)
|
Militante=Militante)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Erro na página inicial: {e}")
|
print(f"Erro na página inicial: {e}")
|
||||||
@@ -713,190 +722,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'))
|
|
||||||
|
|
||||||
material = MaterialVendido(
|
if not all([nome, descricao, preco, quantidade]):
|
||||||
militante_id=militante_id,
|
flash("Todos os campos são obrigatórios", "danger")
|
||||||
tipo_material_id=tipo_material_id,
|
return redirect(url_for("novo_material"))
|
||||||
descricao=descricao,
|
|
||||||
valor=valor,
|
|
||||||
data_venda=data_venda
|
|
||||||
)
|
|
||||||
|
|
||||||
|
db = get_db_connection()
|
||||||
|
material = MaterialVendido(nome=nome, valor=valor)
|
||||||
db.add(material)
|
db.add(material)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
flash("Material cadastrado com sucesso", "success")
|
||||||
return jsonify({
|
return redirect(url_for("listar_materiais"))
|
||||||
'status': 'success',
|
|
||||||
'message': 'Material cadastrado com sucesso!'
|
|
||||||
})
|
|
||||||
|
|
||||||
flash('Material cadastrado com sucesso!', 'success')
|
|
||||||
return redirect(url_for('listar_materiais'))
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.rollback()
|
flash(f"Erro ao cadastrar material: {str(e)}", "danger")
|
||||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
return redirect(url_for("novo_material"))
|
||||||
return jsonify({
|
return render_template("novo_material.html")
|
||||||
'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()
|
||||||
|
materiais = db.query(MaterialVendido).all()
|
||||||
|
return render_template("listar_materiais.html", materiais=materiais)
|
||||||
|
|
||||||
|
@app.route("/materiais/vendidos/novo", methods=["GET", "POST"])
|
||||||
|
@require_login
|
||||||
|
@require_permission(Permission.MANAGE_MATERIALS)
|
||||||
|
def novo_material_vendido():
|
||||||
|
if request.method == "POST":
|
||||||
try:
|
try:
|
||||||
materiais = db.query(MaterialVendido).join(Militante).join(TipoMaterial).all()
|
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()
|
militantes = db.query(Militante).all()
|
||||||
tipos_material = db.query(TipoMaterial).all()
|
materiais = db.query(MaterialVendido).all()
|
||||||
return render_template('listar_materiais.html',
|
return render_template("novo_material_vendido.html",
|
||||||
materiais=materiais,
|
|
||||||
militantes=militantes,
|
militantes=militantes,
|
||||||
tipos_material=tipos_material)
|
materiais=materiais)
|
||||||
finally:
|
|
||||||
db.close()
|
@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 = VendaJornalAvulso(
|
||||||
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({
|
|
||||||
'status': 'error',
|
|
||||||
'message': 'Erro ao cadastrar venda'
|
|
||||||
}), 400
|
|
||||||
flash('Erro ao cadastrar venda', 'danger')
|
|
||||||
return redirect(url_for('nova_venda_jornal'))
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
# Rota para listar vendas de jornal
|
db = get_db_connection()
|
||||||
@app.route("/jornais")
|
militantes = db.query(Militante).all()
|
||||||
|
return render_template("nova_venda_jornal.html", militantes=militantes)
|
||||||
|
|
||||||
|
@app.route("/vendas/jornal")
|
||||||
@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(VendaJornalAvulso).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()
|
|
||||||
flash('Relatório de cotas cadastrado com sucesso!', 'success')
|
|
||||||
return redirect(url_for('listar_relatorios_cotas'))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.rollback()
|
flash(f"Erro ao gerar relatório: {str(e)}", "danger")
|
||||||
app.logger.error(f"Erro ao cadastrar relatório de cotas: {e}")
|
return redirect(url_for("novo_relatorio_cotas"))
|
||||||
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")
|
return render_template("novo_relatorio_cotas.html")
|
||||||
|
|
||||||
db = get_db_connection()
|
@app.route("/relatorios/jornais/novo", methods=["GET", "POST"])
|
||||||
|
@require_login
|
||||||
|
@require_permission(Permission.MANAGE_REPORTS)
|
||||||
|
def novo_relatorio_jornais():
|
||||||
|
if request.method == "POST":
|
||||||
try:
|
try:
|
||||||
setores = db.query(Setor).order_by(Setor.nome).all()
|
data_inicio = request.form.get("data_inicio")
|
||||||
comites = db.query(ComiteCentral).order_by(ComiteCentral.nome).all()
|
data_fim = request.form.get("data_fim")
|
||||||
return render_template("novo_relatorio_cotas.html",
|
|
||||||
setores=setores,
|
if not all([data_inicio, data_fim]):
|
||||||
comites=comites,
|
flash("Todos os campos são obrigatórios", "danger")
|
||||||
hoje=date.today().strftime('%Y-%m-%d'))
|
return redirect(url_for("novo_relatorio_jornais"))
|
||||||
finally:
|
|
||||||
db.close()
|
db = get_db_connection()
|
||||||
|
jornais = db.query(VendaJornalAvulso).filter(
|
||||||
|
VendaJornalAvulso.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(AssinaturaAnual).filter(
|
||||||
|
AssinaturaAnual.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")
|
||||||
@@ -1223,401 +1282,164 @@ def create_app():
|
|||||||
db.close()
|
db.close()
|
||||||
return decorated_function(cr_id)
|
return decorated_function(cr_id)
|
||||||
|
|
||||||
@app.route('/celulas/<int:celula_id>/pagamentos')
|
@app.route('/celulas/<int:celula_id>/comprovantes')
|
||||||
@require_login
|
@require_login
|
||||||
def list_pagamentos_celula(celula_id):
|
def list_comprovantes_celula(celula_id):
|
||||||
@require_instance_access('celula', celula_id)
|
@require_instance_access('celula', celula_id)
|
||||||
def decorated_function(celula_id):
|
def decorated_function(celula_id):
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
try:
|
try:
|
||||||
pagamentos = db.query(Pagamento).filter_by(celula_id=celula_id).all()
|
comprovantes = db.query(Comprovante).filter_by(celula_id=celula_id).all()
|
||||||
return render_template('pagamentos/list.html', pagamentos=pagamentos)
|
return render_template('comprovantes/list.html', comprovantes=comprovantes)
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
return decorated_function(celula_id)
|
return decorated_function(celula_id)
|
||||||
|
|
||||||
@app.route('/setores/<int:setor_id>/pagamentos')
|
@app.route('/setores/<int:setor_id>/comprovantes')
|
||||||
@require_login
|
@require_login
|
||||||
def list_pagamentos_setor(setor_id):
|
def list_comprovantes_setor(setor_id):
|
||||||
@require_instance_access('setor', setor_id)
|
@require_instance_access('setor', setor_id)
|
||||||
def decorated_function(setor_id):
|
def decorated_function(setor_id):
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
try:
|
try:
|
||||||
pagamentos = db.query(Pagamento).join(Usuario).filter(Usuario.setor_id == setor_id).all()
|
comprovantes = db.query(Comprovante).join(Usuario).filter(Usuario.setor_id == setor_id).all()
|
||||||
return render_template('pagamentos/list.html', pagamentos=pagamentos)
|
return render_template('comprovantes/list.html', comprovantes=comprovantes)
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
return decorated_function(setor_id)
|
return decorated_function(setor_id)
|
||||||
|
|
||||||
@app.route('/crs/<int:cr_id>/pagamentos')
|
@app.route('/crs/<int:cr_id>/comprovantes')
|
||||||
@require_login
|
@require_login
|
||||||
def list_pagamentos_cr(cr_id):
|
def list_comprovantes_cr(cr_id):
|
||||||
@require_instance_access('cr', cr_id)
|
@require_instance_access('cr', cr_id)
|
||||||
def decorated_function(cr_id):
|
def decorated_function(cr_id):
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
try:
|
try:
|
||||||
pagamentos = db.query(Pagamento).join(Usuario).filter(Usuario.cr_id == cr_id).all()
|
comprovantes = db.query(Comprovante).join(Usuario).filter(Usuario.cr_id == cr_id).all()
|
||||||
return render_template('pagamentos/list.html', pagamentos=pagamentos)
|
return render_template('comprovantes/list.html', comprovantes=comprovantes)
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
return decorated_function(cr_id)
|
return decorated_function(cr_id)
|
||||||
|
|
||||||
@app.route('/celulas/<int:celula_id>/pagamentos/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_pagamento_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()
|
||||||
try:
|
try:
|
||||||
pagamento = Pagamento(
|
comprovante = Comprovante(
|
||||||
valor=request.form['valor'],
|
valor=request.form['valor'],
|
||||||
data=request.form['data'],
|
data=request.form['data'],
|
||||||
militante_id=request.form['militante_id'],
|
militante_id=request.form['militante_id'],
|
||||||
celula_id=celula_id
|
celula_id=celula_id
|
||||||
)
|
)
|
||||||
db.add(pagamento)
|
db.add(comprovante)
|
||||||
db.commit()
|
db.commit()
|
||||||
flash('Pagamento registrado com sucesso!', 'success')
|
flash('Comprovante registrado com sucesso!', 'success')
|
||||||
return redirect(url_for('list_pagamentos_celula', celula_id=celula_id))
|
return redirect(url_for('list_comprovantes_celula', celula_id=celula_id))
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
return render_template('pagamentos/form.html')
|
return render_template('comprovantes/form.html')
|
||||||
|
|
||||||
@app.route('/setores/<int:setor_id>/pagamentos/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_pagamento_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()
|
||||||
try:
|
try:
|
||||||
pagamento = Pagamento(
|
comprovante = Comprovante(
|
||||||
valor=request.form['valor'],
|
valor=request.form['valor'],
|
||||||
data=request.form['data'],
|
data=request.form['data'],
|
||||||
militante_id=request.form['militante_id'],
|
militante_id=request.form['militante_id'],
|
||||||
setor_id=setor_id
|
setor_id=setor_id
|
||||||
)
|
)
|
||||||
db.add(pagamento)
|
db.add(comprovante)
|
||||||
db.commit()
|
db.commit()
|
||||||
flash('Pagamento registrado com sucesso!', 'success')
|
flash('Comprovante registrado com sucesso!', 'success')
|
||||||
return redirect(url_for('list_pagamentos_setor', setor_id=setor_id))
|
return redirect(url_for('list_comprovantes_setor', setor_id=setor_id))
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
return render_template('pagamentos/form.html')
|
return render_template('comprovantes/form.html')
|
||||||
|
|
||||||
@app.route('/crs/<int:cr_id>/pagamentos/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_pagamento_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()
|
||||||
try:
|
try:
|
||||||
pagamento = Pagamento(
|
comprovante = Comprovante(
|
||||||
valor=request.form['valor'],
|
valor=request.form['valor'],
|
||||||
data=request.form['data'],
|
data=request.form['data'],
|
||||||
militante_id=request.form['militante_id'],
|
militante_id=request.form['militante_id'],
|
||||||
cr_id=cr_id
|
cr_id=cr_id
|
||||||
)
|
)
|
||||||
db.add(pagamento)
|
db.add(comprovante)
|
||||||
db.commit()
|
db.commit()
|
||||||
flash('Pagamento registrado com sucesso!', 'success')
|
flash('Comprovante registrado com sucesso!', 'success')
|
||||||
return redirect(url_for('list_pagamentos_cr', cr_id=cr_id))
|
return redirect(url_for('list_comprovantes_cr', cr_id=cr_id))
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
return render_template('pagamentos/form.html')
|
return render_template('comprovantes/form.html')
|
||||||
|
|
||||||
@app.route("/alterar_senha", methods=["GET", "POST"])
|
@app.route('/dashboard')
|
||||||
@require_login
|
|
||||||
def alterar_senha():
|
|
||||||
"""Rota para alterar a senha do usuário"""
|
|
||||||
if request.method == "POST":
|
|
||||||
senha_atual = request.form.get("senha_atual")
|
|
||||||
nova_senha = request.form.get("nova_senha")
|
|
||||||
confirmar_senha = request.form.get("confirmar_senha")
|
|
||||||
|
|
||||||
if not all([senha_atual, nova_senha, confirmar_senha]):
|
|
||||||
flash("Todos os campos são obrigatórios.", "error")
|
|
||||||
return redirect(url_for("alterar_senha"))
|
|
||||||
|
|
||||||
if nova_senha != confirmar_senha:
|
|
||||||
flash("As senhas não coincidem.", "error")
|
|
||||||
return redirect(url_for("alterar_senha"))
|
|
||||||
|
|
||||||
db = get_db_connection()
|
|
||||||
try:
|
|
||||||
user = db.query(Usuario).get(current_user.id)
|
|
||||||
if not user.check_password(senha_atual):
|
|
||||||
flash("Senha atual incorreta.", "error")
|
|
||||||
return redirect(url_for("alterar_senha"))
|
|
||||||
|
|
||||||
user.password_hash = generate_password_hash(nova_senha)
|
|
||||||
db.commit()
|
|
||||||
flash("Senha alterada com sucesso!", "success")
|
|
||||||
return redirect(url_for("home"))
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
return render_template("alterar_senha.html")
|
|
||||||
|
|
||||||
@app.route('/usuarios/<int:user_id>/toggle_status', methods=['POST'])
|
|
||||||
@require_login
|
|
||||||
def toggle_user_status(user_id):
|
|
||||||
user = db_session.query(Usuario).get_or_404(user_id)
|
|
||||||
|
|
||||||
# Verificar permissões baseado na hierarquia
|
|
||||||
if not current_user.has_permission('system_config'):
|
|
||||||
if current_user.has_permission('manage_cr_sectors'):
|
|
||||||
# Secretário de CR só pode gerenciar membros do seu CR
|
|
||||||
if user.cr_id != current_user.cr_id:
|
|
||||||
flash('Você não tem permissão para gerenciar este usuário.', 'danger')
|
|
||||||
return redirect(url_for('dashboard_admin'))
|
|
||||||
elif current_user.has_permission('manage_sector_cells'):
|
|
||||||
# Secretário de Setor só pode gerenciar membros do seu setor
|
|
||||||
if user.setor_id != current_user.setor_id:
|
|
||||||
flash('Você não tem permissão para gerenciar este usuário.', 'danger')
|
|
||||||
return redirect(url_for('dashboard_admin'))
|
|
||||||
elif current_user.has_permission('manage_cell_members'):
|
|
||||||
# Secretário de Célula só pode gerenciar membros da sua célula
|
|
||||||
if user.celula_id != current_user.celula_id:
|
|
||||||
flash('Você não tem permissão para gerenciar este usuário.', 'danger')
|
|
||||||
return redirect(url_for('dashboard_admin'))
|
|
||||||
else:
|
|
||||||
# Militante básico não pode gerenciar ninguém
|
|
||||||
flash('Você não tem permissão para gerenciar usuários.', 'danger')
|
|
||||||
return redirect(url_for('dashboard_admin'))
|
|
||||||
|
|
||||||
user.ativo = not user.ativo
|
|
||||||
db_session.commit()
|
|
||||||
|
|
||||||
return jsonify({'success': True, 'message': 'Status do usuário alterado com sucesso!'})
|
|
||||||
|
|
||||||
@app.route('/usuarios/<int:user_id>/alterar_nivel', methods=['POST'])
|
|
||||||
@require_login
|
|
||||||
def alterar_nivel(user_id):
|
|
||||||
user = db_session.query(Usuario).get_or_404(user_id)
|
|
||||||
novo_nivel = request.json.get('nivel')
|
|
||||||
|
|
||||||
# Verificar permissões baseado na hierarquia
|
|
||||||
if not current_user.has_permission('system_config'):
|
|
||||||
if current_user.has_permission('manage_cr_sectors'):
|
|
||||||
# Secretário de CR só pode alterar níveis dentro do seu CR
|
|
||||||
if user.cr_id != current_user.cr_id:
|
|
||||||
return jsonify({'success': False, 'message': 'Você não tem permissão para alterar o nível deste usuário.'})
|
|
||||||
elif current_user.has_permission('manage_sector_cells'):
|
|
||||||
# Secretário de Setor só pode alterar níveis dentro do seu setor
|
|
||||||
if user.setor_id != current_user.setor_id:
|
|
||||||
return jsonify({'success': False, 'message': 'Você não tem permissão para alterar o nível deste usuário.'})
|
|
||||||
else:
|
|
||||||
# Outros níveis não podem alterar níveis
|
|
||||||
return jsonify({'success': False, 'message': 'Você não tem permissão para alterar níveis de usuários.'})
|
|
||||||
|
|
||||||
# Verificar se o novo nível é válido para o nível hierárquico do usuário atual
|
|
||||||
if current_user.has_permission('system_config'):
|
|
||||||
# Secretário Geral e Secretário de Organização podem alterar para qualquer nível
|
|
||||||
pass
|
|
||||||
elif current_user.has_permission('manage_cr_sectors'):
|
|
||||||
# Secretário de CR só pode alterar para níveis do CR
|
|
||||||
if novo_nivel not in ['membro_cr', 'secretario_cr']:
|
|
||||||
return jsonify({'success': False, 'message': 'Nível inválido para este CR.'})
|
|
||||||
elif current_user.has_permission('manage_sector_cells'):
|
|
||||||
# Secretário de Setor só pode alterar para níveis do setor
|
|
||||||
if novo_nivel not in ['membro_setor', 'secretario_setor']:
|
|
||||||
return jsonify({'success': False, 'message': 'Nível inválido para este setor.'})
|
|
||||||
|
|
||||||
# Atualizar o nível do usuário
|
|
||||||
user.role = novo_nivel
|
|
||||||
db_session.commit()
|
|
||||||
|
|
||||||
return jsonify({'success': True, 'message': 'Nível do usuário alterado com sucesso!'})
|
|
||||||
|
|
||||||
@app.route('/usuarios/<int:user_id>/toggle_quadro_orientador', methods=['POST'])
|
|
||||||
@require_login
|
|
||||||
def toggle_quadro_orientador(user_id):
|
|
||||||
db = get_db_connection()
|
|
||||||
try:
|
|
||||||
user = db.query(Usuario).get(user_id)
|
|
||||||
if not user:
|
|
||||||
return jsonify({'success': False, 'message': 'Usuário não encontrado'}), 404
|
|
||||||
|
|
||||||
# Verificar permissões
|
|
||||||
if not (current_user.has_permission('system_config') or
|
|
||||||
(current_user.has_permission('manage_cr_sectors') and user.cr_id == current_user.cr_id) or
|
|
||||||
(current_user.has_permission('manage_sector_cells') and user.setor_id == current_user.setor_id)):
|
|
||||||
return jsonify({'success': False, 'message': 'Você não tem permissão para alterar esta responsabilidade'}), 403
|
|
||||||
|
|
||||||
# Verificar se o usuário tem um militante associado
|
|
||||||
if not user.militante:
|
|
||||||
return jsonify({'success': False, 'message': 'Usuário não tem um militante associado'}), 400
|
|
||||||
|
|
||||||
# Alternar o status de Quadro-Orientador
|
|
||||||
user.militante.quadro_orientador = not user.militante.quadro_orientador
|
|
||||||
|
|
||||||
# Atualizar a responsabilidade no campo responsabilidades
|
|
||||||
if user.militante.quadro_orientador:
|
|
||||||
user.militante.responsabilidades |= Militante.QUADRO_ORIENTADOR
|
|
||||||
else:
|
|
||||||
user.militante.responsabilidades &= ~Militante.QUADRO_ORIENTADOR
|
|
||||||
|
|
||||||
db.commit()
|
|
||||||
return jsonify({
|
|
||||||
'success': True,
|
|
||||||
'message': f'Responsabilidade de Quadro-Orientador {"adicionada" if user.militante.quadro_orientador else "removida"} com sucesso'
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
db.rollback()
|
|
||||||
return jsonify({'success': False, 'message': str(e)}), 500
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
@app.route("/cotas/excluir/<int:id>", methods=["POST"])
|
|
||||||
@login_required
|
@login_required
|
||||||
@session_timeout
|
def dashboard():
|
||||||
def excluir_cota(id):
|
"""Rota para a página inicial do dashboard"""
|
||||||
"""Exclui uma cota mensal"""
|
|
||||||
db = get_db_connection()
|
|
||||||
try:
|
try:
|
||||||
cota = db.query(CotaMensal).get(id)
|
session = get_db_connection()
|
||||||
if not cota:
|
# Buscar dados recentes
|
||||||
flash('Cota não encontrada.', 'danger')
|
comprovantes_recentes = session.query(Comprovante).order_by(Comprovante.data_comprovante.desc()).limit(5).all()
|
||||||
return redirect(url_for('listar_cotas'))
|
tipos_comprovante = session.query(TipoComprovante).all()
|
||||||
|
|
||||||
# Excluir a cota
|
return render_template(
|
||||||
db.delete(cota)
|
'dashboard.html',
|
||||||
db.commit()
|
comprovantes_recentes=comprovantes_recentes,
|
||||||
flash('Cota excluída com sucesso!', 'success')
|
tipos_comprovante=tipos_comprovante
|
||||||
except Exception as e:
|
|
||||||
db.rollback()
|
|
||||||
flash('Erro ao excluir cota. Por favor, tente novamente.', 'danger')
|
|
||||||
print(f"Erro ao excluir cota: {e}")
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
return redirect(url_for('listar_cotas'))
|
|
||||||
|
|
||||||
@app.route("/assinaturas")
|
|
||||||
@require_login
|
|
||||||
@require_permission(Permission.MANAGE_CELL_REPORTS)
|
|
||||||
def listar_assinaturas():
|
|
||||||
db = get_db_connection()
|
|
||||||
try:
|
|
||||||
assinaturas = db.query(AssinaturaAnual).join(Militante).all()
|
|
||||||
militantes = db.query(Militante).all()
|
|
||||||
return render_template('listar_assinaturas.html',
|
|
||||||
assinaturas=assinaturas,
|
|
||||||
militantes=militantes)
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
@app.route("/assinaturas/novo", methods=['POST'])
|
|
||||||
@require_login
|
|
||||||
@require_permission(Permission.MANAGE_CELL_REPORTS)
|
|
||||||
def nova_assinatura():
|
|
||||||
db = get_db_connection()
|
|
||||||
try:
|
|
||||||
data = request.form
|
|
||||||
|
|
||||||
# Validar dados
|
|
||||||
if not all(k in data for k in ['militante_id', 'data_inicio', 'data_fim', 'valor']):
|
|
||||||
return jsonify({'success': False, 'message': 'Todos os campos são obrigatórios'})
|
|
||||||
|
|
||||||
# Criar nova assinatura
|
|
||||||
assinatura = AssinaturaAnual(
|
|
||||||
militante_id=data['militante_id'],
|
|
||||||
data_inicio=datetime.strptime(data['data_inicio'], '%Y-%m-%d'),
|
|
||||||
data_fim=datetime.strptime(data['data_fim'], '%Y-%m-%d'),
|
|
||||||
valor=float(data['valor'])
|
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
flash(f'Erro ao carregar dashboard: {str(e)}', 'error')
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|
||||||
db.add(assinatura)
|
@app.route('/comprovantes')
|
||||||
db.commit()
|
@login_required
|
||||||
|
@require_permission(Permission.MANAGE_MATERIALS)
|
||||||
|
def listar_comprovantes():
|
||||||
|
try:
|
||||||
|
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:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
@app.route('/comprovantes/<int:id>', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
@require_permission(Permission.MANAGE_MATERIALS)
|
||||||
|
def excluir_comprovante(id):
|
||||||
|
try:
|
||||||
|
comprovante = Comprovante.query.get_or_404(id)
|
||||||
|
db.session.delete(comprovante)
|
||||||
|
db.session.commit()
|
||||||
return jsonify({'success': True})
|
return jsonify({'success': True})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.rollback()
|
db.session.rollback()
|
||||||
return jsonify({'success': False, 'message': str(e)})
|
return jsonify({'success': False, 'message': str(e)})
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
@app.route("/assinaturas/excluir/<int:id>", methods=['POST'])
|
|
||||||
@require_login
|
|
||||||
@require_permission(Permission.MANAGE_CELL_REPORTS)
|
|
||||||
def excluir_assinatura(id):
|
|
||||||
db = get_db_connection()
|
|
||||||
try:
|
|
||||||
assinatura = db.query(AssinaturaAnual).get(id)
|
|
||||||
if not assinatura:
|
|
||||||
return jsonify({'success': False, 'message': 'Assinatura não encontrada'})
|
|
||||||
|
|
||||||
db.delete(assinatura)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
return jsonify({'success': True})
|
|
||||||
except Exception as e:
|
|
||||||
db.rollback()
|
|
||||||
return jsonify({'success': False, 'message': str(e)})
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
@app.route("/militantes/dados/<int:militante_id>")
|
|
||||||
@require_login
|
|
||||||
@require_permission('gerenciar_militantes')
|
|
||||||
def buscar_dados_militante(militante_id):
|
|
||||||
"""Busca os dados de um militante específico"""
|
|
||||||
db = get_db_connection()
|
|
||||||
try:
|
|
||||||
militante = db.query(Militante).get(militante_id)
|
|
||||||
if not militante:
|
|
||||||
print(f"Militante não encontrado: ID {militante_id}")
|
|
||||||
return jsonify({
|
|
||||||
'status': 'error',
|
|
||||||
'message': 'Militante não encontrado'
|
|
||||||
}), 404
|
|
||||||
|
|
||||||
# Função auxiliar para formatar data com validação
|
|
||||||
def formatar_data_segura(data):
|
|
||||||
try:
|
|
||||||
if not data:
|
|
||||||
return None
|
|
||||||
return data.strftime('%Y-%m-%d')
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Erro ao formatar data: {str(e)}, valor: {data}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Formatar datas com validação
|
|
||||||
data_nascimento = formatar_data_segura(militante.data_nascimento)
|
|
||||||
data_entrada_oci = formatar_data_segura(militante.data_entrada_oci)
|
|
||||||
data_efetivacao_oci = formatar_data_segura(militante.data_efetivacao_oci)
|
|
||||||
|
|
||||||
print(f"Dados do militante {militante_id} recuperados com sucesso")
|
|
||||||
print(f"Datas formatadas: nascimento={data_nascimento}, entrada={data_entrada_oci}, efetivação={data_efetivacao_oci}")
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'status': 'success',
|
|
||||||
'id': militante.id,
|
|
||||||
'nome': militante.nome,
|
|
||||||
'cpf': militante.cpf,
|
|
||||||
'titulo_eleitoral': militante.titulo_eleitoral,
|
|
||||||
'data_nascimento': data_nascimento,
|
|
||||||
'data_entrada_oci': data_entrada_oci,
|
|
||||||
'data_efetivacao_oci': data_efetivacao_oci,
|
|
||||||
'emails': [email.endereco_email for email in militante.emails] if militante.emails else [],
|
|
||||||
'telefone1': militante.telefone1,
|
|
||||||
'telefone2': militante.telefone2,
|
|
||||||
'celula_id': militante.celula_id,
|
|
||||||
'responsabilidades_valor': militante.responsabilidades,
|
|
||||||
'sindicato': militante.sindicato,
|
|
||||||
'cargo_sindical': militante.cargo_sindical,
|
|
||||||
'central_sindical': militante.central_sindical,
|
|
||||||
'dirigente_sindical': militante.dirigente_sindical
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
import traceback
|
|
||||||
print(f"Erro ao buscar dados do militante {militante_id}:")
|
|
||||||
print(traceback.format_exc())
|
|
||||||
return jsonify({
|
|
||||||
'status': 'error',
|
|
||||||
'message': f'Erro ao buscar dados do militante: {str(e)}'
|
|
||||||
}), 500
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
@@ -1687,6 +1509,10 @@ def main():
|
|||||||
# Inicializar o sistema
|
# Inicializar o sistema
|
||||||
init_system()
|
init_system()
|
||||||
|
|
||||||
|
# Configurar modo debug
|
||||||
|
app.debug = True
|
||||||
|
app.config['DEBUG'] = True
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
# Criar a aplicação usando a função main
|
# Criar a aplicação usando a função main
|
||||||
@@ -1695,6 +1521,6 @@ app = main()
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(
|
app.run(
|
||||||
host='0.0.0.0',
|
host='0.0.0.0',
|
||||||
port=5000,
|
port=int(os.getenv('FLASK_PORT', 5000)),
|
||||||
debug=os.getenv('FLASK_ENV') == 'development'
|
debug=True
|
||||||
)
|
)
|
||||||
|
|||||||
103
create_admin.py
103
create_admin.py
@@ -41,102 +41,37 @@ def generate_qr_code(user):
|
|||||||
return qr_path, otp_uri
|
return qr_path, otp_uri
|
||||||
|
|
||||||
def create_admin_user():
|
def create_admin_user():
|
||||||
"""Cria ou atualiza o usuário admin"""
|
"""Cria o usuário admin do sistema"""
|
||||||
|
session = get_db_connection()
|
||||||
try:
|
try:
|
||||||
# Inicializar banco de dados
|
# Buscar role de administrador
|
||||||
init_database()
|
admin_role = session.query(Role).filter_by(nome="Administrador").first()
|
||||||
|
if not admin_role:
|
||||||
|
print("Role de administrador não encontrada!")
|
||||||
|
return
|
||||||
|
|
||||||
# Criar sessão
|
# Verificar se o usuário admin já existe
|
||||||
db = get_db_connection()
|
if not session.query(Usuario).filter_by(username="admin").first():
|
||||||
|
|
||||||
try:
|
|
||||||
# Verificar se já existe um usuário admin
|
|
||||||
admin = db.query(Usuario).filter_by(username="admin").first()
|
|
||||||
|
|
||||||
if admin:
|
|
||||||
print("\n=== Usuário Admin Encontrado ===")
|
|
||||||
if not admin.otp_secret:
|
|
||||||
print("Gerando novo segredo OTP...")
|
|
||||||
admin.generate_otp_secret()
|
|
||||||
db.commit()
|
|
||||||
else:
|
|
||||||
print("\n=== Criando Novo Usuário Admin ===")
|
|
||||||
# Criar novo usuário admin
|
|
||||||
admin = Usuario(
|
admin = Usuario(
|
||||||
username="admin",
|
username="admin",
|
||||||
email="admin@example.com",
|
email="admin@example.com",
|
||||||
is_admin=True
|
is_admin=True
|
||||||
)
|
)
|
||||||
admin.set_password("admin123")
|
admin.set_password("admin123")
|
||||||
admin.generate_otp_secret()
|
admin.tipo = "ADMIN"
|
||||||
|
admin.roles.append(admin_role)
|
||||||
# Adicionar e fazer commit
|
session.add(admin)
|
||||||
db.add(admin)
|
session.commit()
|
||||||
db.commit()
|
print("Usuário admin criado com sucesso!")
|
||||||
|
|
||||||
# Gerar QR code apenas se solicitado ou se for novo usuário
|
|
||||||
if not os.path.exists('admin_qr.png'):
|
|
||||||
qr_path, otp_uri = generate_qr_code(admin)
|
|
||||||
print("\n=== QR Code Gerado ===")
|
|
||||||
print(f"QR Code salvo em: {qr_path}")
|
|
||||||
print(f"URI do OTP: {otp_uri}")
|
|
||||||
else:
|
else:
|
||||||
print("\n=== QR Code Existente ===")
|
print("Usuário admin já existe!")
|
||||||
print("Usando QR Code existente em: admin_qr.png")
|
|
||||||
qr_path = 'admin_qr.png'
|
|
||||||
|
|
||||||
# Mostrar informações
|
|
||||||
print("\n=== Informações do Admin ===")
|
|
||||||
print(f"Username: {admin.username}")
|
|
||||||
print(f"Email: {admin.email}")
|
|
||||||
print(f"Senha: admin123")
|
|
||||||
print(f"Segredo OTP: {admin.otp_secret}")
|
|
||||||
|
|
||||||
# Gerar código atual para verificação
|
|
||||||
totp = pyotp.TOTP(admin.otp_secret)
|
|
||||||
current_code = totp.now()
|
|
||||||
print("\n=== Verificação do OTP ===")
|
|
||||||
print(f"Código OTP atual: {current_code}")
|
|
||||||
print(f"Verificação do código: {totp.verify(current_code)}")
|
|
||||||
|
|
||||||
print("\n=== Instruções para Configuração ===")
|
|
||||||
print("1. Instale um aplicativo autenticador no seu celular")
|
|
||||||
print(" (Google Authenticator, Microsoft Authenticator, etc)")
|
|
||||||
print("2. Abra o aplicativo")
|
|
||||||
print("3. Selecione a opção para adicionar uma nova conta")
|
|
||||||
print("4. Escaneie o QR Code salvo em:", qr_path)
|
|
||||||
print("\nOU configure manualmente:")
|
|
||||||
print(f"- Nome da conta: {admin.username}")
|
|
||||||
print(f"- Segredo: {admin.otp_secret}")
|
|
||||||
print("- Tipo: Baseado em tempo (TOTP)")
|
|
||||||
print("- Algoritmo: SHA1")
|
|
||||||
print("- Dígitos: 6")
|
|
||||||
print("- Intervalo: 30 segundos")
|
|
||||||
|
|
||||||
# Verificação final
|
|
||||||
print("\n=== Teste de Verificação ===")
|
|
||||||
test_code = totp.now()
|
|
||||||
print(f"Código de teste: {test_code}")
|
|
||||||
is_valid = admin.verify_otp(test_code)
|
|
||||||
print(f"Verificação do código: {'Sucesso' if is_valid else 'Falha'}")
|
|
||||||
|
|
||||||
if not is_valid:
|
|
||||||
print("\nALERTA: Verificação do OTP falhou!")
|
|
||||||
print("Por favor, verifique se o segredo OTP está correto.")
|
|
||||||
|
|
||||||
# Fazer commit final para garantir que tudo foi salvo
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.rollback()
|
print(f"Erro ao criar usuário admin: {e}")
|
||||||
raise e
|
session.rollback()
|
||||||
|
raise
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
session.close()
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\nErro durante a execução: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
create_admin_user()
|
create_admin_user()
|
||||||
|
|||||||
65
create_test_users.py
Normal file
65
create_test_users.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
from functions.database import Usuario, Role, get_db_connection
|
||||||
|
|
||||||
|
def create_test_users():
|
||||||
|
"""Cria usuários de teste para o sistema"""
|
||||||
|
session = get_db_connection()
|
||||||
|
try:
|
||||||
|
# Buscar roles
|
||||||
|
secretario_celula = session.query(Role).filter_by(nivel=Role.SECRETARIO_CELULA).first()
|
||||||
|
secretario_setor = session.query(Role).filter_by(nivel=Role.SECRETARIO_SETOR).first()
|
||||||
|
secretario_cr = session.query(Role).filter_by(nivel=Role.SECRETARIO_CR).first()
|
||||||
|
secretario_geral = session.query(Role).filter_by(nivel=Role.SECRETARIO_GERAL).first()
|
||||||
|
|
||||||
|
# Criar usuários de teste
|
||||||
|
usuarios = [
|
||||||
|
{
|
||||||
|
'username': 'celula',
|
||||||
|
'email': 'celula@example.com',
|
||||||
|
'password': 'celula123',
|
||||||
|
'role': secretario_celula,
|
||||||
|
'tipo': 'SECRETARIO_CELULA'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'username': 'setor',
|
||||||
|
'email': 'setor@example.com',
|
||||||
|
'password': 'setor123',
|
||||||
|
'role': secretario_setor,
|
||||||
|
'tipo': 'SECRETARIO_SETOR'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'username': 'cr',
|
||||||
|
'email': 'cr@example.com',
|
||||||
|
'password': 'cr123',
|
||||||
|
'role': secretario_cr,
|
||||||
|
'tipo': 'SECRETARIO_CR'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'username': 'geral',
|
||||||
|
'email': 'geral@example.com',
|
||||||
|
'password': 'geral123',
|
||||||
|
'role': secretario_geral,
|
||||||
|
'tipo': 'SECRETARIO_GERAL'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for user_data in usuarios:
|
||||||
|
# Verificar se o usuário já existe
|
||||||
|
if not session.query(Usuario).filter_by(username=user_data['username']).first():
|
||||||
|
user = Usuario(
|
||||||
|
username=user_data['username'],
|
||||||
|
email=user_data['email']
|
||||||
|
)
|
||||||
|
user.set_password(user_data['password'])
|
||||||
|
user.tipo = user_data['tipo']
|
||||||
|
user.roles.append(user_data['role'])
|
||||||
|
session.add(user)
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
print("Usuários de teste criados com sucesso!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao criar usuários de teste: {e}")
|
||||||
|
session.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
26
docs/rbac.md
26
docs/rbac.md
@@ -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
|
||||||
|
|
||||||
|
|||||||
94
docs/regras_comprovantes.md
Normal file
94
docs/regras_comprovantes.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Regras de Negócio - Comprovantes
|
||||||
|
|
||||||
|
## 1. Estrutura do Comprovante
|
||||||
|
|
||||||
|
### 1.1 Dados Básicos
|
||||||
|
- Todo comprovante deve ter:
|
||||||
|
- Militante associado (obrigatório)
|
||||||
|
- Data do comprovante (obrigatório)
|
||||||
|
- Forma de pagamento (obrigatório)
|
||||||
|
- Campanha financeira (opcional)
|
||||||
|
|
||||||
|
### 1.2 Formas de Pagamento
|
||||||
|
- As formas de pagamento aceitas são:
|
||||||
|
- PIX
|
||||||
|
- Transferência/DOC
|
||||||
|
- Depósito
|
||||||
|
- Maquininha
|
||||||
|
|
||||||
|
## 2. Centralizações
|
||||||
|
|
||||||
|
### 2.1 Tipos de Centralização
|
||||||
|
- Cada comprovante pode ter uma ou mais centralizações
|
||||||
|
- Os tipos de centralização são:
|
||||||
|
- Cota
|
||||||
|
- Jornal
|
||||||
|
- Assinatura
|
||||||
|
|
||||||
|
### 2.2 Valores
|
||||||
|
- Cada centralização deve ter:
|
||||||
|
- Tipo (obrigatório)
|
||||||
|
- Valor (obrigatório, maior que zero)
|
||||||
|
|
||||||
|
## 3. Transações PIX
|
||||||
|
|
||||||
|
### 3.1 Dados da Transação
|
||||||
|
- Para pagamentos via PIX, o comprovante deve incluir:
|
||||||
|
- Chave PIX
|
||||||
|
- Valor
|
||||||
|
- Data de geração
|
||||||
|
- Data de pagamento
|
||||||
|
- Status (Pendente, Pago, Expirado)
|
||||||
|
- QR Code (quando aplicável)
|
||||||
|
|
||||||
|
## 4. Validações
|
||||||
|
|
||||||
|
### 4.1 Obrigatoriedades
|
||||||
|
- Um comprovante deve ter pelo menos uma centralização
|
||||||
|
- O valor total do comprovante deve ser igual à soma das centralizações
|
||||||
|
- A data do comprovante não pode ser futura
|
||||||
|
|
||||||
|
### 4.2 Restrições
|
||||||
|
- Não é permitido excluir comprovantes com centralizações já registradas
|
||||||
|
- Não é permitido alterar valores de centralizações após confirmação
|
||||||
|
- O militante associado deve estar ativo no sistema
|
||||||
|
|
||||||
|
## 5. Permissões
|
||||||
|
|
||||||
|
### 5.1 Acesso
|
||||||
|
- Apenas usuários com permissão `MANAGE_MATERIALS` podem:
|
||||||
|
- Criar comprovantes
|
||||||
|
- Editar comprovantes
|
||||||
|
- Excluir comprovantes
|
||||||
|
- Visualizar lista de comprovantes
|
||||||
|
|
||||||
|
### 5.2 Restrições
|
||||||
|
- Usuários só podem editar comprovantes de sua própria célula/setor/CR
|
||||||
|
- Apenas administradores podem editar comprovantes de qualquer nível
|
||||||
|
|
||||||
|
## 6. Relacionamentos
|
||||||
|
|
||||||
|
### 6.1 Militante
|
||||||
|
- Todo comprovante deve estar associado a um militante
|
||||||
|
- O militante deve estar ativo no sistema
|
||||||
|
- O militante deve pertencer a uma célula/setor/CR válido
|
||||||
|
|
||||||
|
### 6.2 Campanha Financeira
|
||||||
|
- O comprovante pode estar associado a uma campanha financeira
|
||||||
|
- A campanha deve estar ativa no período do comprovante
|
||||||
|
- O valor do comprovante é contabilizado no total da campanha
|
||||||
|
|
||||||
|
## 7. Histórico
|
||||||
|
|
||||||
|
### 7.1 Registro
|
||||||
|
- Todas as alterações em comprovantes devem ser registradas
|
||||||
|
- O sistema mantém histórico de:
|
||||||
|
- Data de criação
|
||||||
|
- Usuário que criou
|
||||||
|
- Data de alteração
|
||||||
|
- Usuário que alterou
|
||||||
|
|
||||||
|
### 7.2 Auditoria
|
||||||
|
- Os comprovantes são auditáveis
|
||||||
|
- O sistema mantém logs de todas as operações
|
||||||
|
- As alterações podem ser rastreadas por usuário e data
|
||||||
@@ -14,6 +14,9 @@ from flask_login import UserMixin
|
|||||||
from .rbac import Role, Permission, role_permissions, user_roles
|
from .rbac import Role, Permission, role_permissions, user_roles
|
||||||
from .base import Base, engine, Session
|
from .base import Base, engine, Session
|
||||||
import logging
|
import logging
|
||||||
|
import qrcode
|
||||||
|
from PIL import Image
|
||||||
|
import re
|
||||||
|
|
||||||
# Configurar caminho do banco de dados
|
# Configurar caminho do banco de dados
|
||||||
db_dir = Path.home() / '.local' / 'share' / 'controles'
|
db_dir = Path.home() / '.local' / 'share' / 'controles'
|
||||||
@@ -190,6 +193,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
|
||||||
@@ -328,7 +332,6 @@ class Pagamento(Base):
|
|||||||
data_pagamento = Column(Date, nullable=False)
|
data_pagamento = Column(Date, nullable=False)
|
||||||
|
|
||||||
militante = relationship("Militante", back_populates="pagamentos")
|
militante = relationship("Militante", back_populates="pagamentos")
|
||||||
transacoes_pix = relationship("TransacaoPIX", back_populates="pagamento")
|
|
||||||
|
|
||||||
class TipoMaterial(Base):
|
class TipoMaterial(Base):
|
||||||
__tablename__ = 'tipos_materiais'
|
__tablename__ = 'tipos_materiais'
|
||||||
@@ -607,6 +610,49 @@ class Relatorio(Base):
|
|||||||
setor = relationship("Setor", foreign_keys=[setor_id])
|
setor = relationship("Setor", foreign_keys=[setor_id])
|
||||||
cr = relationship("ComiteRegional", foreign_keys=[cr_id])
|
cr = relationship("ComiteRegional", foreign_keys=[cr_id])
|
||||||
|
|
||||||
|
class CampanhaFinanceira(Base):
|
||||||
|
__tablename__ = 'campanhas_financeiras'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
nome = Column(String(100), nullable=False)
|
||||||
|
descricao = Column(Text)
|
||||||
|
data_inicio = Column(Date, nullable=False)
|
||||||
|
data_fim = Column(Date, nullable=False)
|
||||||
|
meta = Column(Numeric(10, 2), nullable=False)
|
||||||
|
valor_arrecadado = Column(Numeric(10, 2), default=0)
|
||||||
|
status = Column(String(20), default='Em andamento') # Em andamento, Concluída, Cancelada
|
||||||
|
|
||||||
|
comprovantes = relationship("Comprovante", back_populates="campanha")
|
||||||
|
|
||||||
|
class TipoComprovante(Base):
|
||||||
|
__tablename__ = 'tipos_comprovante'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
descricao = Column(String(50), nullable=False)
|
||||||
|
valor = Column(Numeric(10, 2), nullable=False)
|
||||||
|
|
||||||
|
class CentralizacaoComprovante(Base):
|
||||||
|
__tablename__ = 'centralizacoes_comprovante'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
comprovante_id = Column(Integer, ForeignKey('comprovantes.id'), nullable=False)
|
||||||
|
tipo_comprovante = Column(String(50), nullable=False) # Cota, Jornal, Assinatura, etc.
|
||||||
|
valor = Column(Numeric(10, 2), nullable=False)
|
||||||
|
|
||||||
|
comprovante = relationship("Comprovante", back_populates="centralizacoes")
|
||||||
|
|
||||||
|
class Comprovante(Base):
|
||||||
|
__tablename__ = 'comprovantes'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
militante_id = Column(Integer, ForeignKey('militantes.id'), nullable=False)
|
||||||
|
data_comprovante = Column(Date, nullable=False)
|
||||||
|
forma_pagamento = Column(String(20), nullable=False) # PIX, transferência/DOC, depósito, maquininha
|
||||||
|
campanha_id = Column(Integer, ForeignKey('campanhas_financeiras.id'))
|
||||||
|
|
||||||
|
militante = relationship("Militante", back_populates="comprovantes")
|
||||||
|
transacoes_pix = relationship("TransacaoPIX", back_populates="comprovante")
|
||||||
|
campanha = relationship("CampanhaFinanceira", back_populates="comprovantes")
|
||||||
|
centralizacoes = relationship("CentralizacaoComprovante", back_populates="comprovante", cascade="all, delete-orphan")
|
||||||
|
|
||||||
class TransacaoPIX(Base):
|
class TransacaoPIX(Base):
|
||||||
__tablename__ = 'transacoes_pix'
|
__tablename__ = 'transacoes_pix'
|
||||||
|
|
||||||
@@ -617,9 +663,9 @@ class TransacaoPIX(Base):
|
|||||||
data_pagamento = Column(DateTime)
|
data_pagamento = Column(DateTime)
|
||||||
status = Column(String(20)) # Pendente, Pago, Expirado
|
status = Column(String(20)) # Pendente, Pago, Expirado
|
||||||
qr_code = Column(Text)
|
qr_code = Column(Text)
|
||||||
pagamento_id = Column(Integer, ForeignKey('pagamentos.id'))
|
comprovante_id = Column(Integer, ForeignKey('comprovantes.id'))
|
||||||
|
|
||||||
pagamento = relationship("Pagamento", back_populates="transacoes_pix")
|
comprovante = relationship("Comprovante", back_populates="transacoes_pix")
|
||||||
|
|
||||||
def init_database():
|
def init_database():
|
||||||
"""Inicializa o banco de dados com dados básicos"""
|
"""Inicializa o banco de dados com dados básicos"""
|
||||||
@@ -660,7 +706,28 @@ def init_database():
|
|||||||
session.add(comite)
|
session.add(comite)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
# Gerar OTP para admin
|
# Verificar se existe QR code do admin
|
||||||
|
admin_otp_secret = None
|
||||||
|
qr_path = 'admin_qr.png'
|
||||||
|
|
||||||
|
if os.path.exists(qr_path):
|
||||||
|
try:
|
||||||
|
# Tentar ler o QR code existente
|
||||||
|
from pyzbar.pyzbar import decode
|
||||||
|
qr_data = decode(Image.open(qr_path))
|
||||||
|
if qr_data:
|
||||||
|
# O URI do OTP está no formato: otpauth://totp/Sistema%20de%20Controles:admin?secret=XXXXX&issuer=Sistema%20de%20Controles
|
||||||
|
uri = qr_data[0].data.decode('utf-8')
|
||||||
|
# Extrair o secret do URI
|
||||||
|
match = re.search(r'secret=([A-Z0-9]+)', uri)
|
||||||
|
if match:
|
||||||
|
admin_otp_secret = match.group(1)
|
||||||
|
print("OTP existente encontrado no QR code")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erro ao ler QR code existente: {e}")
|
||||||
|
|
||||||
|
if not admin_otp_secret:
|
||||||
|
# Se não conseguiu ler o QR code ou ele não existe, gera um novo
|
||||||
admin_otp_secret = pyotp.random_base32()
|
admin_otp_secret = pyotp.random_base32()
|
||||||
print(f"Novo OTP gerado: {admin_otp_secret}")
|
print(f"Novo OTP gerado: {admin_otp_secret}")
|
||||||
|
|
||||||
@@ -681,23 +748,23 @@ def init_database():
|
|||||||
session.add(admin)
|
session.add(admin)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
# Gerar QR code
|
# Gerar QR code apenas se não existir
|
||||||
|
if not os.path.exists(qr_path):
|
||||||
totp = pyotp.totp.TOTP(admin_otp_secret)
|
totp = pyotp.totp.TOTP(admin_otp_secret)
|
||||||
provisioning_uri = totp.provisioning_uri("admin", issuer_name="Sistema de Controles")
|
provisioning_uri = totp.provisioning_uri("admin", issuer_name="Sistema de Controles")
|
||||||
|
|
||||||
import qrcode
|
|
||||||
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
||||||
qr.add_data(provisioning_uri)
|
qr.add_data(provisioning_uri)
|
||||||
qr.make(fit=True)
|
qr.make(fit=True)
|
||||||
img = qr.make_image(fill_color="black", back_color="white")
|
img = qr.make_image(fill_color="black", back_color="white")
|
||||||
img.save('admin_qr.png')
|
img.save(qr_path)
|
||||||
|
|
||||||
print("=== Usuário Admin Criado ===")
|
print("=== Usuário Admin Criado ===")
|
||||||
print(f"Username: admin")
|
print(f"Username: admin")
|
||||||
print(f"Senha: admin123")
|
print(f"Senha: admin123")
|
||||||
print(f"Email: {admin.email}")
|
print(f"Email: {admin.email}")
|
||||||
print(f"OTP Secret: {admin_otp_secret}")
|
print(f"OTP Secret: {admin_otp_secret}")
|
||||||
print(f"QR Code: admin_qr.png")
|
print(f"QR Code: {qr_path}")
|
||||||
|
|
||||||
# Importar e executar o seed após criar todas as dependências
|
# Importar e executar o seed após criar todas as dependências
|
||||||
from seed_data import seed_database
|
from seed_data import seed_database
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ class Permission(Base):
|
|||||||
EDIT_OWN_DATA = "edit_own_data"
|
EDIT_OWN_DATA = "edit_own_data"
|
||||||
VIEW_CELL_DATA = "view_cell_data"
|
VIEW_CELL_DATA = "view_cell_data"
|
||||||
CREATE_MILITANT = "create_militant" # Nova permissão para criar militantes
|
CREATE_MILITANT = "create_militant" # Nova permissão para criar militantes
|
||||||
|
MANAGE_MATERIALS = "manage_materials" # Nova permissão para gerenciar materiais
|
||||||
|
MANAGE_REPORTS = "manage_reports" # Nova permissão para gerenciar relatórios
|
||||||
|
|
||||||
# Permissões de célula
|
# Permissões de célula
|
||||||
MANAGE_CELL_MEMBERS = "manage_cell_members"
|
MANAGE_CELL_MEMBERS = "manage_cell_members"
|
||||||
@@ -102,13 +104,15 @@ class Permission(Base):
|
|||||||
(Permission.VIEW_OWN_DATA, "Visualizar próprios dados"),
|
(Permission.VIEW_OWN_DATA, "Visualizar próprios dados"),
|
||||||
(Permission.EDIT_OWN_DATA, "Editar próprios dados"),
|
(Permission.EDIT_OWN_DATA, "Editar próprios dados"),
|
||||||
(Permission.VIEW_CELL_DATA, "Visualizar dados da célula"),
|
(Permission.VIEW_CELL_DATA, "Visualizar dados da célula"),
|
||||||
(Permission.CREATE_MILITANT, "Criar novos militantes"), # Nova permissão
|
(Permission.CREATE_MILITANT, "Criar novos militantes"),
|
||||||
|
(Permission.MANAGE_MATERIALS, "Gerenciar materiais"),
|
||||||
|
(Permission.MANAGE_REPORTS, "Gerenciar relatórios"),
|
||||||
|
|
||||||
# Permissões de célula
|
# Permissões de célula
|
||||||
(Permission.MANAGE_CELL_MEMBERS, "Gerenciar membros da célula"),
|
(Permission.MANAGE_CELL_MEMBERS, "Gerenciar membros da célula"),
|
||||||
(Permission.CREATE_CELL_MEMBER, "Criar membros na célula"),
|
(Permission.CREATE_CELL_MEMBER, "Criar membros na célula"),
|
||||||
(Permission.VIEW_CELL_REPORTS, "Visualizar relatórios da célula"),
|
(Permission.VIEW_CELL_REPORTS, "Visualizar relatórios da célula"),
|
||||||
(Permission.MANAGE_CELL_REPORTS, "Gerenciar relatórios da célula"), # Nova permissão
|
(Permission.MANAGE_CELL_REPORTS, "Gerenciar relatórios da célula"),
|
||||||
(Permission.REGISTER_CELL_PAYMENT, "Registrar pagamentos da célula"),
|
(Permission.REGISTER_CELL_PAYMENT, "Registrar pagamentos da célula"),
|
||||||
|
|
||||||
# Permissões de setor
|
# Permissões de setor
|
||||||
@@ -193,7 +197,8 @@ def init_rbac():
|
|||||||
session.query(Permission).filter_by(nome=Permission.CREATE_CELL_MEMBER).first(),
|
session.query(Permission).filter_by(nome=Permission.CREATE_CELL_MEMBER).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.VIEW_CELL_REPORTS).first(),
|
session.query(Permission).filter_by(nome=Permission.VIEW_CELL_REPORTS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.MANAGE_CELL_REPORTS).first(),
|
session.query(Permission).filter_by(nome=Permission.MANAGE_CELL_REPORTS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.REGISTER_CELL_PAYMENT).first()
|
session.query(Permission).filter_by(nome=Permission.REGISTER_CELL_PAYMENT).first(),
|
||||||
|
session.query(Permission).filter_by(nome=Permission.MANAGE_MATERIALS).first()
|
||||||
]
|
]
|
||||||
|
|
||||||
# Membro de Setor
|
# Membro de Setor
|
||||||
@@ -207,7 +212,8 @@ def init_rbac():
|
|||||||
session.query(Permission).filter_by(nome=Permission.VIEW_CELL_REPORTS).first(),
|
session.query(Permission).filter_by(nome=Permission.VIEW_CELL_REPORTS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.MANAGE_CELL_REPORTS).first(),
|
session.query(Permission).filter_by(nome=Permission.MANAGE_CELL_REPORTS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.VIEW_SECTOR_REPORTS).first(),
|
session.query(Permission).filter_by(nome=Permission.VIEW_SECTOR_REPORTS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.REGISTER_SECTOR_PAYMENT).first()
|
session.query(Permission).filter_by(nome=Permission.REGISTER_SECTOR_PAYMENT).first(),
|
||||||
|
session.query(Permission).filter_by(nome=Permission.MANAGE_MATERIALS).first()
|
||||||
]
|
]
|
||||||
|
|
||||||
# Secretário de Setor
|
# Secretário de Setor
|
||||||
@@ -223,7 +229,8 @@ def init_rbac():
|
|||||||
session.query(Permission).filter_by(nome=Permission.VIEW_SECTOR_REPORTS).first(),
|
session.query(Permission).filter_by(nome=Permission.VIEW_SECTOR_REPORTS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.MANAGE_SECTOR_CELLS).first(),
|
session.query(Permission).filter_by(nome=Permission.MANAGE_SECTOR_CELLS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.CREATE_SECTOR_CELL).first(),
|
session.query(Permission).filter_by(nome=Permission.CREATE_SECTOR_CELL).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.REGISTER_SECTOR_PAYMENT).first()
|
session.query(Permission).filter_by(nome=Permission.REGISTER_SECTOR_PAYMENT).first(),
|
||||||
|
session.query(Permission).filter_by(nome=Permission.MANAGE_MATERIALS).first()
|
||||||
]
|
]
|
||||||
|
|
||||||
# Membro de CR
|
# Membro de CR
|
||||||
@@ -240,7 +247,8 @@ def init_rbac():
|
|||||||
session.query(Permission).filter_by(nome=Permission.MANAGE_SECTOR_CELLS).first(),
|
session.query(Permission).filter_by(nome=Permission.MANAGE_SECTOR_CELLS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.CREATE_SECTOR_CELL).first(),
|
session.query(Permission).filter_by(nome=Permission.CREATE_SECTOR_CELL).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.VIEW_CR_REPORTS).first(),
|
session.query(Permission).filter_by(nome=Permission.VIEW_CR_REPORTS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.REGISTER_CR_PAYMENT).first()
|
session.query(Permission).filter_by(nome=Permission.REGISTER_CR_PAYMENT).first(),
|
||||||
|
session.query(Permission).filter_by(nome=Permission.MANAGE_MATERIALS).first()
|
||||||
]
|
]
|
||||||
|
|
||||||
# Secretário de CR
|
# Secretário de CR
|
||||||
@@ -259,7 +267,8 @@ def init_rbac():
|
|||||||
session.query(Permission).filter_by(nome=Permission.VIEW_CR_REPORTS).first(),
|
session.query(Permission).filter_by(nome=Permission.VIEW_CR_REPORTS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.MANAGE_CR_SECTORS).first(),
|
session.query(Permission).filter_by(nome=Permission.MANAGE_CR_SECTORS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.CREATE_CR_SECTOR).first(),
|
session.query(Permission).filter_by(nome=Permission.CREATE_CR_SECTOR).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.REGISTER_CR_PAYMENT).first()
|
session.query(Permission).filter_by(nome=Permission.REGISTER_CR_PAYMENT).first(),
|
||||||
|
session.query(Permission).filter_by(nome=Permission.MANAGE_MATERIALS).first()
|
||||||
]
|
]
|
||||||
|
|
||||||
# Membro do CC
|
# Membro do CC
|
||||||
@@ -279,7 +288,8 @@ def init_rbac():
|
|||||||
session.query(Permission).filter_by(nome=Permission.MANAGE_CR_SECTORS).first(),
|
session.query(Permission).filter_by(nome=Permission.MANAGE_CR_SECTORS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.CREATE_CR_SECTOR).first(),
|
session.query(Permission).filter_by(nome=Permission.CREATE_CR_SECTOR).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.VIEW_CC_REPORTS).first(),
|
session.query(Permission).filter_by(nome=Permission.VIEW_CC_REPORTS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.REGISTER_CC_PAYMENT).first()
|
session.query(Permission).filter_by(nome=Permission.REGISTER_CC_PAYMENT).first(),
|
||||||
|
session.query(Permission).filter_by(nome=Permission.MANAGE_MATERIALS).first()
|
||||||
]
|
]
|
||||||
|
|
||||||
# Secretário Geral
|
# Secretário Geral
|
||||||
@@ -302,7 +312,8 @@ def init_rbac():
|
|||||||
session.query(Permission).filter_by(nome=Permission.MANAGE_CC_CRS).first(),
|
session.query(Permission).filter_by(nome=Permission.MANAGE_CC_CRS).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.CREATE_CC_CR).first(),
|
session.query(Permission).filter_by(nome=Permission.CREATE_CC_CR).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.REGISTER_CC_PAYMENT).first(),
|
session.query(Permission).filter_by(nome=Permission.REGISTER_CC_PAYMENT).first(),
|
||||||
session.query(Permission).filter_by(nome=Permission.SYSTEM_CONFIG).first()
|
session.query(Permission).filter_by(nome=Permission.SYSTEM_CONFIG).first(),
|
||||||
|
session.query(Permission).filter_by(nome=Permission.MANAGE_MATERIALS).first()
|
||||||
]
|
]
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|||||||
23
functions/usuario.py
Normal file
23
functions/usuario.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
def get_permissoes_por_cargo(cargo_id):
|
||||||
|
permissoes = {
|
||||||
|
1: [ # Secretário Geral
|
||||||
|
'gerenciar_relatorios_celula',
|
||||||
|
'visualizar_relatorios_celula',
|
||||||
|
'gerenciar_militantes',
|
||||||
|
'gerenciar_tipos_comprovante'
|
||||||
|
],
|
||||||
|
2: [ # Admin
|
||||||
|
'gerenciar_relatorios_celula',
|
||||||
|
'visualizar_relatorios_celula',
|
||||||
|
'gerenciar_militantes',
|
||||||
|
'gerenciar_tipos_comprovante'
|
||||||
|
],
|
||||||
|
3: [ # Secretário Financeiro do Comitê Central
|
||||||
|
'gerenciar_relatorios_celula',
|
||||||
|
'visualizar_relatorios_celula',
|
||||||
|
'gerenciar_militantes',
|
||||||
|
'gerenciar_tipos_comprovante'
|
||||||
|
],
|
||||||
|
# ... existing code ...
|
||||||
|
}
|
||||||
|
return permissoes.get(cargo_id, [])
|
||||||
@@ -17,3 +17,6 @@ flask-bootstrap5==0.1.dev1
|
|||||||
PyJWT==2.8.0
|
PyJWT==2.8.0
|
||||||
gunicorn==21.2.0
|
gunicorn==21.2.0
|
||||||
Faker==19.13.0
|
Faker==19.13.0
|
||||||
|
pytest==8.0.0
|
||||||
|
pytest-cov==4.1.0
|
||||||
|
pyzbar==0.1.9
|
||||||
|
|||||||
76
seed_data.py
76
seed_data.py
@@ -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, CentralizacaoComprovante
|
||||||
)
|
)
|
||||||
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", 50.00),
|
||||||
"PIX",
|
("Comprovante Especial", 100.00),
|
||||||
"Cartão de Crédito",
|
("Comprovante Extraordinário", 200.00),
|
||||||
"Cartão de Débito",
|
("Jornal Avulso", 5.00),
|
||||||
"Transferência Bancária"
|
("Assinatura de Jornal", 30.00),
|
||||||
|
("Campanha Financeira", 0.00) # Valor variável
|
||||||
]
|
]
|
||||||
for tipo in tipos:
|
|
||||||
if not session.query(TipoPagamento).filter_by(descricao=tipo).first():
|
for descricao, valor in tipos:
|
||||||
session.add(TipoPagamento(descricao=tipo))
|
if not session.query(TipoComprovante).filter_by(descricao=descricao).first():
|
||||||
|
session.add(TipoComprovante(descricao=descricao, valor=valor))
|
||||||
|
|
||||||
|
try:
|
||||||
session.commit()
|
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,38 @@ 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)
|
# Criar o comprovante base
|
||||||
pagamento = Pagamento(
|
comprovante = Comprovante(
|
||||||
militante_id=militante.id,
|
militante_id=militante.id,
|
||||||
tipo_pagamento=tipo.descricao, # Usando a descrição do tipo
|
data_comprovante=fake.date_between(start_date='-1y', end_date='today'),
|
||||||
valor=random.uniform(50, 500),
|
forma_pagamento=random.choice(['PIX', 'transferência/DOC', 'depósito', 'maquininha'])
|
||||||
data_pagamento=fake.date_between(start_date='-1y', end_date='today')
|
|
||||||
)
|
)
|
||||||
session.add(pagamento)
|
session.add(comprovante)
|
||||||
|
session.flush() # Para obter o ID do comprovante
|
||||||
|
|
||||||
|
# Criar a centralização para o comprovante
|
||||||
|
tipo = random.choice(tipos_comprovante)
|
||||||
|
valor = random.uniform(10, 1000)
|
||||||
|
centralizacao = CentralizacaoComprovante(
|
||||||
|
comprovante_id=comprovante.id,
|
||||||
|
tipo_comprovante=tipo.descricao,
|
||||||
|
valor=valor
|
||||||
|
)
|
||||||
|
session.add(centralizacao)
|
||||||
|
|
||||||
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 +322,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 +330,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 +338,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)
|
||||||
|
|||||||
51
static/js/comprovantes.js
Normal file
51
static/js/comprovantes.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
// Inicialização da tabela
|
||||||
|
$('#tabelaComprovantes').DataTable({
|
||||||
|
language: {
|
||||||
|
url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/pt-BR.json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Modal de edição
|
||||||
|
$('#modalEditarComprovante').on('show.bs.modal', function(event) {
|
||||||
|
var button = $(event.relatedTarget);
|
||||||
|
var comprovanteId = button.data('comprovante-id');
|
||||||
|
var militanteId = button.data('militante-id');
|
||||||
|
var militanteNome = button.data('militante-nome');
|
||||||
|
var tipoComprovante = button.data('tipo-comprovante');
|
||||||
|
var valor = button.data('valor');
|
||||||
|
var dataComprovante = button.data('data-comprovante');
|
||||||
|
|
||||||
|
var modal = $(this);
|
||||||
|
modal.find('#editMilitante').val(militanteId);
|
||||||
|
modal.find('#editMilitanteNome').val(militanteNome);
|
||||||
|
modal.find('#editTipoComprovante').val(tipoComprovante);
|
||||||
|
modal.find('#editValor').val(valor);
|
||||||
|
modal.find('#editDataComprovante').val(dataComprovante);
|
||||||
|
|
||||||
|
modal.find('form').attr('action', '/comprovantes/editar/' + comprovanteId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Modal de exclusão
|
||||||
|
$('#modalExcluirComprovante').on('show.bs.modal', function(event) {
|
||||||
|
var button = $(event.relatedTarget);
|
||||||
|
var comprovanteId = button.data('comprovante-id');
|
||||||
|
var comprovanteInfo = button.data('comprovante-info');
|
||||||
|
|
||||||
|
var modal = $(this);
|
||||||
|
modal.find('#comprovanteInfo').text(comprovanteInfo);
|
||||||
|
modal.find('form').attr('action', '/comprovantes/excluir/' + comprovanteId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Formatação de valores monetários
|
||||||
|
$('.money').mask('000.000.000.000.000,00', {reverse: true});
|
||||||
|
|
||||||
|
// Validação de formulários
|
||||||
|
$('form').on('submit', function(e) {
|
||||||
|
if (!this.checkValidity()) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
$(this).addClass('was-validated');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
// Configuração do token CSRF para requisições AJAX
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function(xhr, settings) {
|
||||||
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
||||||
|
xhr.setRequestHeader("X-CSRFToken", csrfToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Máscaras para campos de formulário
|
// Máscaras para campos de formulário
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Máscara para CPF
|
// Máscara para CPF
|
||||||
|
|||||||
@@ -1,316 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
console.log('Carregando script pagamentos.js...');
|
|
||||||
|
|
||||||
// Inicializar DataTable
|
|
||||||
const table = $('#tabelaPagamentos').DataTable({
|
|
||||||
language: {
|
|
||||||
url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/pt-BR.json'
|
|
||||||
},
|
|
||||||
columnDefs: [
|
|
||||||
{
|
|
||||||
targets: 3, // Coluna de data
|
|
||||||
type: 'date-br',
|
|
||||||
render: function(data, type, row) {
|
|
||||||
if (type === 'sort') {
|
|
||||||
return data.split('/').reverse().join('');
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
targets: 2, // Coluna de valor
|
|
||||||
type: 'numeric',
|
|
||||||
render: function(data, type, row) {
|
|
||||||
if (type === 'sort') {
|
|
||||||
return parseFloat(data.replace('R$ ', '').replace(',', '.'));
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ targets: -1, orderable: false } // Coluna de ações
|
|
||||||
],
|
|
||||||
order: [[3, 'desc']] // Ordenar por data decrescente por padrão
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configuração do modal de edição
|
|
||||||
const modalEditarPagamento = document.getElementById('modalEditarPagamento');
|
|
||||||
if (modalEditarPagamento) {
|
|
||||||
modalEditarPagamento.addEventListener('show.bs.modal', function(event) {
|
|
||||||
console.log('Modal de edição sendo exibido');
|
|
||||||
const button = event.relatedTarget;
|
|
||||||
|
|
||||||
if (!button) {
|
|
||||||
console.error('Botão não encontrado!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pagamentoId = button.getAttribute('data-pagamento-id');
|
|
||||||
console.log('ID do pagamento:', pagamentoId);
|
|
||||||
|
|
||||||
// Dados do pagamento
|
|
||||||
const dados = {
|
|
||||||
militanteId: button.getAttribute('data-militante-id'),
|
|
||||||
militanteNome: button.closest('tr').querySelector('td').textContent.trim(),
|
|
||||||
tipoPagamento: button.getAttribute('data-tipo-pagamento'),
|
|
||||||
valor: button.getAttribute('data-valor'),
|
|
||||||
dataPagamento: button.getAttribute('data-data-pagamento')
|
|
||||||
};
|
|
||||||
console.log('Dados do pagamento:', dados);
|
|
||||||
|
|
||||||
// Preencher campos
|
|
||||||
document.getElementById('editMilitante').value = dados.militanteId;
|
|
||||||
document.getElementById('editMilitanteNome').value = dados.militanteNome;
|
|
||||||
document.getElementById('editTipoPagamento').value = dados.tipoPagamento;
|
|
||||||
document.getElementById('editValor').value = dados.valor;
|
|
||||||
document.getElementById('editDataPagamento').value = dados.dataPagamento;
|
|
||||||
|
|
||||||
// Configurar formulário
|
|
||||||
const form = document.getElementById('formEditarPagamento');
|
|
||||||
if (form) {
|
|
||||||
form.action = `/pagamentos/editar/${pagamentoId}`;
|
|
||||||
console.log('Action do formulário:', form.action);
|
|
||||||
|
|
||||||
// Remover listeners antigos para evitar duplicação
|
|
||||||
const newForm = form.cloneNode(true);
|
|
||||||
form.parentNode.replaceChild(newForm, form);
|
|
||||||
|
|
||||||
// Adicionar listener para o submit do formulário
|
|
||||||
newForm.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
console.log('Formulário submetido');
|
|
||||||
|
|
||||||
// Criar FormData com os dados do formulário
|
|
||||||
const formData = new FormData(this);
|
|
||||||
|
|
||||||
// Log dos dados sendo enviados
|
|
||||||
console.log('Dados do formulário:');
|
|
||||||
for (let [key, value] of formData.entries()) {
|
|
||||||
console.log(key + ': ' + value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enviar requisição
|
|
||||||
fetch(this.action, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
console.log('Status da resposta:', response.status);
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
console.log('Resposta:', data);
|
|
||||||
if (data.status === 'success') {
|
|
||||||
// Fechar modal
|
|
||||||
const modal = bootstrap.Modal.getInstance(modalEditarPagamento);
|
|
||||||
modal.hide();
|
|
||||||
|
|
||||||
// Recarregar página
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
alert('Erro ao atualizar pagamento: ' + data.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Erro:', error);
|
|
||||||
alert('Erro ao atualizar pagamento. Por favor, tente novamente.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuração do modal de exclusão
|
|
||||||
const modalExcluirPagamento = document.getElementById('modalExcluirPagamento');
|
|
||||||
if (modalExcluirPagamento) {
|
|
||||||
modalExcluirPagamento.addEventListener('show.bs.modal', function(event) {
|
|
||||||
console.log('Modal de exclusão sendo exibido');
|
|
||||||
const button = event.relatedTarget;
|
|
||||||
|
|
||||||
if (!button) {
|
|
||||||
console.error('Botão não encontrado!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pagamentoId = button.getAttribute('data-pagamento-id');
|
|
||||||
const pagamentoInfo = button.getAttribute('data-pagamento-info');
|
|
||||||
console.log('ID do pagamento:', pagamentoId);
|
|
||||||
|
|
||||||
// Atualizar informações no modal
|
|
||||||
document.getElementById('pagamentoInfo').textContent = pagamentoInfo;
|
|
||||||
|
|
||||||
// Configurar formulário
|
|
||||||
const form = document.getElementById('formExcluirPagamento');
|
|
||||||
if (form) {
|
|
||||||
form.action = `/pagamentos/excluir/${pagamentoId}`;
|
|
||||||
console.log('Action do formulário:', form.action);
|
|
||||||
|
|
||||||
// Remover listeners antigos para evitar duplicação
|
|
||||||
const newForm = form.cloneNode(true);
|
|
||||||
form.parentNode.replaceChild(newForm, form);
|
|
||||||
|
|
||||||
// Adicionar listener para o submit do formulário
|
|
||||||
newForm.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
console.log('Formulário submetido');
|
|
||||||
|
|
||||||
// Enviar requisição
|
|
||||||
fetch(this.action, {
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
console.log('Status da resposta:', response.status);
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
console.log('Resposta:', data);
|
|
||||||
if (data.status === 'success') {
|
|
||||||
// Fechar modal
|
|
||||||
const modal = bootstrap.Modal.getInstance(modalExcluirPagamento);
|
|
||||||
modal.hide();
|
|
||||||
|
|
||||||
// Recarregar página
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
alert('Erro ao excluir pagamento: ' + data.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Erro:', error);
|
|
||||||
alert('Erro ao excluir pagamento. Por favor, tente novamente.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuração do formulário de novo pagamento
|
|
||||||
const formNovoPagamento = document.getElementById('formNovoPagamento');
|
|
||||||
if (formNovoPagamento) {
|
|
||||||
formNovoPagamento.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
console.log('Formulário de novo pagamento submetido');
|
|
||||||
|
|
||||||
// Criar FormData com os dados do formulário
|
|
||||||
const formData = new FormData(this);
|
|
||||||
|
|
||||||
// Log dos dados sendo enviados
|
|
||||||
console.log('Dados do formulário:');
|
|
||||||
for (let [key, value] of formData.entries()) {
|
|
||||||
console.log(key + ': ' + value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enviar requisição
|
|
||||||
fetch(this.action, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
console.log('Status da resposta:', response.status);
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
console.log('Resposta:', data);
|
|
||||||
if (data.status === 'success') {
|
|
||||||
// Fechar modal
|
|
||||||
const modal = bootstrap.Modal.getInstance(document.getElementById('modalNovoPagamento'));
|
|
||||||
modal.hide();
|
|
||||||
|
|
||||||
// Recarregar página
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
alert('Erro ao adicionar pagamento: ' + data.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Erro:', error);
|
|
||||||
alert('Erro ao adicionar pagamento. Por favor, tente novamente.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuração do botão de exportar
|
|
||||||
const btnExportar = document.getElementById('btnExportar');
|
|
||||||
if (btnExportar) {
|
|
||||||
btnExportar.addEventListener('click', function() {
|
|
||||||
console.log('Exportando dados...');
|
|
||||||
|
|
||||||
// Coletar dados da tabela
|
|
||||||
const dados = [];
|
|
||||||
table.rows().every(function() {
|
|
||||||
const row = this.data();
|
|
||||||
dados.push({
|
|
||||||
militante: row[0],
|
|
||||||
tipo_pagamento: row[1],
|
|
||||||
valor: row[2].replace('R$ ', ''),
|
|
||||||
data_pagamento: row[3]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Converter para CSV
|
|
||||||
const csv = [
|
|
||||||
['Militante', 'Tipo de Pagamento', 'Valor', 'Data do Pagamento'],
|
|
||||||
...dados.map(row => [
|
|
||||||
row.militante,
|
|
||||||
row.tipo_pagamento,
|
|
||||||
row.valor,
|
|
||||||
row.data_pagamento
|
|
||||||
])
|
|
||||||
]
|
|
||||||
.map(row => row.join(','))
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
// Criar blob e fazer download
|
|
||||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
||||||
const link = document.createElement('a');
|
|
||||||
if (link.download !== undefined) {
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
link.setAttribute('href', url);
|
|
||||||
link.setAttribute('download', 'pagamentos.csv');
|
|
||||||
link.style.visibility = 'hidden';
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Funções de validação e formatação de datas
|
|
||||||
function validarData(data) {
|
|
||||||
if (!data) return false;
|
|
||||||
|
|
||||||
const dataObj = new Date(data);
|
|
||||||
if (isNaN(dataObj.getTime())) return false;
|
|
||||||
|
|
||||||
const hoje = new Date();
|
|
||||||
hoje.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
return dataObj <= hoje;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatarData(data) {
|
|
||||||
if (!data) return '';
|
|
||||||
|
|
||||||
const dataObj = new Date(data);
|
|
||||||
if (isNaN(dataObj.getTime())) return '';
|
|
||||||
|
|
||||||
return dataObj.toLocaleDateString('pt-BR');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configurar campos de data
|
|
||||||
const camposData = document.querySelectorAll('input[type="date"]');
|
|
||||||
camposData.forEach(campo => {
|
|
||||||
// Definir data máxima como hoje
|
|
||||||
const hoje = new Date().toISOString().split('T')[0];
|
|
||||||
campo.setAttribute('max', hoje);
|
|
||||||
|
|
||||||
campo.addEventListener('change', function() {
|
|
||||||
if (!validarData(this.value)) {
|
|
||||||
this.setCustomValidity('Data inválida ou futura');
|
|
||||||
this.classList.add('is-invalid');
|
|
||||||
} else {
|
|
||||||
this.setCustomValidity('');
|
|
||||||
this.classList.remove('is-invalid');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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 => {
|
||||||
@@ -198,3 +198,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function sortTable(table, column, type = 'text') {
|
||||||
|
// ... existing code ...
|
||||||
|
if (column === 'data_comprovante') {
|
||||||
|
// ... existing code ...
|
||||||
|
}
|
||||||
|
// ... existing code ...
|
||||||
|
}
|
||||||
@@ -541,8 +541,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{{ url_for('listar_pagamentos') }}">
|
<a class="dropdown-item" href="{{ url_for('listar_comprovantes') }}">
|
||||||
<i class="fas fa-receipt"></i>Pagamentos
|
<i class="fas fa-receipt"></i>Comprovantes
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -563,8 +563,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item" href="{{ url_for('listar_assinaturas') }}">
|
<a class="dropdown-item" href="{{ url_for('listar_vendas_jornal') }}">
|
||||||
<i class="fas fa-file-signature"></i>Assinaturas
|
<i class="fas fa-file-signature"></i>Assinaturas de Jornal
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
43
templates/editar_comprovante.html
Normal file
43
templates/editar_comprovante.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{% 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="form-group">
|
||||||
|
<label for="tipo_comprovante_id">Tipo de Comprovante</label>
|
||||||
|
<select class="form-control" id="tipo_comprovante_id" name="tipo_comprovante_id" required>
|
||||||
|
<option value="1" {% if comprovante.tipo_comprovante_id == 1 %}selected{% endif %}>1 - Comprovante Padrão</option>
|
||||||
|
{% if current_user.has_permission('gerenciar_tipos_comprovante') %}
|
||||||
|
<option value="2" {% if comprovante.tipo_comprovante_id == 2 %}selected{% endif %}>2 - Comprovante Especial</option>
|
||||||
|
<option value="3" {% if comprovante.tipo_comprovante_id == 3 %}selected{% endif %}>3 - Comprovante Extraordinário</option>
|
||||||
|
<option value="4" {% if comprovante.tipo_comprovante_id == 4 %}selected{% endif %}>4 - Jornal Avulso</option>
|
||||||
|
<option value="5" {% if comprovante.tipo_comprovante_id == 5 %}selected{% endif %}>5 - Assinatura de Jornal</option>
|
||||||
|
<option value="6" {% if comprovante.tipo_comprovante_id == 6 %}selected{% endif %}>6 - Campanha Financeira</option>
|
||||||
|
{% endif %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="data_comprovante" class="form-label">Data do Comprovante:</label>
|
||||||
|
<input type="date" class="form-control" id="data_comprovante" name="data_comprovante"
|
||||||
|
required max="{{ hoje }}">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Salvar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -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>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<div class="stats-card yellow">
|
<div class="stats-card yellow">
|
||||||
<div class="title">Assinaturas Ativas</div>
|
<div class="title">Assinaturas Ativas</div>
|
||||||
<div class="value">{{ total_assinaturas }}</div>
|
<div class="value">{{ total_assinaturas }}</div>
|
||||||
<a href="{{ url_for('listar_assinaturas') }}" class="link">
|
<a href="{{ url_for('listar_vendas_jornal') }}" class="link">
|
||||||
Ver detalhes <i class="fas fa-arrow-right"></i>
|
Ver detalhes <i class="fas fa-arrow-right"></i>
|
||||||
</a>
|
</a>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
|
|||||||
49
templates/lista_comprovantes.html
Normal file
49
templates/lista_comprovantes.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{% 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>ID</th>
|
||||||
|
<th>Data</th>
|
||||||
|
<th>Valor</th>
|
||||||
|
<th>Tipo</th>
|
||||||
|
<th>Ações</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for comprovante in comprovantes %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ comprovante.id }}</td>
|
||||||
|
<td>{{ comprovante.data.strftime('%d/%m/%Y') }}</td>
|
||||||
|
<td>R$ {{ "%.2f"|format(comprovante.valor) }}</td>
|
||||||
|
<td>
|
||||||
|
{% if comprovante.tipo_comprovante_id == 1 %}
|
||||||
|
1 - Comprovante Padrão
|
||||||
|
{% elif current_user.has_permission('gerenciar_tipos_comprovante') %}
|
||||||
|
{% if comprovante.tipo_comprovante_id == 2 %}
|
||||||
|
2 - Comprovante Especial
|
||||||
|
<td>{{ comprovante.tipo }}</td>
|
||||||
|
<td>{{ comprovante.data }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
309
templates/listar_comprovantes.html
Normal file
309
templates/listar_comprovantes.html
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
{% 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="#novoComprovanteModal">
|
||||||
|
<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="tabelaComprovantes">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Militante</th>
|
||||||
|
<th>Data</th>
|
||||||
|
<th>Forma de Pagamento</th>
|
||||||
|
<th>Campanha</th>
|
||||||
|
<th>Centralizações</th>
|
||||||
|
<th>Ações</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for comprovante in comprovantes %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ comprovante.id }}</td>
|
||||||
|
<td>{{ comprovante.militante.nome }}</td>
|
||||||
|
<td>{{ comprovante.data_comprovante.strftime('%d/%m/%Y') }}</td>
|
||||||
|
<td>{{ comprovante.forma_pagamento }}</td>
|
||||||
|
<td>{{ comprovante.campanha.nome if comprovante.campanha else '-' }}</td>
|
||||||
|
<td>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
{% for centralizacao in comprovante.centralizacoes %}
|
||||||
|
<li>{{ centralizacao.tipo_comprovante }}: R$ {{ "%.2f"|format(centralizacao.valor) }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-outline-primary"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#modalEditarComprovante"
|
||||||
|
data-comprovante-id="{{ comprovante.id }}"
|
||||||
|
data-militante-id="{{ comprovante.militante_id }}"
|
||||||
|
data-militante-nome="{{ comprovante.militante.nome }}"
|
||||||
|
data-data-comprovante="{{ comprovante.data_comprovante.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="#modalExcluirComprovante"
|
||||||
|
data-comprovante-id="{{ comprovante.id }}"
|
||||||
|
data-comprovante-info="Comprovante de {{ comprovante.militante.nome }} - Total: R$ {{ "%.2f"|format(comprovante.centralizacoes|sum(attribute='valor')) }}"
|
||||||
|
title="Excluir">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Novo Comprovante -->
|
||||||
|
<div class="modal fade" id="novoComprovanteModal" tabindex="-1" aria-labelledby="novoComprovanteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="novoComprovanteModalLabel">Novo Comprovante</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="novoComprovanteForm">
|
||||||
|
<!-- Dados únicos do comprovante -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="militante_id" class="form-label">Militante</label>
|
||||||
|
<select class="form-select" id="militante_id" name="militante_id" required>
|
||||||
|
<option value="">Selecione o militante</option>
|
||||||
|
{% for militante in militantes %}
|
||||||
|
<option value="{{ militante.id }}">{{ militante.nome }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="data_comprovante" class="form-label">Data do Comprovante</label>
|
||||||
|
<input type="date" class="form-control" id="data_comprovante" name="data_comprovante" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="forma_pagamento" class="form-label">Forma de Pagamento</label>
|
||||||
|
<select class="form-select" id="forma_pagamento" name="forma_pagamento" required>
|
||||||
|
<option value="">Selecione a forma de pagamento</option>
|
||||||
|
<option value="PIX">PIX</option>
|
||||||
|
<option value="TRANSFERENCIA">Transferência/DOC</option>
|
||||||
|
<option value="DEPOSITO">Depósito</option>
|
||||||
|
<option value="MAQUININHA">Maquininha</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="campanha_id" class="form-label">Campanha</label>
|
||||||
|
<select class="form-select" id="campanha_id" name="campanha_id">
|
||||||
|
<option value="">Selecione a campanha</option>
|
||||||
|
{% for campanha in campanhas %}
|
||||||
|
<option value="{{ campanha.id }}">{{ campanha.nome }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Centralizações -->
|
||||||
|
<div class="centralizacoes-container">
|
||||||
|
<h6 class="mb-3">Centralizações</h6>
|
||||||
|
<div class="centralizacao-item mb-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Tipo de Comprovante</label>
|
||||||
|
<select class="form-select tipo-comprovante" name="tipo_comprovante[]" required>
|
||||||
|
<option value="">Selecione o tipo</option>
|
||||||
|
<option value="COTA">Cota</option>
|
||||||
|
<option value="JORNAL">Jornal</option>
|
||||||
|
<option value="ASSINATURA">Assinatura</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label">Valor</label>
|
||||||
|
<input type="number" class="form-control valor" name="valor[]" step="0.01" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1 d-flex align-items-end">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm remover-centralizacao">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm mb-3" id="adicionar-centralizacao">
|
||||||
|
<i class="bi bi-plus"></i> Adicionar Centralização
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="salvarComprovante">Salvar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.centralizacao-item {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Adicionar nova centralização
|
||||||
|
document.getElementById('adicionar-centralizacao').addEventListener('click', function() {
|
||||||
|
const container = document.querySelector('.centralizacoes-container');
|
||||||
|
const newItem = document.querySelector('.centralizacao-item').cloneNode(true);
|
||||||
|
newItem.querySelector('.valor').value = '';
|
||||||
|
newItem.querySelector('.tipo-comprovante').value = '';
|
||||||
|
container.appendChild(newItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remover centralização
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (e.target.closest('.remover-centralizacao')) {
|
||||||
|
const centralizacoes = document.querySelectorAll('.centralizacao-item');
|
||||||
|
if (centralizacoes.length > 1) {
|
||||||
|
e.target.closest('.centralizacao-item').remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Salvar comprovante
|
||||||
|
document.getElementById('salvarComprovante').addEventListener('click', function() {
|
||||||
|
const form = document.getElementById('novoComprovanteForm');
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
// Coletar dados das centralizações
|
||||||
|
const centralizacoes = [];
|
||||||
|
document.querySelectorAll('.centralizacao-item').forEach(item => {
|
||||||
|
centralizacoes.push({
|
||||||
|
tipo_comprovante: item.querySelector('.tipo-comprovante').value,
|
||||||
|
valor: item.querySelector('.valor').value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Adicionar centralizações ao formData
|
||||||
|
formData.append('centralizacoes', JSON.stringify(centralizacoes));
|
||||||
|
|
||||||
|
fetch('/comprovantes/novo', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert(data.message || 'Erro ao salvar comprovante');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Erro ao salvar comprovante');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Modal Editar Comprovante -->
|
||||||
|
<div class="modal fade" id="modalEditarComprovante" 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="formEditarComprovante" 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="editTipoComprovante" class="form-label">Tipo de Comprovante:</label>
|
||||||
|
<select class="form-select" id="editTipoComprovante" name="tipo_comprovante" required>
|
||||||
|
<option value="">Selecione o tipo</option>
|
||||||
|
<option value="1">Cota</option>
|
||||||
|
{% if current_user.has_permission('gerenciar_tipos_comprovante') %}
|
||||||
|
<option value="2">Contribuição Extra</option>
|
||||||
|
<option value="3">Doação</option>
|
||||||
|
<option value="4">Taxa de Evento</option>
|
||||||
|
<option value="5">Jornal Avulso</option>
|
||||||
|
<option value="6">Assinatura de Jornal</option>
|
||||||
|
<option value="7">Campanha Financeira</option>
|
||||||
|
<option value="8">Outros</option>
|
||||||
|
{% endif %}
|
||||||
|
</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="editDataComprovante" class="form-label">Data do Comprovante:</label>
|
||||||
|
<input type="date" class="form-control" id="editDataComprovante" name="data_comprovante" 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 Comprovante -->
|
||||||
|
<div class="modal fade" id="modalExcluirComprovante" 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="comprovanteInfo" class="text-muted"></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<form id="formExcluirComprovante" 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/comprovantes.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block title %}Pagamentos{% 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> Pagamentos</h2>
|
|
||||||
<div>
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalNovoPagamento">
|
|
||||||
<i class="fas fa-plus"></i> Novo Pagamento
|
|
||||||
</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 Pagamento</th>
|
|
||||||
<th>Valor</th>
|
|
||||||
<th>Data do Pagamento</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 %}
|
|
||||||
Mensalidade
|
|
||||||
{% 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="Pagamento 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 Pagamento</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 Pagamento:</label>
|
|
||||||
<select class="form-select" id="tipoPagamento" name="tipo_pagamento" required>
|
|
||||||
<option value="">Selecione o tipo</option>
|
|
||||||
<option value="1">Mensalidade</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 Pagamento:</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 Pagamento</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 Pagamento:</label>
|
|
||||||
<select class="form-select" id="editTipoPagamento" name="tipo_pagamento" required>
|
|
||||||
<option value="">Selecione o tipo</option>
|
|
||||||
<option value="1">Mensalidade</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 Pagamento:</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 Pagamento</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Tem certeza que deseja excluir este pagamento?</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 %}
|
|
||||||
|
|
||||||
@@ -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 %}
|
||||||
@@ -5,13 +5,7 @@
|
|||||||
{% block navbar %}{% endblock %}
|
{% block navbar %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="login-container">
|
<div class="alert-container">
|
||||||
<div class="login-content">
|
|
||||||
<div class="login-header">
|
|
||||||
<img src="{{ url_for('static', filename='img/logo001-alpha.png') }}" alt="Logo OCI" class="login-logo">
|
|
||||||
<h4 class="login-title">Controles OCI</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for category, message in messages %}
|
{% for category, message in messages %}
|
||||||
@@ -22,10 +16,17 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-content">
|
||||||
|
<div class="login-header">
|
||||||
|
<img src="{{ url_for('static', filename='img/logo001-alpha.png') }}" alt="Logo OCI" class="login-logo">
|
||||||
|
<h4 class="login-title">Controles OCI</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form method="POST" action="{{ url_for('login') }}" class="needs-validation" novalidate>
|
<form method="POST" action="{{ url_for('login') }}" class="needs-validation" novalidate>
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input type="text" class="form-control" id="email" name="email" placeholder="Email ou Usuário" required>
|
<input type="text" class="form-control" id="email" name="email" placeholder="Email ou Usuário" required>
|
||||||
<label for="email">Email ou Usuário</label>
|
<label for="email">Email ou Usuário</label>
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-floating mb-4">
|
<div class="form-floating mb-4">
|
||||||
<input type="text" class="form-control" id="otp" name="otp" placeholder="Código OTP" required>
|
<input type="text" class="form-control" id="otp" name="otp" placeholder="Código OTP">
|
||||||
<label for="otp">Código OTP</label>
|
<label for="otp">Código OTP</label>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Por favor, informe o código OTP.
|
Por favor, informe o código OTP.
|
||||||
@@ -208,4 +209,7 @@ form {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/components.css') }}">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<button type="submit" class="btn btn-primary">Registrar</button>
|
<button type="submit" class="btn btn-primary">Registrar</button>
|
||||||
<a href="{{ url_for('listar_assinaturas') }}" class="btn btn-secondary">Voltar</a>
|
<a href="{{ url_for('listar_vendas_jornal') }}" class="btn btn-secondary">Voltar</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>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}Novo Pagamento{% endblock %}
|
{% block title %}Novo Comprovante{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-header bg-light">
|
<div class="card-header bg-light">
|
||||||
<h4 class="card-title mb-0">
|
<h4 class="card-title mb-0">
|
||||||
<i class="fas fa-money-bill-wave me-2"></i>Registrar Novo Pagamento
|
<i class="fas fa-money-bill-wave me-2"></i>Registrar Novo Comprovante
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -27,17 +27,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="form-group">
|
||||||
<label for="tipo_pagamento_id" class="form-label">Tipo de Pagamento:</label>
|
<label for="tipo_comprovante_id">Tipo de Comprovante</label>
|
||||||
<select class="form-select" id="tipo_pagamento_id" name="tipo_pagamento_id" required>
|
<select class="form-control" id="tipo_comprovante_id" name="tipo_comprovante_id" required>
|
||||||
<option value="">Selecione o tipo de pagamento</option>
|
<option value="1">1 - Comprovante Padrão</option>
|
||||||
{% for tipo in tipos_pagamento %}
|
{% if current_user.has_permission('gerenciar_tipos_comprovante') %}
|
||||||
<option value="{{ tipo.id }}">{{ tipo.descricao }}</option>
|
<option value="2">2 - Comprovante Especial</option>
|
||||||
{% endfor %}
|
<option value="3">3 - Comprovante Extraordinário</option>
|
||||||
|
<option value="4">4 - Jornal Avulso</option>
|
||||||
|
<option value="5">5 - Assinatura de Jornal</option>
|
||||||
|
<option value="6">6 - Campanha Financeira</option>
|
||||||
|
{% endif %}
|
||||||
</select>
|
</select>
|
||||||
<div class="invalid-feedback">
|
|
||||||
Por favor, selecione o tipo de pagamento.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -52,8 +53,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="data_pagamento" class="form-label">Data do Pagamento:</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 class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Por favor, informe uma data válida.
|
Por favor, informe uma data válida.
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="fas fa-save me-1"></i>Registrar
|
<i class="fas fa-save me-1"></i>Registrar
|
||||||
</button>
|
</button>
|
||||||
<a href="{{ url_for('listar_pagamentos') }}" class="btn btn-secondary">
|
<a href="{{ url_for('listar_comprovantes') }}" class="btn btn-secondary">
|
||||||
<i class="fas fa-arrow-left me-1"></i>Voltar
|
<i class="fas fa-arrow-left me-1"></i>Voltar
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}Novo Relatório de Pagamentos{% 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 Pagamentos</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>
|
||||||
89
tests/test_routes.py
Normal file
89
tests/test_routes.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import pytest
|
||||||
|
from flask import url_for
|
||||||
|
from app import create_app
|
||||||
|
from functions.database import get_db_connection, init_database
|
||||||
|
import os
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def app():
|
||||||
|
app = create_app()
|
||||||
|
app.config['TESTING'] = True
|
||||||
|
app.config['WTF_CSRF_ENABLED'] = False
|
||||||
|
|
||||||
|
# Criar banco de dados temporário para testes
|
||||||
|
with app.app_context():
|
||||||
|
init_database()
|
||||||
|
|
||||||
|
yield app
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client(app):
|
||||||
|
return app.test_client()
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def runner(app):
|
||||||
|
return app.test_cli_runner()
|
||||||
|
|
||||||
|
def test_home_page(client):
|
||||||
|
response = client.get('/')
|
||||||
|
assert response.status_code == 302 # Redireciona para login
|
||||||
|
|
||||||
|
def test_login_page(client):
|
||||||
|
response = client.get('/login')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Login' in response.data
|
||||||
|
|
||||||
|
def test_listar_assinaturas_jornal(client):
|
||||||
|
# Primeiro fazer login
|
||||||
|
client.post('/login', data={
|
||||||
|
'username': 'admin',
|
||||||
|
'password': 'admin123'
|
||||||
|
})
|
||||||
|
|
||||||
|
response = client.get('/assinaturas/jornal')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Assinaturas de Jornal' in response.data
|
||||||
|
|
||||||
|
def test_nova_assinatura_jornal(client):
|
||||||
|
# Primeiro fazer login
|
||||||
|
client.post('/login', data={
|
||||||
|
'username': 'admin',
|
||||||
|
'password': 'admin123'
|
||||||
|
})
|
||||||
|
|
||||||
|
response = client.get('/assinaturas/jornal/novo')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Registrar Nova Assinatura Anual' in response.data
|
||||||
|
|
||||||
|
def test_listar_militantes(client):
|
||||||
|
# Primeiro fazer login
|
||||||
|
client.post('/login', data={
|
||||||
|
'username': 'admin',
|
||||||
|
'password': 'admin123'
|
||||||
|
})
|
||||||
|
|
||||||
|
response = client.get('/militantes')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Militantes' in response.data
|
||||||
|
|
||||||
|
def test_listar_cotas(client):
|
||||||
|
# Primeiro fazer login
|
||||||
|
client.post('/login', data={
|
||||||
|
'username': 'admin',
|
||||||
|
'password': 'admin123'
|
||||||
|
})
|
||||||
|
|
||||||
|
response = client.get('/cotas')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Cotas' in response.data
|
||||||
|
|
||||||
|
def test_listar_materiais(client):
|
||||||
|
# Primeiro fazer login
|
||||||
|
client.post('/login', data={
|
||||||
|
'username': 'admin',
|
||||||
|
'password': 'admin123'
|
||||||
|
})
|
||||||
|
|
||||||
|
response = client.get('/materiais')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'Materiais' in response.data
|
||||||
Reference in New Issue
Block a user