feat: coluna PERFIL em FUNCIONARIO_PAINEL + contar_admins_ativos

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Gustavo
2026-08-13 20:29:45 -03:00
co-authored by Claude Sonnet 5
parent 95b1da7c10
commit 718e845651
2 changed files with 63 additions and 14 deletions
+38 -9
View File
@@ -10,12 +10,26 @@ primeiro com:
NOME VARCHAR(80) NOT NULL,
LOGIN VARCHAR(40) NOT NULL,
SENHA_HASH VARCHAR(255) NOT NULL,
PERFIL ENUM('ADMIN','PADRAO') NOT NULL DEFAULT 'ADMIN',
ATIVO TINYINT(1) NOT NULL DEFAULT 1,
CRIADO_EM DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (ID),
UNIQUE KEY UQ_FUNCIONARIO_PAINEL_LOGIN (LOGIN)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Se a tabela já existir sem a coluna PERFIL (ambientes criados antes dessa
funcionalidade), rode:
ALTER TABLE FUNCIONARIO_PAINEL
ADD COLUMN PERFIL ENUM('ADMIN','PADRAO') NOT NULL DEFAULT 'ADMIN'
AFTER SENHA_HASH;
O DEFAULT 'ADMIN' do ALTER TABLE é intencional: todo funcionário cadastrado
antes dessa migração vira admin automaticamente (decisão do projeto — ver
docs/superpowers/specs/2026-08-13-permissoes-login-sessao-design.md).
Cadastros novos sempre informam PERFIL explicitamente (vindo do
formulário), nunca dependem desse default.
Nunca excluído de verdade — "excluir" aqui é alternar ATIVO para 0
(mesma lógica de nunca apagar histórico usada no resto do projeto).
"""
@@ -27,7 +41,9 @@ def listar_funcionarios() -> list[dict]:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute("SELECT ID, NOME, LOGIN, ATIVO, CRIADO_EM FROM FUNCIONARIO_PAINEL ORDER BY NOME")
cur.execute(
"SELECT ID, NOME, LOGIN, PERFIL, ATIVO, CRIADO_EM FROM FUNCIONARIO_PAINEL ORDER BY NOME"
)
return cur.fetchall()
finally:
conn.close()
@@ -38,7 +54,8 @@ def obter_funcionario(funcionario_id: int) -> dict | None:
try:
with conn.cursor() as cur:
cur.execute(
"SELECT ID, NOME, LOGIN, ATIVO, CRIADO_EM FROM FUNCIONARIO_PAINEL WHERE ID = %s", (funcionario_id,)
"SELECT ID, NOME, LOGIN, PERFIL, ATIVO, CRIADO_EM FROM FUNCIONARIO_PAINEL WHERE ID = %s",
(funcionario_id,),
)
return cur.fetchone()
finally:
@@ -51,7 +68,8 @@ def obter_por_login(login: str) -> dict | None:
try:
with conn.cursor() as cur:
cur.execute(
"SELECT ID, NOME, LOGIN, SENHA_HASH, ATIVO FROM FUNCIONARIO_PAINEL WHERE LOGIN = %s", (login,)
"SELECT ID, NOME, LOGIN, SENHA_HASH, PERFIL, ATIVO FROM FUNCIONARIO_PAINEL WHERE LOGIN = %s",
(login,),
)
return cur.fetchone()
finally:
@@ -73,26 +91,27 @@ def login_em_uso(login: str, ignorar_id: int | None = None) -> bool:
conn.close()
def criar_funcionario(nome: str, login: str, senha: str) -> int:
def criar_funcionario(nome: str, login: str, senha: str, perfil: str) -> int:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"INSERT INTO FUNCIONARIO_PAINEL (NOME, LOGIN, SENHA_HASH, ATIVO) VALUES (%s, %s, %s, 1)",
(nome, login, gerar_hash(senha)),
"INSERT INTO FUNCIONARIO_PAINEL (NOME, LOGIN, SENHA_HASH, PERFIL, ATIVO) VALUES (%s, %s, %s, %s, 1)",
(nome, login, gerar_hash(senha), perfil),
)
return cur.lastrowid
finally:
conn.close()
def atualizar_funcionario(funcionario_id: int, nome: str, login: str) -> None:
"""Só nome/login — troca de senha é redefinir_senha()."""
def atualizar_funcionario(funcionario_id: int, nome: str, login: str, perfil: str) -> None:
"""Nome/login/perfil — troca de senha é redefinir_senha()."""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"UPDATE FUNCIONARIO_PAINEL SET NOME = %s, LOGIN = %s WHERE ID = %s", (nome, login, funcionario_id)
"UPDATE FUNCIONARIO_PAINEL SET NOME = %s, LOGIN = %s, PERFIL = %s WHERE ID = %s",
(nome, login, perfil, funcionario_id),
)
finally:
conn.close()
@@ -117,3 +136,13 @@ def alternar_ativo(funcionario_id: int) -> None:
cur.execute("UPDATE FUNCIONARIO_PAINEL SET ATIVO = 1 - ATIVO WHERE ID = %s", (funcionario_id,))
finally:
conn.close()
def contar_admins_ativos() -> int:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute("SELECT COUNT(*) AS total FROM FUNCIONARIO_PAINEL WHERE PERFIL = 'ADMIN' AND ATIVO = 1")
return cur.fetchone()["total"]
finally:
conn.close()