feat: Melhorias no Dashboard

- Interface:
  - Adicionada saudação personalizada com nome do usuário
  - Melhorado formato da data em português
  - Ajustado layout do header com gradiente e sombra
  - Corrigida categoria de mensagens flash de 'error' para 'danger'

- Card de Cotas:
  - Reorganizado layout para melhor exibição de valores grandes
  - Ajustado tamanho da fonte usando calc(1.2rem + 0.8vw)
  - Adicionado container específico para valor com min-width: 0
  - Redimensionado e reposicionado ícone
  - Melhorado espaçamento e alinhamento

- Lista de Militantes:
  - Ajustada query para ordenar por ID
  - Removida dependência da coluna created_at
  - Adicionado ID do militante na listagem

- Estilos:
  - Adicionadas classes valor-container e icon-container
  - Melhorado responsividade dos valores monetários
  - Ajustado gradiente no header de boas-vindas
  - Refinado espaçamento e margens dos componentes
This commit is contained in:
andersonid
2025-04-02 21:20:48 -03:00
parent d4869dcfaa
commit 8803c971e4
10 changed files with 831 additions and 576 deletions

3
.gitignore vendored
View File

@@ -263,3 +263,6 @@ database.db
admin_qr.png
# End of https://www.toptal.com/developers/gitignore/api/python,flask
# Documentação temporária
docs/alteracoes_db_connection.md

View File

@@ -5,7 +5,7 @@ clean:
rm -rf ~/.local/share/controles/database.db
rm -f admin_qr.png
run: clean
run:
python app.py
reset-admin: clean

743
app.py
View File

