From 179ea3cad091e786fae565eb996e2a9423375b5d Mon Sep 17 00:00:00 2001 From: LS Date: Wed, 9 Apr 2025 09:59:12 -0300 Subject: [PATCH] resolvido merge com nova ui --- Makefile | 14 +- app.py | 2816 ++++++++++++------------ functions/database.py | 64 +- init_db.py | 19 + seed_data.py | 356 +-- static/js/militantes.js | 789 +++---- templates/criar_militante.html | 1 + templates/editar_militante.html | 44 +- templates/home.html | 2 +- templates/listar_militantes.html | 339 +-- templates/modals/militante_editar.html | 104 +- 11 files changed, 2285 insertions(+), 2263 deletions(-) create mode 100644 init_db.py diff --git a/Makefile b/Makefile index 8abd495..a9a5333 100644 --- a/Makefile +++ b/Makefile @@ -2,17 +2,19 @@ install: pip install -r requirements.txt clean: - rm -rf ~/.local/share/controles/database.db + rm -rf ~/.local/share/controles/database.db* rm -f admin_qr.png -run: - python app.py +init-db: clean + python init_db.py -seed: +seed: init-db python seed.py -run-with-seed: clean - python app.py & sleep 5 && python seed.py +run: + python app.py + +run-with-seed: seed run reset-admin: clean python create_admin.py diff --git a/app.py b/app.py index 24ea324..bc9d97c 100644 --- a/app.py +++ b/app.py @@ -54,918 +54,914 @@ import json load_dotenv() -app = Flask(__name__) -app.secret_key = os.getenv('SECRET_KEY', secrets.token_hex(16)) -bootstrap = Bootstrap5(app) +def create_app(): + app = Flask(__name__) + app.secret_key = os.getenv('SECRET_KEY', secrets.token_hex(16)) + bootstrap = Bootstrap5(app) -# Configurar CSRF Protection -csrf = CSRFProtect(app) - -# Configurar Flask-Login -login_manager = LoginManager() -login_manager.init_app(app) -login_manager.login_view = 'login' - -# Adicionar filtros Jinja2 -@app.template_filter('bitwise_and') -def bitwise_and(value1, value2): - """Filtro para operação bit a bit AND""" - return value1 & value2 - -@login_manager.user_loader -def load_user(user_id): - """Carrega o usuário pelo ID""" - db = get_db_connection() - try: - # Carregar o usuário com suas roles - user = db.query(Usuario).options( - joinedload(Usuario.roles) - ).get(user_id) - return user - finally: - db.close() - -def generate_qr_code(user): - """Gera um QR code para o usuário""" - if not user.otp_secret: - user.otp_secret = pyotp.random_base32() + # Configurar CSRF Protection + csrf = CSRFProtect() + csrf.init_app(app) - totp = pyotp.TOTP(user.otp_secret) - qr = qrcode.QRCode(version=1, box_size=10, border=5) - qr.add_data(totp.provisioning_uri(user.email, issuer_name="Sistema de Controles")) - qr.make(fit=True) + # Configurar cabeçalhos CSRF personalizados + app.config['WTF_CSRF_CHECK_DEFAULT'] = False + app.config['WTF_CSRF_HEADERS'] = ['X-CSRFToken'] - img = qr.make_image(fill_color="black", back_color="white") - buffer = BytesIO() - img.save(buffer, format="PNG") - qr_code = base64.b64encode(buffer.getvalue()).decode('utf-8') - - return qr_code + # Configurar Flask-Login + login_manager = LoginManager() + login_manager.init_app(app) + login_manager.login_view = 'login' -# Configuração da sessão do SQLAlchemy -db_session = get_db_connection() + # Adicionar filtros Jinja2 + @app.template_filter('bitwise_and') + def bitwise_and(value1, value2): + """Filtro para operação bit a bit AND""" + return value1 & value2 -# Configurar Flask-Mail -app.config['MAIL_SERVER'] = os.getenv('MAIL_SERVER', 'smtp.gmail.com') -app.config['MAIL_PORT'] = int(os.getenv('MAIL_PORT', 587)) -app.config['MAIL_USE_TLS'] = os.getenv('MAIL_USE_TLS', 'True').lower() == 'true' -app.config['MAIL_USERNAME'] = os.getenv('MAIL_USERNAME') -app.config['MAIL_PASSWORD'] = os.getenv('MAIL_PASSWORD') -app.config['MAIL_DEFAULT_SENDER'] = os.getenv('MAIL_DEFAULT_SENDER') - -mail = Mail(app) - -# Inicializar banco de dados e RBAC -print("Inicializando banco de dados...") -init_database() - -print("Inicializando sistema RBAC...") -init_rbac() - -# Criar admin e usuários de teste -print("Criando usuários iniciais...") -create_admin_user() -create_test_users() - -# Decorator para verificar se o usuário está logado -def login_required(f): - @wraps(f) - def decorated_function(*args, **kwargs): - if 'user_id' not in session: - flash('Por favor, faça login para acessar esta página.', 'warning') - return redirect(url_for('login')) - return f(*args, **kwargs) - return decorated_function - -# Decorator para verificar se a sessão expirou -def session_timeout(f): - @wraps(f) - def decorated_function(*args, **kwargs): - if current_user.is_authenticated: - if 'last_activity' not in session: - session['last_activity'] = time() - return f(*args, **kwargs) - - last_activity = datetime.fromtimestamp(session['last_activity']) - now = datetime.now() - - # Se passaram mais de 30 minutos (configurável) - timeout_minutes = 30 - if now - last_activity > timedelta(minutes=timeout_minutes): - # Registrar o logout por timeout - try: - current_user.ultimo_logout = datetime.now() - current_user.motivo_logout = "Timeout de sessão" - db_session.commit() - except Exception as e: - print(f"Erro ao registrar logout por timeout: {e}") - - session.clear() - flash('Sua sessão expirou. Por favor, faça login novamente.', 'warning') - return redirect(url_for('login')) - - # Atualizar timestamp de último acesso - session['last_activity'] = time() - - # Atualizar também no banco de dados - try: - current_user.update_last_activity() - db_session.commit() - except Exception as e: - print(f"Erro ao atualizar última atividade: {e}") - - return f(*args, **kwargs) - return f(*args, **kwargs) - return decorated_function - -# Rota raiz - redireciona para login se não estiver autenticado -@app.route("/") -@require_login -def index(): - """Rota principal""" - return redirect(url_for('home')) - -# Rota de login -@app.route("/login", methods=["GET", "POST"]) -def login(): - """Rota de login""" - if request.method == "POST": - email_or_username = request.form.get("email") - password = request.form.get("password") - otp = request.form.get("otp") - - if not all([email_or_username, password]): - flash("Email/usuário e senha são obrigatórios.", "danger") - return redirect(url_for("login")) - + @login_manager.user_loader + def load_user(user_id): + """Carrega o usuário pelo ID""" db = get_db_connection() try: - # Tenta encontrar o usuário por email ou username - user = db.query(Usuario).filter( - (Usuario.email == email_or_username) | - (Usuario.username == email_or_username) - ).first() - - if not user or not user.check_password(password): - flash("Email/usuário ou senha incorretos.", "danger") - return redirect(url_for("login")) - - # Verificar OTP se o usuário tiver configurado - if user.otp_secret and not otp: - flash("Código OTP é obrigatório para sua conta.", "danger") - return redirect(url_for("login")) - - if user.otp_secret and not user.verify_otp(otp): - flash("Código OTP inválido.", "danger") - return redirect(url_for("login")) - - # Atualizar último login - user.ultimo_login = datetime.utcnow() - db.commit() - - # Fazer login e setar sessão - login_user(user) - session['user_id'] = user.id - session['username'] = user.username - session['is_admin'] = user.is_admin - - # Redirecionar para home - return redirect(url_for("home")) + # Carregar o usuário com suas roles + user = db.query(Usuario).options( + joinedload(Usuario.roles) + ).get(user_id) + return user finally: db.close() - - return render_template("login.html") -# Rota de logout -@app.route("/logout") -@login_required -def logout(): - db = get_db_connection() - try: - user = current_user - if user: - user.logout() - db.commit() - logout_user() - finally: - db.close() - flash('Logout realizado com sucesso!', 'success') - return redirect(url_for('login')) + def generate_qr_code(user): + """Gera um QR code para o usuário""" + if not user.otp_secret: + user.otp_secret = pyotp.random_base32() + + totp = pyotp.TOTP(user.otp_secret) + qr = qrcode.QRCode(version=1, box_size=10, border=5) + qr.add_data(totp.provisioning_uri(user.email, issuer_name="Sistema de Controles")) + qr.make(fit=True) + + img = qr.make_image(fill_color="black", back_color="white") + buffer = BytesIO() + img.save(buffer, format="PNG") + qr_code = base64.b64encode(buffer.getvalue()).decode('utf-8') + + return qr_code -# Rota home (protegida) -@app.route("/home") -@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" + # Configurar Flask-Mail + app.config['MAIL_SERVER'] = os.getenv('MAIL_SERVER', 'smtp.gmail.com') + app.config['MAIL_PORT'] = int(os.getenv('MAIL_PORT', 587)) + app.config['MAIL_USE_TLS'] = os.getenv('MAIL_USE_TLS', 'True').lower() == 'true' + app.config['MAIL_USERNAME'] = os.getenv('MAIL_USERNAME') + app.config['MAIL_PASSWORD'] = os.getenv('MAIL_PASSWORD') + app.config['MAIL_DEFAULT_SENDER'] = os.getenv('MAIL_DEFAULT_SENDER') - # Formatar data atual em português - data_atual = datetime.now().strftime("%d de %B de %Y") - - # Buscar dados para o dashboard - 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).count() - - # Buscar últimos militantes cadastrados - ultimos_militantes = db.query(Militante)\ - .order_by(Militante.id.desc())\ - .limit(5)\ - .all() - - # Buscar últimos pagamentos - ultimos_pagamentos = db.query(Pagamento)\ - .join(Militante)\ - .order_by(Pagamento.data_pagamento.desc())\ - .limit(5)\ - .all() - - # Buscar tipos de pagamento - tipos_pagamento = db.query(TipoPagamento).all() - - return render_template('home.html', - nome_usuario=nome_usuario, - data_atual=data_atual, - total_militantes=total_militantes, - total_cotas="{:.2f}".format(total_cotas), - total_materiais=total_materiais, - total_assinaturas=total_assinaturas, - ultimos_militantes=ultimos_militantes, - ultimos_pagamentos=ultimos_pagamentos, - tipos_pagamento=tipos_pagamento, - Militante=Militante) - except Exception as e: - print(f"Erro na página inicial: {e}") - import traceback - traceback.print_exc() - flash('Erro ao carregar a página inicial', 'danger') - return render_template('home.html', - nome_usuario="Usuário", - data_atual=datetime.now().strftime("%d/%m/%Y"), - total_militantes=0, - total_cotas="0.00", - total_materiais=0, - total_assinaturas=0, - ultimos_militantes=[], - ultimos_pagamentos=[], - Militante=Militante) - finally: - db.close() + mail = Mail(app) -# Rota para criar um novo militante -@app.route("/militantes/criar", methods=["POST"]) -@require_login -@require_permission('gerenciar_militantes') -def criar_militante(): - """Cria um novo militante""" - db = get_db_connection() - try: - # Validar CPF - cpf = request.form.get('cpf') - if not validar_cpf(cpf): - return jsonify({ - 'status': 'error', - 'message': 'CPF inválido' - }), 400 - - # Verificar se já existe militante com este CPF - militante_existente = db.query(Militante).filter(Militante.cpf == cpf).first() - if militante_existente: - return jsonify({ - 'status': 'error', - 'message': 'CPF já cadastrado' - }), 400 - - # Criar endereço - endereco = Endereco( - cep=request.form.get('cep'), - estado=request.form.get('estado'), - cidade=request.form.get('cidade'), - bairro=request.form.get('bairro'), - logradouro=request.form.get('logradouro'), - numero=request.form.get('numero'), - complemento=request.form.get('complemento') - ) - db.add(endereco) - db.flush() # Gerar ID do endereço - - # Criar militante - militante = Militante( - # Dados Básicos - nome=request.form.get('nome'), - cpf=cpf, - titulo_eleitoral=request.form.get('titulo_eleitoral'), - data_nascimento=datetime.strptime(request.form.get('data_nascimento'), '%Y-%m-%d') if request.form.get('data_nascimento') else None, - data_entrada_oci=datetime.strptime(request.form.get('data_entrada_oci'), '%Y-%m-%d') if request.form.get('data_entrada_oci') else None, - data_efetivacao_oci=datetime.strptime(request.form.get('data_efetivacao_oci'), '%Y-%m-%d') if request.form.get('data_efetivacao_oci') else None, - - # Contato - telefone1=request.form.get('telefone1'), - telefone2=request.form.get('telefone2'), - endereco_id=endereco.id, - - # Profissional - profissao=request.form.get('profissao'), - regime_trabalho=request.form.get('regime_trabalho'), - empresa=request.form.get('empresa'), - contratante=request.form.get('contratante'), - - # Acadêmico - instituicao_ensino=request.form.get('instituicao_ensino'), - tipo_instituicao=request.form.get('tipo_instituicao'), - - # Sindical - sindicato=request.form.get('sindicato'), - cargo_sindical=request.form.get('cargo_sindical'), - central_sindical=request.form.get('central_sindical'), - dirigente_sindical=request.form.get('dirigente_sindical') == 'on', - - # Organização - estado=EstadoMilitante(request.form.get('estado', 'ATIVO')), - celula_id=request.form.get('celula_id', type=int), - responsabilidades=request.form.get('responsabilidades', type=int, default=0), - - # Por padrão, todo novo militante é aspirante - aspirante=True, - data_inicio_aspirante=datetime.now() - ) - db.add(militante) - db.flush() # Gerar ID do militante - - # Criar email principal - email = EmailMilitante( - email=request.form.get('email'), - principal=True, - militante_id=militante.id - ) - db.add(email) - - db.commit() - - return jsonify({ - 'status': 'success', - 'message': 'Militante criado com sucesso!' - }) - - except Exception as e: - db.rollback() - return jsonify({ - 'status': 'error', - 'message': f'Erro ao criar militante: {str(e)}' - }), 500 - finally: - db.close() + # Configuração da sessão do SQLAlchemy + db_session = get_db_connection() -# Rota para listar militantes -@app.route("/militantes") -@require_login -@require_permission(Permission.MANAGE_CELL_MEMBERS) -def listar_militantes(): - db = get_db_connection() - try: - militantes = db.query(Militante).order_by(Militante.nome).all() - celulas = db.query(Celula).order_by(Celula.nome).all() - return render_template('listar_militantes.html', militantes=militantes, Militante=Militante, celulas=celulas) - finally: - db.close() + # Decorator para verificar se o usuário está logado + def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + flash('Por favor, faça login para acessar esta página.', 'warning') + return redirect(url_for('login')) + return f(*args, **kwargs) + return decorated_function -# Rota para excluir militante -@app.route("/militantes/excluir/", methods=["POST"]) -@login_required -@session_timeout -def excluir_militante(id): - try: + # Decorator para verificar se a sessão expirou + def session_timeout(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if current_user.is_authenticated: + if 'last_activity' not in session: + session['last_activity'] = time() + return f(*args, **kwargs) + + last_activity = datetime.fromtimestamp(session['last_activity']) + now = datetime.now() + + # Se passaram mais de 30 minutos (configurável) + timeout_minutes = 30 + if now - last_activity > timedelta(minutes=timeout_minutes): + # Registrar o logout por timeout + try: + current_user.ultimo_logout = datetime.now() + current_user.motivo_logout = "Timeout de sessão" + db_session.commit() + except Exception as e: + print(f"Erro ao registrar logout por timeout: {e}") + + session.clear() + flash('Sua sessão expirou. Por favor, faça login novamente.', 'warning') + return redirect(url_for('login')) + + # Atualizar timestamp de último acesso + session['last_activity'] = time() + + # Atualizar também no banco de dados + try: + current_user.update_last_activity() + db_session.commit() + except Exception as e: + print(f"Erro ao atualizar última atividade: {e}") + + return f(*args, **kwargs) + return f(*args, **kwargs) + return decorated_function + + # Rota raiz - redireciona para login se não estiver autenticado + @app.route("/") + @require_login + def index(): + """Rota principal""" + return redirect(url_for('home')) + + # Rota de login + @app.route("/login", methods=["GET", "POST"]) + def login(): + """Rota de login""" + if request.method == "POST": + email_or_username = request.form.get("email") + password = request.form.get("password") + otp = request.form.get("otp") + + if not all([email_or_username, password]): + flash("Email/usuário e senha são obrigatórios.", "danger") + return redirect(url_for("login")) + + db = get_db_connection() + try: + # Tenta encontrar o usuário por email ou username + user = db.query(Usuario).filter( + (Usuario.email == email_or_username) | + (Usuario.username == email_or_username) + ).first() + + if not user or not user.check_password(password): + flash("Email/usuário ou senha incorretos.", "danger") + return redirect(url_for("login")) + + # Verificar OTP se o usuário tiver configurado + if user.otp_secret and not otp: + flash("Código OTP é obrigatório para sua conta.", "danger") + return redirect(url_for("login")) + + if user.otp_secret and not user.verify_otp(otp): + flash("Código OTP inválido.", "danger") + return redirect(url_for("login")) + + # Atualizar último login + user.ultimo_login = datetime.utcnow() + db.commit() + + # Fazer login e setar sessão + login_user(user) + session['user_id'] = user.id + session['username'] = user.username + session['is_admin'] = user.is_admin + + # Redirecionar para home + return redirect(url_for("home")) + finally: + db.close() + + return render_template("login.html") + + # Rota de logout + @app.route("/logout") + @login_required + def logout(): db = get_db_connection() - militante = db.query(Militante).get(id) - if not militante: - flash('Militante não encontrado', 'danger') - return redirect(url_for('listar_militantes')) + try: + user = current_user + if user: + user.logout() + db.commit() + logout_user() + finally: + db.close() + flash('Logout realizado com sucesso!', 'success') + return redirect(url_for('login')) - 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}") - flash('Erro ao excluir militante', 'danger') - finally: - db.close() - return redirect(url_for('listar_militantes')) + # Rota home (protegida) + @app.route("/home") + @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" -# Rota para criar uma nova cota -@app.route("/cotas/novo", methods=["GET", "POST"]) -@require_login -@require_permission(Permission.MANAGE_CELL_REPORTS) -def nova_cota(): - if request.method == "POST": - 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() + # Formatar data atual em português + data_atual = datetime.now().strftime("%d de %B de %Y") + + # Buscar dados para o dashboard + 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).count() + + # Buscar últimos militantes cadastrados + ultimos_militantes = db.query(Militante)\ + .order_by(Militante.id.desc())\ + .limit(5)\ + .all() + + # Buscar últimos pagamentos + ultimos_pagamentos = db.query(Pagamento)\ + .join(Militante)\ + .order_by(Pagamento.data_pagamento.desc())\ + .limit(5)\ + .all() + + # Buscar tipos de pagamento + tipos_pagamento = db.query(TipoPagamento).all() + + return render_template('home.html', + nome_usuario=nome_usuario, + data_atual=data_atual, + total_militantes=total_militantes, + total_cotas="{:.2f}".format(total_cotas), + total_materiais=total_materiais, + total_assinaturas=total_assinaturas, + ultimos_militantes=ultimos_militantes, + ultimos_pagamentos=ultimos_pagamentos, + tipos_pagamento=tipos_pagamento, + Militante=Militante) + except Exception as e: + print(f"Erro na página inicial: {e}") + import traceback + traceback.print_exc() + flash('Erro ao carregar a página inicial', 'danger') + return render_template('home.html', + nome_usuario="Usuário", + data_atual=datetime.now().strftime("%d/%m/%Y"), + total_militantes=0, + total_cotas="0.00", + total_materiais=0, + total_assinaturas=0, + ultimos_militantes=[], + ultimos_pagamentos=[], + Militante=Militante) + finally: + db.close() + + # Rota para criar um novo militante + @app.route("/militantes/criar", methods=["POST"]) + @require_login + @require_permission('gerenciar_militantes') + def criar_militante(): + """Cria um novo militante""" + db = get_db_connection() + try: + # Validar CPF + cpf = request.form.get('cpf') + if not validar_cpf(cpf): + return jsonify({ + 'status': 'error', + 'message': 'CPF inválido' + }), 400 + + # Verificar se já existe militante com este CPF + militante_existente = db.query(Militante).filter(Militante.cpf == cpf).first() + if militante_existente: + return jsonify({ + 'status': 'error', + 'message': 'CPF já cadastrado' + }), 400 + + # Criar endereço + endereco = Endereco( + cep=request.form.get('cep'), + estado=request.form.get('estado'), + cidade=request.form.get('cidade'), + bairro=request.form.get('bairro'), + logradouro=request.form.get('logradouro'), + numero=request.form.get('numero'), + complemento=request.form.get('complemento') + ) + db.add(endereco) + db.flush() # Gerar ID do endereço + + # Criar militante + militante = Militante( + # Dados Básicos + nome=request.form.get('nome'), + cpf=cpf, + titulo_eleitoral=request.form.get('titulo_eleitoral'), + data_nascimento=datetime.strptime(request.form.get('data_nascimento'), '%Y-%m-%d') if request.form.get('data_nascimento') else None, + data_entrada_oci=datetime.strptime(request.form.get('data_entrada_oci'), '%Y-%m-%d') if request.form.get('data_entrada_oci') else None, + data_efetivacao_oci=datetime.strptime(request.form.get('data_efetivacao_oci'), '%Y-%m-%d') if request.form.get('data_efetivacao_oci') else None, + + # Contato + telefone1=request.form.get('telefone1'), + telefone2=request.form.get('telefone2'), + endereco_id=endereco.id, + + # Profissional + profissao=request.form.get('profissao'), + regime_trabalho=request.form.get('regime_trabalho'), + empresa=request.form.get('empresa'), + contratante=request.form.get('contratante'), + + # Acadêmico + instituicao_ensino=request.form.get('instituicao_ensino'), + tipo_instituicao=request.form.get('tipo_instituicao'), + + # Sindical + sindicato=request.form.get('sindicato'), + cargo_sindical=request.form.get('cargo_sindical'), + central_sindical=request.form.get('central_sindical'), + dirigente_sindical=request.form.get('dirigente_sindical') == 'on', + + # Organização + estado=EstadoMilitante(request.form.get('estado', 'ATIVO')), + celula_id=request.form.get('celula_id', type=int), + responsabilidades=request.form.get('responsabilidades', type=int, default=0), + + # Por padrão, todo novo militante é aspirante + aspirante=True, + data_inicio_aspirante=datetime.now() + ) + db.add(militante) + db.flush() # Gerar ID do militante + + # Criar email principal + email = request.form.get('email') + if email: + if militante.emails: + militante.emails[0].endereco_email = email + else: + novo_email = EmailMilitante( + endereco_email=email, + militante_id=militante.id + ) + db.add(novo_email) + militante.emails.append(novo_email) + + db.commit() + + return jsonify({ + 'status': 'success', + 'message': 'Militante criado com sucesso!' + }) + + except Exception as e: + db.rollback() + return jsonify({ + 'status': 'error', + 'message': f'Erro ao criar militante: {str(e)}' + }), 500 + finally: + db.close() + + # Rota para listar militantes + @app.route("/militantes") + @require_login + @require_permission(Permission.MANAGE_CELL_MEMBERS) + def listar_militantes(): + db = get_db_connection() + try: + militantes = db.query(Militante).order_by(Militante.nome).all() + celulas = db.query(Celula).order_by(Celula.nome).all() + return render_template('listar_militantes.html', militantes=militantes, Militante=Militante, celulas=celulas) + finally: + db.close() + + # Rota para excluir militante + @app.route("/militantes/excluir/", methods=["POST"]) + @login_required + @session_timeout + def excluir_militante(id): + try: + db = get_db_connection() + militante = db.query(Militante).get(id) + if not militante: + flash('Militante não encontrado', 'danger') + return redirect(url_for('listar_militantes')) + + 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}") + flash('Erro ao excluir militante', 'danger') + finally: + db.close() + return redirect(url_for('listar_militantes')) + + # Rota para criar uma nova cota + @app.route("/cotas/novo", methods=["GET", "POST"]) + @require_login + @require_permission(Permission.MANAGE_CELL_REPORTS) + def nova_cota(): + if request.method == "POST": + 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 = get_db_connection() + try: + 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() + + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({ + 'status': 'success', + 'message': 'Cota cadastrada com sucesso!' + }) + + flash('Cota cadastrada com sucesso!', 'success') + return redirect(url_for('listar_cotas')) + except Exception as e: + db.rollback() + print(f"Erro ao cadastrar cota: {e}") + + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({ + 'status': 'error', + 'message': 'Erro ao cadastrar cota. Verifique os dados e tente novamente.' + }), 400 + + flash('Erro ao cadastrar cota', 'danger') + return render_template("nova_cota.html") + finally: + db.close() db = get_db_connection() try: - cotas_mensais = CotaMensal( + militantes = db.query(Militante).order_by(Militante.nome).all() + return render_template("nova_cota.html", militantes=militantes) + finally: + db.close() + + # Rota para listar cotas + @app.route("/cotas") + @require_login + @require_permission(Permission.MANAGE_CELL_REPORTS) + def listar_cotas(): + try: + print("Buscando cotas...") + db = get_db_connection() + cotas = db.query(CotaMensal)\ + .join(Militante)\ + .order_by(CotaMensal.data_vencimento.desc())\ + .all() + + # Calcular status de cada cota + for cota in cotas: + if cota.pago: + cota.status = "paga" + elif cota.data_vencimento < datetime.now().date(): + cota.status = "atrasada" + else: + cota.status = "pendente" + + # Buscar militantes para o modal de nova cota + militantes = db.query(Militante).order_by(Militante.nome).all() + + return render_template("listar_cotas.html", cotas=cotas, militantes=militantes) + except Exception as e: + print(f"Erro ao listar cotas: {e}") + flash('Erro ao listar cotas', 'danger') + return render_template("listar_cotas.html", cotas=[], militantes=[]) + finally: + db.close() + + # Rota para editar cota + @app.route('/cotas/editar/', methods=['GET', 'POST']) + @login_required + @session_timeout + def editar_cota(id): + db = get_db_connection() + try: + cota = db.query(CotaMensal).get_or_404(id) + + if request.method == 'POST': + try: + print("Dados recebidos:", request.form) + cota.militante_id = int(request.form['militante_id']) + cota.valor_antigo = float(request.form['valor_antigo']) + cota.valor_novo = float(request.form['valor_novo']) + cota.data_alteracao = datetime.strptime(request.form['data_alteracao'], '%Y-%m-%d').date() + cota.data_vencimento = datetime.strptime(request.form['data_vencimento'], '%Y-%m-%d').date() + + # Processar o campo pago + pago = request.form.get('pago', '').lower() + print("Valor do campo pago recebido:", pago) + cota.pago = pago == 'true' + print("Status final do pago:", cota.pago) + + db.commit() + print("Commit realizado com sucesso") + + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({ + 'status': 'success', + 'message': 'Cota atualizada com sucesso!' + }) + + flash('Cota atualizada com sucesso!', 'success') + return redirect(url_for('listar_cotas')) + + except Exception as e: + db.rollback() + print(f"Erro ao atualizar cota: {str(e)}") + print(f"Tipo do erro: {type(e)}") + import traceback + traceback.print_exc() + + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({ + 'status': 'error', + 'message': f'Erro ao atualizar cota: {str(e)}' + }), 400 + + flash('Erro ao atualizar cota. Verifique os dados e tente novamente.', 'danger') + return redirect(url_for('editar_cota', id=id)) + + return render_template('editar_cota.html', cota=cota) + + except Exception as e: + print(f"Erro ao carregar cota: {str(e)}") + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({ + 'status': 'error', + 'message': f'Erro ao carregar cota: {str(e)}' + }), 404 + flash('Erro ao carregar 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(): + if request.method == "POST": + militante_id = request.form.get("militante_id") + tipo_pagamento_id = request.form.get("tipo_pagamento_id") + 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=valor, + data_pagamento=data_pagamento + ) + db.add(pagamento) + db.commit() + flash('Pagamento cadastrado com sucesso!', 'success') + return redirect(url_for('listar_pagamentos')) + except Exception as e: + 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 + 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 + @require_permission(Permission.MANAGE_CELL_REPORTS) + def listar_pagamentos(): + try: + print("Buscando pagamentos...") + db = get_db_connection() + pagamentos = db.query(Pagamento)\ + .join(Militante)\ + .order_by(Pagamento.data_pagamento.desc())\ + .all() + + militantes = db.query(Militante).order_by(Militante.nome).all() + tipos_pagamento = db.query(TipoPagamento).all() + + return render_template("listar_pagamentos.html", + pagamentos=pagamentos, + militantes=militantes, + tipos_pagamento=tipos_pagamento) + except Exception as e: + print(f"Erro ao listar pagamentos: {e}") + flash('Erro ao listar pagamentos', 'danger') + return render_template("listar_pagamentos.html", pagamentos=[], militantes=[]) + finally: + db.close() + + # Rota para adicionar pagamento + @app.route("/pagamentos/adicionar", methods=["POST"]) + @require_login + @require_permission(Permission.MANAGE_CELL_REPORTS) + def adicionar_pagamento(): + if request.method == "POST": + try: + militante_id = request.form.get("militante_id") + tipo_pagamento = request.form.get("tipo_pagamento") + valor = float(request.form.get("valor")) + data_pagamento = datetime.strptime(request.form.get("data_pagamento"), "%Y-%m-%d").date() + + db = get_db_connection() + pagamento = Pagamento( + militante_id=militante_id, + tipo_pagamento=tipo_pagamento, + valor=valor, + data_pagamento=data_pagamento + ) + db.add(pagamento) + db.commit() + flash('Pagamento adicionado com sucesso!', 'success') + except Exception as e: + db.rollback() + flash(f'Erro ao adicionar pagamento: {str(e)}', 'danger') + finally: + db.close() + + return redirect(url_for('listar_pagamentos')) + + # Rota para criar um novo material vendido + @app.route("/materiais/novo", methods=["GET", "POST"]) + @require_login + @require_permission(Permission.VIEW_CELL_REPORTS) + 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 = datetime.strptime(request.form.get('data_venda'), '%Y-%m-%d') + + material = MaterialVendido( militante_id=militante_id, - valor_antigo=valor_antigo, - valor_novo=valor_novo, - data_alteracao=data_alteracao, - data_vencimento=data_vencimento, - pago=False + tipo_material_id=tipo_material_id, + descricao=descricao, + valor=valor, + data_venda=data_venda ) - db.add(cotas_mensais) + + db.add(material) db.commit() if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return jsonify({ 'status': 'success', - 'message': 'Cota cadastrada com sucesso!' + 'message': 'Material cadastrado com sucesso!' }) - flash('Cota cadastrada com sucesso!', 'success') - return redirect(url_for('listar_cotas')) + flash('Material cadastrado com sucesso!', 'success') + return redirect(url_for('listar_materiais')) + except Exception as e: db.rollback() - print(f"Erro ao cadastrar cota: {e}") - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return jsonify({ 'status': 'error', - 'message': 'Erro ao cadastrar cota. Verifique os dados e tente novamente.' + 'message': 'Erro ao cadastrar material. Por favor, tente novamente.' }), 400 - flash('Erro ao cadastrar cota', 'danger') - return render_template("nova_cota.html") + flash('Erro ao cadastrar material. Por favor, tente novamente.', 'error') + return redirect(url_for('listar_materiais')) finally: db.close() - 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 -@app.route("/cotas") -@require_login -@require_permission(Permission.MANAGE_CELL_REPORTS) -def listar_cotas(): - try: - print("Buscando cotas...") - db = get_db_connection() - cotas = db.query(CotaMensal)\ - .join(Militante)\ - .order_by(CotaMensal.data_vencimento.desc())\ - .all() - - # Calcular status de cada cota - for cota in cotas: - if cota.pago: - cota.status = "paga" - elif cota.data_vencimento < datetime.now().date(): - cota.status = "atrasada" - else: - cota.status = "pendente" - - # Buscar militantes para o modal de nova cota - militantes = db.query(Militante).order_by(Militante.nome).all() - - return render_template("listar_cotas.html", cotas=cotas, militantes=militantes) - except Exception as e: - print(f"Erro ao listar cotas: {e}") - flash('Erro ao listar cotas', 'danger') - return render_template("listar_cotas.html", cotas=[], militantes=[]) - finally: - db.close() - -# Rota para editar cota -@app.route('/cotas/editar/', methods=['GET', 'POST']) -@login_required -@session_timeout -def editar_cota(id): - db = get_db_connection() - try: - cota = db.query(CotaMensal).get_or_404(id) - - if request.method == 'POST': - try: - print("Dados recebidos:", request.form) - cota.militante_id = int(request.form['militante_id']) - cota.valor_antigo = float(request.form['valor_antigo']) - cota.valor_novo = float(request.form['valor_novo']) - cota.data_alteracao = datetime.strptime(request.form['data_alteracao'], '%Y-%m-%d').date() - cota.data_vencimento = datetime.strptime(request.form['data_vencimento'], '%Y-%m-%d').date() - - # Processar o campo pago - pago = request.form.get('pago', '').lower() - print("Valor do campo pago recebido:", pago) - cota.pago = pago == 'true' - print("Status final do pago:", cota.pago) - - db.commit() - print("Commit realizado com sucesso") - - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'success', - 'message': 'Cota atualizada com sucesso!' - }) - - flash('Cota atualizada com sucesso!', 'success') - return redirect(url_for('listar_cotas')) - - except Exception as e: - db.rollback() - print(f"Erro ao atualizar cota: {str(e)}") - print(f"Tipo do erro: {type(e)}") - import traceback - traceback.print_exc() - - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'error', - 'message': f'Erro ao atualizar cota: {str(e)}' - }), 400 - - flash('Erro ao atualizar cota. Verifique os dados e tente novamente.', 'danger') - return redirect(url_for('editar_cota', id=id)) - - return render_template('editar_cota.html', cota=cota) - - except Exception as e: - print(f"Erro ao carregar cota: {str(e)}") - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'error', - 'message': f'Erro ao carregar cota: {str(e)}' - }), 404 - flash('Erro ao carregar 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(): - if request.method == "POST": - militante_id = request.form.get("militante_id") - tipo_pagamento_id = request.form.get("tipo_pagamento_id") - valor = float(request.form.get("valor")) - data_pagamento = datetime.strptime(request.form.get("data_pagamento"), "%Y-%m-%d").date() - - # Criar novo pagamento + # Rota para listar materiais vendidos + @app.route("/materiais") + @require_login + @require_permission(Permission.VIEW_CELL_REPORTS) + def listar_materiais(): db = get_db_connection() try: - pagamento = Pagamento( + 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() + + # 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(): + db = get_db_connection() + try: + 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') + + venda = VendaJornalAvulso( militante_id=militante_id, - tipo_pagamento_id=tipo_pagamento_id, - valor=valor, - data_pagamento=data_pagamento + quantidade=quantidade, + valor_total=valor_total, + data_venda=data_venda ) - db.add(pagamento) + + db.add(venda) db.commit() - flash('Pagamento cadastrado com sucesso!', 'success') - return redirect(url_for('listar_pagamentos')) + + 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')) + except Exception as e: db.rollback() - print(f"Erro ao cadastrar pagamento: {e}") - flash('Erro ao cadastrar pagamento', 'danger') - return render_template("novo_pagamento.html") + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({ + 'status': 'error', + 'message': 'Erro ao cadastrar venda. Por favor, tente novamente.' + }), 400 + + flash('Erro ao cadastrar venda. Por favor, tente novamente.', 'error') + return redirect(url_for('listar_vendas_jornal')) finally: db.close() - # GET - Renderizar formulário - 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 -@require_permission(Permission.MANAGE_CELL_REPORTS) -def listar_pagamentos(): - try: - print("Buscando pagamentos...") + # Rota para listar vendas de jornal + @app.route("/jornais") + @require_login + @require_permission(Permission.VIEW_CELL_REPORTS) + def listar_vendas_jornal(): db = get_db_connection() - pagamentos = db.query(Pagamento)\ - .join(Militante)\ - .order_by(Pagamento.data_pagamento.desc())\ - .all() - - militantes = db.query(Militante).order_by(Militante.nome).all() - tipos_pagamento = db.query(TipoPagamento).all() - - return render_template("listar_pagamentos.html", - pagamentos=pagamentos, - militantes=militantes, - tipos_pagamento=tipos_pagamento) - except Exception as e: - print(f"Erro ao listar pagamentos: {e}") - flash('Erro ao listar pagamentos', 'danger') - return render_template("listar_pagamentos.html", pagamentos=[], militantes=[]) - finally: - db.close() - -# Rota para adicionar pagamento -@app.route("/pagamentos/adicionar", methods=["POST"]) -@require_login -@require_permission(Permission.MANAGE_CELL_REPORTS) -def adicionar_pagamento(): - if request.method == "POST": try: - militante_id = request.form.get("militante_id") - tipo_pagamento = request.form.get("tipo_pagamento") - valor = float(request.form.get("valor")) - data_pagamento = datetime.strptime(request.form.get("data_pagamento"), "%Y-%m-%d").date() + 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() + + # 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": + 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 = get_db_connection() - pagamento = Pagamento( - militante_id=militante_id, - tipo_pagamento=tipo_pagamento, - valor=valor, - data_pagamento=data_pagamento - ) - db.add(pagamento) - db.commit() - flash('Pagamento adicionado com sucesso!', 'success') - except Exception as e: - db.rollback() - flash(f'Erro ao adicionar pagamento: {str(e)}', 'danger') + 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() + 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() + + 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() - - return redirect(url_for('listar_pagamentos')) -# Rota para criar um novo material vendido -@app.route("/materiais/novo", methods=["GET", "POST"]) -@require_login -@require_permission(Permission.VIEW_CELL_REPORTS) -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 = datetime.strptime(request.form.get('data_venda'), '%Y-%m-%d') + # Rota para listar relatórios de cotas + @app.route("/relatorios/cotas") + @require_login + @require_permission(Permission.VIEW_CELL_REPORTS) + def listar_relatorios_cotas(): + try: + 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: {e}") + flash('Erro ao listar relatórios de cotas', 'danger') + return render_template("listar_relatorios_cotas.html", relatorios=[]) + finally: + db.close() - 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': + # 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": + 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 = get_db_connection() + try: + 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: + 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() + + 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 + @app.route("/relatorios/vendas") + @require_login + @require_permission(Permission.VIEW_CELL_REPORTS) + def listar_relatorios_vendas(): + try: + 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: {e}") + flash('Erro ao listar relatórios de vendas', 'danger') + return render_template("listar_relatorios_vendas.html", relatorios=[]) + finally: + db.close() + + # Rota para editar militante + @app.route("/militantes/editar/", methods=["POST"]) + @require_login + @require_permission('gerenciar_militantes') + def editar_militante(militante_id): + """Edita um militante existente""" + # Verificar token CSRF manualmente + csrf_token = request.headers.get('X-CSRFToken') or request.form.get('csrf_token') + if not csrf_token or not csrf.validate_csrf(csrf_token): return jsonify({ 'status': 'error', - 'message': 'Erro ao cadastrar material. Por favor, tente novamente.' + 'message': 'Token CSRF inválido' }), 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) -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() - -# 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(): - db = get_db_connection() - try: - 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') - - venda = VendaJornalAvulso( - militante_id=militante_id, - quantidade=quantidade, - valor_total=valor_total, - data_venda=data_venda - ) - - db.add(venda) - 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')) - - except Exception as e: - db.rollback() - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'error', - 'message': 'Erro ao cadastrar venda. Por favor, tente novamente.' - }), 400 - - flash('Erro ao cadastrar venda. Por favor, tente novamente.', 'error') - return redirect(url_for('listar_vendas_jornal')) - finally: - db.close() - -# Rota para listar vendas de jornal -@app.route("/jornais") -@require_login -@require_permission(Permission.VIEW_CELL_REPORTS) -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() - -# 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": - 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 = 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() - 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() - - 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 -@app.route("/relatorios/cotas") -@require_login -@require_permission(Permission.VIEW_CELL_REPORTS) -def listar_relatorios_cotas(): - try: - 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: {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 -@app.route("/relatorios/vendas/novo", methods=["GET", "POST"]) -@require_login -@require_permission(Permission.VIEW_CELL_REPORTS) -def novo_relatorio_vendas(): - if request.method == "POST": - 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 = get_db_connection() - try: - 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: - 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() - - 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 -@app.route("/relatorios/vendas") -@require_login -@require_permission(Permission.VIEW_CELL_REPORTS) -def listar_relatorios_vendas(): - try: - 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: {e}") - flash('Erro ao listar relatórios de vendas', 'danger') - return render_template("listar_relatorios_vendas.html", relatorios=[]) - finally: - db.close() - -# Rota para editar militante -@app.route("/militantes/editar/", methods=["POST"]) -@require_login -@require_permission('gerenciar_militantes') -def editar_militante(militante_id): - """Edita um militante existente""" - print(f"Iniciando edição do militante {militante_id}") - print(f"Dados recebidos: {request.form}") - - if request.method == "POST": - db = get_db_connection() - try: - militante = db.query(Militante).options( - joinedload(Militante.endereco), - joinedload(Militante.emails) - ).get(militante_id) - + militante = db.query(Militante).get(militante_id) if not militante: - print(f"Militante {militante_id} não encontrado") return jsonify({ 'status': 'error', 'message': 'Militante não encontrado' }), 404 - print(f"Militante encontrado: {militante.nome}") - # Dados Básicos militante.nome = request.form.get('nome') militante.cpf = request.form.get('cpf') @@ -974,8 +970,6 @@ def editar_militante(militante_id): militante.data_entrada_oci = datetime.strptime(request.form.get('data_entrada_oci'), '%Y-%m-%d') if request.form.get('data_entrada_oci') else None militante.data_efetivacao_oci = datetime.strptime(request.form.get('data_efetivacao_oci'), '%Y-%m-%d') if request.form.get('data_efetivacao_oci') else None - print("Dados básicos atualizados") - # Contato militante.telefone1 = request.form.get('telefone1') militante.telefone2 = request.form.get('telefone2') @@ -988,12 +982,10 @@ def editar_militante(militante_id): else: novo_email = EmailMilitante( endereco_email=email, - militante_id=militante.id, - principal=True + militante_id=militante.id ) db.add(novo_email) - - print("Dados de contato atualizados") + militante.emails.append(novo_email) # Endereço if not militante.endereco: @@ -1008,8 +1000,6 @@ def editar_militante(militante_id): militante.endereco.numero = request.form.get('numero') militante.endereco.complemento = request.form.get('complemento') - print("Dados de endereço atualizados") - # Profissional militante.profissao = request.form.get('profissao') militante.regime_trabalho = request.form.get('regime_trabalho') @@ -1026,8 +1016,6 @@ def editar_militante(militante_id): militante.central_sindical = request.form.get('central_sindical') militante.dirigente_sindical = request.form.get('dirigente_sindical') == 'on' - print("Dados profissionais e sindicais atualizados") - # Organização estado_str = request.form.get('estado', 'ATIVO').lower() print(f"Estado recebido: {estado_str}") @@ -1055,37 +1043,17 @@ def editar_militante(militante_id): # Tratar responsabilidades corretamente try: - responsabilidades_json = request.form.get('responsabilidades') - if responsabilidades_json: - responsabilidades_lista = json.loads(responsabilidades_json) - valor_responsabilidades = 0 - if 'Responsável de Finanças' in responsabilidades_lista: - valor_responsabilidades |= Militante.RESPONSAVEL_FINANCAS - if 'Responsável de Imprensa' in responsabilidades_lista: - valor_responsabilidades |= Militante.RESPONSAVEL_IMPRENSA - if 'Quadro-Orientador' in responsabilidades_lista: - valor_responsabilidades |= Militante.QUADRO_ORIENTADOR - if 'Secretário' in responsabilidades_lista: - valor_responsabilidades |= Militante.SECRETARIO - if 'MPS' in responsabilidades_lista: - valor_responsabilidades |= Militante.MPS - if 'Tesoureiro' in responsabilidades_lista: - valor_responsabilidades |= Militante.TESOUREIRO - if 'MNS' in responsabilidades_lista: - valor_responsabilidades |= Militante.MNS - if 'Juventude' in responsabilidades_lista: - valor_responsabilidades |= Militante.JUVENTUDE - if 'Aspirante' in responsabilidades_lista: - valor_responsabilidades |= Militante.ASPIRANTE - militante.responsabilidades = valor_responsabilidades + responsabilidades_valor = request.form.get('responsabilidades_valor') + if responsabilidades_valor: + militante.responsabilidades = int(responsabilidades_valor) + print(f"Responsabilidades atualizadas: {militante.responsabilidades}") else: militante.responsabilidades = 0 - except (ValueError, TypeError, json.JSONDecodeError) as e: + print("Responsabilidades zeradas") + except (ValueError, TypeError) as e: print(f"Erro ao processar responsabilidades: {str(e)}") militante.responsabilidades = 0 - print("Dados organizacionais atualizados") - # Se o estado mudou para DESLIGADO, registrar data e motivo if militante.estado == EstadoMilitante.DESLIGADO: militante.data_desligamento = datetime.now() @@ -1094,39 +1062,15 @@ def editar_militante(militante_id): db.commit() print("Alterações salvas com sucesso") - # Converter responsabilidades para lista de strings - responsabilidades = [] - if militante.responsabilidades & Militante.RESPONSAVEL_FINANCAS: - responsabilidades.append('Responsável de Finanças') - if militante.responsabilidades & Militante.RESPONSAVEL_IMPRENSA: - responsabilidades.append('Responsável de Imprensa') - if militante.responsabilidades & Militante.QUADRO_ORIENTADOR: - responsabilidades.append('Quadro-Orientador') - if militante.responsabilidades & Militante.SECRETARIO: - responsabilidades.append('Secretário') - if militante.responsabilidades & Militante.MPS: - responsabilidades.append('MPS') - if militante.responsabilidades & Militante.TESOUREIRO: - responsabilidades.append('Tesoureiro') - if militante.responsabilidades & Militante.MNS: - responsabilidades.append('MNS') - if militante.responsabilidades & Militante.JUVENTUDE: - responsabilidades.append('Juventude') - if militante.responsabilidades & Militante.ASPIRANTE: - responsabilidades.append('Aspirante') + # Retornar as responsabilidades atualizadas + responsabilidades = militante.get_responsabilidades() return jsonify({ 'status': 'success', 'message': f'Militante {militante.nome} atualizado com sucesso!', 'responsabilidades': responsabilidades, - 'responsabilidades_bits': militante.responsabilidades + 'responsabilidades_valor': militante.responsabilidades }) - - return jsonify({ - 'status': 'success', - 'message': f'Militante {militante.nome} atualizado com sucesso!' - }) - except Exception as e: print(f"Erro ao atualizar militante: {str(e)}") db.rollback() @@ -1137,563 +1081,539 @@ def editar_militante(militante_id): finally: db.close() -# 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") + # 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: - if db.query(Usuario).filter_by(username=username).first(): - flash('Nome de usuário já existe.', 'danger') - return render_template("novo_usuario.html") - - novo_usuario = Usuario( - username=username, - email=email, - role_id=role_id, - setor_id=setor_id - ) - 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() - print(f"Erro ao cadastrar usuário: {e}") - flash('Erro ao cadastrar usuário', 'danger') - return render_template("novo_usuario.html") - finally: - db.close() - - db = get_db_connection() - try: - 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 + # Verificar se usuário já existe + db = get_db_connection() 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() + if db.query(Usuario).filter_by(username=username).first(): + flash('Nome de usuário já existe.', 'danger') + return render_template("novo_usuario.html") + + novo_usuario = Usuario( + username=username, + email=email, + role_id=role_id, + setor_id=setor_id + ) + 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: - print(f"Erro ao registrar logout por timeout: {e}") - - session.clear() + db.rollback() + print(f"Erro ao cadastrar usuário: {e}") + flash('Erro ao cadastrar usuário', 'danger') + return render_template("novo_usuario.html") + finally: + db.close() + + db = get_db_connection() + try: + 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'}) - - return jsonify({'status': 'active'}) - -@app.route("/qr/") -def get_qr_code(token): - db = get_db_connection() - try: - user = db.query(Usuario).filter_by(username='admin').first() - if not user: - flash('Usuário não encontrado', 'error') - return redirect(url_for('login')) - qr_uri = user.get_otp_uri() - return render_template('mostrar_qr_code.html', qr_uri=qr_uri) - finally: - db.close() - -# Adicionar nova rota para API de setores -@app.route("/api/setores/") -@require_login -def get_setores(cr_id): - setores = db_session.query(Setor).filter_by(cr_id=cr_id).all() - return jsonify({ - 'setores': [{'id': s.id, 'nome': s.nome} for s in setores] - }) - -@app.route('/celulas//militantes') -@require_login -def list_militantes_celula(celula_id): - @require_instance_access('celula', celula_id) - def decorated_function(celula_id): - db = get_db_connection() - try: - militantes = db.query(Usuario).filter_by(celula_id=celula_id).all() - return render_template('militantes/list.html', militantes=militantes) - finally: - db.close() - return decorated_function(celula_id) - -@app.route('/setores//militantes') -@require_login -def list_militantes_setor(setor_id): - @require_instance_access('setor', setor_id) - def decorated_function(setor_id): - db = get_db_connection() - try: - militantes = db.query(Usuario).filter_by(setor_id=setor_id).all() - return render_template('militantes/list.html', militantes=militantes) - finally: - db.close() - return decorated_function(setor_id) - -@app.route('/crs//militantes') -@require_login -def list_militantes_cr(cr_id): - @require_instance_access('cr', cr_id) - def decorated_function(cr_id): - db = get_db_connection() - try: - militantes = db.query(Usuario).filter_by(cr_id=cr_id).all() - return render_template('militantes/list.html', militantes=militantes) - finally: - 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")) + 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/") + def get_qr_code(token): 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") + user = db.query(Usuario).filter_by(username='admin').first() + if not user: + flash('Usuário não encontrado', 'error') + return redirect(url_for('login')) + + qr_uri = user.get_otp_uri() + return render_template('mostrar_qr_code.html', qr_uri=qr_uri) + finally: + db.close() + + # Adicionar nova rota para API de setores + @app.route("/api/setores/") + @require_login + def get_setores(cr_id): + setores = db_session.query(Setor).filter_by(cr_id=cr_id).all() + return jsonify({ + 'setores': [{'id': s.id, 'nome': s.nome} for s in setores] + }) + + @app.route('/celulas//militantes') + @require_login + def list_militantes_celula(celula_id): + @require_instance_access('celula', celula_id) + def decorated_function(celula_id): + db = get_db_connection() + try: + militantes = db.query(Usuario).filter_by(celula_id=celula_id).all() + return render_template('militantes/list.html', militantes=militantes) + finally: + db.close() + return decorated_function(celula_id) + + @app.route('/setores//militantes') + @require_login + def list_militantes_setor(setor_id): + @require_instance_access('setor', setor_id) + def decorated_function(setor_id): + db = get_db_connection() + try: + militantes = db.query(Usuario).filter_by(setor_id=setor_id).all() + return render_template('militantes/list.html', militantes=militantes) + finally: + db.close() + return decorated_function(setor_id) + + @app.route('/crs//militantes') + @require_login + def list_militantes_cr(cr_id): + @require_instance_access('cr', cr_id) + def decorated_function(cr_id): + db = get_db_connection() + try: + militantes = db.query(Usuario).filter_by(cr_id=cr_id).all() + return render_template('militantes/list.html', militantes=militantes) + finally: + 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")) - user.password_hash = generate_password_hash(nova_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() - flash("Senha alterada com sucesso!", "success") - return redirect(url_for("home")) + 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() - - 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')) + @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')) + # 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") + @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/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("/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): - """Retorna os dados de um militante""" - db = get_db_connection() - try: - militante = db.query(Militante).options( - joinedload(Militante.endereco), - joinedload(Militante.emails) - ).get(militante_id) - - if not militante: + @app.route("/militantes/dados/") + @require_login + @require_permission('gerenciar_militantes') + def buscar_dados_militante(militante_id): + """Retorna os dados de um militante""" + db = get_db_connection() + try: + militante = db.query(Militante).options( + joinedload(Militante.endereco), + joinedload(Militante.emails) + ).get(militante_id) + + if not militante: + return jsonify({ + 'status': 'error', + 'message': 'Militante não encontrado' + }), 404 + + # Preparar dados do militante + dados = { + 'id': militante.id, + 'nome': militante.nome, + 'cpf': militante.cpf, + 'titulo_eleitoral': militante.titulo_eleitoral, + 'data_nascimento': militante.data_nascimento.strftime('%Y-%m-%d') if militante.data_nascimento else None, + 'data_entrada_oci': militante.data_entrada_oci.strftime('%Y-%m-%d') if militante.data_entrada_oci else None, + 'data_efetivacao_oci': militante.data_efetivacao_oci.strftime('%Y-%m-%d') if militante.data_efetivacao_oci else None, + 'telefone1': militante.telefone1, + 'telefone2': militante.telefone2, + 'emails': [email.endereco_email for email in militante.emails], + 'profissao': militante.profissao, + 'regime_trabalho': militante.regime_trabalho, + 'empresa': militante.empresa, + 'contratante': militante.contratante, + 'instituicao_ensino': militante.instituicao_ensino, + 'tipo_instituicao': militante.tipo_instituicao, + 'sindicato': militante.sindicato, + 'cargo_sindical': militante.cargo_sindical, + 'dirigente_sindical': militante.dirigente_sindical, + 'central_sindical': militante.central_sindical, + 'celula_id': militante.celula_id, + 'estado': militante.estado.name if militante.estado else None, + 'responsabilidades_valor': militante.responsabilidades, + 'endereco': { + 'cep': militante.endereco.cep if militante.endereco else None, + 'rua': militante.endereco.rua if militante.endereco else None, + 'numero': militante.endereco.numero if militante.endereco else None, + 'complemento': militante.endereco.complemento if militante.endereco else None, + 'bairro': militante.endereco.bairro if militante.endereco else None, + 'cidade': militante.endereco.cidade if militante.endereco else None, + 'estado': militante.endereco.estado if militante.endereco else None + } + } + + return jsonify(dados) + + except Exception as e: + print(f"Erro ao buscar dados do militante: {str(e)}") return jsonify({ 'status': 'error', - 'message': 'Militante não encontrado' - }), 404 - - # Converter responsabilidades para lista de strings - responsabilidades = [] - if militante.responsabilidades & Militante.RESPONSAVEL_FINANCAS: - responsabilidades.append('Responsável de Finanças') - if militante.responsabilidades & Militante.RESPONSAVEL_IMPRENSA: - responsabilidades.append('Responsável de Imprensa') - if militante.responsabilidades & Militante.QUADRO_ORIENTADOR: - responsabilidades.append('Quadro-Orientador') - if militante.responsabilidades & Militante.SECRETARIO: - responsabilidades.append('Secretário') - if militante.responsabilidades & Militante.MPS: - responsabilidades.append('MPS') - if militante.responsabilidades & Militante.TESOUREIRO: - responsabilidades.append('Tesoureiro') - if militante.responsabilidades & Militante.MNS: - responsabilidades.append('MNS') - if militante.responsabilidades & Militante.JUVENTUDE: - responsabilidades.append('Juventude') - if militante.responsabilidades & Militante.ASPIRANTE: - responsabilidades.append('Aspirante') - - return jsonify({ - 'nome': militante.nome, - 'cpf': militante.cpf, - 'titulo_eleitoral': militante.titulo_eleitoral, - 'data_nascimento': militante.data_nascimento.strftime('%Y-%m-%d') if militante.data_nascimento else None, - 'data_entrada_oci': militante.data_entrada_oci.strftime('%Y-%m-%d') if militante.data_entrada_oci else None, - 'data_efetivacao_oci': militante.data_efetivacao_oci.strftime('%Y-%m-%d') if militante.data_efetivacao_oci else None, - 'telefone1': militante.telefone1, - 'telefone2': militante.telefone2, - 'email': militante.emails[0].endereco_email if militante.emails else None, - 'endereco': { - 'cep': militante.endereco.cep if militante.endereco else None, - 'estado': militante.endereco.estado if militante.endereco else None, - 'cidade': militante.endereco.cidade if militante.endereco else None, - 'bairro': militante.endereco.bairro if militante.endereco else None, - 'rua': militante.endereco.rua if militante.endereco else None, - 'numero': militante.endereco.numero if militante.endereco else None, - 'complemento': militante.endereco.complemento if militante.endereco else None - }, - 'profissao': militante.profissao, - 'regime_trabalho': militante.regime_trabalho, - 'empresa': militante.empresa, - 'contratante': militante.contratante, - 'instituicao_ensino': militante.instituicao_ensino, - 'tipo_instituicao': militante.tipo_instituicao, - 'sindicato': militante.sindicato, - 'cargo_sindical': militante.cargo_sindical, - 'central_sindical': militante.central_sindical, - 'dirigente_sindical': militante.dirigente_sindical, - 'estado': militante.estado.value if militante.estado else None, - 'celula_id': militante.celula_id, - 'responsabilidades': responsabilidades, - 'responsabilidades': responsabilidades - }) - except Exception as e: - print(f"Erro ao buscar dados do militante: {str(e)}") - return jsonify({ - 'status': 'error', - 'message': f'Erro ao buscar dados do militante: {str(e)}' - }), 500 - finally: - db.close() + 'message': 'Erro ao buscar dados do militante' + }), 500 -def create_app(): - app = Flask(__name__) - # ... existing code ... - - # ... existing code ... return app def init_system(): @@ -1755,10 +1675,22 @@ def init_system(): print("Username: aligner, tester, deployer") print("Senha: Test123!@#") -if __name__ == '__main__': +def main(): + # Criar a aplicação + app = create_app() + + # Inicializar o sistema init_system() - app.run( + + return app + +# Criar a aplicação usando a função main +app = main() + +if __name__ == '__main__': + app.run( host='0.0.0.0', port=5000, debug=os.getenv('FLASK_ENV') == 'development' + debug=True ) diff --git a/functions/database.py b/functions/database.py index 77c5d6d..2dec155 100644 --- a/functions/database.py +++ b/functions/database.py @@ -20,11 +20,15 @@ db_dir = Path.home() / '.local' / 'share' / 'controles' db_dir.mkdir(parents=True, exist_ok=True) db_path = db_dir / 'database.db' -SessionLocal = sessionmaker(bind=engine) +DATABASE_URL = f"sqlite:///{db_path}" +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) def get_db_connection(): - """Retorna uma nova conexão com o banco de dados""" - db = SessionLocal() + """Retorna uma nova sessão do banco de dados""" + Session = sessionmaker(bind=engine) + db = Session() + try: # Configurar SQLite para melhor tratamento de concorrência db.execute(text("PRAGMA journal_mode=WAL")) @@ -623,10 +627,6 @@ def init_database(): session = get_db_connection() try: - # Configurar SQLite para melhor tratamento de concorrência - session.execute(text("PRAGMA journal_mode=WAL")) - session.execute(text("PRAGMA busy_timeout=5000")) - # Criar todas as tabelas Base.metadata.drop_all(engine) # Remover todas as tabelas existentes Base.metadata.create_all(engine) @@ -660,25 +660,9 @@ def init_database(): session.add(comite) session.commit() - # Verificar se existe um QR code salvo - qr_path = Path('admin_qr.png') - admin_otp_secret = None - - if qr_path.exists(): - try: - import re - with open('admin_qr.txt', 'r') as f: - qr_content = f.read() - match = re.search(r'secret=([A-Z0-9]+)&', qr_content) - if match: - admin_otp_secret = match.group(1) - print(f"Usando OTP existente: {admin_otp_secret}") - except Exception as e: - print(f"Erro ao ler OTP existente: {e}") - - if not admin_otp_secret: - admin_otp_secret = pyotp.random_base32() - print(f"Novo OTP gerado: {admin_otp_secret}") + # Gerar OTP para admin + admin_otp_secret = pyotp.random_base32() + print(f"Novo OTP gerado: {admin_otp_secret}") # Criar usuário admin admin_role = session.query(Role).filter_by(nome="Administrador").first() @@ -697,27 +681,23 @@ def init_database(): session.add(admin) session.commit() - # Gerar novo QR code se não existir - if not qr_path.exists(): - totp = pyotp.totp.TOTP(admin_otp_secret) - provisioning_uri = totp.provisioning_uri("admin", issuer_name="Sistema de Controles") - - with open('admin_qr.txt', 'w') as f: - f.write(provisioning_uri) - - import qrcode - 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") - img.save('admin_qr.png') + # Gerar QR code + totp = pyotp.totp.TOTP(admin_otp_secret) + provisioning_uri = totp.provisioning_uri("admin", issuer_name="Sistema de Controles") + + import qrcode + 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") + img.save('admin_qr.png') 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}") - print(f"QR Code: {qr_path}") + print(f"OTP Secret: {admin_otp_secret}") + print(f"QR Code: admin_qr.png") # Importar e executar o seed após criar todas as dependências from seed_data import seed_database diff --git a/init_db.py b/init_db.py new file mode 100644 index 0000000..d7802f0 --- /dev/null +++ b/init_db.py @@ -0,0 +1,19 @@ +from functions.database import init_database +from functions.rbac import init_rbac +from create_admin import create_admin_user +from create_test_users import create_test_users + +def init_system(): + print("Inicializando banco de dados...") + init_database() + + print("Inicializando sistema RBAC...") + init_rbac() + + print("Criando usuários iniciais...") + create_admin_user() + create_test_users() + +if __name__ == "__main__": + init_system() + print("Sistema inicializado com sucesso!") \ No newline at end of file diff --git a/seed_data.py b/seed_data.py index 7a3262c..d541d26 100644 --- a/seed_data.py +++ b/seed_data.py @@ -3,16 +3,60 @@ from functions.database import ( Base, Militante, CotaMensal, TipoPagamento, Pagamento, MaterialVendido, TipoMaterial, VendaJornalAvulso, AssinaturaAnual, RelatorioCotasMensais, RelatorioVendasMateriais, engine, SessionLocal, - Setor, ComiteCentral, Usuario, Role, EmailMilitante, Endereco + Setor, ComiteCentral, Usuario, Role, EmailMilitante, Endereco, + ComiteRegional, Celula, EstadoMilitante ) import random from faker import Faker import time +from werkzeug.security import generate_password_hash fake = Faker('pt_BR') +def criar_estrutura_organizacional(session): + """Cria a estrutura organizacional básica""" + print("\nCriando estrutura organizacional...") + + # Criar Comitê Central + cc = ComiteCentral(nome="Comitê Central SP") + session.add(cc) + session.flush() + + # Criar Comitês Regionais + crs = [] + for nome in ["CR São Paulo", "CR ABC", "CR Campinas"]: + cr = ComiteRegional(nome=nome) + session.add(cr) + session.flush() + crs.append(cr) + + # Criar Setores para cada CR + setores = [] + for cr in crs: + for i in range(2): # 2 setores por CR + setor = Setor( + nome=f"Setor {i+1} - {cr.nome}", + cr_id=cr.id + ) + session.add(setor) + session.flush() + setores.append(setor) + + # Criar Células para cada Setor + for setor in setores: + for i in range(2): # 2 células por setor + celula = Celula( + nome=f"Célula {i+1} - {setor.nome}", + setor_id=setor.id + ) + session.add(celula) + + session.commit() + return crs, setores + def criar_tipos_pagamento(session): """Cria tipos de pagamento padrão""" + print("\nCriando tipos de pagamento...") tipos = [ "Dinheiro", "PIX", @@ -27,6 +71,7 @@ def criar_tipos_pagamento(session): def criar_tipos_material(session): """Cria tipos de material padrão""" + print("\nCriando tipos de material...") tipos = [ "Jornal", "Revista", @@ -39,42 +84,66 @@ def criar_tipos_material(session): session.add(TipoMaterial(descricao=tipo)) session.commit() -def criar_militantes(session, num_militantes): +def criar_militantes(session, num_militantes, setores): + """Cria militantes com todos os dados necessários""" print(f"\nCriando {num_militantes} militantes...") militantes = [] emails_usados = set() - # Obter um setor existente - setor = session.query(Setor).first() - if not setor: - print("Erro: Nenhum setor encontrado!") - return [] - for i in range(num_militantes): try: + # Dados básicos nome = fake.name() cpf = fake.cpf() + # Email único while True: email = fake.email() if email not in emails_usados: emails_usados.add(email) break + # Criar endereço endereco = Endereco( + cep=fake.postcode(), estado=fake.estado_sigla(), cidade=fake.city(), bairro=fake.bairro(), rua=fake.street_name(), numero=str(random.randint(1, 999)), - complemento=f"Bloco {random.randint(1, 10)}, Apto {random.randint(1, 999)}" if random.random() < 0.3 else None, - cep=fake.postcode() + complemento=f"Bloco {random.randint(1, 10)}, Apto {random.randint(1, 999)}" if random.random() < 0.3 else None ) session.add(endereco) session.flush() - print(f"Criando militante {i+1}: {nome} (CPF: {cpf})") + # Selecionar setor e célula aleatórios + setor = random.choice(setores) + celula = random.choice(session.query(Celula).filter_by(setor_id=setor.id).all()) + # Definir responsabilidades + responsabilidades = 0 + if random.random() < 0.2: # 20% chance de ser Responsável de Finanças + responsabilidades |= Militante.RESPONSAVEL_FINANCAS + if random.random() < 0.2: # 20% chance de ser Responsável de Imprensa + responsabilidades |= Militante.RESPONSAVEL_IMPRENSA + if random.random() < 0.2: # 20% chance de ser Quadro-Orientador + responsabilidades |= Militante.QUADRO_ORIENTADOR + if random.random() < 0.2: # 20% chance de ser Secretário + responsabilidades |= Militante.SECRETARIO + if random.random() < 0.2: # 20% chance de ser MPS + responsabilidades |= Militante.MPS + if random.random() < 0.2: # 20% chance de ser Tesoureiro + responsabilidades |= Militante.TESOUREIRO + if random.random() < 0.2: # 20% chance de ser MNS + responsabilidades |= Militante.MNS + if random.random() < 0.2: # 20% chance de ser da Juventude + responsabilidades |= Militante.JUVENTUDE + if random.random() < 0.3: # 30% chance de ser Aspirante + responsabilidades |= Militante.ASPIRANTE + + print(f"Criando militante {i+1}: {nome}") + + # Criar militante com todos os dados militante = Militante( nome=nome, cpf=cpf, @@ -95,11 +164,14 @@ def criar_militantes(session, num_militantes): dirigente_sindical=random.random() < 0.2, central_sindical=random.choice(['CUT', 'CSP-Conlutas', 'CTB', 'Força Sindical']) if random.random() < 0.4 else None, endereco_id=endereco.id, - responsabilidades=random.randint(0, 1023) + celula_id=celula.id, + responsabilidades=responsabilidades, + estado=random.choice(list(EstadoMilitante)) ) session.add(militante) session.flush() + # Criar email do militante email_militante = EmailMilitante( militante_id=militante.id, endereco_email=email @@ -107,8 +179,6 @@ def criar_militantes(session, num_militantes): session.add(email_militante) militantes.append(militante) - - # Commit a cada militante para evitar transações muito longas session.commit() except Exception as e: @@ -118,12 +188,13 @@ def criar_militantes(session, num_militantes): return militantes -def criar_cotas(session, militantes, quantidade_por_militante=3): - print(f"Criando {quantidade_por_militante} cotas para cada um dos {len(militantes)} militantes...") +def criar_cotas(session, militantes): + """Cria cotas mensais para os militantes""" + print("\nCriando cotas mensais...") for militante in militantes: try: - print(f"Criando cotas para militante {militante.nome}") - for i in range(quantidade_por_militante): + # Criar 12 cotas (1 ano) para cada militante + for i in range(12): data_base = datetime.now() - timedelta(days=30 * i) valor = random.uniform(50, 200) cota = CotaMensal( @@ -139,164 +210,123 @@ def criar_cotas(session, militantes, quantidade_por_militante=3): except Exception as e: print(f"Erro ao criar cotas para militante {militante.nome}: {e}") session.rollback() - continue - print("Cotas criadas com sucesso!") -def criar_pagamentos(militantes): - """Cria pagamentos fictícios""" - tipos_pagamento = ["Cota", "Jornal", "Assinatura", "Campanha Financeira"] - for militante in militantes: - for _ in range(random.randint(1, 5)): - pagamento = Pagamento( - militante_id=militante.id, - tipo_pagamento=random.choice(tipos_pagamento), - valor=random.uniform(50, 500), - data_pagamento=fake.date_between(start_date='-1y', end_date='today') - ) - db_session.add(pagamento) - db_session.commit() - -def criar_materiais_vendidos(militantes): - """Cria materiais vendidos fictícios""" - tipos_material = db_session.query(TipoMaterial).all() - for militante in militantes: - for _ in range(random.randint(1, 3)): - material = MaterialVendido( - militante_id=militante.id, - tipo_material_id=random.choice(tipos_material).id, - descricao=fake.sentence(), - valor=random.uniform(20, 100), - data_venda=fake.date_time_between(start_date='-1y', end_date='now') - ) - db_session.add(material) - db_session.commit() - -def criar_vendas_jornal(militantes): - """Cria vendas de jornal avulso fictícias""" - for militante in militantes: - for _ in range(random.randint(1, 4)): - venda = VendaJornalAvulso( - militante_id=militante.id, - quantidade=random.randint(1, 10), - valor_total=random.uniform(10, 100), - data_venda=fake.date_time_between(start_date='-1y', end_date='now') - ) - db_session.add(venda) - db_session.commit() - -def criar_assinaturas(militantes): - """Cria assinaturas anuais fictícias""" - tipos_material = db_session.query(TipoMaterial).all() - for militante in militantes: - if random.random() < 0.3: # 30% de chance de ter assinatura - data_inicio = fake.date_time_between(start_date='-1y', end_date='now') - assinatura = AssinaturaAnual( - militante_id=militante.id, - tipo_material_id=random.choice(tipos_material).id, - quantidade=random.randint(1, 3), - valor_total=random.uniform(100, 500), - data_inicio=data_inicio, - data_fim=data_inicio + timedelta(days=365) - ) - db_session.add(assinatura) - db_session.commit() - -def criar_relatorios(): - """Cria relatórios fictícios""" - for _ in range(12): # Um relatório por mês do último ano - data = fake.date_time_between(start_date='-1y', end_date='now') - - relatorio_cotas = RelatorioCotasMensais( - setor_id=random.randint(1, 5), - comite_id=random.randint(1, 3), - total_cotas=random.uniform(1000, 5000), - data_relatorio=data - ) - - relatorio_vendas = RelatorioVendasMateriais( - setor_id=random.randint(1, 5), - comite_id=random.randint(1, 3), - total_vendas=random.uniform(500, 3000), - data_relatorio=data - ) - - db_session.add(relatorio_cotas) - db_session.add(relatorio_vendas) +def criar_pagamentos(session, militantes): + """Cria pagamentos para os militantes""" + print("\nCriando pagamentos...") + tipos_pagamento = session.query(TipoPagamento).all() - db_session.commit() + for militante in militantes: + try: + # Criar entre 3 e 8 pagamentos por militante + for _ in range(random.randint(3, 8)): + tipo = random.choice(tipos_pagamento) + pagamento = Pagamento( + 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') + ) + session.add(pagamento) + session.commit() + except Exception as e: + print(f"Erro ao criar pagamentos para militante {militante.nome}: {e}") + session.rollback() -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_materiais_vendidos(session, militantes): + """Cria registros de materiais vendidos""" + print("\nCriando materiais vendidos...") + tipos_material = session.query(TipoMaterial).all() + + for militante in militantes: + try: + # Criar entre 2 e 5 materiais vendidos por militante + for _ in range(random.randint(2, 5)): + material = MaterialVendido( + militante_id=militante.id, + tipo_material_id=random.choice(tipos_material).id, + descricao=fake.sentence(), + valor=random.uniform(20, 100), + data_venda=fake.date_time_between(start_date='-1y', end_date='now') + ) + session.add(material) + session.commit() + except Exception as e: + print(f"Erro ao criar materiais vendidos para militante {militante.nome}: {e}") + session.rollback() -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_vendas_jornal(session, militantes): + """Cria vendas de jornal avulso""" + print("\nCriando vendas de jornal...") + for militante in militantes: + try: + # Criar entre 2 e 6 vendas de jornal por militante + for _ in range(random.randint(2, 6)): + quantidade = random.randint(1, 10) + valor_unitario = random.uniform(5, 15) + venda = VendaJornalAvulso( + militante_id=militante.id, + quantidade=quantidade, + valor_total=quantidade * valor_unitario, + data_venda=fake.date_time_between(start_date='-1y', end_date='now') + ) + session.add(venda) + session.commit() + except Exception as e: + print(f"Erro ao criar vendas de jornal para militante {militante.nome}: {e}") + session.rollback() -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 criar_assinaturas(session, militantes): + """Cria assinaturas anuais""" + print("\nCriando assinaturas anuais...") + tipos_material = session.query(TipoMaterial).all() + + for militante in militantes: + try: + # 30% de chance de ter assinatura + if random.random() < 0.3: + data_inicio = fake.date_time_between(start_date='-1y', end_date='now') + assinatura = AssinaturaAnual( + militante_id=militante.id, + tipo_material_id=random.choice(tipos_material).id, + quantidade=random.randint(1, 3), + valor_total=random.uniform(100, 500), + data_inicio=data_inicio, + data_fim=data_inicio + timedelta(days=365) + ) + session.add(assinatura) + session.commit() + except Exception as e: + print(f"Erro ao criar assinatura para militante {militante.nome}: {e}") + session.rollback() def seed_database(): - """Função principal para popular o banco de dados com dados fictícios""" - print("Populando banco de dados com dados fictícios...") - + """Função principal para popular o banco de dados""" session = SessionLocal() try: + print("Iniciando população do banco de dados...") + + # Criar estrutura organizacional + crs, setores = criar_estrutura_organizacional(session) + + # Criar tipos básicos criar_tipos_pagamento(session) criar_tipos_material(session) - militantes = criar_militantes(session, 50) - if militantes: - criar_cotas(session, militantes) - print("Dados fictícios criados com sucesso!") + # Criar militantes (30 militantes para teste) + militantes = criar_militantes(session, 30, setores) + + # Criar dados financeiros e materiais + criar_cotas(session, militantes) + criar_pagamentos(session, militantes) + criar_materiais_vendidos(session, militantes) + criar_vendas_jornal(session, militantes) + criar_assinaturas(session, militantes) + + print("\nBanco de dados populado com sucesso!") + except Exception as e: - print(f"Erro ao popular banco de dados: {e}") + print(f"Erro durante a população do banco: {e}") session.rollback() finally: session.close() diff --git a/static/js/militantes.js b/static/js/militantes.js index e45117d..5937842 100644 --- a/static/js/militantes.js +++ b/static/js/militantes.js @@ -1,5 +1,29 @@ console.log('Carregando script militantes.js...'); +// Constantes para responsabilidades +const Militante = { + RESPONSAVEL_FINANCAS: 256, + RESPONSAVEL_IMPRENSA: 512, + QUADRO_ORIENTADOR: 64, + SECRETARIO: 1, + TESOUREIRO: 2, + IMPRENSA: 4, + MNS: 8, + MPS: 16, + JUVENTUDE: 32, + ASPIRANTE: 128 +}; + +// Função para obter o token CSRF da meta tag +function getCsrfToken() { + const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'); + if (!token) { + console.error('CSRF token não encontrado!'); + return ''; + } + return token; +} + // Variáveis globais para controle dos filtros e paginação let filtroAtual = 'todos'; let filtroResponsabilidade = null; @@ -10,7 +34,7 @@ let totalRows = 0; // Mapa de responsabilidades para valores numéricos const RESPONSABILIDADES_MAP = { - 'Secretario': 1, + 'Secretário': 1, 'Tesoureiro': 2, 'Imprensa': 4, 'MNS': 8, @@ -18,13 +42,13 @@ const RESPONSABILIDADES_MAP = { 'Juventude': 32, 'Quadro-Orientador': 64, 'Aspirante': 128, - 'Responsavel de Financas': 256, - 'Responsavel de Imprensa': 512 + 'Responsável de Finanças': 256, + 'Responsável de Imprensa': 512 }; // Mapa reverso para converter valores em nomes const RESPONSABILIDADES_REVERSE_MAP = { - 1: 'Secretario', + 1: 'Secretário', 2: 'Tesoureiro', 4: 'Imprensa', 8: 'MNS', @@ -32,8 +56,8 @@ const RESPONSABILIDADES_REVERSE_MAP = { 32: 'Juventude', 64: 'Quadro-Orientador', 128: 'Aspirante', - 256: 'Responsavel de Financas', - 512: 'Responsavel de Imprensa' + 256: 'Responsável de Finanças', + 512: 'Responsável de Imprensa' }; // Função para validar data no formato DD/MM/YYYY @@ -185,31 +209,30 @@ function filtrarMilitantes() { // Filtro de responsabilidades if (filtroResponsabilidade) { - const badges = row.querySelectorAll('.badge'); const responsabilidadeMap = { - 'responsavel-financas': 'RFI', - 'responsavel-imprensa': 'RIM', - 'quadro-orientador': 'QOR', - 'secretario': 'SEC', - 'tesoureiro': 'TES', - 'imprensa': 'IMP', - 'mns': 'MNS', - 'mps': 'MPS', - 'juventude': 'JUV', - 'aspirante': 'ASP' + 'responsavel-financas': Militante.RESPONSAVEL_FINANCAS, + 'responsavel-imprensa': Militante.RESPONSAVEL_IMPRENSA, + 'quadro-orientador': Militante.QUADRO_ORIENTADOR, + 'secretario': Militante.SECRETARIO, + 'tesoureiro': Militante.TESOUREIRO, + 'imprensa': Militante.IMPRENSA, + 'mns': Militante.MNS, + 'mps': Militante.MPS, + 'juventude': Militante.JUVENTUDE, + 'aspirante': Militante.ASPIRANTE }; - const responsabilidadeTexto = responsabilidadeMap[filtroResponsabilidade]; - const hasResponsabilidade = Array.from(badges).some(badge => - badge.textContent.trim() === responsabilidadeTexto - ); - if (!hasResponsabilidade) { + + const valorResponsabilidade = responsabilidadeMap[filtroResponsabilidade]; + const responsabilidades = parseInt(row.getAttribute('data-responsabilidades') || '0'); + + if ((responsabilidades & valorResponsabilidade) === 0) { shouldShow = false; } } // Filtro de célula if (filtroCelula) { - const celula = row.querySelector('[data-celula]').getAttribute('data-celula'); + const celula = row.querySelector('[data-celula-id]')?.getAttribute('data-celula-id'); if (celula !== filtroCelula) { shouldShow = false; } @@ -258,187 +281,130 @@ function configurarCampoData(campo) { } // Função para carregar os dados do militante no modal de edição -function carregarDadosMilitante(id) { - console.log('Carregando dados do militante:', id); - - fetch(`/militantes/dados/${id}`) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); +async function carregarDadosMilitante(militanteId) { + try { + console.log('Carregando dados do militante:', militanteId); + const response = await fetch(`/militantes/dados/${militanteId}`); + const data = await response.json(); + + if (data.status === 'error') { + throw new Error(data.message); + } + + console.log('Dados recebidos:', data); + + // Mapear campos básicos + const campos = [ + 'nome', 'cpf', 'titulo_eleitoral', 'data_nascimento', 'data_entrada_oci', + 'data_efetivacao_oci', 'telefone1', 'telefone2', 'profissao', + 'regime_trabalho', 'empresa', 'contratante', 'instituicao_ensino', + 'tipo_instituicao', 'sindicato', 'cargo_sindical', 'central_sindical', + 'dirigente_sindical' + ]; + + campos.forEach(campo => { + const elemento = document.getElementById(`edit_${campo}`); + if (elemento) { + if (elemento.type === 'checkbox') { + elemento.checked = data[campo]; + } else { + elemento.value = data[campo] || ''; + } } - return response.text().then(text => { - try { - return JSON.parse(text); - } catch (e) { - console.error('Erro ao fazer parse do JSON:', e); - console.log('Texto recebido:', text); - throw e; - } - }); - }) - .then(data => { - console.log('Dados recebidos:', data); - - // Mapear campos básicos - const camposTexto = { - 'edit_militante_id': data.id, - 'edit_nome': data.nome || '', - 'edit_cpf': data.cpf || '', - 'edit_titulo_eleitoral': data.titulo_eleitoral || '', - 'edit_email': data.email || '', - 'edit_telefone1': data.telefone1 || '', - 'edit_telefone2': data.telefone2 || '', - 'edit_sindicato': data.sindicato || '', - 'edit_cargo_sindical': data.cargo_sindical || '', - 'edit_central_sindical': data.central_sindical || '' - }; - - // Preencher campos de texto simples - Object.entries(camposTexto).forEach(([id, valor]) => { - const campo = document.getElementById(id); - if (campo) { - campo.value = valor; - } else { - console.warn(`Campo não encontrado: ${id}`); - } - }); - - // Tratar campos de seleção - const selects = { - 'edit_estado_militante': data.estado || 'ATIVO', - 'edit_celula': data.celula_id || '' - }; - - Object.entries(selects).forEach(([id, valor]) => { - const campo = document.getElementById(id); - if (campo) { - campo.value = valor; - } else { - console.warn(`Campo select não encontrado: ${id}`); - } - }); - - // Tratar campos de checkbox - const checkboxes = { - 'edit_dirigente_sindical': data.dirigente_sindical || false - }; - - Object.entries(checkboxes).forEach(([id, valor]) => { - const campo = document.getElementById(id); - if (campo) { - campo.checked = !!valor; - } else { - console.warn(`Campo checkbox não encontrado: ${id}`); - } - }); - - // Tratar campos de data - const camposData = { - 'edit_data_nascimento': data.data_nascimento, - 'edit_data_entrada': data.data_entrada_oci, - 'edit_data_efetivacao': data.data_efetivacao_oci - }; - - Object.entries(camposData).forEach(([id, valor]) => { - const campo = document.getElementById(id); - if (campo) { - // Se o valor existe e está em formato ISO, converter para DD/MM/AAAA - if (valor && typeof valor === 'string' && valor.includes('-')) { - campo.value = formatarData(valor); - } else { - campo.value = valor || ''; - } - } else { - console.warn(`Campo data não encontrado: ${id}`); - } - }); - - // Converter responsabilidades - const responsabilidadesBits = data.responsabilidades_bits || data.responsabilidades || 0; - console.log('Responsabilidades bits:', responsabilidadesBits); - - // Resetar todas as badges para o estado inicial - const badges = document.querySelectorAll('#modalEditarMilitante .badge-clickable'); - badges.forEach(badge => { - // Remover todas as classes exceto 'badge' e 'badge-clickable' - const classesToKeep = ['badge', 'badge-clickable']; - badge.classList.forEach(cls => { - if (!classesToKeep.includes(cls)) { - badge.classList.remove(cls); - } - }); - - // Adicionar a classe bg-light como estado inicial - badge.classList.add('bg-light'); - badge.classList.remove('active'); - }); - - // Ativar as badges correspondentes às responsabilidades - badges.forEach(badge => { - const value = parseInt(badge.getAttribute('data-value')); - if ((responsabilidadesBits & value) === value) { - badge.classList.remove('bg-light'); - badge.classList.add('active'); - - // Adicionar a classe original - const originalClass = badge.getAttribute('data-original-class'); - if (originalClass) { - originalClass.split(' ').forEach(cls => { - badge.classList.add(cls); - }); - } - } - }); - - // Atualizar o valor total das responsabilidades - const responsabilidadesInput = document.getElementById('responsabilidades_values'); - if (responsabilidadesInput) { - responsabilidadesInput.value = responsabilidadesBits; - } - - // Reinicializar os badges clicáveis - initBadgeClickable(); - }) - .catch(error => { - console.error('Erro ao carregar dados do militante:', error); - // Mostrar alerta de erro - mostrarAlerta('Erro ao carregar dados do militante: ' + error.message, 'danger'); }); + + // Preencher o email (primeiro email da lista) + const emailElement = document.getElementById('edit_email'); + if (emailElement) { + if (data.emails && data.emails.length > 0) { + emailElement.value = data.emails[0].endereco_email; + } else { + emailElement.value = ''; + } + } + + // Mapear campos de endereço + if (data.endereco) { + const camposEndereco = ['cep', 'estado', 'cidade', 'bairro', 'rua', 'numero', 'complemento']; + camposEndereco.forEach(campo => { + const elemento = document.getElementById(`edit_${campo}`); + if (elemento) { + elemento.value = data.endereco[campo] || ''; + } + }); + } + + // Mapear estado + const selectEstado = document.getElementById('edit_estado'); + if (selectEstado && data.estado) { + selectEstado.value = data.estado; + } + + // Mapear célula + const selectCelula = document.getElementById('edit_celula_id'); + if (selectCelula && data.celula_id) { + selectCelula.value = data.celula_id; + } + + // Processar responsabilidades + if (data.responsabilidades_valor !== undefined) { + console.log('Valor das responsabilidades:', data.responsabilidades_valor); + + // Resetar todas as badges para seu estado inicial + document.querySelectorAll('.badge-clickable').forEach(badge => { + badge.classList.remove('active'); + const originalClass = badge.getAttribute('data-original-class'); + if (originalClass) { + badge.className = `badge badge-clickable ${originalClass}`; + } + }); + + // Ativar as badges correspondentes + document.querySelectorAll('.badge-clickable').forEach(badge => { + const valor = parseInt(badge.getAttribute('data-value')); + if (!isNaN(valor) && (data.responsabilidades_valor & valor)) { + badge.classList.add('active'); + } + }); + + // Atualizar o campo hidden com o valor total + document.getElementById('responsabilidades_values').value = data.responsabilidades_valor; + } + + } catch (error) { + console.error('Erro ao carregar dados:', error); + alert('Erro ao carregar dados do militante'); + } } // Função para mostrar alertas -function mostrarAlerta(mensagem, tipo = 'success') { - console.log('Mostrando alerta:', mensagem, tipo); - - // Criar div de alerta se não existir - let alertPlaceholder = document.getElementById('alertPlaceholder'); - if (!alertPlaceholder) { - alertPlaceholder = document.createElement('div'); - alertPlaceholder.id = 'alertPlaceholder'; - alertPlaceholder.style.position = 'fixed'; - alertPlaceholder.style.top = '20px'; - alertPlaceholder.style.right = '20px'; - alertPlaceholder.style.zIndex = '9999'; - document.body.appendChild(alertPlaceholder); - } - - // Criar wrapper para o alerta - const wrapper = document.createElement('div'); - wrapper.innerHTML = ` - +function mostrarAlerta(mensagem, tipo) { + const alertDiv = document.createElement('div'); + alertDiv.className = `alert alert-${tipo} alert-dismissible fade show`; + alertDiv.role = 'alert'; + alertDiv.innerHTML = ` + ${mensagem} + `; - alertPlaceholder.appendChild(wrapper); + + const container = document.querySelector('.container'); + container.insertBefore(alertDiv, container.firstChild); // Remover o alerta após 5 segundos setTimeout(() => { - wrapper.firstElementChild.classList.remove('show'); - setTimeout(() => wrapper.remove(), 150); + alertDiv.remove(); }, 5000); } +// Configurar o token CSRF para todas as requisições AJAX +$.ajaxSetup({ + beforeSend: function(xhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", getCsrfToken()); + } + } +}); + // Configurar eventos quando o DOM estiver carregado document.addEventListener('DOMContentLoaded', function() { console.log('DOM carregado, configurando eventos...'); @@ -526,151 +492,241 @@ document.addEventListener('DOMContentLoaded', function() { // Configurar modal de edição if (modalEditarMilitante) { - modalEditarMilitante.addEventListener('show.bs.modal', function(event) { - console.log('Modal sendo aberto...'); - - // Obter o botão que disparou o modal + modalEditarMilitante.addEventListener('show.bs.modal', async function(event) { + console.log('Modal de edição aberto'); const button = event.relatedTarget; + + if (!button) { + console.error('Botão não encontrado'); + alert('Erro ao abrir modal: botão não encontrado'); + return; + } + const militanteId = button.getAttribute('data-militante-id'); const militanteNome = button.getAttribute('data-militante-nome'); - console.log('ID do militante:', militanteId); - console.log('Nome do militante:', militanteNome); + console.log('Dados do botão:', { militanteId, militanteNome }); - // Atualizar o título do modal - const modalTitle = modalEditarMilitante.querySelector('.modal-title'); - if (modalTitle) { - modalTitle.innerHTML = `Editar ${militanteNome}`; + if (!militanteId) { + console.error('ID do militante não encontrado no botão'); + alert('Erro ao abrir modal: ID do militante não encontrado'); + return; } // Definir o ID do militante no campo hidden const idField = document.getElementById('edit_militante_id'); - if (idField) { - idField.value = militanteId; + if (!idField) { + console.error('Campo hidden para ID do militante não encontrado'); + alert('Erro ao abrir modal: campo para ID do militante não encontrado'); + return; } - // Buscar dados do militante - carregarDadosMilitante(militanteId); + idField.value = militanteId; + console.log('ID do militante definido:', idField.value); + + // Atualizar título do modal + const modalTitle = this.querySelector('.modal-title'); + if (modalTitle) { + modalTitle.textContent = `Editar ${militanteNome}`; + } + + try { + // Carregar dados do militante + const response = await fetch(`/militantes/dados/${militanteId}`); + if (!response.ok) { + throw new Error(`Erro HTTP: ${response.status}`); + } + + const data = await response.json(); + console.log('Dados recebidos:', data); + + if (data.status === 'error') { + throw new Error(data.message || 'Erro ao carregar dados do militante'); + } + + // Mapear campos básicos + const campos = [ + 'nome', 'cpf', 'titulo_eleitoral', 'data_nascimento', 'data_entrada_oci', + 'data_efetivacao_oci', 'telefone1', 'telefone2', 'profissao', + 'regime_trabalho', 'empresa', 'contratante', 'instituicao_ensino', + 'tipo_instituicao', 'sindicato', 'cargo_sindical', 'central_sindical', + 'dirigente_sindical' + ]; + + campos.forEach(campo => { + const elemento = document.getElementById(`edit_${campo}`); + if (elemento) { + if (elemento.type === 'checkbox') { + elemento.checked = data[campo]; + } else { + elemento.value = data[campo] || ''; + } + } + }); + + // Preencher o email (primeiro email da lista) + const emailElement = document.getElementById('edit_email'); + if (emailElement) { + if (data.emails && data.emails.length > 0) { + emailElement.value = data.emails[0].endereco_email; + } else { + emailElement.value = ''; + } + } + + // Mapear campos de endereço + if (data.endereco) { + const camposEndereco = ['cep', 'estado', 'cidade', 'bairro', 'rua', 'numero', 'complemento']; + camposEndereco.forEach(campo => { + const elemento = document.getElementById(`edit_${campo}`); + if (elemento) { + elemento.value = data.endereco[campo] || ''; + } + }); + } + + // Mapear estado + const selectEstado = document.getElementById('edit_estado'); + if (selectEstado && data.estado) { + selectEstado.value = data.estado; + } + + // Mapear célula + const selectCelula = document.getElementById('edit_celula_id'); + if (selectCelula && data.celula_id) { + selectCelula.value = data.celula_id; + } + + // Processar responsabilidades + if (data.responsabilidades_valor !== undefined) { + console.log('Valor das responsabilidades:', data.responsabilidades_valor); + + // Resetar todas as badges para seu estado inicial + document.querySelectorAll('.badge-clickable').forEach(badge => { + badge.classList.remove('active'); + const originalClass = badge.getAttribute('data-original-class'); + if (originalClass) { + badge.className = `badge badge-clickable ${originalClass}`; + } + }); + + // Ativar as badges correspondentes + document.querySelectorAll('.badge-clickable').forEach(badge => { + const valor = parseInt(badge.getAttribute('data-value')); + if (!isNaN(valor) && (data.responsabilidades_valor & valor)) { + badge.classList.add('active'); + } + }); + + // Atualizar o campo hidden com o valor total + document.getElementById('responsabilidades_values').value = data.responsabilidades_valor; + } + + } catch (error) { + console.error('Erro ao carregar dados:', error); + alert('Erro ao carregar dados do militante'); + } + }); + + // Configurar clique nas badges + document.querySelectorAll('.badge-clickable').forEach(badge => { + badge.addEventListener('click', function() { + // Toggle classe active + this.classList.toggle('active'); + + // Atualizar classes visuais + if (this.classList.contains('active')) { + const originalClass = this.getAttribute('data-original-class'); + this.className = `badge badge-clickable ${originalClass} active`; + } else { + const originalClass = this.getAttribute('data-original-class'); + this.className = `badge badge-clickable ${originalClass}`; + } + + // Atualizar o valor total das responsabilidades + let valorTotal = 0; + document.querySelectorAll('.badge-clickable.active').forEach(activeBadge => { + const valor = parseInt(activeBadge.getAttribute('data-value')); + if (!isNaN(valor)) { + valorTotal |= valor; + } + }); + + // Atualizar o campo hidden + const responsabilidadesInput = document.getElementById('responsabilidades_values'); + if (responsabilidadesInput) { + responsabilidadesInput.value = valorTotal; + console.log('Valor total das responsabilidades atualizado:', valorTotal); + } + }); }); // Configurar formulário de edição const formEditarMilitante = document.getElementById('formEditarMilitante'); if (formEditarMilitante) { - formEditarMilitante.addEventListener('submit', function(e) { - e.preventDefault(); - console.log('Formulário submetido'); + formEditarMilitante.addEventListener('submit', async function(event) { + event.preventDefault(); + console.log('Formulário de edição enviado'); - // Validar todas as datas antes do envio - const camposData = this.querySelectorAll('.date-mask'); - let datasValidas = true; + // Obter ID do militante + const militanteId = document.getElementById('edit_militante_id').value; + console.log('ID do militante:', militanteId); - camposData.forEach(campo => { - const valor = campo.value; - if (valor && !validarData(valor)) { - datasValidas = false; - $(campo).addClass('is-invalid'); - if (!$(campo).next('.invalid-feedback').length) { - $(campo).after('
Data inválida
'); - } - } else { - $(campo).removeClass('is-invalid'); - $(campo).next('.invalid-feedback').remove(); - } - }); - - if (!datasValidas) { - mostrarAlerta('Existem datas inválidas no formulário', 'danger'); + if (!militanteId) { + console.error('ID do militante não encontrado'); + alert('Erro ao salvar: ID do militante não encontrado'); return; } - - // Converter datas para formato ISO - camposData.forEach(campo => { - if (campo.value) { - campo.value = converterDataParaISO(campo.value); - console.log(`Data convertida para ISO: ${campo.name} = ${campo.value}`); - } - }); - + // Criar FormData com todos os campos const formData = new FormData(this); - // Verificar se o input de responsabilidades existe - const responsabilidadesInput = document.getElementById('responsabilidades_values'); - if (!responsabilidadesInput) { - mostrarAlerta('Erro: Campo de responsabilidades não encontrado', 'danger'); - return; - } + // Garantir que o valor das responsabilidades seja enviado + const responsabilidadesValue = document.getElementById('responsabilidades_values').value; + formData.set('responsabilidades_valor', responsabilidadesValue); + console.log('Valor das responsabilidades sendo enviado:', responsabilidadesValue); - // Converter responsabilidades para array antes de enviar - const responsabilidadesBits = parseInt(responsabilidadesInput.value) || 0; - const responsabilidades = []; - - // Converter bits para nomes de responsabilidades - for (const [valor, nome] of Object.entries(RESPONSABILIDADES_REVERSE_MAP)) { - if ((responsabilidadesBits & parseInt(valor)) === parseInt(valor)) { - responsabilidades.push(nome); - } - } - - // Substituir o valor numérico pelo array de responsabilidades - formData.set('responsabilidades', JSON.stringify(responsabilidades)); - - // Log dos dados sendo enviados - console.log('Dados do formulário:'); - for (let [key, value] of formData.entries()) { - console.log(`${key}: ${value}`); - } - - // Obter o ID do militante - const militanteId = document.getElementById('edit_militante_id').value; - if (!militanteId) { - mostrarAlerta('Erro: ID do militante não encontrado', 'danger'); - return; - } - - // Enviar dados para o servidor - fetch(`/militantes/editar/${militanteId}`, { - method: 'POST', - body: formData - }) - .then(response => { + try { + // Construir URL + const url = `/militantes/editar/${militanteId}`; + console.log('URL da requisição:', url); + + // Enviar requisição + const response = await fetch(url, { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-Token': getCsrfToken() + }, + body: formData + }); + if (!response.ok) { - return response.text().then(text => { - try { - const jsonResponse = JSON.parse(text); - return jsonResponse; - } catch (e) { - console.error('Erro ao fazer parse do JSON:', e); - console.log('Texto recebido:', text); - throw new Error('Erro ao processar resposta do servidor'); - } - }); + throw new Error(`Erro HTTP: ${response.status}`); } - return response.json(); - }) - .then(data => { + + const data = await response.json(); console.log('Resposta do servidor:', data); - if (data.success) { + + if (data.status === 'success') { + // Fechar modal + const modal = bootstrap.Modal.getInstance(document.getElementById('modalEditarMilitante')); + modal.hide(); + + // Mostrar mensagem de sucesso mostrarAlerta('Militante atualizado com sucesso!', 'success'); - // Fechar o modal - const modal = bootstrap.Modal.getInstance(document.getElementById('modalEditarMilitante')); - if (modal) { - modal.hide(); - } else { - console.warn('Modal não encontrado'); - } - - // Recarregar a página após 1 segundo - setTimeout(() => window.location.reload(), 1000); + // Recarregar página após um pequeno delay para a mensagem ser vista + setTimeout(() => { + location.reload(); + }, 1000); } else { - throw new Error(data.message || 'Erro ao atualizar militante'); + throw new Error(data.message || 'Erro desconhecido ao salvar militante'); } - }) - .catch(error => { - console.error('Erro:', error); - mostrarAlerta(error.message || 'Erro ao atualizar militante', 'danger'); - }); + } catch (error) { + console.error('Erro ao salvar:', error); + mostrarAlerta(`Erro ao salvar militante: ${error.message}`, 'danger'); + } }); } @@ -719,10 +775,11 @@ document.addEventListener('DOMContentLoaded', function() { fetch(this.action, { method: 'POST', - body: formData, headers: { - 'X-Requested-With': 'XMLHttpRequest' - } + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-Token': getCsrfToken() + }, + body: formData }) .then(response => response.json()) .then(data => { @@ -743,8 +800,8 @@ document.addEventListener('DOMContentLoaded', function() { tr.innerHTML = ` ${data.militante.nome} ${data.militante.cpf} - ${data.militante.email} - ${data.militante.telefone} + ${data.militante.emails && data.militante.emails.length > 0 ? data.militante.emails[0].endereco_email : ''} + ${data.militante.telefone1} ${data.militante.filiado ? 'Sim' : 'Não'} @@ -759,8 +816,8 @@ document.addEventListener('DOMContentLoaded', function() { data-militante-id="${data.militante.id}" data-militante-nome="${data.militante.nome}" data-militante-cpf="${data.militante.cpf}" - data-militante-email="${data.militante.email}" - data-militante-telefone="${data.militante.telefone}" + data-militante-email="${data.militante.emails && data.militante.emails.length > 0 ? data.militante.emails[0].endereco_email : ''}" + data-militante-telefone="${data.militante.telefone1}" data-militante-endereco="${data.militante.endereco}" data-militante-filiado="${data.militante.filiado}" title="Editar"> @@ -973,73 +1030,63 @@ document.addEventListener('DOMContentLoaded', function() { }); }); - // Inicializar badges clicáveis - initBadgeClickable(); - // Inicializar paginação updateVisibleRows(); updatePagination(); }); -// Função para inicializar as badges clicáveis -function initBadgeClickable() { - const badges = document.querySelectorAll('.badge-clickable'); - const selectedValues = []; +// Função para atualizar o valor total das responsabilidades +function atualizarValorResponsabilidades() { + const badges = document.querySelectorAll('.badge-clickable.active'); + let valorTotal = 0; - // Inicializar o array com os valores das badges que já estão ativas badges.forEach(badge => { - if (badge.classList.contains('active')) { - const value = parseInt(badge.getAttribute('data-value')); - if (!selectedValues.includes(value)) { - selectedValues.push(value); - } + const valor = parseInt(badge.getAttribute('data-value')); + if (!isNaN(valor)) { + valorTotal |= valor; } }); - // Atualizar o input hidden com o valor total inicial - const totalValue = selectedValues.reduce((a, b) => a + b, 0); - document.getElementById('responsabilidades_values').value = totalValue; - - // Adicionar evento de clique para cada badge - badges.forEach(badge => { - badge.addEventListener('click', function() { - const value = parseInt(this.getAttribute('data-value')); - const originalClass = this.getAttribute('data-original-class'); - - // Toggle do estado ativo - if (this.classList.contains('active')) { - // Desativar badge - this.classList.remove('active'); - originalClass.split(' ').forEach(cls => { - this.classList.remove(cls); - }); - this.classList.add('bg-light'); - - // Remover valor do array - const index = selectedValues.indexOf(value); - if (index > -1) { - selectedValues.splice(index, 1); - } - } else { - // Ativar badge - this.classList.add('active'); - this.classList.remove('bg-light'); - originalClass.split(' ').forEach(cls => { - this.classList.add(cls); - }); - - // Adicionar valor ao array - if (!selectedValues.includes(value)) { - selectedValues.push(value); - } - } - - // Atualizar o input hidden com o novo valor total - const newTotal = selectedValues.reduce((a, b) => a + b, 0); - document.getElementById('responsabilidades_values').value = newTotal; - - console.log('Valores selecionados:', selectedValues); - console.log('Valor total:', newTotal); + const input = document.getElementById('responsabilidades_values'); + if (input) { + input.value = valorTotal; + } + return valorTotal; +} + +// Função para salvar as alterações do militante +async function salvarAlteracoesMilitante(militanteId) { + try { + const form = document.getElementById('formEditarMilitante'); + const formData = new FormData(form); + + // Adicionar responsabilidades + let responsabilidadesValor = 0; + form.querySelectorAll('input[name="responsabilidades"]:checked').forEach(checkbox => { + responsabilidadesValor |= parseInt(checkbox.value); }); - }); + formData.append('responsabilidades_valor', responsabilidadesValor); + + const response = await fetch(`/militantes/editar/${militanteId}`, { + method: 'POST', + body: formData, + headers: { + 'X-CSRFToken': getCsrfToken() + } + }); + + const data = await response.json(); + + if (data.status === 'success') { + mostrarAlerta(data.message, 'success'); + setTimeout(() => { + window.location.reload(); + }, 1500); + } else { + mostrarAlerta(data.message || 'Erro ao salvar alterações', 'danger'); + } + } catch (error) { + console.error('Erro ao salvar alterações:', error); + mostrarAlerta('Erro ao salvar alterações. Por favor, tente novamente.', 'danger'); + } } \ No newline at end of file diff --git a/templates/criar_militante.html b/templates/criar_militante.html index 7ec3827..52271a3 100644 --- a/templates/criar_militante.html +++ b/templates/criar_militante.html @@ -17,6 +17,7 @@ {% endwith %}
+
diff --git a/templates/editar_militante.html b/templates/editar_militante.html index a98451a..15a24d4 100644 --- a/templates/editar_militante.html +++ b/templates/editar_militante.html @@ -16,7 +16,9 @@ {% endif %} {% endwith %} - + + +
@@ -28,7 +30,7 @@
- +
Por favor, insira um email válido.
@@ -209,21 +211,43 @@ {% endblock %} \ No newline at end of file diff --git a/templates/home.html b/templates/home.html index 083afa7..cf4212b 100644 --- a/templates/home.html +++ b/templates/home.html @@ -87,7 +87,7 @@ data-militante-nome="{{ militante.nome }}">
{{ militante.nome }}
- {{ militante.email }} + {{ militante.emails[0].endereco_email if militante.emails else '' }}
diff --git a/templates/listar_militantes.html b/templates/listar_militantes.html index 8e33eef..745d975 100644 --- a/templates/listar_militantes.html +++ b/templates/listar_militantes.html @@ -8,181 +8,182 @@ {% block title %}Militantes{% endblock %} {% block content %} -
-
-
-

- Militantes -

- {% if current_user.has_permission('gerenciar_militantes') %} - - {% endif %} +
+
+
+
+

+ Militantes +

+
+ + +
+
-
-
-
-
-
-
- - - - -
-
-
-
- - -
- -
-
- -
- - - - - - - - - - - - - - {% for militante in militantes %} - - - - - - - - - - {% endfor %} - -
Nome CPF Email Telefone Célula ResponsabilidadesAções
{{ militante.nome }}{{ militante.cpf }}{{ militante.email }}{{ militante.telefone }}{{ militante.celula.nome }} - {% if militante.responsabilidades|bitwise_and(Militante.RESPONSAVEL_FINANCAS) %} - RFI - {% endif %} - {% if militante.responsabilidades|bitwise_and(Militante.RESPONSAVEL_IMPRENSA) %} - RIM - {% endif %} - {% if militante.responsabilidades|bitwise_and(Militante.QUADRO_ORIENTADOR) %} - QOR - {% endif %} - {% if militante.responsabilidades|bitwise_and(Militante.SECRETARIO) %} - SEC - {% endif %} - {% if militante.responsabilidades|bitwise_and(Militante.TESOUREIRO) %} - TES - {% endif %} - {% if militante.responsabilidades|bitwise_and(Militante.IMPRENSA) %} - IMP - {% endif %} - {% if militante.responsabilidades|bitwise_and(Militante.MNS) %} - MNS - {% endif %} - {% if militante.responsabilidades|bitwise_and(Militante.MPS) %} - MPS - {% endif %} - {% if militante.responsabilidades|bitwise_and(Militante.JUVENTUDE) %} - JUV - {% endif %} - {% if militante.responsabilidades|bitwise_and(Militante.ASPIRANTE) %} - ASP - {% endif %} - -
- {% if current_user.has_permission('gerenciar_militantes') %} - - - {% endif %} +
+
+
+
+
+
+
+ + + +
-
-
+
+
+
+ + +
+
+
-
-
- Mostrando {{ militantes|length }} militantes -
-
-
- Mostrar - - linhas +
+ + + + + + + + + + + + + {% for militante in militantes %} + + + + + + + + + {% endfor %} + +
Nome CPF Email Telefone ResponsabilidadesAções
{{ militante.nome }}{{ militante.cpf }}{{ militante.emails[0].endereco_email if militante.emails else '' }}{{ militante.telefone1 }} + {% if militante.responsabilidades is defined and militante.responsabilidades %} + {% if militante.responsabilidades|bitwise_and(Militante.RESPONSAVEL_FINANCAS) %} + RFI + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.RESPONSAVEL_IMPRENSA) %} + RIM + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.QUADRO_ORIENTADOR) %} + QOR + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.SECRETARIO) %} + SEC + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.TESOUREIRO) %} + TES + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.IMPRENSA) %} + IMP + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.MNS) %} + MNS + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.MPS) %} + MPS + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.JUVENTUDE) %} + JUV + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.ASPIRANTE) %} + ASP + {% endif %} + {% endif %} + +
+ + +
+
+
+ +
+
+ Mostrando {{ militantes|length }} militantes +
+
+
+ Mostrar + + linhas +
+ +
+
-
diff --git a/templates/modals/militante_editar.html b/templates/modals/militante_editar.html index aa67a46..f0f91c4 100644 --- a/templates/modals/militante_editar.html +++ b/templates/modals/militante_editar.html @@ -9,12 +9,12 @@
- +