Implandação da customização das permissões dos usuarios e das notificaçoes

This commit is contained in:
2026-08-26 17:24:25 -03:00
parent 808c2c7545
commit 5bb5d74e22
71 changed files with 2487 additions and 269 deletions

View File

@@ -6,6 +6,10 @@ class User < ApplicationRecord
:rememberable,
:validatable
# Perfil de acesso: nulo é válido (motorista não usa; e quem ainda não foi
# migrado cai no padrão do papel — ver #permissoes_efetivas).
belongs_to :perfil_acesso, optional: true, inverse_of: :users
# ── Roles ──────────────────────────────────────────────────
# externo: acesso somente-leitura ao dashboard (ex.: gerências de outras áreas
# que só precisam visualizar os indicadores). Login normal por e-mail/senha.
@@ -57,12 +61,112 @@ class User < ApplicationRecord
ROLES_LABEL[role] || role.humanize
end
# Conta desativada não entra — nem por e-mail/senha.
#
# Só o login por PIN checava `ativo?` (Users::SessionsController), então
# desativar um gerente não impedia nada, apesar do formulário prometer
# "usuários inativos não conseguem fazer login". Devise chama este método em
# todo login e em toda request de sessão existente, então desativar alguém já
# derruba a sessão aberta dele.
def active_for_authentication?
super && ativo?
end
def inactive_message
ativo? ? super : :conta_desativada
end
# ── Permissões ──────────────────────────────────────────────
# O que o usuário pode fazer sai do PERFIL DE ACESSO (PerfilAcesso), que o ADM
# monta na tela. Enquanto ele não tem perfil, vale o padrão do papel legado
# (Permissao::PADRAO_POR_ROLE) — é o que mantém o sistema funcionando durante a
# migração e o que faz um usuário recém-criado não ficar sem nada.
def pode?(chave)
permissoes_efetivas.include?(chave.to_s)
end
# Set (não Array): `pode?` é chamado dezenas de vezes por render de tela — uma
# vez por item de menu e por botão.
#
# ⚠️ A primeira linha existe para NÃO tocar o banco quando não há perfil: os
# specs de policy usam `build(:admin)` (objeto sem id), e `perfil_acesso`
# dispararia consulta com id nulo.
def permissoes_efetivas
@permissoes_efetivas ||=
if perfil_acesso_id.nil?
Set.new(Permissao.do_role(role))
else
Set.new(perfil_acesso&.permissoes_validas || Permissao.do_role(role))
end
end
# A coluna nasce numa migration própria. Se o código subir antes de `db:migrate`
# rodar, `perfil_acesso_id` não existe e TODA tela quebraria por NoMethodError —
# aqui o sistema simplesmente continua no padrão do papel até a coluna chegar.
# Pode sair depois que a migration estiver aplicada em produção.
def perfil_acesso_id
return nil unless self.class.column_names.include?('perfil_acesso_id')
super
end
# Trocar o perfil no meio da request (tela de usuários) tem que refletir.
def recarregar_permissoes!
@permissoes_efetivas = nil
self
end
# Primeira tela que este usuário pode abrir, na ordem em que o menu as mostra.
#
# Existe para não haver LOOP: a raiz do app é o dashboard e o tratamento de
# "acesso negado" redireciona para a raiz — um perfil sem `dashboard.ver`
# entraria em nega → raiz → nega para sempre. `nil` significa "não pode abrir
# nada", e quem chama mostra a tela de sem-acesso.
HOME_POR_PERMISSAO = [
['dashboard.ver', :dashboard_path],
['dashboard.operacoes', :operacoes_dashboard_path],
['consolidacao.ver', :consolidacoes_path],
['admin.usuarios', :admin_usuarios_path],
['admin.perfis', :admin_perfis_path],
['admin.configuracoes', :admin_configuracoes_path],
['notificacao.contatos', :admin_contatos_path],
['notificacao.eventos', :admin_eventos_path],
['notificacao.envios', :admin_envios_path]
].freeze
def home_rota
return :motorista_dashboard_path if motorista?
HOME_POR_PERMISSAO.find { |chave, _| pode?(chave) }&.last
end
# Mantidos porque a navbar e vários controllers já os chamam — agora são só
# apelidos das chaves correspondentes.
def pode_ver_config?
admin? || gerente?
pode?('admin.configuracoes')
end
def pode_consolidar?
admin? || gerente? || operador?
pode?('consolidacao.ver')
end
# ── Trava do último administrador ───────────────────────────
# Quem, ATIVO, ainda consegue reabrir a porta do sistema (mexer em perfis e em
# usuários). Sem isto, o ADM consegue se trancar para fora: a tela que controla
# acesso é justamente a que ele perderia, e a volta só existiria por console.
#
# `ignorando` simula "e se esta pessoa for desativada, excluída ou trocada de
# perfil?" — passe o usuário que está sendo alterado.
def self.administradores_de_acesso(ignorando: nil)
ativos.includes(:perfil_acesso)
.reject { |u| ignorando && u.id == ignorando.id }
.select { |u| u.pode?('admin.perfis') && u.pode?('admin.usuarios_gerenciar') }
end
def self.ultimo_administrador?(usuario)
return false unless usuario.pode?('admin.perfis') && usuario.pode?('admin.usuarios_gerenciar')
administradores_de_acesso(ignorando: usuario).empty?
end
# Token de login rápido via QR Code (extrato) — Fase 7/8