diff --git a/app.py b/app.py index e9e59eb..44e556a 100644 --- a/app.py +++ b/app.py @@ -22,6 +22,16 @@ from functions.database import ( init_database, EstadoMilitante, Endereco, + TipoComprovante, + Comprovante, + VendaJornal, + AssinaturaJornal, + CampanhaFinanceira, + TransacaoPIX, + Permission, + Role, + RolePermission, + UserRole ) from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, joinedload @@ -34,7 +44,7 @@ from time import time from flask_mail import Mail, Message import os from dotenv import load_dotenv -from functions.rbac import Role, Permission, init_rbac +from functions.rbac import init_rbac from functions.decorators import require_permission, require_role, require_minimum_role, require_login, require_instance_permission, require_instance_access import re import secrets @@ -272,15 +282,15 @@ def create_app(): .limit(5)\ .all() - # Buscar últimos pagamentos - ultimos_pagamentos = db.query(Pagamento)\ + # Buscar últimos comprovantes + ultimos_comprovantes = db.query(Comprovante)\ .join(Militante)\ - .order_by(Pagamento.data_pagamento.desc())\ + .order_by(Comprovante.data_comprovante.desc())\ .limit(5)\ .all() - - # Buscar tipos de pagamento - tipos_pagamento = db.query(TipoPagamento).all() + + # Buscar tipos de comprovante + tipos_comprovante = db.query(TipoComprovante).all() return render_template('home.html', nome_usuario=nome_usuario, @@ -290,9 +300,9 @@ def create_app(): total_materiais=total_materiais, total_assinaturas=total_assinaturas, ultimos_militantes=ultimos_militantes, - ultimos_pagamentos=ultimos_pagamentos, - tipos_pagamento=tipos_pagamento, - Militante=Militante) + ultimos_comprovantes=ultimos_comprovantes, + tipos_comprovante=tipos_comprovante, + user=current_user) except Exception as e: print(f"Erro na página inicial: {e}") import traceback @@ -306,7 +316,7 @@ def create_app(): total_materiais=0, total_assinaturas=0, ultimos_militantes=[], - ultimos_pagamentos=[], + ultimos_comprovantes=[], Militante=Militante) finally: db.close() @@ -713,190 +723,240 @@ def create_app(): # Rota para criar um novo material vendido @app.route("/materiais/novo", methods=["GET", "POST"]) @require_login - @require_permission(Permission.VIEW_CELL_REPORTS) + @require_permission(Permission.MANAGE_MATERIALS) def novo_material(): - db = get_db_connection() - try: - 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 = converter_data(request.form.get('data_venda')) + if request.method == "POST": + try: + nome = request.form.get("nome") + descricao = request.form.get("descricao") + preco = float(request.form.get("preco")) + quantidade = int(request.form.get("quantidade")) + + if not all([nome, descricao, preco, quantidade]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("novo_material")) + + db = get_db_connection() + material = MaterialVendido(nome=nome, valor=valor) + db.add(material) + db.commit() + + flash("Material cadastrado com sucesso", "success") + return redirect(url_for("listar_materiais")) + except Exception as e: + flash(f"Erro ao cadastrar material: {str(e)}", "danger") + return redirect(url_for("novo_material")) + return render_template("novo_material.html") - material = MaterialVendido( - militante_id=militante_id, - tipo_material_id=tipo_material_id, - descricao=descricao, - valor=valor, - data_venda=data_venda - ) - - db.add(material) - db.commit() - - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'success', - 'message': 'Material cadastrado com sucesso!' - }) - - flash('Material cadastrado com sucesso!', 'success') - return redirect(url_for('listar_materiais')) - - except Exception as e: - db.rollback() - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'error', - 'message': 'Erro ao cadastrar material. Por favor, tente novamente.' - }), 400 - - flash('Erro ao cadastrar material. Por favor, tente novamente.', 'error') - return redirect(url_for('listar_materiais')) - finally: - db.close() - - # Rota para listar materiais vendidos @app.route("/materiais") @require_login - @require_permission(Permission.VIEW_CELL_REPORTS) + @require_permission(Permission.MANAGE_MATERIALS) def listar_materiais(): db = get_db_connection() - try: - materiais = db.query(MaterialVendido).join(Militante).join(TipoMaterial).all() - militantes = db.query(Militante).all() - tipos_material = db.query(TipoMaterial).all() - return render_template('listar_materiais.html', - materiais=materiais, - militantes=militantes, - tipos_material=tipos_material) - finally: - db.close() + materiais = db.query(MaterialVendido).all() + return render_template("listar_materiais.html", materiais=materiais) + + @app.route("/materiais/vendidos/novo", methods=["GET", "POST"]) + @require_login + @require_permission(Permission.MANAGE_MATERIALS) + def novo_material_vendido(): + if request.method == "POST": + try: + militante_id = request.form.get("militante_id") + material_id = request.form.get("material_id") + quantidade = request.form.get("quantidade") + data_venda = request.form.get("data_venda") + + if not all([militante_id, material_id, quantidade, data_venda]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("novo_material_vendido")) + + db = get_db_connection() + material_vendido = MaterialVendido( + militante_id=militante_id, + material_id=material_id, + quantidade=quantidade, + data_venda=data_venda + ) + db.add(material_vendido) + db.commit() + + flash("Material vendido registrado com sucesso", "success") + return redirect(url_for("listar_materiais_vendidos")) + except Exception as e: + flash(f"Erro ao registrar material vendido: {str(e)}", "danger") + return redirect(url_for("novo_material_vendido")) + + db = get_db_connection() + militantes = db.query(Militante).all() + materiais = db.query(MaterialVendido).all() + return render_template("novo_material_vendido.html", + militantes=militantes, + materiais=materiais) + + @app.route("/materiais/vendidos") + @require_login + @require_permission(Permission.MANAGE_MATERIALS) + def listar_materiais_vendidos(): + db = get_db_connection() + materiais_vendidos = db.query(MaterialVendido).all() + return render_template("listar_materiais_vendidos.html", + materiais_vendidos=materiais_vendidos) # Rota para criar uma nova venda de jornal - @app.route("/jornais/novo", methods=["GET", "POST"]) + @app.route("/vendas/jornal/novo", methods=["GET", "POST"]) @require_login - @require_permission(Permission.VIEW_CELL_REPORTS) + @require_permission(Permission.MANAGE_MATERIALS) def nova_venda_jornal(): if request.method == "POST": try: - militante_id = request.form.get('militante_id') - quantidade = int(request.form.get('quantidade')) - valor_total = float(request.form.get('valor_total')) - data_venda = converter_data(request.form.get('data_venda')) + militante_id = request.form.get("militante_id") + quantidade = request.form.get("quantidade") + data_venda = request.form.get("data_venda") - if not validar_data(data_venda): - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'error', - 'message': 'Data de venda inválida ou futura' - }), 400 - flash('Data de venda inválida ou futura', 'danger') - return redirect(url_for('nova_venda_jornal')) + if not all([militante_id, quantidade, data_venda]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("nova_venda_jornal")) db = get_db_connection() - venda = VendaJornalAvulso( + venda_jornal = VendaJornal( militante_id=militante_id, quantidade=quantidade, - valor_total=valor_total, data_venda=data_venda ) - db.add(venda) + db.add(venda_jornal) db.commit() - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'success', - 'message': 'Venda cadastrada com sucesso!' - }) - flash('Venda cadastrada com sucesso!', 'success') - return redirect(url_for('listar_vendas_jornal')) + flash("Venda de jornal registrada com sucesso", "success") + return redirect(url_for("listar_vendas_jornal")) except Exception as e: - db.rollback() - app.logger.error(f"Erro ao cadastrar venda: {e}") - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'error', - 'message': 'Erro ao cadastrar venda' - }), 400 - flash('Erro ao cadastrar venda', 'danger') - return redirect(url_for('nova_venda_jornal')) - finally: - db.close() + flash(f"Erro ao registrar venda de jornal: {str(e)}", "danger") + return redirect(url_for("nova_venda_jornal")) + + db = get_db_connection() + militantes = db.query(Militante).all() + return render_template("nova_venda_jornal.html", militantes=militantes) - # Rota para listar vendas de jornal - @app.route("/jornais") + @app.route("/vendas/jornal") @require_login - @require_permission(Permission.VIEW_CELL_REPORTS) + @require_permission(Permission.MANAGE_MATERIALS) def listar_vendas_jornal(): db = get_db_connection() - try: - vendas = db.query(VendaJornalAvulso).join(Militante).all() - militantes = db.query(Militante).all() - return render_template('listar_vendas_jornal.html', - vendas=vendas, - militantes=militantes) - finally: - db.close() + vendas_jornal = db.query(VendaJornal).all() + return render_template("listar_vendas_jornal.html", vendas_jornal=vendas_jornal) # 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) + @require_permission(Permission.MANAGE_REPORTS) def novo_relatorio_cotas(): if request.method == "POST": try: - setor_id = request.form.get("setor_id") - comite_id = request.form.get("comite_id") - total_cotas = float(request.form.get("total_cotas")) - data_relatorio = request.form.get("data_relatorio") + data_inicio = request.form.get("data_inicio") + data_fim = request.form.get("data_fim") - # Validar data - if not validar_data(data_relatorio): - flash('Data do relatório inválida', 'danger') - return render_template("novo_relatorio_cotas.html") - - # Converter data - data_relatorio = converter_data(data_relatorio) - - # Validar data futura - if data_relatorio > date.today(): - flash('A data do relatório não pode ser futura', 'danger') - return render_template("novo_relatorio_cotas.html") + if not all([data_inicio, data_fim]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("novo_relatorio_cotas")) db = get_db_connection() - try: - 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: - db.rollback() - app.logger.error(f"Erro ao cadastrar relatório de cotas: {e}") - flash('Erro ao cadastrar relatório de cotas', 'danger') - return render_template("novo_relatorio_cotas.html") - finally: - db.close() - except ValueError as e: - flash(str(e), 'danger') - return render_template("novo_relatorio_cotas.html") + cotas = db.query(CotaMensal).filter( + CotaMensal.data_cota.between(data_inicio, data_fim) + ).all() + + return render_template("relatorio_cotas.html", + cotas=cotas, + data_inicio=data_inicio, + data_fim=data_fim) + except Exception as e: + flash(f"Erro ao gerar relatório: {str(e)}", "danger") + return redirect(url_for("novo_relatorio_cotas")) + + 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, - hoje=date.today().strftime('%Y-%m-%d')) - finally: - db.close() + @app.route("/relatorios/jornais/novo", methods=["GET", "POST"]) + @require_login + @require_permission(Permission.MANAGE_REPORTS) + def novo_relatorio_jornais(): + if request.method == "POST": + try: + data_inicio = request.form.get("data_inicio") + data_fim = request.form.get("data_fim") + + if not all([data_inicio, data_fim]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("novo_relatorio_jornais")) + + db = get_db_connection() + jornais = db.query(VendaJornal).filter( + VendaJornal.data_venda.between(data_inicio, data_fim) + ).all() + + return render_template("relatorio_jornais.html", + jornais=jornais, + data_inicio=data_inicio, + data_fim=data_fim) + except Exception as e: + flash(f"Erro ao gerar relatório: {str(e)}", "danger") + return redirect(url_for("novo_relatorio_jornais")) + + return render_template("novo_relatorio_jornais.html") + + @app.route("/relatorios/assinaturas/novo", methods=["GET", "POST"]) + @require_login + @require_permission(Permission.MANAGE_REPORTS) + def novo_relatorio_assinaturas(): + if request.method == "POST": + try: + data_inicio = request.form.get("data_inicio") + data_fim = request.form.get("data_fim") + + if not all([data_inicio, data_fim]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("novo_relatorio_assinaturas")) + + db = get_db_connection() + assinaturas = db.query(AssinaturaJornal).filter( + AssinaturaJornal.data_assinatura.between(data_inicio, data_fim) + ).all() + + return render_template("relatorio_assinaturas.html", + assinaturas=assinaturas, + data_inicio=data_inicio, + data_fim=data_fim) + except Exception as e: + flash(f"Erro ao gerar relatório: {str(e)}", "danger") + return redirect(url_for("novo_relatorio_assinaturas")) + + return render_template("novo_relatorio_assinaturas.html") + + @app.route("/relatorios/campanhas/novo", methods=["GET", "POST"]) + @require_login + @require_permission(Permission.MANAGE_REPORTS) + def novo_relatorio_campanhas(): + if request.method == "POST": + try: + data_inicio = request.form.get("data_inicio") + data_fim = request.form.get("data_fim") + + if not all([data_inicio, data_fim]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("novo_relatorio_campanhas")) + + db = get_db_connection() + campanhas = db.query(CampanhaFinanceira).filter( + CampanhaFinanceira.data_campanha.between(data_inicio, data_fim) + ).all() + + return render_template("relatorio_campanhas.html", + campanhas=campanhas, + data_inicio=data_inicio, + data_fim=data_fim) + except Exception as e: + flash(f"Erro ao gerar relatório: {str(e)}", "danger") + return redirect(url_for("novo_relatorio_campanhas")) + + return render_template("novo_relatorio_campanhas.html") # Rota para listar relatórios de cotas @app.route("/relatorios/cotas") @@ -1223,486 +1283,6 @@ def create_app(): db.close() return decorated_function(cr_id) - @app.route('/celulas//pagamentos') - @require_login - def list_pagamentos_celula(celula_id): - @require_instance_access('celula', celula_id) - def decorated_function(celula_id): - db = get_db_connection() - try: - pagamentos = db.query(Pagamento).filter_by(celula_id=celula_id).all() - return render_template('pagamentos/list.html', pagamentos=pagamentos) - finally: - db.close() - return decorated_function(celula_id) - - @app.route('/setores//pagamentos') - @require_login - def list_pagamentos_setor(setor_id): - @require_instance_access('setor', setor_id) - def decorated_function(setor_id): - db = get_db_connection() - try: - pagamentos = db.query(Pagamento).join(Usuario).filter(Usuario.setor_id == setor_id).all() - return render_template('pagamentos/list.html', pagamentos=pagamentos) - finally: - db.close() - return decorated_function(setor_id) - - @app.route('/crs//pagamentos') - @require_login - def list_pagamentos_cr(cr_id): - @require_instance_access('cr', cr_id) - def decorated_function(cr_id): - db = get_db_connection() - try: - pagamentos = db.query(Pagamento).join(Usuario).filter(Usuario.cr_id == cr_id).all() - return render_template('pagamentos/list.html', pagamentos=pagamentos) - finally: - db.close() - return decorated_function(cr_id) - - @app.route('/celulas//pagamentos/novo', methods=['GET', 'POST']) - @require_login - @require_instance_permission('REGISTER_CELL_PAYMENT', 'celula_id') - def novo_pagamento_celula(celula_id): - if request.method == 'POST': - db = get_db_connection() - try: - pagamento = Pagamento( - valor=request.form['valor'], - data=request.form['data'], - militante_id=request.form['militante_id'], - celula_id=celula_id - ) - db.add(pagamento) - db.commit() - flash('Pagamento registrado com sucesso!', 'success') - return redirect(url_for('list_pagamentos_celula', celula_id=celula_id)) - finally: - db.close() - return render_template('pagamentos/form.html') - - @app.route('/setores//pagamentos/novo', methods=['GET', 'POST']) - @require_login - @require_instance_permission('REGISTER_SECTOR_PAYMENT', 'setor_id') - def novo_pagamento_setor(setor_id): - if request.method == 'POST': - db = get_db_connection() - try: - pagamento = Pagamento( - valor=request.form['valor'], - data=request.form['data'], - militante_id=request.form['militante_id'], - setor_id=setor_id - ) - db.add(pagamento) - db.commit() - flash('Pagamento registrado com sucesso!', 'success') - return redirect(url_for('list_pagamentos_setor', setor_id=setor_id)) - finally: - db.close() - return render_template('pagamentos/form.html') - - @app.route('/crs//pagamentos/novo', methods=['GET', 'POST']) - @require_login - @require_instance_permission('REGISTER_CR_PAYMENT', 'cr_id') - def novo_pagamento_cr(cr_id): - if request.method == 'POST': - db = get_db_connection() - try: - pagamento = Pagamento( - valor=request.form['valor'], - data=request.form['data'], - militante_id=request.form['militante_id'], - cr_id=cr_id - ) - db.add(pagamento) - db.commit() - flash('Pagamento registrado com sucesso!', 'success') - return redirect(url_for('list_pagamentos_cr', cr_id=cr_id)) - finally: - db.close() - return render_template('pagamentos/form.html') - - @app.route("/alterar_senha", methods=["GET", "POST"]) - @require_login - def alterar_senha(): - """Rota para alterar a senha do usuário""" - if request.method == "POST": - senha_atual = request.form.get("senha_atual") - nova_senha = request.form.get("nova_senha") - confirmar_senha = request.form.get("confirmar_senha") - - if not all([senha_atual, nova_senha, confirmar_senha]): - flash("Todos os campos são obrigatórios.", "error") - return redirect(url_for("alterar_senha")) - - if nova_senha != confirmar_senha: - flash("As senhas não coincidem.", "error") - return redirect(url_for("alterar_senha")) - - db = get_db_connection() - try: - user = db.query(Usuario).get(current_user.id) - if not user.check_password(senha_atual): - flash("Senha atual incorreta.", "error") - return redirect(url_for("alterar_senha")) - - user.password_hash = generate_password_hash(nova_senha) - db.commit() - flash("Senha alterada com sucesso!", "success") - return redirect(url_for("home")) - finally: - db.close() - - return render_template("alterar_senha.html") - - @app.route('/usuarios//toggle_status', methods=['POST']) - @require_login - def toggle_user_status(user_id): - user = db_session.query(Usuario).get_or_404(user_id) - - # Verificar permissões baseado na hierarquia - if not current_user.has_permission('system_config'): - if current_user.has_permission('manage_cr_sectors'): - # Secretário de CR só pode gerenciar membros do seu CR - if user.cr_id != current_user.cr_id: - flash('Você não tem permissão para gerenciar este usuário.', 'danger') - return redirect(url_for('dashboard_admin')) - elif current_user.has_permission('manage_sector_cells'): - # Secretário de Setor só pode gerenciar membros do seu setor - if user.setor_id != current_user.setor_id: - flash('Você não tem permissão para gerenciar este usuário.', 'danger') - return redirect(url_for('dashboard_admin')) - elif current_user.has_permission('manage_cell_members'): - # Secretário de Célula só pode gerenciar membros da sua célula - if user.celula_id != current_user.celula_id: - flash('Você não tem permissão para gerenciar este usuário.', 'danger') - return redirect(url_for('dashboard_admin')) - else: - # Militante básico não pode gerenciar ninguém - flash('Você não tem permissão para gerenciar usuários.', 'danger') - return redirect(url_for('dashboard_admin')) - - user.ativo = not user.ativo - db_session.commit() - - return jsonify({'success': True, 'message': 'Status do usuário alterado com sucesso!'}) - - @app.route('/usuarios//alterar_nivel', methods=['POST']) - @require_login - def alterar_nivel(user_id): - user = db_session.query(Usuario).get_or_404(user_id) - novo_nivel = request.json.get('nivel') - - # Verificar permissões baseado na hierarquia - if not current_user.has_permission('system_config'): - if current_user.has_permission('manage_cr_sectors'): - # Secretário de CR só pode alterar níveis dentro do seu CR - if user.cr_id != current_user.cr_id: - return jsonify({'success': False, 'message': 'Você não tem permissão para alterar o nível deste usuário.'}) - elif current_user.has_permission('manage_sector_cells'): - # Secretário de Setor só pode alterar níveis dentro do seu setor - if user.setor_id != current_user.setor_id: - return jsonify({'success': False, 'message': 'Você não tem permissão para alterar o nível deste usuário.'}) - else: - # Outros níveis não podem alterar níveis - return jsonify({'success': False, 'message': 'Você não tem permissão para alterar níveis de usuários.'}) - - # Verificar se o novo nível é válido para o nível hierárquico do usuário atual - if current_user.has_permission('system_config'): - # Secretário Geral e Secretário de Organização podem alterar para qualquer nível - pass - elif current_user.has_permission('manage_cr_sectors'): - # Secretário de CR só pode alterar para níveis do CR - if novo_nivel not in ['membro_cr', 'secretario_cr']: - return jsonify({'success': False, 'message': 'Nível inválido para este CR.'}) - elif current_user.has_permission('manage_sector_cells'): - # Secretário de Setor só pode alterar para níveis do setor - if novo_nivel not in ['membro_setor', 'secretario_setor']: - return jsonify({'success': False, 'message': 'Nível inválido para este setor.'}) - - # Atualizar o nível do usuário - user.role = novo_nivel - db_session.commit() - - return jsonify({'success': True, 'message': 'Nível do usuário alterado com sucesso!'}) - - @app.route('/usuarios//toggle_quadro_orientador', methods=['POST']) - @require_login - def toggle_quadro_orientador(user_id): - db = get_db_connection() - try: - user = db.query(Usuario).get(user_id) - if not user: - return jsonify({'success': False, 'message': 'Usuário não encontrado'}), 404 - - # Verificar permissões - if not (current_user.has_permission('system_config') or - (current_user.has_permission('manage_cr_sectors') and user.cr_id == current_user.cr_id) or - (current_user.has_permission('manage_sector_cells') and user.setor_id == current_user.setor_id)): - return jsonify({'success': False, 'message': 'Você não tem permissão para alterar esta responsabilidade'}), 403 - - # Verificar se o usuário tem um militante associado - if not user.militante: - return jsonify({'success': False, 'message': 'Usuário não tem um militante associado'}), 400 - - # Alternar o status de Quadro-Orientador - user.militante.quadro_orientador = not user.militante.quadro_orientador - - # Atualizar a responsabilidade no campo responsabilidades - if user.militante.quadro_orientador: - user.militante.responsabilidades |= Militante.QUADRO_ORIENTADOR - else: - user.militante.responsabilidades &= ~Militante.QUADRO_ORIENTADOR - - db.commit() - return jsonify({ - 'success': True, - 'message': f'Responsabilidade de Quadro-Orientador {"adicionada" if user.militante.quadro_orientador else "removida"} com sucesso' - }) - except Exception as e: - db.rollback() - return jsonify({'success': False, 'message': str(e)}), 500 - finally: - db.close() - - @app.route("/cotas/excluir/", methods=["POST"]) - @login_required - @session_timeout - def excluir_cota(id): - """Exclui uma cota mensal""" - db = get_db_connection() - try: - cota = db.query(CotaMensal).get(id) - if not cota: - flash('Cota não encontrada.', 'danger') - return redirect(url_for('listar_cotas')) - - # Excluir a cota - db.delete(cota) - db.commit() - flash('Cota excluída com sucesso!', 'success') - except Exception as e: - db.rollback() - flash('Erro ao excluir cota. Por favor, tente novamente.', 'danger') - print(f"Erro ao excluir cota: {e}") - finally: - db.close() - return redirect(url_for('listar_cotas')) - - @app.route("/assinaturas") - @require_login - @require_permission(Permission.MANAGE_CELL_REPORTS) - def listar_assinaturas(): - db = get_db_connection() - try: - assinaturas = db.query(AssinaturaAnual).join(Militante).all() - militantes = db.query(Militante).all() - return render_template('listar_assinaturas.html', - assinaturas=assinaturas, - militantes=militantes) - finally: - db.close() - - @app.route("/assinaturas/novo", methods=['POST']) - @require_login - @require_permission(Permission.MANAGE_CELL_REPORTS) - def nova_assinatura(): - db = get_db_connection() - try: - data = request.form - - # Validar dados - if not all(k in data for k in ['militante_id', 'data_inicio', 'data_fim', 'valor']): - return jsonify({'success': False, 'message': 'Todos os campos são obrigatórios'}) - - # Criar nova assinatura - assinatura = AssinaturaAnual( - militante_id=data['militante_id'], - data_inicio=datetime.strptime(data['data_inicio'], '%Y-%m-%d'), - data_fim=datetime.strptime(data['data_fim'], '%Y-%m-%d'), - valor=float(data['valor']) - ) - - db.add(assinatura) - db.commit() - - return jsonify({'success': True}) - except Exception as e: - db.rollback() - return jsonify({'success': False, 'message': str(e)}) - finally: - db.close() - - @app.route("/assinaturas/excluir/", methods=['POST']) - @require_login - @require_permission(Permission.MANAGE_CELL_REPORTS) - def excluir_assinatura(id): - db = get_db_connection() - try: - assinatura = db.query(AssinaturaAnual).get(id) - if not assinatura: - return jsonify({'success': False, 'message': 'Assinatura não encontrada'}) - - db.delete(assinatura) - db.commit() - - return jsonify({'success': True}) - except Exception as e: - db.rollback() - return jsonify({'success': False, 'message': str(e)}) - finally: - db.close() - - @app.route("/militantes/dados/") - @require_login - @require_permission('gerenciar_militantes') - def buscar_dados_militante(militante_id): - """Busca os dados de um militante específico""" - db = get_db_connection() - try: - militante = db.query(Militante).get(militante_id) - if not militante: - print(f"Militante não encontrado: ID {militante_id}") - return jsonify({ - 'status': 'error', - 'message': 'Militante não encontrado' - }), 404 - - # Função auxiliar para formatar data com validação - def formatar_data_segura(data): - try: - if not data: - return None - return data.strftime('%Y-%m-%d') - except Exception as e: - print(f"Erro ao formatar data: {str(e)}, valor: {data}") - return None - - # Formatar datas com validação - data_nascimento = formatar_data_segura(militante.data_nascimento) - data_entrada_oci = formatar_data_segura(militante.data_entrada_oci) - data_efetivacao_oci = formatar_data_segura(militante.data_efetivacao_oci) - - print(f"Dados do militante {militante_id} recuperados com sucesso") - print(f"Datas formatadas: nascimento={data_nascimento}, entrada={data_entrada_oci}, efetivação={data_efetivacao_oci}") - - return jsonify({ - 'status': 'success', - 'id': militante.id, - 'nome': militante.nome, - 'cpf': militante.cpf, - 'titulo_eleitoral': militante.titulo_eleitoral, - 'data_nascimento': data_nascimento, - 'data_entrada_oci': data_entrada_oci, - 'data_efetivacao_oci': data_efetivacao_oci, - 'emails': [email.endereco_email for email in militante.emails] if militante.emails else [], - 'telefone1': militante.telefone1, - 'telefone2': militante.telefone2, - 'celula_id': militante.celula_id, - 'responsabilidades_valor': militante.responsabilidades, - 'sindicato': militante.sindicato, - 'cargo_sindical': militante.cargo_sindical, - 'central_sindical': militante.central_sindical, - 'dirigente_sindical': militante.dirigente_sindical - }) - except Exception as e: - import traceback - print(f"Erro ao buscar dados do militante {militante_id}:") - print(traceback.format_exc()) - return jsonify({ - 'status': 'error', - 'message': f'Erro ao buscar dados do militante: {str(e)}' - }), 500 - finally: - db.close() - - @app.route("/comprovantes/novo", methods=["GET", "POST"]) - @require_login - def novo_comprovante(): - if request.method == "POST": - # Verificar permissão para tipos específicos de comprovante - tipo_comprovante = request.form.get('tipo_comprovante') - if tipo_comprovante not in ['1'] and not current_user.has_permission(Permission.MANAGE_PAYMENT_TYPES): - flash('Você não tem permissão para registrar este tipo de comprovante.', 'error') - return redirect(url_for('novo_comprovante')) - - try: - militante_id = request.form.get("militante_id") - tipo_comprovante = request.form.get("tipo_comprovante") - data_comprovante = request.form.get("data_comprovante") - - if not all([militante_id, tipo_comprovante, data_comprovante]): - flash("Todos os campos são obrigatórios", "danger") - return redirect(url_for("novo_comprovante")) - - db = get_db_connection() - comprovante = Comprovante( - militante_id=militante_id, - tipo_comprovante=tipo_comprovante, - data_comprovante=data_comprovante - ) - db.add(comprovante) - db.commit() - flash("Comprovante cadastrado com sucesso!", "success") - return redirect(url_for("listar_comprovantes")) - except Exception as e: - db.rollback() - flash(f"Erro ao cadastrar comprovante: {str(e)}", "danger") - return redirect(url_for("novo_comprovante")) - return render_template("novo_comprovante.html") - - @app.route("/comprovantes") - @require_login - @require_permission(Permission.MANAGE_CELL_REPORTS) - def listar_comprovantes(): - db = get_db_connection() - try: - comprovantes = db.query(Comprovante).join(Militante).all() - militantes = db.query(Militante).all() - return render_template("listar_comprovantes.html", - comprovantes=comprovantes, - militantes=militantes) - finally: - db.close() - - @app.route("/comprovantes/adicionar", methods=["POST"]) - @require_login - @require_permission(Permission.MANAGE_CELL_REPORTS) - def adicionar_comprovante(): - # Verificar permissão para tipos específicos de comprovante - tipo_comprovante = request.form.get('tipo_comprovante') - if tipo_comprovante not in ['1'] and not current_user.has_permission(Permission.MANAGE_PAYMENT_TYPES): - flash('Você não tem permissão para registrar este tipo de comprovante.', 'error') - return redirect(url_for('listar_comprovantes')) - - try: - militante_id = request.form.get("militante_id") - tipo_comprovante = request.form.get("tipo_comprovante") - data_comprovante = request.form.get("data_comprovante") - - if not all([militante_id, tipo_comprovante, data_comprovante]): - flash("Todos os campos são obrigatórios", "danger") - return redirect(url_for("adicionar_comprovante")) - - db = get_db_connection() - comprovante = Comprovante( - militante_id=militante_id, - tipo_comprovante=tipo_comprovante, - data_comprovante=data_comprovante - ) - db.add(comprovante) - db.commit() - flash("Comprovante adicionado com sucesso!", "success") - return redirect(url_for("listar_comprovantes")) - except Exception as e: - db.rollback() - flash(f"Erro ao adicionar comprovante: {str(e)}", "danger") - return redirect(url_for("adicionar_comprovante")) - return render_template("adicionar_comprovante.html") - @app.route('/celulas//comprovantes') @require_login def list_comprovantes_celula(celula_id): @@ -1744,7 +1324,7 @@ def create_app(): @app.route('/celulas//comprovantes/novo', methods=['GET', 'POST']) @require_login - @require_instance_permission('REGISTER_CELL_PAYMENT', 'celula_id') + @require_instance_permission('REGISTER_CELL_RECEIPT', 'celula_id') def novo_comprovante_celula(celula_id): if request.method == 'POST': db = get_db_connection() @@ -1765,7 +1345,7 @@ def create_app(): @app.route('/setores//comprovantes/novo', methods=['GET', 'POST']) @require_login - @require_instance_permission('REGISTER_SECTOR_PAYMENT', 'setor_id') + @require_instance_permission('REGISTER_SECTOR_RECEIPT', 'setor_id') def novo_comprovante_setor(setor_id): if request.method == 'POST': db = get_db_connection() @@ -1786,7 +1366,7 @@ def create_app(): @app.route('/crs//comprovantes/novo', methods=['GET', 'POST']) @require_login - @require_instance_permission('REGISTER_CR_PAYMENT', 'cr_id') + @require_instance_permission('REGISTER_CR_RECEIPT', 'cr_id') def novo_comprovante_cr(cr_id): if request.method == 'POST': db = get_db_connection() @@ -1805,6 +1385,249 @@ def create_app(): db.close() return render_template('comprovantes/form.html') + @app.route('/dashboard') + @login_required + def dashboard(): + """Rota para a página inicial do dashboard""" + try: + session = get_db_connection() + # Buscar dados recentes + comprovantes_recentes = session.query(Comprovante).order_by(Comprovante.data_comprovante.desc()).limit(5).all() + tipos_comprovante = session.query(TipoComprovante).all() + + return render_template( + 'dashboard.html', + comprovantes_recentes=comprovantes_recentes, + tipos_comprovante=tipos_comprovante + ) + except Exception as e: + flash(f'Erro ao carregar dashboard: {str(e)}', 'error') + return redirect(url_for('index')) + finally: + session.close() + + @app.route('/novo_comprovante', methods=['GET', 'POST']) + @login_required + def novo_comprovante(): + """Rota para criar um novo comprovante""" + try: + session = get_db_connection() + if request.method == 'POST': + militante_id = request.form.get('militante_id') + tipo_comprovante = request.form.get('tipo_comprovante') + valor = request.form.get('valor') + data_comprovante = request.form.get('data_comprovante') + + if not all([militante_id, tipo_comprovante, valor, data_comprovante]): + flash('Todos os campos são obrigatórios', 'error') + return redirect(url_for('novo_comprovante')) + + comprovante = Comprovante( + militante_id=militante_id, + tipo_comprovante=tipo_comprovante, + valor=float(valor.replace('R$', '').replace('.', '').replace(',', '.')), + data_comprovante=datetime.strptime(data_comprovante, '%Y-%m-%d') + ) + + session.add(comprovante) + session.commit() + flash('Comprovante criado com sucesso!', 'success') + return redirect(url_for('listar_comprovantes')) + + militantes = session.query(Militante).all() + tipos_comprovante = session.query(TipoComprovante).all() + return render_template( + 'novo_comprovante.html', + militantes=militantes, + tipos_comprovante=tipos_comprovante + ) + except Exception as e: + flash(f'Erro ao criar comprovante: {str(e)}', 'error') + return redirect(url_for('listar_comprovantes')) + finally: + session.close() + + @app.route('/listar_comprovantes') + @login_required + def listar_comprovantes(): + """Rota para listar todos os comprovantes""" + try: + session = get_db_connection() + comprovantes = session.query(Comprovante).all() + return render_template('listar_comprovantes.html', comprovantes=comprovantes) + except Exception as e: + flash(f'Erro ao listar comprovantes: {str(e)}', 'error') + return redirect(url_for('dashboard')) + finally: + session.close() + + @app.route('/adicionar_comprovante', methods=['POST']) + @login_required + def adicionar_comprovante(): + """Rota para adicionar um novo comprovante via AJAX""" + try: + session = get_db_connection() + data = request.get_json() + + comprovante = Comprovante( + militante_id=data['militante_id'], + tipo_comprovante=data['tipo_comprovante'], + valor=float(data['valor'].replace('R$', '').replace('.', '').replace(',', '.')), + data_comprovante=datetime.strptime(data['data_comprovante'], '%Y-%m-%d') + ) + + session.add(comprovante) + session.commit() + + return jsonify({ + 'status': 'success', + 'message': 'Comprovante adicionado com sucesso!' + }) + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': f'Erro ao adicionar comprovante: {str(e)}' + }) + finally: + session.close() + + @app.route("/relatorios/comprovantes/novo", methods=["GET", "POST"]) + @require_login + @require_permission(Permission.MANAGE_CELL_REPORTS) + def novo_relatorio_comprovantes(): + if request.method == "POST": + try: + data_inicio = request.form.get("data_inicio") + data_fim = request.form.get("data_fim") + + if not all([data_inicio, data_fim]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("novo_relatorio_comprovantes")) + + db = get_db_connection() + comprovantes = db.query(Comprovante).filter( + Comprovante.data_comprovante.between(data_inicio, data_fim) + ).all() + + return render_template("relatorio_comprovantes.html", + comprovantes=comprovantes, + data_inicio=data_inicio, + data_fim=data_fim) + except Exception as e: + flash(f"Erro ao gerar relatório: {str(e)}", "danger") + return redirect(url_for("novo_relatorio_comprovantes")) + return render_template("novo_relatorio_comprovantes.html") + + @app.route("/relatorios/comprovantes/celula/", methods=["GET", "POST"]) + @require_login + @require_permission(Permission.MANAGE_CELL_REPORTS) + def relatorio_comprovantes_celula(celula_id): + if request.method == "POST": + try: + data_inicio = request.form.get("data_inicio") + data_fim = request.form.get("data_fim") + + if not all([data_inicio, data_fim]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("relatorio_comprovantes_celula", celula_id=celula_id)) + + db = get_db_connection() + comprovantes = db.query(Comprovante).join(Militante).filter( + Militante.celula_id == celula_id, + Comprovante.data_comprovante.between(data_inicio, data_fim) + ).all() + + return render_template("relatorio_comprovantes.html", + comprovantes=comprovantes, + data_inicio=data_inicio, + data_fim=data_fim) + except Exception as e: + flash(f"Erro ao gerar relatório: {str(e)}", "danger") + return redirect(url_for("relatorio_comprovantes_celula", celula_id=celula_id)) + return render_template("novo_relatorio_comprovantes.html", celula_id=celula_id) + + @app.route("/assinaturas/jornal/novo", methods=["GET", "POST"]) + @require_login + @require_permission(Permission.MANAGE_MATERIALS) + def nova_assinatura_jornal(): + if request.method == "POST": + try: + militante_id = request.form.get("militante_id") + data_inicio = request.form.get("data_inicio") + data_fim = request.form.get("data_fim") + + if not all([militante_id, data_inicio, data_fim]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("nova_assinatura_jornal")) + + db = get_db_connection() + assinatura = AssinaturaJornal( + militante_id=militante_id, + data_inicio=data_inicio, + data_fim=data_fim + ) + db.add(assinatura) + db.commit() + + flash("Assinatura de jornal registrada com sucesso", "success") + return redirect(url_for("listar_assinaturas_jornal")) + except Exception as e: + flash(f"Erro ao registrar assinatura de jornal: {str(e)}", "danger") + return redirect(url_for("nova_assinatura_jornal")) + + db = get_db_connection() + militantes = db.query(Militante).all() + return render_template("nova_assinatura_jornal.html", militantes=militantes) + + @app.route("/assinaturas/jornal") + @require_login + @require_permission(Permission.MANAGE_MATERIALS) + def listar_assinaturas_jornal(): + db = get_db_connection() + assinaturas = db.query(AssinaturaJornal).all() + return render_template("listar_assinaturas_jornal.html", assinaturas=assinaturas) + + @app.route("/campanhas/financeira/novo", methods=["GET", "POST"]) + @require_login + @require_permission(Permission.MANAGE_MATERIALS) + def nova_campanha_financeira(): + if request.method == "POST": + try: + militante_id = request.form.get("militante_id") + valor = request.form.get("valor") + data_campanha = request.form.get("data_campanha") + + if not all([militante_id, valor, data_campanha]): + flash("Todos os campos são obrigatórios", "danger") + return redirect(url_for("nova_campanha_financeira")) + + db = get_db_connection() + campanha = CampanhaFinanceira( + militante_id=militante_id, + valor=valor, + data_campanha=data_campanha + ) + db.add(campanha) + db.commit() + + flash("Campanha financeira registrada com sucesso", "success") + return redirect(url_for("listar_campanhas_financeira")) + except Exception as e: + flash(f"Erro ao registrar campanha financeira: {str(e)}", "danger") + return redirect(url_for("nova_campanha_financeira")) + + db = get_db_connection() + militantes = db.query(Militante).all() + return render_template("nova_campanha_financeira.html", militantes=militantes) + + @app.route("/campanhas/financeira") + @require_login + @require_permission(Permission.MANAGE_MATERIALS) + def listar_campanhas_financeira(): + db = get_db_connection() + campanhas = db.query(CampanhaFinanceira).all() + return render_template("listar_campanhas_financeira.html", campanhas=campanhas) + return app def init_system(): diff --git a/docs/rbac.md b/docs/rbac.md index 27f6fe3..c51dc64 100644 --- a/docs/rbac.md +++ b/docs/rbac.md @@ -109,22 +109,26 @@ CREATE TABLE user_roles ( - `manage_cell_members`: Gerenciar membros da célula - `create_cell_member`: Criar novos membros na célula - `view_cell_reports`: Visualizar relatórios da célula +- `REGISTER_CELL_RECEIPT`: Registrar comprovantes da célula ### Permissões de Setor - `manage_sector_cells`: Gerenciar células do setor - `create_sector_cell`: Criar novas células no setor - `view_sector_reports`: Visualizar relatórios do setor +- `REGISTER_SECTOR_RECEIPT`: Registrar comprovantes do setor ### Permissões de CR - `manage_cr_sectors`: Gerenciar setores do CR - `create_cr_sector`: Criar novos setores no CR - `view_cr_reports`: Visualizar relatórios do CR +- `REGISTER_CR_RECEIPT`: Registrar comprovantes do CR ### Permissões de CC - `manage_cc_crs`: Gerenciar CRs - `create_cc_cr`: Criar novos CRs - `view_cc_reports`: Visualizar relatórios nacionais - `system_config`: Configurar o sistema +- `REGISTER_CC_RECEIPT`: Registrar comprovantes do CC ## Uso no Código @@ -166,12 +170,12 @@ O sistema possui uma estrutura hierárquica com os seguintes níveis: - `MANAGE_CELL_MEMBERS`: Gerenciar membros da célula - `VIEW_CELL_DATA`: Visualizar dados da célula - `VIEW_CELL_REPORTS`: Visualizar relatórios da célula - - `REGISTER_CELL_PAYMENT`: Registrar pagamentos da célula + - `REGISTER_CELL_RECEIPT`: Registrar comprovantes da célula - **Tesoureiro(a)**: - `VIEW_CELL_DATA`: Visualizar dados da célula - `VIEW_CELL_REPORTS`: Visualizar relatórios da célula - - `REGISTER_CELL_PAYMENT`: Registrar pagamentos da célula + - `REGISTER_CELL_RECEIPT`: Registrar comprovantes da célula - **Militante**: - `VIEW_OWN_DATA`: Visualizar apenas seus próprios dados @@ -180,32 +184,32 @@ O sistema possui uma estrutura hierárquica com os seguintes níveis: - **Secretário(a)**: - `MANAGE_SECTOR_CELLS`: Gerenciar células do setor - `VIEW_SECTOR_REPORTS`: Visualizar relatórios do setor - - `REGISTER_SECTOR_PAYMENT`: Registrar pagamentos do setor + - `REGISTER_SECTOR_RECEIPT`: Registrar comprovantes do setor - **Tesoureiro(a)**: - `VIEW_SECTOR_REPORTS`: Visualizar relatórios do setor - - `REGISTER_SECTOR_PAYMENT`: Registrar pagamentos do setor + - `REGISTER_SECTOR_RECEIPT`: Registrar comprovantes do setor ### CR - **Secretário(a)**: - `MANAGE_CR_SECTORS`: Gerenciar setores do CR - `VIEW_CR_REPORTS`: Visualizar relatórios do CR - - `REGISTER_CR_PAYMENT`: Registrar pagamentos do CR + - `REGISTER_CR_RECEIPT`: Registrar comprovantes do CR - **Tesoureiro(a)**: - `VIEW_CR_REPORTS`: Visualizar relatórios do CR - - `REGISTER_CR_PAYMENT`: Registrar pagamentos do CR + - `REGISTER_CR_RECEIPT`: Registrar comprovantes do CR ### CC - **Secretário(a)**: - `MANAGE_CC_CRS`: Gerenciar CRs - `VIEW_CC_REPORTS`: Visualizar relatórios do CC - - `REGISTER_CC_PAYMENT`: Registrar pagamentos do CC + - `REGISTER_CC_RECEIPT`: Registrar comprovantes do CC - `SYSTEM_CONFIG`: Configurar o sistema - **Tesoureiro(a)**: - `VIEW_CC_REPORTS`: Visualizar relatórios do CC - - `REGISTER_CC_PAYMENT`: Registrar pagamentos do CC + - `REGISTER_CC_RECEIPT`: Registrar comprovantes do CC ## Regras de Acesso a Dados @@ -214,10 +218,10 @@ O sistema possui uma estrutura hierárquica com os seguintes níveis: - Secretários e tesoureiros podem ver dados de sua instância - O CC tem acesso a todos os dados -2. **Registro de Pagamentos**: - - Apenas tesoureiros e secretários podem registrar pagamentos +2. **Registro de Comprovantes**: + - Apenas tesoureiros e secretários podem registrar comprovantes - O registro é restrito à instância do usuário - - O CC pode registrar pagamentos em qualquer nível + - O CC pode registrar comprovantes em qualquer nível ## Implementação Técnica diff --git a/functions/database.py b/functions/database.py index 54e0d1c..885461b 100644 --- a/functions/database.py +++ b/functions/database.py @@ -190,6 +190,7 @@ class Militante(Base): vendas_jornais = relationship("VendaJornalAvulso", back_populates="militante") assinaturas = relationship("AssinaturaAnual", back_populates="militante") celula = relationship("Celula", back_populates="militantes", foreign_keys=[celula_id]) + comprovantes = relationship("Comprovante", back_populates="militante") # Constantes para responsabilidades SECRETARIO = 1 @@ -621,6 +622,22 @@ class TransacaoPIX(Base): pagamento = relationship("Pagamento", back_populates="transacoes_pix") +class TipoComprovante(Base): + __tablename__ = 'tipos_comprovante' + id = Column(Integer, primary_key=True) + descricao = Column(String(50), nullable=False) + valor = Column(Float, nullable=False) + +class Comprovante(Base): + __tablename__ = 'comprovantes' + id = Column(Integer, primary_key=True) + militante_id = Column(Integer, ForeignKey('militantes.id'), nullable=False) + tipo_comprovante = Column(String(50)) # Cota, Jornal, Assinatura, etc. + data_comprovante = Column(Date, nullable=False) + + militante = relationship("Militante", back_populates="comprovantes") + transacoes_pix = relationship("TransacaoPIX", back_populates="comprovante") + def init_database(): """Inicializa o banco de dados com dados básicos""" print("Inicializando banco de dados...") diff --git a/requirements.txt b/requirements.txt index 855bb4b..6f6c92e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ Werkzeug==3.0.1 python-dotenv==1.0.1 pyotp==2.9.0 qrcode==7.4.2 -Pillow==10.2.0 +Pillow==9.5.0 email-validator==2.1.0.post1 cryptography==42.0.2 bcrypt==4.1.2 diff --git a/seed_data.py b/seed_data.py index d541d26..947ade1 100644 --- a/seed_data.py +++ b/seed_data.py @@ -1,10 +1,11 @@ from datetime import datetime, timedelta from functions.database import ( - Base, Militante, CotaMensal, TipoPagamento, Pagamento, + Base, Militante, CotaMensal, TipoComprovante, Comprovante, MaterialVendido, TipoMaterial, VendaJornalAvulso, AssinaturaAnual, RelatorioCotasMensais, RelatorioVendasMateriais, engine, SessionLocal, Setor, ComiteCentral, Usuario, Role, EmailMilitante, Endereco, - ComiteRegional, Celula, EstadoMilitante + ComiteRegional, Celula, EstadoMilitante, get_db_connection, + init_database ) import random from faker import Faker @@ -54,20 +55,28 @@ def criar_estrutura_organizacional(session): session.commit() return crs, setores -def criar_tipos_pagamento(session): - """Cria tipos de pagamento padrão""" - print("\nCriando tipos de pagamento...") +def criar_tipos_comprovante(session): + """Cria tipos de comprovante padrão""" + print("\nCriando tipos de comprovante...") tipos = [ - "Dinheiro", - "PIX", - "Cartão de Crédito", - "Cartão de Débito", - "Transferência Bancária" + "Comprovante Padrão", + "Comprovante Especial", + "Comprovante Extraordinário", + "Jornal Avulso", + "Assinatura de Jornal", + "Campanha Financeira" ] + for tipo in tipos: - if not session.query(TipoPagamento).filter_by(descricao=tipo).first(): - session.add(TipoPagamento(descricao=tipo)) - session.commit() + if not session.query(TipoComprovante).filter_by(descricao=tipo).first(): + session.add(TipoComprovante(descricao=tipo)) + + try: + session.commit() + print("Tipos de comprovante criados com sucesso!") + except Exception as e: + session.rollback() + print(f"Erro ao criar tipos de comprovante: {e}") def criar_tipos_material(session): """Cria tipos de material padrão""" @@ -211,27 +220,27 @@ def criar_cotas(session, militantes): print(f"Erro ao criar cotas para militante {militante.nome}: {e}") session.rollback() -def criar_pagamentos(session, militantes): - """Cria pagamentos para os militantes""" - print("\nCriando pagamentos...") - tipos_pagamento = session.query(TipoPagamento).all() +def criar_comprovantes(session, militantes): + """Cria comprovantes para os militantes""" + print("\nCriando comprovantes...") + tipos_comprovante = session.query(TipoComprovante).all() for militante in militantes: try: - # Criar entre 3 e 8 pagamentos por militante + # Criar entre 3 e 8 comprovantes por militante for _ in range(random.randint(3, 8)): - tipo = random.choice(tipos_pagamento) - pagamento = Pagamento( + tipo = random.choice(tipos_comprovante) + comprovante = Comprovante( militante_id=militante.id, - tipo_pagamento=tipo.descricao, # Usando a descrição do tipo - valor=random.uniform(50, 500), - data_pagamento=fake.date_between(start_date='-1y', end_date='today') + tipo_comprovante=tipo.descricao, # Usando a descrição do tipo + valor=random.uniform(10, 1000), + data_comprovante=fake.date_between(start_date='-1y', end_date='today') ) - session.add(pagamento) + session.add(comprovante) session.commit() except Exception as e: - print(f"Erro ao criar pagamentos para militante {militante.nome}: {e}") session.rollback() + print(f"Erro ao criar comprovantes para militante {militante.nome}: {e}") def criar_materiais_vendidos(session, militantes): """Cria registros de materiais vendidos""" @@ -302,7 +311,7 @@ def criar_assinaturas(session, militantes): def seed_database(): """Função principal para popular o banco de dados""" - session = SessionLocal() + session = get_db_connection() try: print("Iniciando população do banco de dados...") @@ -310,7 +319,7 @@ def seed_database(): crs, setores = criar_estrutura_organizacional(session) # Criar tipos básicos - criar_tipos_pagamento(session) + criar_tipos_comprovante(session) criar_tipos_material(session) # Criar militantes (30 militantes para teste) @@ -318,7 +327,7 @@ def seed_database(): # Criar dados financeiros e materiais criar_cotas(session, militantes) - criar_pagamentos(session, militantes) + criar_comprovantes(session, militantes) criar_materiais_vendidos(session, militantes) criar_vendas_jornal(session, militantes) criar_assinaturas(session, militantes) diff --git a/static/js/home.js b/static/js/home.js index d72440e..f4f3fec 100644 --- a/static/js/home.js +++ b/static/js/home.js @@ -1,10 +1,10 @@ document.addEventListener('DOMContentLoaded', function() { - // Configurar clique nos itens da lista de pagamentos - document.querySelectorAll('.list-group-item[onclick*="carregarDadosPagamento"]').forEach(item => { + // Configurar clique nos itens da lista de comprovantes + document.querySelectorAll('.list-group-item[onclick*="carregarDadosComprovante"]').forEach(item => { item.addEventListener('click', function(e) { - const pagamentoId = this.getAttribute('data-pagamento-id'); - if (pagamentoId) { - carregarDadosPagamento(pagamentoId); + const comprovanteId = this.getAttribute('data-comprovante-id'); + if (comprovanteId) { + carregarDadosComprovante(comprovanteId); } }); }); diff --git a/static/js/table_sort.js b/static/js/table_sort.js index eda9c51..80a8614 100644 --- a/static/js/table_sort.js +++ b/static/js/table_sort.js @@ -56,7 +56,7 @@ function configurarOrdenacaoTabela(tabelaId) { if (column === 'data' || column === 'data_vencimento' || column === 'data_alteracao' || - column === 'data_pagamento' || + column === 'data_comprovante' || column === 'data_venda' || column === 'data_relatorio') { const aDate = converterDataParaComparacao(aValue); @@ -112,7 +112,7 @@ document.addEventListener('DOMContentLoaded', function() { 'materiaisTable', 'vendasTable', 'cotasTable', - 'pagamentosTable' + 'comprovantesTable' ]; tabelas.forEach(tabelaId => { @@ -197,4 +197,12 @@ document.addEventListener('DOMContentLoaded', function() { }); }); }); -}); \ No newline at end of file +}); + +function sortTable(table, column, type = 'text') { + // ... existing code ... + if (column === 'data_comprovante') { + // ... existing code ... + } + // ... existing code ... +} \ No newline at end of file diff --git a/templates/editar_comprovante.html b/templates/editar_comprovante.html index 7b1859f..568d759 100644 --- a/templates/editar_comprovante.html +++ b/templates/editar_comprovante.html @@ -29,8 +29,8 @@
- - Data do Comprovante: +
diff --git a/templates/editar_pagamento.html b/templates/editar_pagamento.html deleted file mode 100644 index 0ff6158..0000000 --- a/templates/editar_pagamento.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Editar Comprovante{% endblock %} - -{% block content %} -
-
-
-
-
-

