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

@@ -0,0 +1,196 @@
# app/services/notificacao/catalogo_variaveis.rb
#
# TODAS as variáveis que o editor de mensagem oferece, de quatro origens:
#
# :contexto — vêm com o disparo (motorista, consolidação, valor…). Só têm
# valor nos gatilhos que as produzem; em outro gatilho saem
# vazias, e a tela avisa isso.
# :sistema — não dependem de contexto nenhum (empresa, data, hora, mês).
# :dados — CONSULTAM O BANCO na hora do envio (entregas do mês, total a
# pagar…). Resolvidas sob demanda: só a variável realmente
# escrita no template vira query.
# :personalizada — cadastradas pelo ADM (VariavelPersonalizada), valor fixo.
#
# Antes, `Notificacao::Variaveis::POR_GATILHO` amarrava a lista ao gatilho: o ADM
# via 6 variáveis e nada mais, e qualquer texto com número (“entregas do mês”)
# precisava de deploy. Aqui a lista é aberta — e o que não se aplica é MARCADO em
# vez de escondido.
#
# ⚠️ Um `resolver` NUNCA pode levantar: notificação não derruba fechamento. O
# ResolvedorVariaveis engole e devolve vazio.
module Notificacao
module CatalogoVariaveis
ORIGENS = {
contexto: 'Do que aconteceu',
sistema: 'Empresa e data',
dados: 'Números do sistema',
personalizada: 'Suas variáveis'
}.freeze
# ── Sistema: sempre disponíveis ───────────────────────────
SISTEMA = {
'empresa' => {
descricao: 'Nome da empresa', amostra: 'Reem Transportes',
resolver: -> { Configuracao.valor('empresa_nome').presence || 'Reem Transportes' }
},
'data' => {
descricao: 'Data de hoje (dd/mm/aaaa)', amostra: '26/08/2026',
resolver: -> { Date.current.strftime('%d/%m/%Y') }
},
'hora' => {
descricao: 'Hora do envio', amostra: '14:35',
resolver: -> { Time.current.strftime('%H:%M') }
},
'dia_semana' => {
descricao: 'Dia da semana por extenso', amostra: 'quarta-feira',
resolver: -> { I18n.l(Date.current, format: '%A') rescue Date::DAYNAMES[Date.current.wday] }
},
'mes' => {
descricao: 'Mês por extenso', amostra: 'agosto',
resolver: -> { I18n.l(Date.current, format: '%B') rescue Date::MONTHNAMES[Date.current.month] }
},
'mes_ano' => {
descricao: 'Mês/ano (mm/aaaa)', amostra: '08/2026',
resolver: -> { Date.current.strftime('%m/%Y') }
},
'link_sistema' => {
descricao: 'Endereço do sistema', amostra: 'https://app.reemtransportes.com.br',
resolver: -> { Configuracao.valor('url_sistema').presence || ENV.fetch('APP_URL', '') }
}
}.freeze
# ── Contexto: quem produz é o gatilho ─────────────────────
# `gatilhos:` diz em quais a variável tem valor — é o que a tela usa para
# avisar "esta sai vazia aqui".
CONTEXTO = {
'contato' => { descricao: 'Primeiro nome de quem recebe', amostra: 'Carlos', gatilhos: :todos },
'quem' => { descricao: 'Quem executou a ação no sistema', amostra: 'Ana Paula', gatilhos: :todos },
'motorista' => { descricao: 'Nome do motorista', amostra: 'Carlos Matheus Pimentel' },
'consolidacao' => { descricao: 'Nome da consolidação', amostra: 'UBS NORTE 0114/08' },
'periodo' => { descricao: 'Período do fechamento', amostra: '01/08/2026 a 14/08/2026' },
'valor' => { descricao: 'Valor do motorista', amostra: 'R$ 5.060,00' },
'entregas' => { descricao: 'Entregas atendidas no fechamento', amostra: '280' },
'link_painel' => { descricao: 'Link do painel do motorista', amostra: 'https://app.reem.com.br/motorista' },
'o_que_mudou' => { descricao: 'O que foi alterado', amostra: 'Desconto de R$ 120,00 (avaria)' },
'operacao' => { descricao: 'Operação afetada', amostra: 'UBS NORTE AGO 2026' },
'nf' => { descricao: 'Nota fiscal', amostra: '85382' },
'entregues' => { descricao: 'Notas entregues (resumo)', amostra: '4.851' },
'recusas' => { descricao: 'Notas não entregues (resumo)', amostra: '122' },
'pendentes' => { descricao: 'Notas em aberto (resumo)', amostra: '0' },
'retentativas' => { descricao: 'Visitas além da primeira (resumo)', amostra: '4' },
'fora_operacao' => { descricao: 'Notas fora da operação (resumo)', amostra: '2' }
}.freeze
# Quais variáveis de contexto cada gatilho realmente preenche.
POR_GATILHO = {
'manual' => [],
'consolidacao_finalizada' => %w[motorista consolidacao periodo valor entregas link_painel],
'pagamento_efetuado' => %w[motorista consolidacao periodo valor entregas link_painel],
'valor_alterado' => %w[motorista consolidacao periodo valor entregas link_painel o_que_mudou],
'operacao_alterada' => %w[operacao o_que_mudou nf],
'agendado' => %w[operacao periodo entregues recusas pendentes retentativas fora_operacao]
}.freeze
# ── Dados: consultam o banco na hora do envio ─────────────
# Cada uma responde a uma pergunta que o ADM hoje só conseguiria escrevendo à
# mão — e que ficaria desatualizada na mensagem seguinte.
DADOS = {
'entregas_mes' => {
descricao: 'Entregas atendidas no mês corrente', amostra: '4.977',
resolver: -> { Entrega.atendidas.da_conta_gade.no_periodo_checkout(Date.current.beginning_of_month, Date.current).count }
},
'entregas_hoje' => {
descricao: 'Entregas atendidas hoje', amostra: '213',
resolver: -> { Entrega.atendidas.da_conta_gade.no_periodo_checkout(Date.current, Date.current).count }
},
'motoristas_ativos_mes' => {
descricao: 'Motoristas com entrega no mês', amostra: '17',
resolver: -> { Entrega.motoristas_ativos(inicio: Date.current.beginning_of_month, fim: Date.current).size }
},
'consolidacoes_abertas' => {
descricao: 'Consolidações em rascunho', amostra: '3',
resolver: -> { Consolidacao.ativas.rascunho.count }
},
'total_a_pagar_mes' => {
descricao: 'Total a pagar no mês (fechado e não pago)', amostra: 'R$ 72.508,00',
resolver: lambda {
totais = Analytics::TotaisPorMotorista.new(inicio: Date.current.beginning_of_month,
fim: Date.current.end_of_month)
Formatador.moeda(totais.total_pendente)
}
},
'total_pago_mes' => {
descricao: 'Total já pago no mês', amostra: 'R$ 21.300,00',
resolver: lambda {
totais = Analytics::TotaisPorMotorista.new(inicio: Date.current.beginning_of_month,
fim: Date.current.end_of_month)
Formatador.moeda(totais.total_pago)
}
},
'total_consolidado_mes' => {
descricao: 'Total consolidado no mês', amostra: 'R$ 93.808,00',
resolver: lambda {
totais = Analytics::TotaisPorMotorista.new(inicio: Date.current.beginning_of_month,
fim: Date.current.end_of_month)
Formatador.moeda(totais.total_geral)
}
}
}.freeze
module_function
# Todas as variáveis conhecidas, incluindo as que o ADM cadastrou.
# { chave => { origem:, descricao:, amostra: } }
def todas
base = {}
SISTEMA.each { |c, m| base[c] = { origem: :sistema, descricao: m[:descricao], amostra: m[:amostra] } }
CONTEXTO.each { |c, m| base[c] = { origem: :contexto, descricao: m[:descricao], amostra: m[:amostra] } }
DADOS.each { |c, m| base[c] = { origem: :dados, descricao: m[:descricao], amostra: m[:amostra] } }
personalizadas.each do |v|
base[v.chave] = { origem: :personalizada, descricao: v.descricao.presence || 'Sua variável', amostra: v.valor }
end
base
end
# A variável tem valor NESTE gatilho? Contexto de outro gatilho sai vazio —
# a tela mostra, em vez de esconder a variável (foi o pedido: liberdade com
# aviso, não lista curta).
def aplicavel?(chave, gatilho)
meta = CONTEXTO[chave.to_s]
return true if meta.nil? # sistema/dados/personalizada: sempre
return true if meta[:gatilhos] == :todos
Array(POR_GATILHO[gatilho.to_s]).include?(chave.to_s)
end
def origem(chave)
todas.dig(chave.to_s, :origem)
end
def existe?(chave) = todas.key?(chave.to_s)
# Agrupadas para a tela do editor, na ordem de ORIGENS.
def por_origem(gatilho = nil)
agrupadas = ORIGENS.keys.index_with { |o| [] }
todas.each do |chave, meta|
agrupadas[meta[:origem]] << meta.merge(chave: chave, aplicavel: gatilho.nil? || aplicavel?(chave, gatilho))
end
agrupadas
end
# Contexto do PREVIEW: valores de amostra, nunca de envio real.
def amostra(gatilho = nil)
todas.filter_map { |chave, meta|
[chave, meta[:amostra].to_s] if gatilho.nil? || aplicavel?(chave, gatilho)
}.to_h
end
def personalizadas
return [] unless VariavelPersonalizada.table_exists?
VariavelPersonalizada.ativas.to_a
rescue StandardError
[]
end
end
end