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:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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
|
||||
|
||||
2
Makefile
2
Makefile
@@ -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
743
app.py
@@ -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()
|
||||
|
||||
187
create_admin.py
187
create_admin.py
@@ -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()
|
||||
|
||||
Binary file not shown.
@@ -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()
|
||||
126
seed_data.py
126
seed_data.py
@@ -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()
|
||||
@@ -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);
|
||||
}
|
||||
29
templates/editar_cota.html
Normal file
29
templates/editar_cota.html
Normal 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 %}
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user