- Editar Comprovante -

-
-
-
- {% csrf_token %} -
- - -
-
- - -
- -
-
-
-
-
-
-{% endblock %} \ No newline at end of file diff --git a/templates/editar_relatorio_pagamentos.html b/templates/editar_relatorio_comprovantes.html similarity index 86% rename from templates/editar_relatorio_pagamentos.html rename to templates/editar_relatorio_comprovantes.html index 13f461a..5df9acf 100644 --- a/templates/editar_relatorio_pagamentos.html +++ b/templates/editar_relatorio_comprovantes.html @@ -1,12 +1,12 @@ {% extends 'base.html' %} -{% block title %}Editar Relatório de Pagamentos{% endblock %} +{% block title %}Editar Relatório de Comprovantes{% endblock %} {% block content %}
-

Editar Relatório de Pagamentos

+

Editar Relatório de Comprovantes

{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} @@ -44,10 +44,10 @@
- - + +
- Por favor, insira o total de pagamentos. + Por favor, insira o total de comprovantes.
@@ -61,7 +61,7 @@
- Voltar + Voltar
diff --git a/templates/lista_pagamentos.html b/templates/lista_pagamentos.html deleted file mode 100644 index fd69b5e..0000000 --- a/templates/lista_pagamentos.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Lista de Comprovantes{% endblock %} - -{% block content %} -
-
-
-
-
-

