feat: coluna PERFIL em FUNCIONARIO_PAINEL + contar_admins_ativos
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
95b1da7c10
commit
718e845651
+38
-9
@@ -10,12 +10,26 @@ primeiro com:
|
|||||||
NOME VARCHAR(80) NOT NULL,
|
NOME VARCHAR(80) NOT NULL,
|
||||||
LOGIN VARCHAR(40) NOT NULL,
|
LOGIN VARCHAR(40) NOT NULL,
|
||||||
SENHA_HASH VARCHAR(255) NOT NULL,
|
SENHA_HASH VARCHAR(255) NOT NULL,
|
||||||
|
PERFIL ENUM('ADMIN','PADRAO') NOT NULL DEFAULT 'ADMIN',
|
||||||
ATIVO TINYINT(1) NOT NULL DEFAULT 1,
|
ATIVO TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
CRIADO_EM DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
CRIADO_EM DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (ID),
|
PRIMARY KEY (ID),
|
||||||
UNIQUE KEY UQ_FUNCIONARIO_PAINEL_LOGIN (LOGIN)
|
UNIQUE KEY UQ_FUNCIONARIO_PAINEL_LOGIN (LOGIN)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
) 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
|
Nunca excluído de verdade — "excluir" aqui é alternar ATIVO para 0
|
||||||
(mesma lógica de nunca apagar histórico usada no resto do projeto).
|
(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()
|
conn = get_connection()
|
||||||
try:
|
try:
|
||||||
with conn.cursor() as cur:
|
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()
|
return cur.fetchall()
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -38,7 +54,8 @@ def obter_funcionario(funcionario_id: int) -> dict | None:
|
|||||||
try:
|
try:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(
|
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()
|
return cur.fetchone()
|
||||||
finally:
|
finally:
|
||||||
@@ -51,7 +68,8 @@ def obter_por_login(login: str) -> dict | None:
|
|||||||
try:
|
try:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(
|
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()
|
return cur.fetchone()
|
||||||
finally:
|
finally:
|
||||||
@@ -73,26 +91,27 @@ def login_em_uso(login: str, ignorar_id: int | None = None) -> bool:
|
|||||||
conn.close()
|
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()
|
conn = get_connection()
|
||||||
try:
|
try:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT INTO FUNCIONARIO_PAINEL (NOME, LOGIN, SENHA_HASH, ATIVO) VALUES (%s, %s, %s, 1)",
|
"INSERT INTO FUNCIONARIO_PAINEL (NOME, LOGIN, SENHA_HASH, PERFIL, ATIVO) VALUES (%s, %s, %s, %s, 1)",
|
||||||
(nome, login, gerar_hash(senha)),
|
(nome, login, gerar_hash(senha), perfil),
|
||||||
)
|
)
|
||||||
return cur.lastrowid
|
return cur.lastrowid
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
def atualizar_funcionario(funcionario_id: int, nome: str, login: str) -> None:
|
def atualizar_funcionario(funcionario_id: int, nome: str, login: str, perfil: str) -> None:
|
||||||
"""Só nome/login — troca de senha é redefinir_senha()."""
|
"""Nome/login/perfil — troca de senha é redefinir_senha()."""
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
try:
|
try:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(
|
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:
|
finally:
|
||||||
conn.close()
|
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,))
|
cur.execute("UPDATE FUNCIONARIO_PAINEL SET ATIVO = 1 - ATIVO WHERE ID = %s", (funcionario_id,))
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
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()
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ def _limpar():
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def funcionario_id():
|
def funcionario_id():
|
||||||
_limpar()
|
_limpar()
|
||||||
novo_id = db_funcionarios.criar_funcionario("Fulano Teste", LOGIN_TESTE, "senhaForte123")
|
novo_id = db_funcionarios.criar_funcionario("Fulano Teste", LOGIN_TESTE, "senhaForte123", "PADRAO")
|
||||||
yield novo_id
|
yield novo_id
|
||||||
_limpar()
|
_limpar()
|
||||||
|
|
||||||
@@ -30,14 +30,16 @@ def test_criar_e_obter_funcionario(funcionario_id):
|
|||||||
assert f["NOME"] == "Fulano Teste"
|
assert f["NOME"] == "Fulano Teste"
|
||||||
assert f["LOGIN"] == LOGIN_TESTE
|
assert f["LOGIN"] == LOGIN_TESTE
|
||||||
assert f["ATIVO"] == 1
|
assert f["ATIVO"] == 1
|
||||||
|
assert f["PERFIL"] == "PADRAO"
|
||||||
assert "SENHA_HASH" not in f # obter_funcionario não expõe o hash
|
assert "SENHA_HASH" not in f # obter_funcionario não expõe o hash
|
||||||
|
|
||||||
|
|
||||||
def test_obter_por_login_inclui_senha_hash(funcionario_id):
|
def test_obter_por_login_inclui_senha_hash_e_perfil(funcionario_id):
|
||||||
f = db_funcionarios.obter_por_login(LOGIN_TESTE)
|
f = db_funcionarios.obter_por_login(LOGIN_TESTE)
|
||||||
assert f is not None
|
assert f is not None
|
||||||
assert "SENHA_HASH" in f
|
assert "SENHA_HASH" in f
|
||||||
assert f["SENHA_HASH"] != "senhaForte123" # nunca em texto puro
|
assert f["SENHA_HASH"] != "senhaForte123" # nunca em texto puro
|
||||||
|
assert f["PERFIL"] == "PADRAO"
|
||||||
|
|
||||||
|
|
||||||
def test_obter_por_login_inexistente(funcionario_id):
|
def test_obter_por_login_inexistente(funcionario_id):
|
||||||
@@ -51,16 +53,18 @@ def test_login_em_uso(funcionario_id):
|
|||||||
|
|
||||||
|
|
||||||
def test_listar_funcionarios_inclui_o_criado(funcionario_id):
|
def test_listar_funcionarios_inclui_o_criado(funcionario_id):
|
||||||
logins = [f["LOGIN"] for f in db_funcionarios.listar_funcionarios()]
|
encontrados = {f["LOGIN"]: f for f in db_funcionarios.listar_funcionarios()}
|
||||||
assert LOGIN_TESTE in logins
|
assert LOGIN_TESTE in encontrados
|
||||||
|
assert encontrados[LOGIN_TESTE]["PERFIL"] == "PADRAO"
|
||||||
|
|
||||||
|
|
||||||
def test_atualizar_funcionario(funcionario_id):
|
def test_atualizar_funcionario(funcionario_id):
|
||||||
novo_login = "teste.db.funcionarios.editado"
|
novo_login = "teste.db.funcionarios.editado"
|
||||||
db_funcionarios.atualizar_funcionario(funcionario_id, "Fulano Editado", novo_login)
|
db_funcionarios.atualizar_funcionario(funcionario_id, "Fulano Editado", novo_login, "ADMIN")
|
||||||
f = db_funcionarios.obter_funcionario(funcionario_id)
|
f = db_funcionarios.obter_funcionario(funcionario_id)
|
||||||
assert f["NOME"] == "Fulano Editado"
|
assert f["NOME"] == "Fulano Editado"
|
||||||
assert f["LOGIN"] == novo_login
|
assert f["LOGIN"] == novo_login
|
||||||
|
assert f["PERFIL"] == "ADMIN"
|
||||||
|
|
||||||
|
|
||||||
def test_redefinir_senha(funcionario_id):
|
def test_redefinir_senha(funcionario_id):
|
||||||
@@ -76,3 +80,19 @@ def test_alternar_ativo(funcionario_id):
|
|||||||
assert db_funcionarios.obter_funcionario(funcionario_id)["ATIVO"] == 0
|
assert db_funcionarios.obter_funcionario(funcionario_id)["ATIVO"] == 0
|
||||||
db_funcionarios.alternar_ativo(funcionario_id)
|
db_funcionarios.alternar_ativo(funcionario_id)
|
||||||
assert db_funcionarios.obter_funcionario(funcionario_id)["ATIVO"] == 1
|
assert db_funcionarios.obter_funcionario(funcionario_id)["ATIVO"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_contar_admins_ativos(funcionario_id):
|
||||||
|
# funcionario_id é PADRAO (fixture acima) — não conta.
|
||||||
|
antes = db_funcionarios.contar_admins_ativos()
|
||||||
|
|
||||||
|
admin_id = db_funcionarios.criar_funcionario(
|
||||||
|
"Admin Teste", "teste.db.funcionarios.admin", "senhaForte123", "ADMIN"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
assert db_funcionarios.contar_admins_ativos() == antes + 1
|
||||||
|
|
||||||
|
db_funcionarios.alternar_ativo(admin_id) # desativa
|
||||||
|
assert db_funcionarios.contar_admins_ativos() == antes
|
||||||
|
finally:
|
||||||
|
_limpar()
|
||||||
|
|||||||
Reference in New Issue
Block a user