@@ -237,38 +237,52 @@ def logout():
@require_login
def home():
"""Página inicial do sistema com dashboard"""
db = get_db_connection()
try:
# Buscar nome do usuário
usuario = db.query(Usuario).get(session.get('user_id'))
nome_usuario = usuario.username if usuario else "Usuário"
# Formatar data atual em português
meses = {
1: 'janeiro', 2: 'fevereiro', 3: 'março', 4: 'abril',
5: 'maio', 6: 'junho', 7: 'julho', 8: 'agosto',
9: 'setembro', 10: 'outubro', 11: 'novembro', 12: 'dezembro'
1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril',
5: 'Maio', 6: 'Junho', 7: 'Julho', 8: 'Agosto',
9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'
}
data_atual = datetime.now()
data_formatada = f"{data_atual.day} de {meses[data_atual.month]} de {data_atual.year}"
# Buscar totais
total_militantes = db_session.query(Militante).count()
total_cotas = db_session.query(func.sum(CotaMensal.valor_novo)).scalar() or 0
total_materiais = db_session.query(MaterialVendido).count()
total_assinaturas = db_session.query(AssinaturaAnual).filter(
total_militantes = db.query(Militante).count()
total_cotas = db.query(func.sum(CotaMensal.valor_novo)).scalar() or 0
total_materiais = db.query(MaterialVendido).count()
total_assinaturas = db.query(AssinaturaAnual).filter(
AssinaturaAnual.data_fim >= datetime.now()
).count()
# Buscar últimos militantes cadastrados
ultimos_militantes = db_session.query(Militante)\
# Buscar últimos militantes cadastrados com tratamento de erro
try:
ultimos_militantes = db.query(Militante)\
.order_by(Militante.id.desc())\
.limit(5)\
.all()
except Exception as e:
print(f"Erro ao buscar últimos militantes: {e}")
ultimos_militantes = []
# Buscar últimos pagamentos
ultimos_pagamentos = db_session.query(Pagamento)\
# Buscar últimos pagamentos com tratamento de erro
try:
ultimos_pagamentos = db.query(Pagamento)\
.join(Militante)\
.order_by(Pagamento.data_pagamento.desc())\
.limit(5)\
.all()
except Exception as e:
print(f"Erro ao buscar últimos pagamentos: {e}")
ultimos_pagamentos = []
return render_template('home.html',
nome_usuario=nome_usuario,
data_atual=data_formatada,
total_militantes=total_militantes,
total_cotas="{:.2f}".format(total_cotas),
@@ -280,15 +294,28 @@ def home():
print(f"Erro na página inicial: {e}")
import traceback
traceback.print_exc()
flash('Erro ao carregar a página inicial', 'error')
flash('Erro ao carregar a página inicial', 'danger')
# Mesmo com erro, tentar formatar a data e buscar o nome do usuário
try:
data_atual = datetime.now()
data_formatada = f"{data_atual.day} de {meses[data_atual.month]} de {data_atual.year}"
nome_usuario = db.query(Usuario).get(session.get('user_id')).username
except:
data_formatada = datetime.now().strftime("%d/%m/%Y")
nome_usuario = "Usuário"
return render_template('home.html',
data_atual=datetime.now().strftime("%d/%m/%Y"),
total_militantes=0,
nome_usuario=nome_usuario,
data_atual=data_formatada,
total_militantes=db.query(Militante).count(),
total_cotas="0.00",
total_materiais=0,
total_assinaturas=0,
ultimos_militantes=[],
ultimos_pagamentos=[])
finally:
db.close()
# Rota para criar um novo militante
@app.route('/militantes/criar', methods=['GET', 'POST'])
@@ -371,167 +398,207 @@ def criar_militante():
@require_permission(Permission.VIEW_CELL_DATA)
def listar_militantes():
try:
militantes = db_session.query(Militante).order_by(Militante.nome).all()
print("Buscando militantes...")
db = get_db_connection()
militantes = db.query(Militante).order_by(Militante.nome).all()
print(f"Total de militantes encontrados: {len(militantes)}")
for militante in militantes:
print(f"Militante: {militante.nome} (ID: {militante.id})")
return render_template("listar_militantes.html", militantes=militantes)
except Exception as e:
print(f"Erro ao listar militantes: {str(e)}")
flash("Erro ao carregar lista de militantes. Por favor, tente novamente.", "danger")
print(f"Erro ao listar militantes: {e}")
flash('Erro ao listar militantes', 'danger')
return render_template("listar_militantes.html", militantes=[])
finally:
db.close()
# Rota para excluir militante
@app.route("/militantes/excluir/<int:id>", methods=["POST"])
@login_required
@session_timeout
def excluir_militante(id):
try:
militante = db_session.query(Militante).get(id)
db = get_db_connection()
militante = db.query(Militante).get(id)
if not militante:
flash('Militante não encontrado.', 'error')
flash('Militante não encontrado', 'danger')
return redirect(url_for('listar_militantes'))
# Verificar se existem registros relacionados
tem_cotas = db_session.query(CotaMensal).filter_by(militante_id=id).first() is not None
tem_pagamentos = db_session.query(Pagamento).filter_by(militante_id=id).first() is not None
tem_materiais = db_session.query(MaterialVendido).filter_by(militante_id=id).first() is not None
tem_vendas = db_session.query(VendaJornalAvulso).filter_by(militante_id=id).first() is not None
tem_assinaturas = db_session.query(AssinaturaAnual).filter_by(militante_id=id).first() is not None
if any([tem_cotas, tem_pagamentos, tem_materiais, tem_vendas, tem_assinaturas]):
flash('Não é possível excluir o militante pois existem registros relacionados.', 'error')
return redirect(url_for('listar_militantes'))
db_session.delete(militante)
db_session.commit()
db.delete(militante)
db.commit()
flash('Militante excluído com sucesso!', 'success')
except Exception as e:
db.rollback()
print(f"Erro ao excluir militante: {e}")
db_session.rollback()
flash('Erro ao excluir militante.', 'error')
flash('Erro ao excluir militante', 'danger')
finally:
db.close()
return redirect(url_for('listar_militantes'))
# Rota para criar uma nova cota mensal
# Rota para criar uma nova cota
@app.route("/cotas/novo", methods=["GET", "POST"])
@require_login
@require_permission(Permission.VIEW_CELL_REPORTS)
def nova_cota():
if request.method == "POST":
cotas_mensais = CotaMensal(
militante_id=request.form["militante_id"],
valor_antigo=request.form["valor_antigo"],
valor_novo=request.form["valor_novo"],
data_alteracao=datetime.strptime(request.form["data_alteracao"], "%Y-%m-%d")
)
militante_id = request.form.get("militante_id")
valor_antigo = float(request.form.get("valor_antigo"))
valor_novo = float(request.form.get("valor_novo"))
data_alteracao = datetime.strptime(request.form.get("data_alteracao"), "%Y-%m-%d").date()
data_vencimento = datetime.strptime(request.form.get("data_vencimento"), "%Y-%m-%d").date()
db_session.add(cotas_mensais)
db = get_db_connection()
try:
db_session.commit()
return redirect(url_for("listar_cotas"))
cotas_mensais = CotaMensal(
militante_id=militante_id,
valor_antigo=valor_antigo,
valor_novo=valor_novo,
data_alteracao=data_alteracao,
data_vencimento=data_vencimento,
pago=False
)
db.add(cotas_mensais)
db.commit()
flash('Cota cadastrada com sucesso!', 'success')
return redirect(url_for('listar_cotas'))
except Exception as e:
print(e)
db_session.rollback()
flash('Erro ao cadastrar cota mensal. Verifique se os dados fornecidos são válidos.', 'error')
db.rollback()
print(f"Erro ao cadastrar cota: {e}")
flash('Erro ao cadastrar cota', 'danger')
return render_template("nova_cota.html")
finally:
db.close()
return render_template("nova_cota.html")
db = get_db_connection()
try:
militantes = db.query(Militante).order_by(Militante.nome).all()
return render_template("nova_cota.html", militantes=militantes)
finally:
db.close()
# Rota para listar cotas mensais
# Rota para listar cotas
@app.route("/cotas")
@require_login
@require_permission(Permission.VIEW_CELL_REPORTS)
def listar_cotas():
"""Rota para listar cotas mensais"""
try:
print("Buscando cotas...")
# Buscar cotas com informações do militante
cotas = db_session.query(CotaMensal)\
db = get_db_connection()
cotas = db.query(CotaMensal)\
.join(Militante)\
.order_by(CotaMensal.data_vencimento.desc())\
.all()
# Calcular status das cotas
hoje = datetime.now().date()
print(f"Total de cotas encontradas: {len(cotas)}")
# Calcular status de cada cota
for cota in cotas:
if cota.pago:
cota.status = 'paga'
elif cota.data_vencimento < hoje:
cota.status = 'atrasada'
cota.status = "paga"
elif cota.data_vencimento < datetime.now().date():
cota.status = "atrasada"
else:
cota.status = 'pendente'
cota.status = "pendente"
print(f"Cota {cota.id}: Militante={cota.militante.nome}, Valor={cota.valor_novo}, Status={cota.status}")
return render_template("listar_cotas.html", cotas=cotas)
except Exception as e:
print(f"Erro ao listar cotas: {str(e)}")
flash("Erro ao carregar lista de cotas. Por favor, tente novamente.", "danger")
print(f"Erro ao listar cotas: {e}")
flash('Erro ao listar cotas', 'danger')
return render_template("listar_cotas.html", cotas=[])
finally:
db.close()
# Rota para editar cota
@app.route("/cotas/editar/<int:id>", methods=["GET", "POST"])
@login_required
@session_timeout
def editar_cota(id):
db = get_db_connection()
try:
cota = db.query(CotaMensal).filter_by(id=id).first()
if not cota:
flash('Cota não encontrada', 'danger')
return redirect(url_for('listar_cotas'))
if request.method == "POST":
valor_novo = float(request.form.get("valor_novo"))
data_vencimento = datetime.strptime(request.form.get("data_vencimento"), "%Y-%m-%d").date()
pago = request.form.get("pago") == "true"
cota.valor_novo = valor_novo
cota.data_vencimento = data_vencimento
cota.pago = pago
cota.data_alteracao = datetime.now().date()
db.commit()
flash('Cota atualizada com sucesso!', 'success')
return redirect(url_for('listar_cotas'))
return render_template("editar_cota.html", cota=cota)
except Exception as e:
db.rollback()
print(f"Erro ao editar cota: {e}")
flash('Erro ao editar cota', 'danger')
return redirect(url_for('listar_cotas'))
finally:
db.close()
# Rota para criar um novo pagamento
@app.route("/pagamentos/novo", methods=["GET", "POST"])
@require_login
def novo_pagamento():
"""Rota para criar um novo pagamento"""
user = current_user
# Verificar se o usuário tem permissão para registrar pagamentos em alguma instância
if not (user.has_permission(Permission.REGISTER_CELL_PAYMENT) or
user.has_permission(Permission.REGISTER_SECTOR_PAYMENT) or
user.has_permission(Permission.REGISTER_CR_PAYMENT) or
user.has_permission(Permission.REGISTER_CC_PAYMENT)):
flash('Você não tem permissão para registrar pagamentos.', 'error')
return redirect(url_for('home'))
if request.method == "POST":
try:
militante_id = request.form.get("militante_id")
tipo_pagamento_id = request.form.get("tipo_pagamento_id")
valor = request.form.get("valor")
data_pagamento = request.form.get("data_pagamento")
# Validações
if not all([militante_id, tipo_pagamento_id, valor, data_pagamento]):
flash("Todos os campos são obrigatórios.", "danger")
return redirect(url_for("novo_pagamento"))
valor = float(request.form.get("valor"))
data_pagamento = datetime.strptime(request.form.get("data_pagamento"), "%Y-%m-%d").date()
# Criar novo pagamento
db = get_db_connection()
try:
pagamento = Pagamento(
militante_id=militante_id,
tipo_pagamento_id=tipo_pagamento_id,
valor=float(valor),
data_pagamento=datetime.strptime(data_pagamento, "%Y-%m-%d")
valor=valor,
data_pagamento=data_pagamento
)
db_session.add(pagamento)
db_session.commit()
flash("Pagamento registrado com sucesso!", "success")
return redirect(url_for("listar_pagamentos"))
db.add(pagamento)
db.commit()
flash('Pagamento cadastrado com sucesso!', 'success')
return redirect(url_for('listar_pagamentos'))
except Exception as e:
db_session.rollback()
app.logger.error(f"Erro ao registrar pagamento: {str(e)}")
flash("Erro ao registrar pagamento. Por favor, tente novamente.", "danger")
return redirect(url_for("novo_pagamento"))
db.rollback()
print(f"Erro ao cadastrar pagamento: {e}")
flash('Erro ao cadastrar pagamento', 'danger')
return render_template("novo_pagamento.html")
finally:
db.close()
# GET - Renderizar formulário
militantes = db_session.query(Militante).order_by(Militante.nome).all()
tipos_pagamento = db_session.query(TipoPagamento).order_by(TipoPagamento.descricao).all()
hoje = datetime.now().strftime("%Y-%m-%d")
return render_template("novo_pagamento.html",
militantes=militantes,
tipos_pagamento=tipos_pagamento,
hoje=hoje)
db = get_db_connection()
try:
militantes = db.query(Militante).order_by(Militante.nome).all()
tipos_pagamento = db.query(TipoPagamento).order_by(TipoPagamento.descricao).all()
return render_template("novo_pagamento.html", militantes=militantes, tipos_pagamento=tipos_pagamento)
finally:
db.close()
# Rota para listar pagamentos
@app.route("/pagamentos")
@require_login
def listar_pagamentos():
try:
pagamentos = db_session.query(Pagamento).order_by(Pagamento.data_pagamento.desc()).all()
db = get_db_connection()
pagamentos = db.query(Pagamento).order_by(Pagamento.data_pagamento.desc()).all()
return render_template("listar_pagamentos.html", pagamentos=pagamentos)
except Exception as e:
print(f"Erro ao listar pagamentos: {str(e)}")
flash("Erro ao carregar lista de pagamentos. Por favor, tente novamente.", "danger")
print(f"Erro ao listar pagamentos: {e}")
flash('Erro ao listar pagamentos', 'danger')
return render_template("listar_pagamentos.html", pagamentos=[])
finally:
db.close()
# Rota para criar um novo material vendido
@app.route("/materiais/novo", methods=["GET", "POST"])
@@ -539,25 +606,40 @@ def listar_pagamentos():
@require_permission(Permission.VIEW_CELL_REPORTS)
def novo_material():
if request.method == "POST":
materiais_vendidos = MaterialVendido(
militante_id=request.form["militante_id"],
tipo_material_id=request.form["tipo_material_id"],
descricao=request.form["descricao"],
valor=request.form["valor"],
data_venda=datetime.strptime(request.form["data_venda"], "%Y-%m-%d"),
)
militante_id = request.form.get("militante_id")
tipo_material_id = request.form.get("tipo_material_id")
descricao = request.form.get("descricao")
valor = float(request.form.get("valor"))
data_venda = datetime.strptime(request.form.get("data_venda"), "%Y-%m-%d").date()
db_session.add(materiais_vendidos)
db = get_db_connection()
try:
db_session.commit()
return redirect(url_for("listar_materiais"))
materiais_vendidos = MaterialVendido(
militante_id=militante_id,
tipo_material_id=tipo_material_id,
descricao=descricao,
valor=valor,
data_venda=data_venda
)
db.add(materiais_vendidos)
db.commit()
flash('Material vendido cadastrado com sucesso!', 'success')
return redirect(url_for('listar_materiais'))
except Exception as e:
print(e)
db_session.rollback()
flash('Erro ao cadastrar material vendido. Verifique se os dados fornecidos são válidos.', 'error')
db.rollback()
print(f"Erro ao cadastrar material vendido: {e}")
flash('Erro ao cadastrar material vendido', 'danger')
return render_template("novo_material.html")
finally:
db.close()
return render_template("novo_material.html")
db = get_db_connection()
try:
militantes = db.query(Militante).order_by(Militante.nome).all()
tipos_material = db.query(TipoMaterial).order_by(TipoMaterial.descricao).all()
return render_template("novo_material.html", militantes=militantes, tipos_material=tipos_material)
finally:
db.close()
# Rota para listar materiais vendidos
@app.route("/materiais")
@@ -565,315 +647,352 @@ def novo_material():
@require_permission(Permission.VIEW_CELL_REPORTS)
def listar_materiais():
try:
materiais = db_session.query(MaterialVendido).order_by(MaterialVendido.data_venda.desc()).all()
db = get_db_connection()
materiais = db.query(MaterialVendido).order_by(MaterialVendido.data_venda.desc()).all()
return render_template("listar_materiais.html", materiais=materiais)
except Exception as e:
print(f"Erro ao listar materiais: {str(e)}")
flash("Erro ao carregar lista de materiais. Por favor, tente novamente.", "danger")
print(f"Erro ao listar materiais: {e}")
flash('Erro ao listar materiais', 'danger')
return render_template("listar_materiais.html", materiais=[])
finally:
db.close()
# Rota para criar uma nova venda de jornais avulsos
# Rota para criar uma nova venda de jornal
@app.route("/jornais/novo", methods=["GET", "POST"])
@require_login
@require_permission(Permission.VIEW_CELL_REPORTS)
def nova_venda_jornal():
if request.method == "POST":
vendas_jornais_avulsos = VendaJornalAvulso(
militante_id=request.form["militante_id"],
quantidade=request.form["quantidade"],
valor_total=request.form["valor_total"],
data_venda=datetime.strptime(request.form["data_venda"], "%Y-%m-%d"),
)
militante_id = request.form.get("militante_id")
quantidade = int(request.form.get("quantidade"))
valor_total = float(request.form.get("valor_total"))
data_venda = datetime.strptime(request.form.get("data_venda"), "%Y-%m-%d").date()
db_session.add(vendas_jornais_avulsos)
db = get_db_connection()
try:
db_session.commit()
return redirect(url_for("listar_vendas_jornal"))
vendas_jornais_avulsos = VendaJornalAvulso(
militante_id=militante_id,
quantidade=quantidade,
valor_total=valor_total,
data_venda=data_venda
)
db.add(vendas_jornais_avulsos)
db.commit()
flash('Venda de jornal cadastrada com sucesso!', 'success')
return redirect(url_for('listar_vendas_jornal'))
except Exception as e:
print(e)
db_session.rollback()
flash('Erro ao cadastrar venda de jornal avulso. Verifique se os dados fornecidos são válidos.', 'error')
db.rollback()
print(f"Erro ao cadastrar venda de jornal: {e}")
flash('Erro ao cadastrar venda de jornal', 'danger')
return render_template("nova_venda_jornal.html")
finally:
db.close()
return render_template("nova_venda_jornal.html")
db = get_db_connection()
try:
militantes = db.query(Militante).order_by(Militante.nome).all()
return render_template("nova_venda_jornal.html", militantes=militantes)
finally:
db.close()
# Rota para listar vendas de jornais avulsos
# Rota para listar vendas de jornal
@app.route("/jornais")
@require_login
@require_permission(Permission.VIEW_CELL_REPORTS)
def listar_vendas_jornal():
try:
vendas = db_session.query(VendaJornalAvulso).order_by(VendaJornalAvulso.data_venda.desc()).all()
db = get_db_connection()
vendas = db.query(VendaJornalAvulso).order_by(VendaJornalAvulso.data_venda.desc()).all()
return render_template("listar_vendas_jornal.html", vendas=vendas)
except Exception as e:
print(f"Erro ao listar vendas de jornais: {str(e)}")
flash("Erro ao carregar lista de vendas de jornais. Por favor, tente novamente.", "danger")
print(f"Erro ao listar vendas de jornal: {e}")
flash('Erro ao listar vendas de jornal', 'danger')
return render_template("listar_vendas_jornal.html", vendas=[])
finally:
db.close()
# Rota para criar uma nova assinatura anual
# Rota para criar uma nova assinatura
@app.route("/assinaturas/novo", methods=["GET", "POST"])
@require_login
@require_permission(Permission.VIEW_CELL_REPORTS)
def nova_assinatura():
if request.method == "POST":
assinaturas_anuais = AssinaturaAnual(
militante_id=request.form["militante_id"],
tipo_material_id=request.form["tipo_material_id"],
quantidade=request.form["quantidade"],
valor_total=request.form["valor_total"],
data_inicio=datetime.strptime(request.form["data_inicio"], "%Y-%m-%d"),
data_fim=datetime.strptime(request.form["data_fim"], "%Y-%m-%d")
)
militante_id = request.form.get("militante_id")
tipo_material_id = request.form.get("tipo_material_id")
quantidade = int(request.form.get("quantidade"))
valor_total = float(request.form.get("valor_total"))
data_inicio = datetime.strptime(request.form.get("data_inicio"), "%Y-%m-%d").date()
data_fim = datetime.strptime(request.form.get("data_fim"), "%Y-%m-%d").date()
db_session.add(assinaturas_anuais)
db = get_db_connection()
try:
db_session.commit()
return redirect(url_for("listar_assinaturas"))
assinaturas_anuais = AssinaturaAnual(
militante_id=militante_id,
tipo_material_id=tipo_material_id,
quantidade=quantidade,
valor_total=valor_total,
data_inicio=data_inicio,
data_fim=data_fim
)
db.add(assinaturas_anuais)
db.commit()
flash('Assinatura cadastrada com sucesso!', 'success')
return redirect(url_for('listar_assinaturas'))
except Exception as e:
print(e)
db_session.rollback()
flash('Erro ao cadastrar assinatura anual. Verifique se os dados fornecidos são válidos.', 'error')
db.rollback()
print(f"Erro ao cadastrar assinatura: {e}")
flash('Erro ao cadastrar assinatura', 'danger')
return render_template("nova_assinatura.html")
finally:
db.close()
return render_template("nova_assinatura.html")
db = get_db_connection()
try:
militantes = db.query(Militante).order_by(Militante.nome).all()
tipos_material = db.query(TipoMaterial).order_by(TipoMaterial.descricao).all()
return render_template("nova_assinatura.html", militantes=militantes, tipos_material=tipos_material)
finally:
db.close()
# Rota para listar assinaturas anuais
# Rota para listar assinaturas
@app.route("/assinaturas")
@require_login
@require_permission(Permission.VIEW_CELL_REPORTS)
def listar_assinaturas():
try:
assinaturas = db_session.query(AssinaturaAnual).order_by(AssinaturaAnual.data_inicio.desc()).all()
db = get_db_connection()
assinaturas = db.query(AssinaturaAnual).order_by(AssinaturaAnual.data_inicio.desc()).all()
return render_template("listar_assinaturas.html", assinaturas=assinaturas)
except Exception as e:
print(f"Erro ao listar assinaturas: {str(e)}")
flash("Erro ao carregar lista de assinaturas. Por favor, tente novamente.", "danger")
print(f"Erro ao listar assinaturas: {e}")
flash('Erro ao listar assinaturas', 'danger')
return render_template("listar_assinaturas.html", assinaturas=[])
finally:
db.close()
# Rota para criar um novo relatório de cotas mensais
# Rota para criar um novo relatório de cotas
@app.route("/relatorios/cotas/novo", methods=["GET", "POST"])
@require_login
@require_permission(Permission.VIEW_CELL_REPORTS)
def novo_relatorio_cotas():
if request.method == "POST":
relatorio_cotas_mensais = RelatorioCotasMensais(
setor_id=request.form["setor_id"],
comite_id=request.form["comite_id"],
total_cotas=request.form["total_cotas"],
data_relatorio=datetime.strptime(request.form["data_relatorio"], "%Y-%m-%d")
)
setor_id = request.form.get("setor_id")
comite_id = request.form.get("comite_id")
total_cotas = float(request.form.get("total_cotas"))
data_relatorio = datetime.strptime(request.form.get("data_relatorio"), "%Y-%m-%d").date()
db_session.add(relatorio_cotas_mensais)
db = get_db_connection()
try:
db_session.commit()
return redirect(url_for("listar_relatorios_cotas"))
relatorio_cotas_mensais = RelatorioCotasMensais(
setor_id=setor_id,
comite_id=comite_id,
total_cotas=total_cotas,
data_relatorio=data_relatorio
)
db.add(relatorio_cotas_mensais)
db.commit()
flash('Relatório de cotas cadastrado com sucesso!', 'success')
return redirect(url_for('listar_relatorios_cotas'))
except Exception as e:
print(e)
db_session.rollback()
flash('Erro ao cadastrar relatório de cotas mensais. Verifique se os dados fornecidos são válidos.', 'error')
db.rollback()
print(f"Erro ao cadastrar relatório de cotas: {e}")
flash('Erro ao cadastrar relatório de cotas', 'danger')
return render_template("novo_relatorio_cotas.html")
finally:
db.close()
return render_template("novo_relatorio_cotas.html")
db = get_db_connection()
try:
setores = db.query(Setor).order_by(Setor.nome).all()
comites = db.query(ComiteCentral).order_by(ComiteCentral.nome).all()
return render_template("novo_relatorio_cotas.html", setores=setores, comites=comites)
finally:
db.close()
# Rota para listar relatórios de cotas mensais
# Rota para listar relatórios de cotas
@app.route("/relatorios/cotas")
@require_login
@require_permission(Permission.VIEW_CELL_REPORTS)
def listar_relatorios_cotas():
try:
relatorios = db_session.query(RelatorioCotasMensais).order_by(RelatorioCotasMensais.data_relatorio.desc()).all()
db = get_db_connection()
relatorios = db.query(RelatorioCotasMensais).order_by(RelatorioCotasMensais.data_relatorio.desc()).all()
return render_template("listar_relatorios_cotas.html", relatorios=relatorios)
except Exception as e:
print(f"Erro ao listar relatórios de cotas: {str(e)}")
flash("Erro ao carregar lista de relatórios de cotas. Por favor, tente novamente.", "danger")
print(f"Erro ao listar relatórios de cotas: {e}")
flash('Erro ao listar relatórios de cotas', 'danger')
return render_template("listar_relatorios_cotas.html", relatorios=[])
finally:
db.close()
# Rota para criar um novo relatório de vendas de materiais
# Rota para criar um novo relatório de vendas
@app.route("/relatorios/vendas/novo", methods=["GET", "POST"])
@require_login
@require_permission(Permission.VIEW_CELL_REPORTS)
def novo_relatorio_vendas():
if request.method == "POST":
relatorio_vendas_materiais = RelatorioVendasMateriais(
setor_id=request.form["setor_id"],
comite_id=request.form["comite_id"],
total_vendas=request.form["total_vendas"],
data_relatorio=datetime.strptime(request.form["data_relatorio"], "%Y-%m-%d")
)
setor_id = request.form.get("setor_id")
comite_id = request.form.get("comite_id")
total_vendas = float(request.form.get("total_vendas"))
data_relatorio = datetime.strptime(request.form.get("data_relatorio"), "%Y-%m-%d").date()
db_session.add(relatorio_vendas_materiais)
db = get_db_connection()
try:
db_session.commit()
return redirect(url_for("listar_relatorios_vendas"))
relatorio_vendas_materiais = RelatorioVendasMateriais(
setor_id=setor_id,
comite_id=comite_id,
total_vendas=total_vendas,
data_relatorio=data_relatorio
)
db.add(relatorio_vendas_materiais)
db.commit()
flash('Relatório de vendas cadastrado com sucesso!', 'success')
return redirect(url_for('listar_relatorios_vendas'))
except Exception as e:
print(e)
db_session.rollback()
flash('Erro ao cadastrar relatório de vendas de materiais. Verifique se os dados fornecidos são válidos.', 'error')
db.rollback()
print(f"Erro ao cadastrar relatório de vendas: {e}")
flash('Erro ao cadastrar relatório de vendas', 'danger')
return render_template("novo_relatorio_vendas.html")
finally:
db.close()
return render_template("novo_relatorio_vendas.html")
db = get_db_connection()
try:
setores = db.query(Setor).order_by(Setor.nome).all()
comites = db.query(ComiteCentral).order_by(ComiteCentral.nome).all()
return render_template("novo_relatorio_vendas.html", setores=setores, comites=comites)
finally:
db.close()
# Rota para listar relatórios de vendas de materiais
# Rota para listar relatórios de vendas
@app.route("/relatorios/vendas")
@require_login
@require_permission(Permission.VIEW_CELL_REPORTS)
def listar_relatorios_vendas():
try:
relatorios = db_session.query(RelatorioVendasMateriais).order_by(RelatorioVendasMateriais.data_relatorio.desc()).all()
db = get_db_connection()
relatorios = db.query(RelatorioVendasMateriais).order_by(RelatorioVendasMateriais.data_relatorio.desc()).all()
return render_template("listar_relatorios_vendas.html", relatorios=relatorios)
except Exception as e:
print(f"Erro ao listar relatórios de vendas: {str(e)}")
flash("Erro ao carregar lista de relatórios de vendas. Por favor, tente novamente.", "danger")
print(f"Erro ao listar relatórios de vendas: {e}")
flash('Erro ao listar relatórios de vendas', 'danger')
return render_template("listar_relatorios_vendas.html", relatorios=[])
finally:
db.close()
@app.route('/militantes/<int:id>/editar', methods=['GET', 'POST'])
@require_login
@require_permission(Permission.MANAGE_CELL_MEMBERS)
# Rota para editar militante
@app.route("/militantes/editar/<int:id>", methods=["GET", "POST"])
@login_required
@session_timeout
def editar_militante(id):
militante = db_session.query(Militante).get(id)
db = get_db_connection()
try:
militante = db.query(Militante).get(id)
if not militante:
flash('Militante não encontrado.', 'error')
flash('Militante não encontrado', 'danger')
return redirect(url_for('listar_militantes'))
if request.method == "POST":
cpf = request.form["cpf"]
if cpf != militante.cpf and not validar_cpf(cpf): # Só valida se o CPF foi alterado
flash('CPF inválido. Por favor, verifique o número informado.', 'error')
militante.nome = request.form.get("nome")
militante.cpf = request.form.get("cpf")
militante.email = request.form.get("email")
militante.telefone = request.form.get("telefone")
militante.endereco = request.form.get("endereco")
militante.filiado = request.form.get("filiado") == "on"
if not validar_cpf(militante.cpf):
flash('CPF inválido', 'danger')
return render_template("editar_militante.html", militante=militante)
try:
militante.nome = request.form["nome"]
militante.cpf = cpf
militante.email = request.form["email"]
militante.telefone = request.form["telefone"]
militante.endereco = request.form["endereco"]
militante.filiado = bool(request.form.get("filiado", False))
db_session.commit()
db.commit()
flash('Militante atualizado com sucesso!', 'success')
return redirect(url_for('listar_militantes'))
except Exception as e:
db_session.rollback()
flash('Erro ao atualizar militante. Verifique se o CPF ou email já não estão cadastrados.', 'error')
db.rollback()
print(f"Erro ao atualizar militante: {e}")
flash('Erro ao atualizar militante', 'danger')
return render_template("editar_militante.html", militante=militante)
return render_template("editar_militante.html", militante=militante)
@app.route('/dash')
@require_login
def dashboard_admin():
"""
Rota para o dashboard administrativo
"""
db = get_db_connection()
try:
# Carregar usuários com seus relacionamentos
users = db.query(Usuario).options(
joinedload(Usuario.roles),
joinedload(Usuario.militante)
).all()
# Filtrar usuários baseado nas permissões
if not current_user.has_permission('system_config'):
if current_user.has_permission('manage_cr_sectors'):
users = [u for u in users if u.cr_id == current_user.cr_id]
elif current_user.has_permission('manage_sector_cells'):
users = [u for u in users if u.setor_id == current_user.setor_id]
elif current_user.has_permission('manage_cell_members'):
users = [u for u in users if u.celula_id == current_user.celula_id]
else:
users = []
# Carregar dados necessários antes de fechar a sessão
for user in users:
if user.militante:
user.militante.get_responsabilidades_list()
return render_template('dashboard.html', users=users)
finally:
db.close()
@app.route("/usuarios/<int:user_id>/otp/reset", methods=["POST"])
@require_login
@require_permission('system_config')
def reset_otp(user_id):
"""Resetar OTP de um usuário"""
# Rota para criar um novo usuário
@app.route("/usuarios/novo", methods=["GET", "POST"])
@login_required
def novo_usuario():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
email = request.form.get("email")
role_id = request.form.get("role_id")
setor_id = request.form.get("setor_id")
# Verificar se usuário já existe
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
if db.query(Usuario).filter_by(username=username).first():
flash('Nome de usuário já existe.', 'danger')
return render_template("novo_usuario.html")
# Gerar novo OTP
user.otp_secret = pyotp.random_base32()
db.commit()
return jsonify({'success': True, 'message': 'OTP resetado com sucesso'})
finally:
db.close()
@app.route("/usuarios/<int:user_id>/password/reset", methods=["POST"])
@require_login
@require_permission('system_config')
def reset_password(user_id):
"""Resetar senha de um usuário"""
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
# Gerar nova senha aleatória
new_password = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
user.password_hash = generate_password_hash(new_password)
db.commit()
# Enviar email com a nova senha
try:
msg = Message(
'Sua senha foi resetada',
recipients=[user.email],
body=f'''
Olá {user.username},
Sua senha foi resetada pelo administrador do sistema.
Sua nova senha é: {new_password}
Por favor, altere sua senha após o primeiro login.
Atenciosamente,
Sistema de Controle OCI
'''
novo_usuario = Usuario(
username=username,
email=email,
role_id=role_id,
setor_id=setor_id
)
mail.send(msg)
return jsonify({'success': True, 'message': 'Senha resetada e enviada por email'})
novo_usuario.set_password(password)
novo_usuario.otp_secret = pyotp.random_base32()
db.add(novo_usuario)
db.commit()
flash('Usuário cadastrado com sucesso!', 'success')
return redirect(url_for('listar_usuarios'))
except Exception as e:
db.rollback()
return jsonify({'success': False, 'message': f'Erro ao enviar email: {str(e)}'}), 500
print(f"Erro ao cadastrar usuário: {e}")
flash('Erro ao cadastrar usuário', 'danger')
return render_template("novo_usuario.html")
finally:
db.close()
@app.route('/check_session')
def check_session():
if not current_user.is_authenticated:
return jsonify({'status': 'expired'})
db = get_db_connection()
try:
if current_user.is_session_expired():
current_user.logout()
db.commit()
logout_user()
return jsonify({'status': 'expired'})
current_user.update_last_activity()
db.commit()
return jsonify({'status': 'active'})
roles = db.query(Role).order_by(Role.nome).all()
setores = db.query(Setor).order_by(Setor.nome).all()
return render_template("novo_usuario.html", roles=roles, setores=setores)
finally:
db.close()
# Rota para verificar status da sessão
@app.route('/check_session')
def check_session():
if 'user_id' not in session:
return jsonify({'status': 'expired'})
if 'last_activity' in session:
last_activity = datetime.fromtimestamp(session['last_activity'])
now = datetime.now()
if now - last_activity > timedelta(hours=2):
# Registrar o logout por timeout
try:
db = get_db_connection()
user = db.query(Usuario).get(session.get('user_id'))
if user:
user.ultimo_logout = datetime.now()
user.motivo_logout = "Timeout de sessão"
db.commit()
db.close()
except Exception as e:
print(f"Erro ao registrar logout por timeout: {e}")
session.clear()
return jsonify({'status': 'expired'})
return jsonify({'status': 'active'})
@app.route("/qr/<token>")
def get_qr_code(token):
db = get_db_connection()

View File

@@ -1,91 +1,134 @@
import os
from functions.database import get_db_connection, Usuario
from functions.rbac import Role
import pyotp
from functions.database import init_database, Usuario, Role, get_db_connection
import qrcode
import base64
from io import BytesIO
import os
from pathlib import Path
import pyotp
def create_admin():
"""Cria o usuário admin se não existir"""
db = get_db_connection()
def generate_qr_code(user):
"""
Gera o QR code para um usuário específico
Args:
user: Instância do modelo Usuario
Returns:
Path: Caminho do arquivo QR code gerado
"""
# Gerar QR Code apenas na raiz do projeto
qr_path = Path('admin_qr.png')
# Remover arquivo antigo se existir
if qr_path.exists():
os.remove(str(qr_path))
# Gerar e salvar QR Code
qr = qrcode.QRCode(version=1, box_size=10, border=5)
# Gerar URI do OTP
totp = pyotp.TOTP(user.otp_secret)
otp_uri = totp.provisioning_uri(
name=user.username,
issuer_name="Sistema de Controles"
)
qr.add_data(otp_uri)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save(str(qr_path))
print(f"\nQR Code gerado em: {os.path.abspath(qr_path)}")
return qr_path, otp_uri
def create_admin_user():
"""Cria ou atualiza o usuário admin"""
try:
# Verificar se o admin já existe
admin = db.query(Usuario).filter_by(username='admin').first()
# Inicializar banco de dados
init_database()
# Criar sessão
db = get_db_connection()
try:
# Verificar se já existe um usuário admin
admin = db.query(Usuario).filter_by(username="admin").first()
if admin:
print("Usuário admin já existe")
# Verificar se o arquivo admin_qr.png existe
if os.path.exists('admin_qr.png'):
print("Usando OTP existente do arquivo admin_qr.png")
# Extrair o OTP secret do QR code existente
with open('admin_qr.png', 'rb') as f:
qr_data = f.read()
# Aqui você precisaria implementar a lógica para extrair o OTP secret do QR code
# Por enquanto, vamos apenas manter o OTP existente
return
print("\n=== Usuário Admin Encontrado ===")
else:
print("Gerando novo OTP para o admin...")
# Gerar novo OTP
otp_secret = pyotp.random_base32()
admin.otp_secret = otp_secret
db.commit()
else:
print("Criando usuário admin...")
# Criar usuário admin
print("\n=== Criando Novo Usuário Admin ===")
# Criar novo usuário admin
admin = Usuario(
username='admin',
password='admin123',
username="admin",
email="admin@example.com",
is_admin=True
)
admin.email = 'admin@controles.com'
admin.set_password("admin123")
admin.generate_otp_secret()
# Adicionar e fazer commit
db.add(admin)
db.commit()
# Gerar OTP
otp_secret = pyotp.random_base32()
admin.otp_secret = otp_secret
# Gerar QR code uma única vez
qr_path, otp_uri = generate_qr_code(admin)
# 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}")
print("\n=== QR Code Gerado ===")
print(f"QR Code salvo em: {qr_path}")
print(f"URI do OTP: {otp_uri}")
# 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()
# Atribuir role de Secretário Geral
admin_role = db.query(Role).filter_by(nivel=Role.SECRETARIO_GERAL).first()
if admin_role:
admin.roles.append(admin_role)
db.commit()
# Gerar QR code
totp = pyotp.TOTP(otp_secret)
provisioning_uri = totp.provisioning_uri(admin.username, issuer_name="Sistema de Controles")
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(provisioning_uri)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# Salvar QR code como base64
buffered = BytesIO()
img.save(buffered, format="PNG")
qr_base64 = base64.b64encode(buffered.getvalue()).decode()
# Salvar QR code como arquivo
img.save('admin_qr.png')
print("\nConfiguração do OTP para o admin:")
print(f"OTP Secret: {otp_secret}")
print("\nInstruções:")
print("1. Use um aplicativo autenticador (como Google Authenticator ou Authy)")
print("2. Escaneie o QR code ou insira o OTP Secret manualmente")
print("3. Use o código gerado para fazer login")
print("\nQR code salvo em 'admin_qr.png'")
except Exception as e:
print(f"Erro ao criar admin: {str(e)}")
db.rollback()
raise
raise e
finally:
db.close()
if __name__ == '__main__':
create_admin()
except Exception as e:
print(f"\nErro durante a execução: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
create_admin_user()

View File

@@ -1,6 +1,6 @@
from datetime import datetime, timedelta
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, Numeric, Date, Enum
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, Numeric, Date, Enum, create_engine, text
from sqlalchemy.orm import sessionmaker, relationship, backref
import os
import pyotp
@@ -13,6 +13,7 @@ import enum
from flask_login import UserMixin
from .rbac import Role, Permission, role_permissions, user_roles
from .base import Base, engine, Session
import logging
# Configurar caminho do banco de dados
db_dir = Path.home() / '.local' / 'share' / 'controles'
@@ -296,6 +297,8 @@ class CotaMensal(Base):
valor_antigo = Column(Numeric(10, 2), nullable=False)
valor_novo = Column(Numeric(10, 2), nullable=False)
data_alteracao = Column(Date, nullable=False)
data_vencimento = Column(Date, nullable=False)
pago = Column(Boolean, default=False)
militante = relationship("Militante", back_populates="cotas_mensais")
@@ -456,31 +459,19 @@ class Usuario(Base, UserMixin):
cr = relationship('ComiteRegional', back_populates='usuarios')
celula = relationship('Celula', back_populates='usuarios')
def get_id(self):
return str(self.id)
@property
def is_authenticated(self):
return True
@property
def is_active(self):
return self.ativo
@property
def is_anonymous(self):
return False
def __init__(self, username, password, is_admin=False, email=None, tipo="USUARIO"):
def __init__(self, username, email=None, is_admin=False):
self.username = username
self.password_hash = generate_password_hash(password)
self.email = email
self.is_admin = is_admin
self.email = email
self.ativo = True
self.session_timeout = 30
self.tipo = tipo
self.tipo = "USUARIO"
self.ultima_atividade = datetime.utcnow()
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
@@ -621,73 +612,6 @@ class TransacaoPIX(Base):
pagamento = relationship("Pagamento", back_populates="transacoes_pix")
# Remover o banco de dados existente (se existir)
if os.path.exists(db_path):
os.remove(db_path)
def init_rbac():
"""Inicializa o sistema RBAC"""
print("Inicializando sistema RBAC...")
session = SessionLocal()
try:
# Verificar se já existe um admin
admin = session.query(Usuario).filter_by(username="admin").first()
if not admin:
print("Criando role de administrador...")
# Criar role de admin
admin_role = session.query(Role).filter_by(nome="Administrador").first()
if not admin_role:
admin_role = Role(nome="Administrador", nivel=Role.SECRETARIO_GERAL)
session.add(admin_role)
session.commit()
print("Criando usuário admin...")
# Criar usuário admin
admin = Usuario(
username="admin",
password="admin123",
is_admin=True
)
admin.email = "admin@example.com"
admin.role_id = admin_role.id
# Adicionar apenas a permissão de system_config ao admin
permission = session.query(Permission).filter_by(nome='system_config').first()
if permission and permission not in admin_role.permissions:
admin_role.permissions.append(permission)
session.add(admin)
session.commit()
print("=== Usuário Admin Criado ===")
print(f"Username: admin")
print(f"Senha: admin123")
print(f"Email: {admin.email}")
print(f"OTP Secret: {admin.otp_secret}")
else:
print("Usuário admin já existe")
# Garantir que o admin tenha apenas a permissão de system_config
admin_role = session.query(Role).filter_by(nome="Administrador").first()
if admin_role:
# Remover todas as permissões atuais
admin_role.permissions = []
# Adicionar apenas a permissão de system_config
permission = session.query(Permission).filter_by(nome='system_config').first()
if permission:
admin_role.permissions.append(permission)
session.commit()
except Exception as e:
print(f"Erro na inicialização do sistema RBAC: {e}")
session.rollback()
raise
finally:
session.close()
def init_database():
"""Inicializa o banco de dados com dados básicos"""
print("Inicializando banco de dados...")
@@ -769,12 +693,5 @@ def init_database():
finally:
session.close()
# Inicializar o sistema RBAC
init_rbac()
# Inicializar o banco de dados automaticamente quando o módulo for importado
init_database()
# Executar a criação dos dados iniciais
if __name__ == "__main__":
init_database()

View File

@@ -2,7 +2,8 @@ from datetime import datetime, timedelta
from functions.database import (
Base, Militante, CotaMensal, TipoPagamento, Pagamento,
MaterialVendido, TipoMaterial, VendaJornalAvulso, AssinaturaAnual,
RelatorioCotasMensais, RelatorioVendasMateriais, engine, get_db_connection
RelatorioCotasMensais, RelatorioVendasMateriais, engine, get_db_connection,
Setor, ComiteCentral, Usuario, Role
)
import random
from faker import Faker
@@ -38,26 +39,47 @@ def criar_tipos_material():
db_session.add(TipoMaterial(descricao=tipo))
db_session.commit()
def criar_militantes(quantidade=50):
"""Cria militantes fictícios"""
def criar_militantes(num_militantes):
print(f"\nCriando {num_militantes} militantes...")
militantes = []
for _ in range(quantidade):
emails_usados = set() # Conjunto para rastrear emails já usados
for i in range(num_militantes):
nome = fake.name()
cpf = fake.cpf()
# Gerar email único
while True:
email = fake.email()
if email not in emails_usados:
emails_usados.add(email)
break
telefone = fake.phone_number()
endereco = fake.address()
filiado = fake.boolean()
print(f"Criando militante {i+1}: {nome} (CPF: {cpf})")
militante = Militante(
nome=fake.name(),
cpf=fake.cpf(),
email=fake.email(),
telefone=fake.phone_number(),
endereco=fake.address(),
filiado=random.choice([True, False])
nome=nome,
cpf=cpf,
email=email,
telefone=telefone,
endereco=endereco,
filiado=filiado
)
db_session.add(militante)
militantes.append(militante)
db_session.add_all(militantes)
db_session.commit()
return militantes
def criar_cotas(militantes, quantidade_por_militante=3):
"""Cria cotas mensais fictícias"""
print(f"Criando {quantidade_por_militante} cotas para cada um dos {len(militantes)} militantes...")
for militante in militantes:
print(f"Criando cotas para militante {militante.nome}")
for i in range(quantidade_por_militante):
data_base = datetime.now() - timedelta(days=30 * i)
valor = random.uniform(50, 200)
@@ -70,7 +92,9 @@ def criar_cotas(militantes, quantidade_por_militante=3):
pago=random.choice([True, False])
)
db_session.add(cota)
print(f" Cota criada: valor={valor:.2f}, vencimento={data_base + timedelta(days=30)}")
db_session.commit()
print("Cotas criadas com sucesso!")
def criar_pagamentos(militantes):
"""Cria pagamentos fictícios"""
@@ -155,38 +179,82 @@ def criar_relatorios():
db_session.commit()
def criar_setores():
"""Cria setores padrão"""
setores = [
"Setor 1",
"Setor 2",
"Setor 3",
"Setor 4",
"Setor 5"
]
for setor in setores:
if not db_session.query(Setor).filter_by(nome=setor).first():
db_session.add(Setor(nome=setor))
db_session.commit()
def criar_comites():
"""Cria comitês padrão"""
comites = [
"Comitê 1",
"Comitê 2",
"Comitê 3"
]
for comite in comites:
if not db_session.query(ComiteCentral).filter_by(nome=comite).first():
db_session.add(ComiteCentral(nome=comite))
db_session.commit()
def criar_roles():
"""Cria roles padrão"""
roles = [
("admin", 1), # Nível 1: Administrador
("gestor", 2), # Nível 2: Gestor
("usuario", 3) # Nível 3: Usuário comum
]
for nome, nivel in roles:
if not db_session.query(Role).filter_by(nome=nome).first():
db_session.add(Role(nome=nome, nivel=nivel))
db_session.commit()
def criar_usuario_admin():
"""Cria usuário admin inicial"""
if not db_session.query(Usuario).filter_by(username='admin').first():
role_admin = db_session.query(Role).filter_by(nome='admin').first()
setor = db_session.query(Setor).first()
admin = Usuario(
username='admin',
email='admin@example.com',
is_admin=True,
ativo=True,
role_id=role_admin.id if role_admin else None,
setor_id=setor.id if setor else None
)
admin.set_password('admin123') # Método que deve existir na classe Usuario
db_session.add(admin)
db_session.commit()
print("Usuário admin criado com sucesso!")
def seed_database():
"""Função principal para popular o banco de dados com dados fictícios"""
print("Iniciando população do banco de dados com dados fictícios...")
print("Populando banco de dados com dados fictícios...")
print("Criando tipos de pagamento...")
criar_tipos_pagamento()
print("Criando tipos de material...")
criar_tipos_material()
criar_setores()
criar_comites()
criar_roles()
print("Criando militantes...")
militantes = criar_militantes(50)
print("Criando cotas mensais...")
criar_cotas(militantes)
print("Criando pagamentos...")
criar_pagamentos(militantes)
print("Criando materiais vendidos...")
criar_materiais_vendidos(militantes)
print("Criando vendas de jornal...")
criar_vendas_jornal(militantes)
print("Criando assinaturas...")
criar_assinaturas(militantes)
print("Criando relatórios...")
criar_relatorios()
print("Banco de dados populado com sucesso!")
print("Dados fictícios criados com sucesso!")
if __name__ == "__main__":
seed_database()

View File

@@ -227,47 +227,41 @@ body {
/* Alert styles */
.alert {
position: fixed;
top: 1rem;
left: 50%;
transform: translateX(-50%);
z-index: 1050;
min-width: 300px;
max-width: 90%;
text-align: center;
border: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 1rem;
backdrop-filter: blur(5px);
border-radius: 12px;
border-radius: 8px;
padding: 1rem 1.5rem;
margin-bottom: 1rem;
opacity: 1 !important;
background-color: rgba(255, 255, 255, 0.98) !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.alert i {
font-size: 1.2rem;
margin-right: 8px;
}
.alert-success {
background: rgba(40, 167, 69, 0.95);
color: white;
color: #155724 !important;
background-color: #d4edda !important;
border-left: 4px solid #28a745;
}
.alert-danger {
background: rgba(220, 53, 69, 0.95);
color: white;
color: #721c24 !important;
background-color: #f8d7da !important;
border-left: 4px solid #dc3545;
}
.alert-warning {
background: rgba(255, 193, 7, 0.95);
color: #333;
color: #856404 !important;
background-color: #fff3cd !important;
border-left: 4px solid #ffc107;
}
.alert-info {
background: rgba(23, 162, 184, 0.95);
color: white;
color: #0c5460 !important;
background-color: #d1ecf1 !important;
border-left: 4px solid #17a2b8;
}
/* Animações para feedback */
@@ -417,3 +411,40 @@ body {
height: 30px;
}
}
.welcome-header {
background: linear-gradient(to right, var(--background-color), rgba(232, 0, 12, 0.05));
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.welcome-header h2 {
font-size: 2rem;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
.welcome-header h4 {
font-size: 1.2rem;
color: var(--secondary-color);
opacity: 0.8;
margin: 0;
}
.card-header {
background: linear-gradient(to right, var(--secondary-dark), var(--secondary-color));
color: var(--text-light);
padding: 1rem 1.5rem;
border: none;
}
.list-group-item-action {
transition: all 0.3s ease;
}
.list-group-item-action:hover {
transform: translateX(5px);
background-color: rgba(232, 0, 12, 0.05);
}

View File

@@ -0,0 +1,29 @@
{% extends "layout.html" %}
{% block content %}
<div class="container mt-4">
<h2>Editar Cota</h2>
<form method="POST" class="needs-validation" novalidate>
<div class="mb-3">
<label for="valor_novo" class="form-label">Valor</label>
<input type="number" step="0.01" class="form-control" id="valor_novo" name="valor_novo" value="{{ cota.valor_novo }}" required>
<div class="invalid-feedback">
Por favor, insira um valor válido.
</div>
</div>
<div class="mb-3">
<label for="data_vencimento" class="form-label">Data de Vencimento</label>
<input type="date" class="form-control" id="data_vencimento" name="data_vencimento" value="{{ cota.data_vencimento }}" required>
<div class="invalid-feedback">
Por favor, selecione uma data de vencimento.
</div>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="pago" name="pago" value="true" {% if cota.pago %}checked{% endif %}>
<label class="form-check-label" for="pago">Pago</label>
</div>
<button type="submit" class="btn btn-primary">Salvar</button>
<a href="{{ url_for('listar_cotas') }}" class="btn btn-secondary">Cancelar</a>
</form>
</div>
{% endblock %}

View File

@@ -5,9 +5,12 @@
{% block content %}
<div class="row g-4">
<div class="col-12">
<h3 class="date-header">
<div class="welcome-header">
<h2 class="mb-2">Olá, {{ nome_usuario }}!</h2>
<h4 class="text-muted">
{{ data_atual }}
</h3>
</h4>
</div>
</div>
{% if current_user.has_permission('view_cell_data') %}
@@ -37,16 +40,16 @@
<div class="col-md-6 col-lg-3">
<div class="card bg-success text-white">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title mb-0">Total de Cotas</h6>
<h2 class="my-2">R$ {{ total_cotas }}</h2>
<h6 class="card-title mb-2">Total de Cotas</h6>
<div class="d-flex justify-content-between align-items-start">
<div class="valor-container">
<h2 class="valor-cota mb-0">R$ {{ total_cotas }}</h2>
</div>
<div class="fs-1">
<div class="icon-container">
<i class="fas fa-dollar-sign"></i>
</div>
</div>
<a href="{{ url_for('listar_cotas') }}" class="text-white text-decoration-none">
<a href="{{ url_for('listar_cotas') }}" class="text-white text-decoration-none mt-2 d-block">
Ver detalhes <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
@@ -100,7 +103,7 @@
<!-- Últimos Militantes -->
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-light">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-user-plus me-2"></i>Últimos Militantes Cadastrados
</h5>
@@ -112,7 +115,7 @@
<a href="{{ url_for('editar_militante', id=militante.id) }}" class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">{{ militante.nome }}</h6>
<small class="text-muted">{{ militante.created_at.strftime('%d/%m/%Y') }}</small>
<small class="text-muted">#{{ militante.id }}</small>
</div>
<small class="text-muted">{{ militante.email }}</small>
</a>
@@ -156,4 +159,46 @@
</div>
{% endif %}
</div>
<style>
.welcome-header {
background: linear-gradient(to right, var(--background-color), rgba(232, 0, 12, 0.05));
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.welcome-header h2 {
font-size: 2rem;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
.welcome-header h4 {
font-size: 1.2rem;
color: var(--secondary-color);
opacity: 0.8;
margin: 0;
}
.valor-container {
flex: 1;
min-width: 0; /* Permite que o texto quebre corretamente */
}
.valor-cota {
font-size: calc(1.2rem + 0.8vw);
line-height: 1.2;
word-break: break-word;
margin-right: 0.5rem;
}
.icon-container {
font-size: 1.5rem;
opacity: 0.8;
margin-left: 8px;
margin-top: 4px;
}
</style>
{% endblock %}