- Lista de Comprovantes -

-
-
- - - - - - - - - {% for comprovante in comprovantes %} - - - - - {% endfor %} - -
Tipo de ComprovanteData do Comprovante
{{ comprovante.tipo }}{{ comprovante.data }}
-
-
-
-
-
-{% endblock %} \ No newline at end of file diff --git a/templates/listar_pagamentos.html b/templates/listar_pagamentos.html deleted file mode 100644 index 3afaff2..0000000 --- a/templates/listar_pagamentos.html +++ /dev/null @@ -1,203 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Comprovantes{% endblock %} - -{% block content %} -
-
-

Comprovantes

-
- - -
-
- -
-
-
- - - - - - - - - - - - {% for pagamento in pagamentos %} - - - - - - - - {% endfor %} - -
MilitanteTipo de ComprovanteValorData do ComprovanteAções
{{ pagamento.militante.nome if pagamento.militante else 'N/A' }} - {% if pagamento.tipo_pagamento == 1 %} - Cota - {% elif pagamento.tipo_pagamento == 2 %} - Contribuição Extra - {% elif pagamento.tipo_pagamento == 3 %} - Doação - {% elif pagamento.tipo_pagamento == 4 %} - Taxa de Evento - {% elif pagamento.tipo_pagamento == 5 %} - Outros - {% else %} - Não Definido - {% endif %} - R$ {{ "%.2f"|format(pagamento.valor) }}{{ pagamento.data_pagamento.strftime('%d/%m/%Y') }} - - -
-
-
-
-
- - - - - - - - - -{% endblock %} - -{% block scripts %} - -{% endblock %} - diff --git a/templates/listar_relatorios_pagamentos.html b/templates/listar_relatorios_comprovantes.html similarity index 77% rename from templates/listar_relatorios_pagamentos.html rename to templates/listar_relatorios_comprovantes.html index de978f3..15365ca 100644 --- a/templates/listar_relatorios_pagamentos.html +++ b/templates/listar_relatorios_comprovantes.html @@ -1,12 +1,12 @@ {% extends 'base.html' %} -{% block title %}Listar Relatórios de Pagamentos{% endblock %} +{% block title %}Listar Relatórios de Comprovantes{% endblock %} {% block content %}
-

