feat: implementa sistema de comprovantes com centralizações e PIX
This commit is contained in:
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
|
||||||
|
|||||||
297
app.py
297
app.py
@@ -24,14 +24,14 @@ from functions.database import (
|
|||||||
Endereco,
|
Endereco,
|
||||||
TipoComprovante,
|
TipoComprovante,
|
||||||
Comprovante,
|
Comprovante,
|
||||||
VendaJornal,
|
|
||||||
AssinaturaJornal,
|
|
||||||
CampanhaFinanceira,
|
CampanhaFinanceira,
|
||||||
TransacaoPIX,
|
TransacaoPIX,
|
||||||
Permission,
|
Permission,
|
||||||
Role,
|
Role,
|
||||||
RolePermission,
|
Atividade,
|
||||||
UserRole
|
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
|
||||||
@@ -73,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)
|
||||||
@@ -88,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"""
|
||||||
@@ -214,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"))
|
||||||
|
|
||||||
@@ -282,27 +286,22 @@ def create_app():
|
|||||||
.limit(5)\
|
.limit(5)\
|
||||||
.all()
|
.all()
|
||||||
|
|
||||||
# Buscar últimos comprovantes
|
# Buscar últimos pagamentos
|
||||||
ultimos_comprovantes = db.query(Comprovante)\
|
ultimos_pagamentos = db.query(Pagamento)\
|
||||||
.join(Militante)\
|
.order_by(Pagamento.data_pagamento.desc())\
|
||||||
.order_by(Comprovante.data_comprovante.desc())\
|
|
||||||
.limit(5)\
|
.limit(5)\
|
||||||
.all()
|
.all()
|
||||||
|
|
||||||
# Buscar tipos de comprovante
|
|
||||||
tipos_comprovante = db.query(TipoComprovante).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_comprovantes=ultimos_comprovantes,
|
ultimos_pagamentos=ultimos_pagamentos,
|
||||||
tipos_comprovante=tipos_comprovante,
|
Militante=Militante)
|
||||||
user=current_user)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Erro na página inicial: {e}")
|
print(f"Erro na página inicial: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
@@ -316,7 +315,7 @@ def create_app():
|
|||||||
total_materiais=0,
|
total_materiais=0,
|
||||||
total_assinaturas=0,
|
total_assinaturas=0,
|
||||||
ultimos_militantes=[],
|
ultimos_militantes=[],
|
||||||
ultimos_comprovantes=[],
|
ultimos_pagamentos=[],
|
||||||
Militante=Militante)
|
Militante=Militante)
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
@@ -819,7 +818,7 @@ def create_app():
|
|||||||
return redirect(url_for("nova_venda_jornal"))
|
return redirect(url_for("nova_venda_jornal"))
|
||||||
|
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
venda_jornal = VendaJornal(
|
venda_jornal = VendaJornalAvulso(
|
||||||
militante_id=militante_id,
|
militante_id=militante_id,
|
||||||
quantidade=quantidade,
|
quantidade=quantidade,
|
||||||
data_venda=data_venda
|
data_venda=data_venda
|
||||||
@@ -842,7 +841,7 @@ def create_app():
|
|||||||
@require_permission(Permission.MANAGE_MATERIALS)
|
@require_permission(Permission.MANAGE_MATERIALS)
|
||||||
def listar_vendas_jornal():
|
def listar_vendas_jornal():
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
vendas_jornal = db.query(VendaJornal).all()
|
vendas_jornal = db.query(VendaJornalAvulso).all()
|
||||||
return render_template("listar_vendas_jornal.html", vendas_jornal=vendas_jornal)
|
return render_template("listar_vendas_jornal.html", vendas_jornal=vendas_jornal)
|
||||||
|
|
||||||
# Rota para criar um novo relatório de cotas
|
# Rota para criar um novo relatório de cotas
|
||||||
@@ -888,8 +887,8 @@ def create_app():
|
|||||||
return redirect(url_for("novo_relatorio_jornais"))
|
return redirect(url_for("novo_relatorio_jornais"))
|
||||||
|
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
jornais = db.query(VendaJornal).filter(
|
jornais = db.query(VendaJornalAvulso).filter(
|
||||||
VendaJornal.data_venda.between(data_inicio, data_fim)
|
VendaJornalAvulso.data_venda.between(data_inicio, data_fim)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
return render_template("relatorio_jornais.html",
|
return render_template("relatorio_jornais.html",
|
||||||
@@ -916,8 +915,8 @@ def create_app():
|
|||||||
return redirect(url_for("novo_relatorio_assinaturas"))
|
return redirect(url_for("novo_relatorio_assinaturas"))
|
||||||
|
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
assinaturas = db.query(AssinaturaJornal).filter(
|
assinaturas = db.query(AssinaturaAnual).filter(
|
||||||
AssinaturaJornal.data_assinatura.between(data_inicio, data_fim)
|
AssinaturaAnual.data_assinatura.between(data_inicio, data_fim)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
return render_template("relatorio_assinaturas.html",
|
return render_template("relatorio_assinaturas.html",
|
||||||
@@ -1406,227 +1405,41 @@ def create_app():
|
|||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
@app.route('/novo_comprovante', methods=['GET', 'POST'])
|
@app.route('/comprovantes')
|
||||||
@login_required
|
|
||||||
def novo_comprovante():
|
|
||||||
"""Rota para criar um novo comprovante"""
|
|
||||||
try:
|
|
||||||
session = get_db_connection()
|
|
||||||
if request.method == 'POST':
|
|
||||||
militante_id = request.form.get('militante_id')
|
|
||||||
tipo_comprovante = request.form.get('tipo_comprovante')
|
|
||||||
valor = request.form.get('valor')
|
|
||||||
data_comprovante = request.form.get('data_comprovante')
|
|
||||||
|
|
||||||
if not all([militante_id, tipo_comprovante, valor, data_comprovante]):
|
|
||||||
flash('Todos os campos são obrigatórios', 'error')
|
|
||||||
return redirect(url_for('novo_comprovante'))
|
|
||||||
|
|
||||||
comprovante = Comprovante(
|
|
||||||
militante_id=militante_id,
|
|
||||||
tipo_comprovante=tipo_comprovante,
|
|
||||||
valor=float(valor.replace('R$', '').replace('.', '').replace(',', '.')),
|
|
||||||
data_comprovante=datetime.strptime(data_comprovante, '%Y-%m-%d')
|
|
||||||
)
|
|
||||||
|
|
||||||
session.add(comprovante)
|
|
||||||
session.commit()
|
|
||||||
flash('Comprovante criado com sucesso!', 'success')
|
|
||||||
return redirect(url_for('listar_comprovantes'))
|
|
||||||
|
|
||||||
militantes = session.query(Militante).all()
|
|
||||||
tipos_comprovante = session.query(TipoComprovante).all()
|
|
||||||
return render_template(
|
|
||||||
'novo_comprovante.html',
|
|
||||||
militantes=militantes,
|
|
||||||
tipos_comprovante=tipos_comprovante
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
flash(f'Erro ao criar comprovante: {str(e)}', 'error')
|
|
||||||
return redirect(url_for('listar_comprovantes'))
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
@app.route('/listar_comprovantes')
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@require_permission(Permission.MANAGE_MATERIALS)
|
||||||
def listar_comprovantes():
|
def listar_comprovantes():
|
||||||
"""Rota para listar todos os comprovantes"""
|
|
||||||
try:
|
try:
|
||||||
session = get_db_connection()
|
db = get_db_connection()
|
||||||
comprovantes = session.query(Comprovante).all()
|
comprovantes = db.query(Comprovante)\
|
||||||
return render_template('listar_comprovantes.html', comprovantes=comprovantes)
|
.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:
|
except Exception as e:
|
||||||
flash(f'Erro ao listar comprovantes: {str(e)}', 'error')
|
flash(f'Erro ao listar comprovantes: {str(e)}', 'error')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
db.close()
|
||||||
|
|
||||||
@app.route('/adicionar_comprovante', methods=['POST'])
|
@app.route('/comprovantes/<int:id>', methods=['DELETE'])
|
||||||
@login_required
|
@login_required
|
||||||
def adicionar_comprovante():
|
|
||||||
"""Rota para adicionar um novo comprovante via AJAX"""
|
|
||||||
try:
|
|
||||||
session = get_db_connection()
|
|
||||||
data = request.get_json()
|
|
||||||
|
|
||||||
comprovante = Comprovante(
|
|
||||||
militante_id=data['militante_id'],
|
|
||||||
tipo_comprovante=data['tipo_comprovante'],
|
|
||||||
valor=float(data['valor'].replace('R$', '').replace('.', '').replace(',', '.')),
|
|
||||||
data_comprovante=datetime.strptime(data['data_comprovante'], '%Y-%m-%d')
|
|
||||||
)
|
|
||||||
|
|
||||||
session.add(comprovante)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'status': 'success',
|
|
||||||
'message': 'Comprovante adicionado com sucesso!'
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({
|
|
||||||
'status': 'error',
|
|
||||||
'message': f'Erro ao adicionar comprovante: {str(e)}'
|
|
||||||
})
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
@app.route("/relatorios/comprovantes/novo", methods=["GET", "POST"])
|
|
||||||
@require_login
|
|
||||||
@require_permission(Permission.MANAGE_CELL_REPORTS)
|
|
||||||
def novo_relatorio_comprovantes():
|
|
||||||
if request.method == "POST":
|
|
||||||
try:
|
|
||||||
data_inicio = request.form.get("data_inicio")
|
|
||||||
data_fim = request.form.get("data_fim")
|
|
||||||
|
|
||||||
if not all([data_inicio, data_fim]):
|
|
||||||
flash("Todos os campos são obrigatórios", "danger")
|
|
||||||
return redirect(url_for("novo_relatorio_comprovantes"))
|
|
||||||
|
|
||||||
db = get_db_connection()
|
|
||||||
comprovantes = db.query(Comprovante).filter(
|
|
||||||
Comprovante.data_comprovante.between(data_inicio, data_fim)
|
|
||||||
).all()
|
|
||||||
|
|
||||||
return render_template("relatorio_comprovantes.html",
|
|
||||||
comprovantes=comprovantes,
|
|
||||||
data_inicio=data_inicio,
|
|
||||||
data_fim=data_fim)
|
|
||||||
except Exception as e:
|
|
||||||
flash(f"Erro ao gerar relatório: {str(e)}", "danger")
|
|
||||||
return redirect(url_for("novo_relatorio_comprovantes"))
|
|
||||||
return render_template("novo_relatorio_comprovantes.html")
|
|
||||||
|
|
||||||
@app.route("/relatorios/comprovantes/celula/<int:celula_id>", methods=["GET", "POST"])
|
|
||||||
@require_login
|
|
||||||
@require_permission(Permission.MANAGE_CELL_REPORTS)
|
|
||||||
def relatorio_comprovantes_celula(celula_id):
|
|
||||||
if request.method == "POST":
|
|
||||||
try:
|
|
||||||
data_inicio = request.form.get("data_inicio")
|
|
||||||
data_fim = request.form.get("data_fim")
|
|
||||||
|
|
||||||
if not all([data_inicio, data_fim]):
|
|
||||||
flash("Todos os campos são obrigatórios", "danger")
|
|
||||||
return redirect(url_for("relatorio_comprovantes_celula", celula_id=celula_id))
|
|
||||||
|
|
||||||
db = get_db_connection()
|
|
||||||
comprovantes = db.query(Comprovante).join(Militante).filter(
|
|
||||||
Militante.celula_id == celula_id,
|
|
||||||
Comprovante.data_comprovante.between(data_inicio, data_fim)
|
|
||||||
).all()
|
|
||||||
|
|
||||||
return render_template("relatorio_comprovantes.html",
|
|
||||||
comprovantes=comprovantes,
|
|
||||||
data_inicio=data_inicio,
|
|
||||||
data_fim=data_fim)
|
|
||||||
except Exception as e:
|
|
||||||
flash(f"Erro ao gerar relatório: {str(e)}", "danger")
|
|
||||||
return redirect(url_for("relatorio_comprovantes_celula", celula_id=celula_id))
|
|
||||||
return render_template("novo_relatorio_comprovantes.html", celula_id=celula_id)
|
|
||||||
|
|
||||||
@app.route("/assinaturas/jornal/novo", methods=["GET", "POST"])
|
|
||||||
@require_login
|
|
||||||
@require_permission(Permission.MANAGE_MATERIALS)
|
@require_permission(Permission.MANAGE_MATERIALS)
|
||||||
def nova_assinatura_jornal():
|
def excluir_comprovante(id):
|
||||||
if request.method == "POST":
|
|
||||||
try:
|
try:
|
||||||
militante_id = request.form.get("militante_id")
|
comprovante = Comprovante.query.get_or_404(id)
|
||||||
data_inicio = request.form.get("data_inicio")
|
db.session.delete(comprovante)
|
||||||
data_fim = request.form.get("data_fim")
|
db.session.commit()
|
||||||
|
return jsonify({'success': True})
|
||||||
if not all([militante_id, data_inicio, data_fim]):
|
|
||||||
flash("Todos os campos são obrigatórios", "danger")
|
|
||||||
return redirect(url_for("nova_assinatura_jornal"))
|
|
||||||
|
|
||||||
db = get_db_connection()
|
|
||||||
assinatura = AssinaturaJornal(
|
|
||||||
militante_id=militante_id,
|
|
||||||
data_inicio=data_inicio,
|
|
||||||
data_fim=data_fim
|
|
||||||
)
|
|
||||||
db.add(assinatura)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
flash("Assinatura de jornal registrada com sucesso", "success")
|
|
||||||
return redirect(url_for("listar_assinaturas_jornal"))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
flash(f"Erro ao registrar assinatura de jornal: {str(e)}", "danger")
|
db.session.rollback()
|
||||||
return redirect(url_for("nova_assinatura_jornal"))
|
return jsonify({'success': False, 'message': str(e)})
|
||||||
|
|
||||||
db = get_db_connection()
|
|
||||||
militantes = db.query(Militante).all()
|
|
||||||
return render_template("nova_assinatura_jornal.html", militantes=militantes)
|
|
||||||
|
|
||||||
@app.route("/assinaturas/jornal")
|
|
||||||
@require_login
|
|
||||||
@require_permission(Permission.MANAGE_MATERIALS)
|
|
||||||
def listar_assinaturas_jornal():
|
|
||||||
db = get_db_connection()
|
|
||||||
assinaturas = db.query(AssinaturaJornal).all()
|
|
||||||
return render_template("listar_assinaturas_jornal.html", assinaturas=assinaturas)
|
|
||||||
|
|
||||||
@app.route("/campanhas/financeira/novo", methods=["GET", "POST"])
|
|
||||||
@require_login
|
|
||||||
@require_permission(Permission.MANAGE_MATERIALS)
|
|
||||||
def nova_campanha_financeira():
|
|
||||||
if request.method == "POST":
|
|
||||||
try:
|
|
||||||
militante_id = request.form.get("militante_id")
|
|
||||||
valor = request.form.get("valor")
|
|
||||||
data_campanha = request.form.get("data_campanha")
|
|
||||||
|
|
||||||
if not all([militante_id, valor, data_campanha]):
|
|
||||||
flash("Todos os campos são obrigatórios", "danger")
|
|
||||||
return redirect(url_for("nova_campanha_financeira"))
|
|
||||||
|
|
||||||
db = get_db_connection()
|
|
||||||
campanha = CampanhaFinanceira(
|
|
||||||
militante_id=militante_id,
|
|
||||||
valor=valor,
|
|
||||||
data_campanha=data_campanha
|
|
||||||
)
|
|
||||||
db.add(campanha)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
flash("Campanha financeira registrada com sucesso", "success")
|
|
||||||
return redirect(url_for("listar_campanhas_financeira"))
|
|
||||||
except Exception as e:
|
|
||||||
flash(f"Erro ao registrar campanha financeira: {str(e)}", "danger")
|
|
||||||
return redirect(url_for("nova_campanha_financeira"))
|
|
||||||
|
|
||||||
db = get_db_connection()
|
|
||||||
militantes = db.query(Militante).all()
|
|
||||||
return render_template("nova_campanha_financeira.html", militantes=militantes)
|
|
||||||
|
|
||||||
@app.route("/campanhas/financeira")
|
|
||||||
@require_login
|
|
||||||
@require_permission(Permission.MANAGE_MATERIALS)
|
|
||||||
def listar_campanhas_financeira():
|
|
||||||
db = get_db_connection()
|
|
||||||
campanhas = db.query(CampanhaFinanceira).all()
|
|
||||||
return render_template("listar_campanhas_financeira.html", campanhas=campanhas)
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
@@ -1696,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
|
||||||
@@ -1704,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()
|
||||||
@@ -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'
|
||||||
@@ -329,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'
|
||||||
@@ -608,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'
|
||||||
|
|
||||||
@@ -618,25 +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")
|
||||||
|
|
||||||
class TipoComprovante(Base):
|
|
||||||
__tablename__ = 'tipos_comprovante'
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
descricao = Column(String(50), nullable=False)
|
|
||||||
valor = Column(Float, nullable=False)
|
|
||||||
|
|
||||||
class Comprovante(Base):
|
|
||||||
__tablename__ = 'comprovantes'
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
militante_id = Column(Integer, ForeignKey('militantes.id'), nullable=False)
|
|
||||||
tipo_comprovante = Column(String(50)) # Cota, Jornal, Assinatura, etc.
|
|
||||||
data_comprovante = Column(Date, nullable=False)
|
|
||||||
|
|
||||||
militante = relationship("Militante", back_populates="comprovantes")
|
|
||||||
transacoes_pix = relationship("TransacaoPIX", back_populates="comprovante")
|
|
||||||
|
|
||||||
def init_database():
|
def init_database():
|
||||||
"""Inicializa o banco de dados com dados básicos"""
|
"""Inicializa o banco de dados com dados básicos"""
|
||||||
@@ -677,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}")
|
||||||
|
|
||||||
@@ -698,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()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Werkzeug==3.0.1
|
|||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
pyotp==2.9.0
|
pyotp==2.9.0
|
||||||
qrcode==7.4.2
|
qrcode==7.4.2
|
||||||
Pillow==9.5.0
|
Pillow==10.2.0
|
||||||
email-validator==2.1.0.post1
|
email-validator==2.1.0.post1
|
||||||
cryptography==42.0.2
|
cryptography==42.0.2
|
||||||
bcrypt==4.1.2
|
bcrypt==4.1.2
|
||||||
@@ -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
|
||||||
|
|||||||
39
seed_data.py
39
seed_data.py
@@ -5,7 +5,7 @@ from functions.database import (
|
|||||||
RelatorioCotasMensais, RelatorioVendasMateriais, engine, SessionLocal,
|
RelatorioCotasMensais, RelatorioVendasMateriais, engine, SessionLocal,
|
||||||
Setor, ComiteCentral, Usuario, Role, EmailMilitante, Endereco,
|
Setor, ComiteCentral, Usuario, Role, EmailMilitante, Endereco,
|
||||||
ComiteRegional, Celula, EstadoMilitante, get_db_connection,
|
ComiteRegional, Celula, EstadoMilitante, get_db_connection,
|
||||||
init_database
|
init_database, CentralizacaoComprovante
|
||||||
)
|
)
|
||||||
import random
|
import random
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
@@ -59,17 +59,17 @@ def criar_tipos_comprovante(session):
|
|||||||
"""Cria tipos de comprovante padrão"""
|
"""Cria tipos de comprovante padrão"""
|
||||||
print("\nCriando tipos de comprovante...")
|
print("\nCriando tipos de comprovante...")
|
||||||
tipos = [
|
tipos = [
|
||||||
"Comprovante Padrão",
|
("Comprovante Padrão", 50.00),
|
||||||
"Comprovante Especial",
|
("Comprovante Especial", 100.00),
|
||||||
"Comprovante Extraordinário",
|
("Comprovante Extraordinário", 200.00),
|
||||||
"Jornal Avulso",
|
("Jornal Avulso", 5.00),
|
||||||
"Assinatura de Jornal",
|
("Assinatura de Jornal", 30.00),
|
||||||
"Campanha Financeira"
|
("Campanha Financeira", 0.00) # Valor variável
|
||||||
]
|
]
|
||||||
|
|
||||||
for tipo in tipos:
|
for descricao, valor in tipos:
|
||||||
if not session.query(TipoComprovante).filter_by(descricao=tipo).first():
|
if not session.query(TipoComprovante).filter_by(descricao=descricao).first():
|
||||||
session.add(TipoComprovante(descricao=tipo))
|
session.add(TipoComprovante(descricao=descricao, valor=valor))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
session.commit()
|
session.commit()
|
||||||
@@ -229,14 +229,25 @@ def criar_comprovantes(session, militantes):
|
|||||||
try:
|
try:
|
||||||
# Criar entre 3 e 8 comprovantes 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_comprovante)
|
# Criar o comprovante base
|
||||||
comprovante = Comprovante(
|
comprovante = Comprovante(
|
||||||
militante_id=militante.id,
|
militante_id=militante.id,
|
||||||
tipo_comprovante=tipo.descricao, # Usando a descrição do tipo
|
data_comprovante=fake.date_between(start_date='-1y', end_date='today'),
|
||||||
valor=random.uniform(10, 1000),
|
forma_pagamento=random.choice(['PIX', 'transferência/DOC', 'depósito', 'maquininha'])
|
||||||
data_comprovante=fake.date_between(start_date='-1y', end_date='today')
|
|
||||||
)
|
)
|
||||||
session.add(comprovante)
|
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:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h2><i class="fas fa-money-bill-wave"></i> Comprovantes</h2>
|
<h2><i class="fas fa-money-bill-wave"></i> Comprovantes</h2>
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalNovoComprovante">
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#novoComprovanteModal">
|
||||||
<i class="fas fa-plus"></i> Novo Comprovante
|
<i class="fas fa-plus"></i> Novo Comprovante
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-primary" id="btnExportar">
|
<button type="button" class="btn btn-outline-primary" id="btnExportar">
|
||||||
@@ -22,40 +22,30 @@
|
|||||||
<table class="table table-striped table-hover" id="tabelaComprovantes">
|
<table class="table table-striped table-hover" id="tabelaComprovantes">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
<th>Militante</th>
|
<th>Militante</th>
|
||||||
<th>Tipo de Comprovante</th>
|
<th>Data</th>
|
||||||
<th>Valor</th>
|
<th>Forma de Pagamento</th>
|
||||||
<th>Data do Comprovante</th>
|
<th>Campanha</th>
|
||||||
|
<th>Centralizações</th>
|
||||||
<th>Ações</th>
|
<th>Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for comprovante in comprovantes %}
|
{% for comprovante in comprovantes %}
|
||||||
<tr>
|
<tr>
|
||||||
<td data-militante="{{ comprovante.militante_id }}">{{ comprovante.militante.nome if comprovante.militante else 'N/A' }}</td>
|
<td>{{ comprovante.id }}</td>
|
||||||
<td data-tipo="{{ comprovante.tipo_comprovante }}">
|
<td>{{ comprovante.militante.nome }}</td>
|
||||||
{% if comprovante.tipo_comprovante == 1 %}
|
<td>{{ comprovante.data_comprovante.strftime('%d/%m/%Y') }}</td>
|
||||||
Cota
|
<td>{{ comprovante.forma_pagamento }}</td>
|
||||||
{% elif comprovante.tipo_comprovante == 2 %}
|
<td>{{ comprovante.campanha.nome if comprovante.campanha else '-' }}</td>
|
||||||
Contribuição Extra
|
<td>
|
||||||
{% elif comprovante.tipo_comprovante == 3 %}
|
<ul class="list-unstyled">
|
||||||
Doação
|
{% for centralizacao in comprovante.centralizacoes %}
|
||||||
{% elif comprovante.tipo_comprovante == 4 %}
|
<li>{{ centralizacao.tipo_comprovante }}: R$ {{ "%.2f"|format(centralizacao.valor) }}</li>
|
||||||
Taxa de Evento
|
{% endfor %}
|
||||||
{% elif comprovante.tipo_comprovante == 5 %}
|
</ul>
|
||||||
Jornal Avulso
|
|
||||||
{% elif comprovante.tipo_comprovante == 6 %}
|
|
||||||
Assinatura de Jornal
|
|
||||||
{% elif comprovante.tipo_comprovante == 7 %}
|
|
||||||
Campanha Financeira
|
|
||||||
{% elif comprovante.tipo_comprovante == 8 %}
|
|
||||||
Outros
|
|
||||||
{% else %}
|
|
||||||
Não Definido
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td data-valor="{{ comprovante.valor }}">R$ {{ "%.2f"|format(comprovante.valor) }}</td>
|
|
||||||
<td data-data="{{ comprovante.data_comprovante }}">{{ comprovante.data_comprovante.strftime('%d/%m/%Y') }}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-sm btn-outline-primary"
|
class="btn btn-sm btn-outline-primary"
|
||||||
@@ -63,8 +53,7 @@
|
|||||||
data-bs-target="#modalEditarComprovante"
|
data-bs-target="#modalEditarComprovante"
|
||||||
data-comprovante-id="{{ comprovante.id }}"
|
data-comprovante-id="{{ comprovante.id }}"
|
||||||
data-militante-id="{{ comprovante.militante_id }}"
|
data-militante-id="{{ comprovante.militante_id }}"
|
||||||
data-tipo-comprovante="{{ comprovante.tipo_comprovante }}"
|
data-militante-nome="{{ comprovante.militante.nome }}"
|
||||||
data-valor="{{ comprovante.valor }}"
|
|
||||||
data-data-comprovante="{{ comprovante.data_comprovante.strftime('%Y-%m-%d') }}"
|
data-data-comprovante="{{ comprovante.data_comprovante.strftime('%Y-%m-%d') }}"
|
||||||
title="Editar">
|
title="Editar">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
@@ -74,7 +63,7 @@
|
|||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#modalExcluirComprovante"
|
data-bs-target="#modalExcluirComprovante"
|
||||||
data-comprovante-id="{{ comprovante.id }}"
|
data-comprovante-id="{{ comprovante.id }}"
|
||||||
data-comprovante-info="Comprovante de {{ comprovante.militante.nome if comprovante.militante else 'N/A' }} - R$ {{ "%.2f"|format(comprovante.valor) }}"
|
data-comprovante-info="Comprovante de {{ comprovante.militante.nome }} - Total: R$ {{ "%.2f"|format(comprovante.centralizacoes|sum(attribute='valor')) }}"
|
||||||
title="Excluir">
|
title="Excluir">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -89,58 +78,159 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal Novo Comprovante -->
|
<!-- Modal Novo Comprovante -->
|
||||||
<div class="modal fade" id="modalNovoComprovante" tabindex="-1">
|
<div class="modal fade" id="novoComprovanteModal" tabindex="-1" aria-labelledby="novoComprovanteModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title"><i class="fas fa-plus"></i> Novo Comprovante</h5>
|
<h5 class="modal-title" id="novoComprovanteModalLabel">Novo Comprovante</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="formNovoComprovante" method="post" action="{{ url_for('adicionar_comprovante') }}">
|
<form id="novoComprovanteForm">
|
||||||
<div class="mb-3">
|
<!-- Dados únicos do comprovante -->
|
||||||
<label for="militante" class="form-label">Militante:</label>
|
<div class="row mb-3">
|
||||||
<select class="form-select" id="militante" name="militante_id" required>
|
<div class="col-md-6">
|
||||||
<option value="">Selecione um militante</option>
|
<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 %}
|
{% for militante in militantes %}
|
||||||
<option value="{{ militante.id }}">{{ militante.nome }}</option>
|
<option value="{{ militante.id }}">{{ militante.nome }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="col-md-6">
|
||||||
<label for="tipoComprovante" class="form-label">Tipo de Comprovante:</label>
|
<label for="data_comprovante" class="form-label">Data do Comprovante</label>
|
||||||
<select class="form-select" id="tipoComprovante" name="tipo_comprovante" required>
|
<input type="date" class="form-control" id="data_comprovante" name="data_comprovante" required>
|
||||||
<option value="">Selecione o tipo</option>
|
</div>
|
||||||
<option value="1">Cota</option>
|
</div>
|
||||||
{% if current_user.has_permission('gerenciar_tipos_comprovante') %}
|
<div class="row mb-3">
|
||||||
<option value="2">Contribuição Extra</option>
|
<div class="col-md-6">
|
||||||
<option value="3">Doação</option>
|
<label for="forma_pagamento" class="form-label">Forma de Pagamento</label>
|
||||||
<option value="4">Taxa de Evento</option>
|
<select class="form-select" id="forma_pagamento" name="forma_pagamento" required>
|
||||||
<option value="5">Jornal Avulso</option>
|
<option value="">Selecione a forma de pagamento</option>
|
||||||
<option value="6">Assinatura de Jornal</option>
|
<option value="PIX">PIX</option>
|
||||||
<option value="7">Campanha Financeira</option>
|
<option value="TRANSFERENCIA">Transferência/DOC</option>
|
||||||
<option value="8">Outros</option>
|
<option value="DEPOSITO">Depósito</option>
|
||||||
{% endif %}
|
<option value="MAQUININHA">Maquininha</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="col-md-6">
|
||||||
<label for="valor" class="form-label">Valor:</label>
|
<label for="campanha_id" class="form-label">Campanha</label>
|
||||||
<input type="number" step="0.01" class="form-control" id="valor" name="valor" required>
|
<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>
|
||||||
<div class="mb-3">
|
</div>
|
||||||
<label for="dataComprovante" class="form-label">Data do Comprovante:</label>
|
|
||||||
<input type="date" class="form-control" id="dataComprovante" name="data_comprovante" required>
|
<!-- 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>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
<button type="submit" class="btn btn-primary">Salvar</button>
|
<button type="button" class="btn btn-primary" id="salvarComprovante">Salvar</button>
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Modal Editar Comprovante -->
|
||||||
<div class="modal fade" id="modalEditarComprovante" tabindex="-1">
|
<div class="modal fade" id="modalEditarComprovante" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
|
|||||||
@@ -47,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.
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
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