252 lines
11 KiB
Ruby
252 lines
11 KiB
Ruby
# app/models/permissao.rb
|
|
#
|
|
# Catálogo das permissões do sistema — fonte ÚNICA de "o que dá para liberar".
|
|
# Não é tabela: permissão é código (cada chave corresponde a um `authorize` real),
|
|
# então versioná-la no banco só criaria duas verdades. O que o ADM cadastra é o
|
|
# PERFIL (PerfilAcesso), que guarda uma lista destas chaves.
|
|
#
|
|
# REGRA DE GRANULARIDADE: uma chave por TELA (`ver`) e uma chave a mais só quando
|
|
# a ação tem régua diferente do "ver" — foi assim que o sistema já se comportava
|
|
# (ex.: ver consolidação x registrar pagamento). Quebrar mais fino do que isso
|
|
# encheria a tela de perfil de caixinhas que ninguém sabe responder.
|
|
#
|
|
# ⚠️ Toda chave nova precisa (1) entrar aqui, (2) ser usada por alguma policy ou
|
|
# view e (3) entrar em PADRAO_POR_ROLE se algum papel legado já tinha aquele
|
|
# acesso — senão o fallback silenciosamente tira acesso de quem tinha.
|
|
module Permissao
|
|
# Ordem em que os grupos aparecem na tela de perfil.
|
|
GRUPOS = {
|
|
'dashboard' => 'Dashboard e operações',
|
|
'consolidacao' => 'Consolidações e pagamento',
|
|
'notificacao' => 'Notificações',
|
|
'admin' => 'Administração'
|
|
}.freeze
|
|
|
|
# chave => { grupo, label (o que o ADM lê), descricao (o efeito real) }
|
|
TODAS = {
|
|
# ── Dashboard ────────────────────────────────────────────
|
|
'dashboard.ver' => {
|
|
grupo: 'dashboard', label: 'Ver o dashboard',
|
|
descricao: 'Tela inicial com os números do período.'
|
|
},
|
|
'dashboard.financeiro' => {
|
|
grupo: 'dashboard', label: 'Ver custos e pagamentos',
|
|
descricao: 'Bloco financeiro, relatório em PDF e a lista de entregas em aberto.'
|
|
},
|
|
'dashboard.operacoes' => {
|
|
grupo: 'dashboard', label: 'Ver o dashboard de operações',
|
|
descricao: 'Entregas por operação, insucessos, mapa e planilha espelho.'
|
|
},
|
|
'dashboard.exportar' => {
|
|
grupo: 'dashboard', label: 'Baixar a planilha da operação',
|
|
descricao: 'Download do .xlsx que é entregue ao cliente — é dado saindo do sistema.'
|
|
},
|
|
'dashboard.metricas' => {
|
|
grupo: 'dashboard', label: 'Consultar as métricas pela API',
|
|
descricao: 'Endpoint interno /api/v1/dashboard/metricas.'
|
|
},
|
|
|
|
# ── Consolidações ────────────────────────────────────────
|
|
'consolidacao.ver' => {
|
|
grupo: 'consolidacao', label: 'Ver consolidações',
|
|
descricao: 'Lista, arquivadas, detalhe e prévia do extrato.'
|
|
},
|
|
'consolidacao.criar' => {
|
|
grupo: 'consolidacao', label: 'Criar consolidação',
|
|
descricao: 'Nova consolidação e nota avulsa.'
|
|
},
|
|
'consolidacao.editar' => {
|
|
grupo: 'consolidacao', label: 'Classificar e lançar',
|
|
descricao: 'Wizard, classificação das entregas, apontamentos e veículos.'
|
|
},
|
|
'consolidacao.editar_finalizada' => {
|
|
grupo: 'consolidacao', label: 'Mexer depois de finalizada',
|
|
descricao: 'Alterar uma consolidação já fechada — muda valor que o motorista vai receber.'
|
|
},
|
|
'consolidacao.finalizar' => {
|
|
grupo: 'consolidacao', label: 'Finalizar consolidação',
|
|
descricao: 'Fechar o período (inclusive o fechamento parcial).'
|
|
},
|
|
'consolidacao.arquivar' => {
|
|
grupo: 'consolidacao', label: 'Arquivar e reativar',
|
|
descricao: 'Tirar da lista sem apagar, e restaurar.'
|
|
},
|
|
'consolidacao.excluir' => {
|
|
grupo: 'consolidacao', label: 'Excluir de vez',
|
|
descricao: 'Apagar consolidação ou remover um motorista definitivamente.'
|
|
},
|
|
'consolidacao.registrar_pagamento' => {
|
|
grupo: 'consolidacao', label: 'Registrar pagamento',
|
|
descricao: 'Marcar motorista como pago (forma de pagamento e nota fiscal).'
|
|
},
|
|
'consolidacao.cancelar_pagamento' => {
|
|
grupo: 'consolidacao', label: 'Estornar pagamento',
|
|
descricao: 'Desfazer um pagamento já registrado.'
|
|
},
|
|
'consolidacao.gerir_motoristas' => {
|
|
grupo: 'consolidacao', label: 'Adicionar e arquivar motorista',
|
|
descricao: 'Quem entra e quem sai do fechamento (sem excluir).'
|
|
},
|
|
'consolidacao.exportar_pdf' => {
|
|
grupo: 'consolidacao', label: 'Baixar relatórios em PDF',
|
|
descricao: 'Extrato do motorista, relatório individual, financeiro e totais.'
|
|
},
|
|
|
|
# ── Notificações ─────────────────────────────────────────
|
|
'notificacao.contatos' => {
|
|
grupo: 'notificacao', label: 'Ver contatos e grupos',
|
|
descricao: 'Quem recebe as mensagens.'
|
|
},
|
|
'notificacao.contatos_gerenciar' => {
|
|
grupo: 'notificacao', label: 'Criar e editar contatos/grupos',
|
|
descricao: 'Cadastro de destinatários e de grupos de WhatsApp.'
|
|
},
|
|
'notificacao.contatos_excluir' => {
|
|
grupo: 'notificacao', label: 'Excluir contatos/grupos',
|
|
descricao: 'Remoção definitiva de destinatário.'
|
|
},
|
|
'notificacao.eventos' => {
|
|
grupo: 'notificacao', label: 'Ver eventos',
|
|
descricao: 'Lista dos avisos configurados e seus gatilhos.'
|
|
},
|
|
'notificacao.eventos_gerenciar' => {
|
|
grupo: 'notificacao', label: 'Criar e editar eventos',
|
|
descricao: 'Gatilho, grupos assinantes e o editor de mensagem.'
|
|
},
|
|
'notificacao.disparar_manual' => {
|
|
grupo: 'notificacao', label: 'Disparar mensagem agora',
|
|
descricao: 'Botão que envia de verdade, em nome da empresa.'
|
|
},
|
|
'notificacao.variaveis' => {
|
|
grupo: 'notificacao', label: 'Criar variáveis de mensagem',
|
|
descricao: 'Cadastra {{variaveis}} próprias usadas nos textos de aviso.'
|
|
},
|
|
'notificacao.envios' => {
|
|
grupo: 'notificacao', label: 'Ver o histórico de envios',
|
|
descricao: 'O que saiu, para quem, e o que falhou.'
|
|
},
|
|
'notificacao.credenciais' => {
|
|
grupo: 'notificacao', label: 'Configurar e-mail e WhatsApp',
|
|
descricao: 'Credenciais de SMTP/Twilio e limites de envio.'
|
|
},
|
|
'notificacao.whatsapp_sessao' => {
|
|
grupo: 'notificacao', label: 'Parear o WhatsApp',
|
|
descricao: 'Ler o QR Code, testar e desconectar a sessão.'
|
|
},
|
|
|
|
# ── Administração ────────────────────────────────────────
|
|
'admin.usuarios' => {
|
|
grupo: 'admin', label: 'Ver usuários',
|
|
descricao: 'Lista de quem tem acesso ao sistema.'
|
|
},
|
|
'admin.usuarios_gerenciar' => {
|
|
grupo: 'admin', label: 'Criar, editar e desativar usuários',
|
|
descricao: 'Inclui definir o tipo de conta e o perfil de acesso de cada um.'
|
|
},
|
|
'admin.perfis' => {
|
|
grupo: 'admin', label: 'Gerenciar perfis de acesso',
|
|
descricao: 'Criar perfis e marcar o que cada um enxerga — esta tela.'
|
|
},
|
|
'admin.configuracoes' => {
|
|
grupo: 'admin', label: 'Ver configurações',
|
|
descricao: 'Tabela de preços e parâmetros do sistema.'
|
|
},
|
|
'admin.configuracoes_editar' => {
|
|
grupo: 'admin', label: 'Alterar preços',
|
|
descricao: 'Muda o valor de todo fechamento feito daqui para a frente.'
|
|
},
|
|
'admin.auditoria' => {
|
|
grupo: 'admin', label: 'Ver a auditoria',
|
|
descricao: 'Registro de quem fez o quê no sistema.'
|
|
},
|
|
'admin.edicao_lancamento' => {
|
|
grupo: 'admin', label: 'Corrigir lançamento no SimpliRoute',
|
|
descricao: 'Escreve na API do rastreio — altera o dado de origem.'
|
|
},
|
|
'admin.planilha_simpli_route' => {
|
|
grupo: 'admin', label: 'Gerar planilha de carga',
|
|
descricao: 'Download do .xlsx de importação do SimpliRoute.'
|
|
},
|
|
'admin.romaneio' => {
|
|
grupo: 'admin', label: 'Gerar romaneio de entrega',
|
|
descricao: 'Importa o plano do dia, edita a tabela e imprime o "CONTROLE DE ' \
|
|
'ENTREGA" que o motorista assina ao receber a carga.'
|
|
}
|
|
}.freeze
|
|
|
|
# O que cada papel legado podia fazer ANTES dos perfis existirem. É o fallback
|
|
# de quem ainda não tem perfil (usuário recém-criado, objeto não persistido nos
|
|
# specs) e a receita dos perfis pré-prontos criados na migration de dados.
|
|
#
|
|
# ⚠️ Reproduz o comportamento atual de propósito — com duas exceções
|
|
# deliberadas, que são justamente o que o ADM pediu para consertar:
|
|
# • `externo` perde `dashboard.financeiro` (hoje enxerga custo e pagamento
|
|
# porque o DashboardController não passa pelo Pundit);
|
|
# • `operador` não recebe `admin.auditoria` (hoje a URL responde para ele por
|
|
# causa do `policy_class: ApplicationPolicy`, mas o menu nunca ofereceu).
|
|
PADRAO_POR_ROLE = {
|
|
'admin' => :todas,
|
|
'gerente' => %w[
|
|
dashboard.ver dashboard.financeiro dashboard.operacoes dashboard.exportar dashboard.metricas
|
|
consolidacao.ver consolidacao.criar consolidacao.editar consolidacao.editar_finalizada
|
|
consolidacao.finalizar consolidacao.arquivar consolidacao.excluir
|
|
consolidacao.registrar_pagamento consolidacao.cancelar_pagamento
|
|
consolidacao.gerir_motoristas consolidacao.exportar_pdf
|
|
notificacao.contatos notificacao.contatos_gerenciar notificacao.envios
|
|
admin.usuarios admin.configuracoes admin.auditoria admin.planilha_simpli_route
|
|
admin.romaneio
|
|
].freeze,
|
|
'operador' => %w[
|
|
dashboard.ver dashboard.financeiro dashboard.operacoes dashboard.exportar dashboard.metricas
|
|
consolidacao.ver consolidacao.criar consolidacao.editar consolidacao.finalizar
|
|
consolidacao.gerir_motoristas consolidacao.exportar_pdf
|
|
].freeze,
|
|
'externo' => %w[dashboard.ver dashboard.operacoes].freeze,
|
|
# Motorista não usa permissão: o que ele acessa é a área própria dele, que
|
|
# continua governada pelo tipo de conta (Motorista::DashboardController).
|
|
'motorista' => [].freeze
|
|
}.freeze
|
|
|
|
module_function
|
|
|
|
def chaves
|
|
@chaves ||= TODAS.keys.freeze
|
|
end
|
|
|
|
def existe?(chave)
|
|
TODAS.key?(chave.to_s)
|
|
end
|
|
|
|
def label(chave)
|
|
TODAS.dig(chave.to_s, :label) || chave.to_s
|
|
end
|
|
|
|
def descricao(chave)
|
|
TODAS.dig(chave.to_s, :descricao)
|
|
end
|
|
|
|
def grupo_label(grupo)
|
|
GRUPOS[grupo.to_s] || grupo.to_s
|
|
end
|
|
|
|
# { 'dashboard' => [['dashboard.ver', {...}], ...], ... } na ordem de GRUPOS —
|
|
# é como a tela de perfil desenha as seções.
|
|
def por_grupo
|
|
@por_grupo ||= GRUPOS.keys.index_with { |g| TODAS.select { |_, cfg| cfg[:grupo] == g }.to_a }.freeze
|
|
end
|
|
|
|
# Chaves do papel legado. `:todas` (admin) vira a lista inteira.
|
|
def do_role(role)
|
|
padrao = PADRAO_POR_ROLE[role.to_s]
|
|
return chaves if padrao == :todas
|
|
|
|
padrao || []
|
|
end
|
|
|
|
# Descarta o que não existe no catálogo. Uma chave removida do código não pode
|
|
# travar o cadastro de um perfil salvo meses atrás — ela simplesmente some.
|
|
def filtrar(lista)
|
|
Array(lista).map(&:to_s).uniq.select { |chave| existe?(chave) }
|
|
end
|
|
end
|