Lista de Relatórios de Pagamentos

+

Lista de Relatórios de Comprovantes

{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} @@ -17,7 +17,7 @@ {% endwith %} @@ -28,7 +28,7 @@ ID Setor Comitê Central - Total de Pagamentos + Total de Comprovantes Data do Relatório Ações @@ -39,11 +39,11 @@ {{ relatorio.id }} {{ relatorio.setor.nome }} {{ relatorio.comite.nome }} - R$ {{ "%.2f"|format(relatorio.total_pagamentos) }} + R$ {{ "%.2f"|format(relatorio.total_comprovantes) }} {{ relatorio.data_relatorio.strftime('%d/%m/%Y') }} - Editar - Excluir + Editar + Excluir {% endfor %} diff --git a/templates/novo_pagamento.html b/templates/novo_pagamento.html deleted file mode 100644 index 943a224..0000000 --- a/templates/novo_pagamento.html +++ /dev/null @@ -1,94 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Novo Comprovante{% endblock %} - -{% block content %} -
-
-
-
-
-

- Registrar Novo Comprovante -

-
-
-
-
- - -
- Por favor, selecione um militante. -
-
- -
- - -
- Por favor, selecione o tipo de comprovante. -
-
- -
- -
- R$ - -
-
- Por favor, informe um valor válido. -
-
- -
- - -
- Por favor, informe uma data válida. -
-
- -
- - - Voltar - -
-
-
-
-
-
-
-{% endblock %} - -{% block extra_js %} - - -{% endblock %} diff --git a/templates/novo_relatorio_pagamentos.html b/templates/novo_relatorio_comprovantes.html similarity index 87% rename from templates/novo_relatorio_pagamentos.html rename to templates/novo_relatorio_comprovantes.html index c4f1eff..531fe5a 100644 --- a/templates/novo_relatorio_pagamentos.html +++ b/templates/novo_relatorio_comprovantes.html @@ -1,12 +1,12 @@ {% extends 'base.html' %} -{% block title %}Novo Relatório de Cotas{% endblock %} +{% block title %}Novo Relatório de Comprovantes{% endblock %} {% block content %}
-

Novo Relatório de Cotas

+

Novo Relatório de Comprovantes

{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} @@ -44,10 +44,10 @@
- - + +
- Por favor, insira o total de pagamentos. + Por favor, insira o total de comprovantes.
@@ -61,7 +61,7 @@
- Voltar + Voltar