- <%= contato.nome %>
- <%= contato.telefone.presence || 'â' %>
+
+ <%= contato.nome %>
+ <% if contato.grupo_whatsapp? %>
+ grupo
+ <% end %>
+
+
+ <% if contato.grupo_whatsapp? %>
+ <%= contato.whatsapp_grupo_nome.presence || contato.whatsapp_grupo_jid %>
+ <% else %>
+ <%= contato.telefone.presence || 'â' %>
+ <% end %>
+
<%= contato.email.presence || 'â' %>
<% if contato.grupo_contato %>
diff --git a/config/routes.rb b/config/routes.rb
index 0d925e6..ec50104 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -121,7 +121,12 @@ Rails.application.routes.draw do
# singular igual ao plural para o Inflector (que nĂŁo fala portuguĂȘs), e aĂ
# o Rails sufixa o helper de index com `_index` â pegadinha silenciosa.
resources :grupos, except: [:show], controller: 'grupos_contato'
- resources :contatos, except: [:show]
+ resources :contatos, except: [:show] do
+ # Grupos do WhatsApp de que o nĂșmero conectado participa (JSON). Carregado
+ # sob demanda pelo form: se fosse no render da tela, uma ponte fora do ar
+ # faria o cadastro de contato inteiro esperar o timeout.
+ collection { get :grupos_whatsapp }
+ end
resources :eventos, except: [:show], controller: 'eventos_notificacao' do
member do
post :disparar # gatilho `manual` â manda mensagem de verdade
diff --git a/db/migrate/20260824000008_add_grupo_whatsapp_to_contatos.rb b/db/migrate/20260824000008_add_grupo_whatsapp_to_contatos.rb
new file mode 100644
index 0000000..8ca374b
--- /dev/null
+++ b/db/migrate/20260824000008_add_grupo_whatsapp_to_contatos.rb
@@ -0,0 +1,27 @@
+# Permite que um destinatĂĄrio seja um GRUPO do WhatsApp, e nĂŁo sĂł uma pessoa.
+#
+# O grupo entra como Contato de propĂłsito: assim ele herda tudo que jĂĄ existe â
+# pertence a um grupo de contatos, recebe os eventos que o grupo assina e cada
+# envio fica registrado em notificacao_envios. Uma tabela separada duplicaria
+# essa mĂĄquina inteira.
+#
+# `whatsapp_grupo_jid` guarda o identificador do WhatsApp (ex.:
+# "120363012345678901@g.us"). NĂO Ă© telefone: nĂŁo tem DDD, nĂŁo normaliza, e a
+# validação de nĂșmero nĂŁo se aplica â daĂ a coluna prĂłpria em vez de reaproveitar
+# `telefone`.
+#
+# â ïž Nome: `grupo_contato_id` Ă© o GRUPO INTERNO (Diretoria, Motoristas...);
+# `whatsapp_grupo_*` Ă© o grupo lĂĄ no WhatsApp. Coisas diferentes.
+class AddGrupoWhatsappToContatos < ActiveRecord::Migration[7.1]
+ def change
+ change_table :contatos, bulk: true do |t|
+ t.string :tipo, null: false, default: 'pessoa' # pessoa | grupo_whatsapp
+ t.string :whatsapp_grupo_jid
+ # Nome do grupo no momento do cadastro: o JID Ă© opaco e a tela precisa
+ # mostrar algo legĂvel mesmo com a ponte fora do ar.
+ t.string :whatsapp_grupo_nome
+ end
+
+ add_index :contatos, :whatsapp_grupo_jid
+ end
+end
diff --git a/spec/models/contato_spec.rb b/spec/models/contato_spec.rb
index 7139c18..61fdce6 100644
--- a/spec/models/contato_spec.rb
+++ b/spec/models/contato_spec.rb
@@ -41,3 +41,50 @@ RSpec.describe Contato do
end
end
end
+
+RSpec.describe Contato, 'grupo do WhatsApp' do
+ let(:jid) { '120363012345678901@g.us' }
+
+ def grupo(**extra)
+ build(:contato, { tipo: 'grupo_whatsapp', nome: 'Motoristas SP',
+ whatsapp_grupo_jid: jid, telefone: nil }.merge(extra))
+ end
+
+ it 'aceita grupo sem telefone nem e-mail' do
+ expect(grupo).to be_valid
+ expect(grupo.destino_whatsapp).to eq(jid)
+ end
+
+ it 'recusa identificador que nĂŁo Ă© de grupo' do
+ contato = grupo(whatsapp_grupo_jid: '5511920051157')
+ expect(contato).not_to be_valid
+ expect(contato.errors[:whatsapp_grupo_jid].join).to include('@g.us')
+ end
+
+ # Cadastro meio pessoa, meio grupo mandaria a mesma mensagem duas vezes.
+ it 'limpa telefone e e-mail ao virar grupo' do
+ contato = grupo(telefone: '11 92005-1157', email: 'x@y.com')
+ contato.validate
+ expect(contato.telefone).to be_nil
+ expect(contato.email).to be_nil
+ end
+
+ it 'limpa o grupo ao voltar a ser pessoa' do
+ contato = build(:contato, tipo: 'pessoa', telefone: '11 92005-1157',
+ whatsapp_grupo_jid: jid, whatsapp_grupo_nome: 'Motoristas')
+ contato.validate
+ expect(contato.whatsapp_grupo_jid).to be_nil
+ end
+
+ # Cortar "Motoristas SP" em "Motoristas" no {{contato}} sĂł empobreceria o texto.
+ it '{{contato}} usa o nome inteiro do grupo' do
+ expect(grupo.primeiro_nome).to eq('Motoristas SP')
+ expect(build(:contato, nome: 'Carlos Matheus').primeiro_nome).to eq('Carlos')
+ end
+
+ it 'entra no canal whatsapp e fica fora do canal e-mail' do
+ salvo = grupo.tap(&:save!)
+ expect(Contato.com_canal('whatsapp')).to include(salvo)
+ expect(Contato.com_canal('email')).not_to include(salvo)
+ end
+end
diff --git a/spec/services/notificacao/whatsapp_spec.rb b/spec/services/notificacao/whatsapp_spec.rb
index a5174c4..6fb535a 100644
--- a/spec/services/notificacao/whatsapp_spec.rb
+++ b/spec/services/notificacao/whatsapp_spec.rb
@@ -33,3 +33,26 @@ RSpec.describe Notificacao::Whatsapp do
expect(resposta.erro).to include('rede fora')
end
end
+
+RSpec.describe Notificacao::Whatsapp, 'grupo do WhatsApp' do
+ let(:jid) { '120363012345678901@g.us' }
+
+ it 'manda o JID cru para a ponte, sem tentar normalizar como telefone' do
+ config = ConfiguracaoNotificacao.new(whatsapp_ativo: true, whatsapp_provedor: 'baileys')
+ ponte = instance_double(Notificacao::ClienteWhatsapp,
+ enviar: Notificacao::ClienteWhatsapp::Resposta.new(ok: true))
+ allow(Notificacao::ClienteWhatsapp).to receive(:padrao).and_return(ponte)
+
+ expect(described_class.enviar(para: jid, texto: 'oi', config: config)).to be_ok
+ expect(ponte).to have_received(:enviar).with(para: jid, texto: 'oi')
+ end
+
+ it 'recusa com mensagem clara quando o provedor Ă© Twilio' do
+ config = ConfiguracaoNotificacao.new(whatsapp_ativo: true, whatsapp_provedor: 'twilio')
+
+ resposta = described_class.enviar(para: jid, texto: 'oi', config: config)
+
+ expect(resposta).not_to be_ok
+ expect(resposta.erro).to include('grupo')
+ end
+end
From 985e2e3b55838ca81382fd840f38cdf7f2055f09568e083917090e5e2982d4e3 Mon Sep 17 00:00:00 2001
From: victor
Date: Tue, 25 Aug 2026 00:41:26 -0300
Subject: [PATCH 09/40] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20para=20que=20poss?=
=?UTF-8?q?a=20mandar=20=20mesagens=20do=20Zap=20para=20grupos?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.env.example | 5 +-
app/models/configuracao_notificacao.rb | 7 +-
app/services/notificacao/cliente_whatsapp.rb | 6 +
.../configuracao_notificacoes/show.html.erb | 14 +-
app/views/admin/contatos/_form.html.erb | 171 ++++++++-----
app/views/admin/contatos/edit.html.erb | 10 +-
app/views/admin/contatos/index.html.erb | 189 ++++++++++-----
app/views/admin/contatos/new.html.erb | 10 +-
.../admin/eventos_notificacao/_form.html.erb | 224 ++++++++++++------
.../admin/eventos_notificacao/edit.html.erb | 10 +-
.../admin/eventos_notificacao/index.html.erb | 169 +++++++++----
.../admin/eventos_notificacao/new.html.erb | 10 +-
app/views/admin/grupos_contato/_form.html.erb | 59 +++--
app/views/admin/grupos_contato/edit.html.erb | 8 +-
app/views/admin/grupos_contato/index.html.erb | 148 +++++++-----
app/views/admin/grupos_contato/new.html.erb | 8 +-
.../admin/mensagem_templates/edit.html.erb | 121 +++++++---
.../admin/notificacao_envios/index.html.erb | 195 +++++++++------
.../admin/whatsapp_sessoes/show.html.erb | 101 +++++---
19 files changed, 971 insertions(+), 494 deletions(-)
diff --git a/.env.example b/.env.example
index c3b4c29..35c17f0 100644
--- a/.env.example
+++ b/.env.example
@@ -79,10 +79,11 @@ NOTIFICACAO_SECRET=
APP_HOST=localhost:3000
APP_NAME=Reem LogĂstica
-# ââ WhatsApp (Baileys â sessĂŁo pareada por QR code) ââââââââââââââââââââââââ
+# ââ WhatsApp (ponte prĂłpria â sessĂŁo pareada por QR code) ââââââââââââââââââ
# Token compartilhado entre o Rails e o container `whatsapp`. Qualquer string
# longa e aleatĂłria: `openssl rand -hex 32`. Sem ele a ponte recusa TUDO.
WHATSAPP_TOKEN=troque_por_uma_string_aleatoria_longa
# URL da ponte dentro da rede do compose (nĂŁo mexa, a menos que troque o nome
# do serviço no docker-compose.yml).
-BAILEYS_URL=http://whatsapp:3001
+# (o nome antigo `BAILEYS_URL` ainda é aceito, para .env jå em produção)
+WHATSAPP_URL=http://whatsapp:3001
diff --git a/app/models/configuracao_notificacao.rb b/app/models/configuracao_notificacao.rb
index 5ff8836..55d1ad4 100644
--- a/app/models/configuracao_notificacao.rb
+++ b/app/models/configuracao_notificacao.rb
@@ -123,8 +123,13 @@ class ConfiguracaoNotificacao < ApplicationRecord
def baileys_exigido? = whatsapp_ativo? && baileys?
# URL e token da ponte: banco > .env, mesma hierarquia do resto da tela.
+ #
+ # `WHATSAPP_URL` Ă© o nome atual â a tela e o .env.example falam em "ponte do
+ # WhatsApp", nĂŁo no nome da biblioteca. `BAILEYS_URL` continua sendo lido para
+ # não quebrar quem jå tem a variåvel antiga no .env em produção.
def baileys_url_efetiva
- baileys_url.presence || ENV['BAILEYS_URL'].presence || BAILEYS_URL_PADRAO
+ baileys_url.presence || ENV['WHATSAPP_URL'].presence ||
+ ENV['BAILEYS_URL'].presence || BAILEYS_URL_PADRAO
end
def baileys_token_efetivo
diff --git a/app/services/notificacao/cliente_whatsapp.rb b/app/services/notificacao/cliente_whatsapp.rb
index 5c0581c..bbc57d4 100644
--- a/app/services/notificacao/cliente_whatsapp.rb
+++ b/app/services/notificacao/cliente_whatsapp.rb
@@ -97,6 +97,12 @@ module Notificacao
def mensagem_de_erro(resposta, dados)
return 'Token da ponte do WhatsApp invĂĄlido.' if resposta.code == '401'
return 'WhatsApp desconectado â leia o QR code na tela.' if resposta.code == '503'
+ # 404 = a ponte respondeu, mas nĂŁo conhece a rota: a imagem do container
+ # `whatsapp` Ă© anterior a ela. "rota desconhecida" cru na tela nĂŁo diz o
+ # que fazer; o conserto Ă© rebuildar a ponte, nĂŁo mexer no Rails.
+ if resposta.code == '404'
+ return 'Ponte do WhatsApp desatualizada (nĂŁo conhece esta rota) â refaça o build do container `whatsapp`.'
+ end
dados['erro'].presence || "HTTP #{resposta.code}"
end
diff --git a/app/views/admin/configuracao_notificacoes/show.html.erb b/app/views/admin/configuracao_notificacoes/show.html.erb
index 8714221..8487387 100644
--- a/app/views/admin/configuracao_notificacoes/show.html.erb
+++ b/app/views/admin/configuracao_notificacoes/show.html.erb
@@ -181,7 +181,7 @@
- <%# Provedor: sem este seletor as colunas do Baileys existiam no banco mas
+ <%# Provedor: sem este seletor as colunas da ponte existiam no banco mas
nĂŁo havia como sair do Twilio pela tela â o pareamento por QR ficava
inalcançåvel. %>
@@ -206,13 +206,15 @@
- <%# ââ Modo QR (Baileys) âââââââââââââââââââââââââââââââââ %>
+ <%# ââ Modo QR: a ponte prĂłpria do WhatsApp âââââââââââââââ %>
-
Modo QR code
+
+ <%= icone :telefone, tamanho: 'w-4 h-4', espaco: false %> Modo QR code
+
â ïž Canal nĂŁo oficial â a Meta pode banir o nĂșmero.
Use um chip dedicado. Em branco, os dois campos abaixo usam
- BAILEYS_URL e
+ WHATSAPP_URL e
WHATSAPP_TOKEN do .env.
@@ -230,7 +232,9 @@
- Modo Twilio
+
+ <%= icone :chave, tamanho: 'w-4 h-4', espaco: false %> Modo Twilio
+
Credenciais em Twilio Console â Account Info
diff --git a/app/views/admin/contatos/_form.html.erb b/app/views/admin/contatos/_form.html.erb
index 0f0a5ad..d4c02db 100644
--- a/app/views/admin/contatos/_form.html.erb
+++ b/app/views/admin/contatos/_form.html.erb
@@ -1,8 +1,27 @@
-<%= form_with model: [:admin, contato], data: { turbo: false }, class: 'space-y-5' do |f| %>
+<%
+ input = 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ placeholder-gray-600 focus:outline-none focus:border-[#f97316] focus:ring-1
+ focus:ring-[#f97316] transition-colors'
+ rotulo = 'block text-sm font-medium text-gray-300 mb-1.5'
+ ajuda = 'text-gray-500 text-xs mt-1.5'
+ toggle = "w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer
+ peer-checked:after:translate-x-full peer-checked:after:border-white
+ after:content-[''] after:absolute after:top-[2px] after:left-[2px]
+ after:bg-white after:border-gray-300 after:border after:rounded-full
+ after:h-5 after:w-5 after:transition-all peer-checked:bg-[#f97316]"
+%>
+<%= form_with model: [:admin, contato], data: { turbo: false },
+ class: 'bg-[#1a1a1a] rounded-2xl border border-white/5 p-8 space-y-6' do |f| %>
+
<% if contato.errors.any? %>
-
+
+
+ <%= pluralize(contato.errors.count, 'erro', 'erros') %> encontrado<%= contato.errors.count > 1 ? 's' : '' %>:
+
- <% contato.errors.full_messages.each do |msg| %><%= msg %> <% end %>
+ <% contato.errors.full_messages.each do |msg| %>
+ <%= msg %>
+ <% end %>
<% end %>
@@ -10,42 +29,46 @@
<%# Pessoa x grupo do WhatsApp. O JavaScript no fim troca os blocos; sem JS a
pĂĄgina continua utilizĂĄvel â os dois blocos ficam visĂveis e o model limpa
o que nĂŁo pertence ao tipo escolhido. %>
-
- <%= f.label :tipo, 'Tipo de destinatĂĄrio', class: 'block text-gray-400 text-sm mb-1' %>
- <%= f.select :tipo,
- [['Pessoa (nĂșmero ou e-mail)', 'pessoa'], ['Grupo do WhatsApp', 'grupo_whatsapp']], {},
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500',
- id: 'contato-tipo' %>
-
-
-
- <%= f.label :nome, class: 'block text-gray-400 text-sm mb-1' %>
- <%= f.text_field :nome, required: true,
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
-
Como aparece nas listas e em {{contato}}.
-
-
-
-
- <%= f.label :telefone, 'WhatsApp', class: 'block text-gray-400 text-sm mb-1' %>
- <%= f.text_field :telefone, placeholder: '(11) 92005-1157',
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
-
Pode digitar com mĂĄscara â Ă© normalizado ao salvar.
+
+
+ <%= f.label :tipo, 'Tipo de destinatĂĄrio', class: rotulo %>
+ <%= f.select :tipo,
+ [['Pessoa (nĂșmero ou e-mail)', 'pessoa'], ['Grupo do WhatsApp', 'grupo_whatsapp']], {},
+ class: "#{input} cursor-pointer", id: 'contato-tipo' %>
+
- <%= f.label :email, 'E-mail', class: 'block text-gray-400 text-sm mb-1' %>
- <%= f.email_field :email,
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
+ <%= f.label :nome, class: rotulo %>
+ <%= f.text_field :nome, required: true, placeholder: 'Maria Souza', class: input %>
+
Como aparece nas listas e em {{contato}}.
+ <%# ââ Pessoa ââââââââââââââââââââââââââââââââââââââââââââââââ %>
+
+
Como falar com esta pessoa
+
+
+ <%= f.label :telefone, 'WhatsApp', class: rotulo %>
+ <%= f.text_field :telefone, placeholder: '(11) 92005-1157', class: input %>
+
Pode digitar com mĂĄscara â Ă© normalizado ao salvar.
+
+
+ <%= f.label :email, 'E-mail', class: rotulo %>
+ <%= f.email_field :email, placeholder: 'maria@empresa.com', class: input %>
+
Sem e-mail, este contato sĂł recebe WhatsApp.
+
+
+
+
+ <%# ââ Grupo do WhatsApp âââââââââââââââââââââââââââââââââââââ %>
<%# O identificador do grupo Ă© opaco ("120363âŠ@g.us") â digitar na mĂŁo Ă© pedir
erro. A lista vem da ponte, com o nĂșmero jĂĄ conectado. %>
-
- <%= f.label :whatsapp_grupo_jid, 'Grupo do WhatsApp', class: 'block text-gray-400 text-sm mb-1' %>
-
-
+
+
Qual grupo do WhatsApp
+
+
<% if contato.whatsapp_grupo_jid.present? %>
<%= contato.whatsapp_grupo_nome.presence || contato.whatsapp_grupo_jid %>
@@ -55,38 +78,53 @@
<% end %>
- Buscar grupos
+ class="inline-flex items-center justify-center gap-2 px-6 py-3 border border-white/10
+ hover:border-white/20 text-gray-300 hover:text-white rounded-xl
+ transition-colors whitespace-nowrap cursor-pointer disabled:opacity-50">
+ <%= icone :buscar, cor: nil, tamanho: 'w-4 h-4', espaco: false %>
+ Buscar grupos
<%= f.hidden_field :whatsapp_grupo_nome, id: 'grupo-nome' %>
-
+
SĂł aparecem grupos em que o nĂșmero conectado jĂĄ Ă© membro. Grupo com "somente
administradores" recusa o envio se o nĂșmero nĂŁo for admin.
-
- <%= f.label :grupo_contato_id, 'Grupo', class: 'block text-gray-400 text-sm mb-1' %>
- <%= f.collection_select :grupo_contato_id, @grupos, :id, :nome, { include_blank: 'Sem grupo (nĂŁo recebe nada)' },
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
-
Ă o grupo que define quais eventos este contato recebe.
+ <%# ââ Assinatura e situação âââââââââââââââââââââââââââââââââ %>
+
+
+ <%= f.label :grupo_contato_id, 'Grupo', class: rotulo %>
+ <%= f.collection_select :grupo_contato_id, @grupos, :id, :nome,
+ { include_blank: 'Sem grupo (nĂŁo recebe nada)' }, class: "#{input} cursor-pointer" %>
+
Ă o grupo que define quais eventos este contato recebe.
+
+
+
+ <%= f.label :observacao, 'Observação', class: rotulo %>
+ <%= f.text_field :observacao, placeholder: 'Opcional', class: input %>
+
+
+
+
+ <%= f.label :ativo, 'Contato ativo', class: 'block text-sm font-medium text-gray-300' %>
+
Contato inativo continua cadastrado, mas nĂŁo recebe nada
+
+
+ <%= f.check_box :ativo, class: 'sr-only peer' %>
+
+
+
-
- <%= f.label :observacao, 'Observação', class: 'block text-gray-400 text-sm mb-1' %>
- <%= f.text_field :observacao,
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
-
-
-
- <%= f.check_box :ativo, class: 'rounded border-white/20 bg-[#1a1a1a] text-orange-500' %>
- Ativo
-
-
-
- <%= f.submit 'Salvar', class: 'px-4 py-2.5 rounded-xl text-sm font-semibold bg-orange-500 text-black hover:bg-orange-400 cursor-pointer' %>
- <%= link_to 'Cancelar', admin_contatos_path, data: { turbo: false }, class: 'text-gray-400 hover:text-white text-sm' %>
+
+ <%= link_to 'Cancelar', admin_contatos_path, data: { turbo: false },
+ class: 'px-6 py-3 text-gray-400 hover:text-white border border-white/10
+ hover:border-white/20 rounded-xl transition-colors' %>
+ <%= f.submit contato.new_record? ? 'Criar Contato' : 'Salvar AlteraçÔes',
+ class: 'px-8 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold
+ rounded-xl transition-colors cursor-pointer min-h-[48px]' %>
<% end %>
@@ -101,6 +139,10 @@
var botao = document.getElementById('btn-carregar-grupos');
if (!tipo || !pessoa || !grupo) return;
+ // O rĂłtulo do botĂŁo vive num
prĂłprio: trocar o textContent do botĂŁo
+ // inteiro apagaria o Ăcone junto.
+ var botaoTexto = botao.querySelector('[data-btn-texto]');
+
function alternar() {
var ehGrupo = tipo.value === 'grupo_whatsapp';
pessoa.classList.toggle('hidden', ehGrupo);
@@ -109,6 +151,15 @@
tipo.addEventListener('change', alternar);
alternar();
+ // Estado do aviso: erro em vermelho, sucesso em verde, neutro em cinza â
+ // o texto sozinho nĂŁo dizia se a busca deu certo.
+ function avisar(texto, estado) {
+ aviso.textContent = texto;
+ aviso.className = 'text-xs mt-1.5 ' + (
+ estado === 'erro' ? 'text-red-400' : estado === 'ok' ? 'text-green-400' : 'text-gray-500'
+ );
+ }
+
// O nome do grupo Ă© gravado junto com o id: a tela precisa mostrar algo
// legĂvel mesmo com a ponte fora do ar (o id sozinho nĂŁo diz nada).
select.addEventListener('change', function () {
@@ -118,13 +169,13 @@
botao.addEventListener('click', function () {
botao.disabled = true;
- botao.textContent = 'BuscandoâŠ';
+ botaoTexto.textContent = 'BuscandoâŠ';
fetch(grupo.dataset.url, { headers: { Accept: 'application/json' } })
.then(function (r) { return r.json(); })
.then(function (dados) {
- if (dados.erro) { aviso.textContent = dados.erro; return; }
+ if (dados.erro) { avisar(dados.erro, 'erro'); return; }
if (!dados.grupos.length) {
- aviso.textContent = 'Nenhum grupo encontrado â o nĂșmero conectado nĂŁo participa de nenhum.';
+ avisar('Nenhum grupo encontrado â o nĂșmero conectado nĂŁo participa de nenhum.', 'erro');
return;
}
select.innerHTML = 'â escolha â ';
@@ -138,10 +189,10 @@
o.textContent = g.nome + ' (' + g.participantes + ')' + alerta;
select.appendChild(o);
});
- aviso.textContent = dados.grupos.length + ' grupo(s) encontrado(s).';
+ avisar(dados.grupos.length + ' grupo(s) encontrado(s) â escolha um acima.', 'ok');
})
- .catch(function () { aviso.textContent = 'NĂŁo foi possĂvel falar com a ponte do WhatsApp.'; })
- .finally(function () { botao.disabled = false; botao.textContent = 'Buscar grupos'; });
+ .catch(function () { avisar('NĂŁo foi possĂvel falar com a ponte do WhatsApp.', 'erro'); })
+ .finally(function () { botao.disabled = false; botaoTexto.textContent = 'Buscar grupos'; });
});
})();
diff --git a/app/views/admin/contatos/edit.html.erb b/app/views/admin/contatos/edit.html.erb
index a584faa..7698323 100644
--- a/app/views/admin/contatos/edit.html.erb
+++ b/app/views/admin/contatos/edit.html.erb
@@ -1,4 +1,8 @@
-
-
Editar contato
- <%= render "form", contato: @contato %>
+
+
+
Editar: <%= @contato.nome %>
+
Atualize os dados deste contato
+
+ <%= render 'shared/flash' %>
+ <%= render 'form', contato: @contato %>
diff --git a/app/views/admin/contatos/index.html.erb b/app/views/admin/contatos/index.html.erb
index 12fa0bb..e622273 100644
--- a/app/views/admin/contatos/index.html.erb
+++ b/app/views/admin/contatos/index.html.erb
@@ -1,85 +1,146 @@
+
+ <%# Header â mesmo padrĂŁo de UsuĂĄrios: tĂtulo, subtĂtulo e ação primĂĄria %>
-
Contatos
+
+ <%= icone :usuarios, espaco: false %> Contatos
+
Quem recebe as mensagens. Quem tem sĂł nĂșmero recebe sĂł WhatsApp; quem tem sĂł
e-mail recebe sĂł e-mail.
- <%= link_to 'Novo contato', new_admin_contato_path(grupo_id: params[:grupo_id]), data: { turbo: false },
- class: 'px-4 py-2.5 rounded-xl text-sm font-semibold bg-orange-500 text-black hover:bg-orange-400' %>
+ <%= link_to new_admin_contato_path(grupo_id: params[:grupo_id]), data: { turbo: false },
+ class: 'inline-flex items-center gap-2 px-5 py-3 bg-[#f97316] hover:bg-orange-500
+ text-white font-semibold rounded-xl transition-colors min-h-[48px] whitespace-nowrap' do %>
+ <%= icone :adicionar, cor: nil, tamanho: 'w-5 h-5', espaco: false %> Novo contato
+ <% end %>
-
diff --git a/app/views/admin/contatos/new.html.erb b/app/views/admin/contatos/new.html.erb
index a2f5fa8..9cf802c 100644
--- a/app/views/admin/contatos/new.html.erb
+++ b/app/views/admin/contatos/new.html.erb
@@ -1,4 +1,8 @@
-
-
Novo contato
- <%= render "form", contato: @contato %>
+
+
+
Novo contato
+
Cadastre quem vai receber as mensagens
+
+ <%= render 'shared/flash' %>
+ <%= render 'form', contato: @contato %>
diff --git a/app/views/admin/eventos_notificacao/_form.html.erb b/app/views/admin/eventos_notificacao/_form.html.erb
index 70fa856..7cca379 100644
--- a/app/views/admin/eventos_notificacao/_form.html.erb
+++ b/app/views/admin/eventos_notificacao/_form.html.erb
@@ -1,102 +1,172 @@
-<%= form_with model: [:admin, evento], url: (evento.persisted? ? admin_evento_path(evento) : admin_eventos_path),
- data: { turbo: false }, class: 'space-y-5' do |f| %>
+<%
+ input = 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ placeholder-gray-600 focus:outline-none focus:border-[#f97316] focus:ring-1
+ focus:ring-[#f97316] transition-colors'
+ menor = 'w-full px-3 py-2.5 bg-[#0a0a0a] border border-white/10 rounded-xl text-white text-sm
+ focus:outline-none focus:border-[#f97316] focus:ring-1 focus:ring-[#f97316] transition-colors'
+ rotulo = 'block text-sm font-medium text-gray-300 mb-1.5'
+ secao = 'text-sm font-medium text-gray-300 mb-4'
+ toggle = "w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer
+ peer-checked:after:translate-x-full peer-checked:after:border-white
+ after:content-[''] after:absolute after:top-[2px] after:left-[2px]
+ after:bg-white after:border-gray-300 after:border after:rounded-full
+ after:h-5 after:w-5 after:transition-all peer-checked:bg-[#f97316]"
+%>
+<%= form_with model: [:admin, evento],
+ url: (evento.persisted? ? admin_evento_path(evento) : admin_eventos_path),
+ data: { turbo: false },
+ class: 'bg-[#1a1a1a] rounded-2xl border border-white/5 p-8 space-y-6' do |f| %>
+
<% if evento.errors.any? %>
-
+
+
+ <%= pluralize(evento.errors.count, 'erro', 'erros') %> encontrado<%= evento.errors.count > 1 ? 's' : '' %>:
+
- <% evento.errors.full_messages.each do |msg| %><%= msg %> <% end %>
+ <% evento.errors.full_messages.each do |msg| %>
+ <%= msg %>
+ <% end %>
<% end %>
-
- <%= f.label :nome, class: 'block text-gray-400 text-sm mb-1' %>
- <%= f.text_field :nome, required: true, placeholder: 'Aviso de fechamento para a diretoria',
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
+
+
+ <%= f.label :nome, class: rotulo %>
+ <%= f.text_field :nome, required: true,
+ placeholder: 'Aviso de fechamento para a diretoria', class: input %>
+
+
+
+ <%= f.label :gatilho, 'Gatilho (o que faz disparar)', class: rotulo %>
+ <% if evento.sistema? %>
+
+ <%= icone :travado, cor: 'text-gray-500', tamanho: 'w-4 h-4', espaco: false %>
+ <%= evento.gatilho_label %>
+
+
+ Evento de sistema: o gatilho e a chave sĂŁo fixos porque o cĂłdigo dispara por eles.
+
+ <% else %>
+ <%= f.select :gatilho,
+ EventoNotificacao::GATILHOS.map { |g| [EventoNotificacao::GATILHO_LABEL[g], g] }, {},
+ class: "#{input} cursor-pointer" %>
+ <% end %>
+
+
+
+ <%= f.label :descricao, 'Descrição', class: rotulo %>
+ <%= f.text_field :descricao, placeholder: 'Opcional', class: input %>
+
-
- <%= f.label :gatilho, 'Gatilho (o que faz disparar)', class: 'block text-gray-400 text-sm mb-1' %>
- <% if evento.sistema? %>
-
<%= evento.gatilho_label %>
-
Evento de sistema: o gatilho e a chave sĂŁo fixos porque o cĂłdigo dispara por eles.
- <% else %>
- <%= f.select :gatilho, EventoNotificacao::GATILHOS.map { |g| [EventoNotificacao::GATILHO_LABEL[g], g] }, {},
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
- <% end %>
-
-
-
- <%= f.label :descricao, 'Descrição', class: 'block text-gray-400 text-sm mb-1' %>
- <%= f.text_field :descricao,
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
-
-
- <%# SĂł faz sentido no gatilho `agendado`; deixo sempre visĂvel para nĂŁo
- depender de JS, com a explicação de quando vale. %>
-
- Agendamento (sĂł vale no gatilho "Agendado")
-
+ <%# Agendamento â sĂł faz sentido no gatilho `agendado`; deixo sempre visĂvel
+ para não depender de JS, com a explicação de quando vale. %>
+
+
+ Agendamento
+ â sĂł vale no gatilho "Agendado"
+
+
- <%= f.label :frequencia, 'FrequĂȘncia', class: 'block text-gray-500 text-xs mb-1' %>
- <%= f.select :frequencia, EventoNotificacao::FREQUENCIAS.map { |x| [x.capitalize, x] }, { include_blank: 'â' },
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5' %>
+ <%= f.label :frequencia, 'FrequĂȘncia', class: rotulo %>
+ <%= f.select :frequencia, EventoNotificacao::FREQUENCIAS.map { |x| [x.capitalize, x] },
+ { include_blank: 'â' }, class: "#{menor} cursor-pointer" %>
- <%= f.label :hora, 'Hora', class: 'block text-gray-500 text-xs mb-1' %>
- <%= f.number_field :hora, min: 0, max: 23,
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5' %>
+ <%= f.label :hora, 'Hora', class: rotulo %>
+ <%= f.number_field :hora, min: 0, max: 23, class: menor %>
- <%= f.label :dia_semana, 'Dia (semanal)', class: 'block text-gray-500 text-xs mb-1' %>
- <%= f.select :dia_semana, Date::DAYNAMES.each_with_index.map { |nome, i| [nome, i] }, { include_blank: 'â' },
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5' %>
+ <%= f.label :dia_semana, 'Dia (semanal)', class: rotulo %>
+ <%= f.select :dia_semana, Date::DAYNAMES.each_with_index.map { |nome, i| [nome, i] },
+ { include_blank: 'â' }, class: "#{menor} cursor-pointer" %>
-
-
-
- <%= f.check_box :notificar_envolvido, class: 'mt-1 rounded border-white/20 bg-[#1a1a1a] text-orange-500' %>
-
- Avisar também a pessoa envolvida no fato
- Ex.: o motorista dono do pagamento, além dos grupos. Só se aplica aos gatilhos de consolidação/pagamento/valor.
-
-
-
-
- <%= f.check_box :ativo, class: 'rounded border-white/20 bg-[#1a1a1a] text-orange-500' %>
- Ativo
-
+
<%# Assinatura por grupo: TODOS os grupos ativos aparecem, marcados os que jĂĄ
assinam. Sem isso a tela sĂł mostraria os jĂĄ configurados e nĂŁo haveria por
onde adicionar. %>
-
- Quais grupos recebem
+
+
Quais grupos recebem
+
<% if @linhas_assinatura.blank? %>
-
- Nenhum grupo cadastrado ainda â
- <%= link_to 'crie um grupo', new_admin_grupo_path, data: { turbo: false }, class: 'text-orange-400 underline' %>
- antes.
-
- <% end %>
- <% Array(@linhas_assinatura).each do |linha| %>
- <% grupo = linha[:grupo]; assinatura = linha[:assinatura] %>
-
-
- <%= check_box_tag "assinaturas[#{grupo.id}][ativo]", '1', assinatura&.ativo,
- class: 'rounded border-white/20 bg-[#1a1a1a] text-orange-500' %>
- <%= grupo.nome %>
- (<%= grupo.contatos.size %>)
-
- <%= select_tag "assinaturas[#{grupo.id}][canal]",
- options_for_select(GrupoEventoAssinatura::CANAIS.map { |c| [GrupoEventoAssinatura::CANAL_LABEL[c], c] }, assinatura&.canal || 'ambos'),
- class: 'bg-[#141414] border border-white/10 rounded-xl text-white text-sm px-3 py-2' %>
+
+
<%= icone :vazio, tamanho: 'w-8 h-8', cor: 'text-gray-600', espaco: false %>
+
+ Nenhum grupo cadastrado ainda â
+ <%= link_to 'crie um grupo', new_admin_grupo_path, data: { turbo: false },
+ class: 'text-[#f97316] hover:text-orange-400 underline' %>
+ antes.
+
+
+ <% else %>
+
+ <% Array(@linhas_assinatura).each do |linha| %>
+ <% grupo = linha[:grupo]; assinatura = linha[:assinatura] %>
+
+
+ <%= check_box_tag "assinaturas[#{grupo.id}][ativo]", '1', assinatura&.ativo,
+ class: 'w-4 h-4 rounded border-white/20 bg-[#1a1a1a] text-[#f97316]
+ focus:ring-[#f97316] focus:ring-offset-0 cursor-pointer' %>
+ <%= grupo.nome %>
+
+ <%= grupo.contatos.size %> contato(s)
+
+
+ <%= select_tag "assinaturas[#{grupo.id}][canal]",
+ options_for_select(
+ GrupoEventoAssinatura::CANAIS.map { |c| [GrupoEventoAssinatura::CANAL_LABEL[c], c] },
+ assinatura&.canal || 'ambos'
+ ),
+ class: 'px-3 py-2 bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm
+ focus:outline-none focus:border-[#f97316] focus:ring-1 focus:ring-[#f97316]
+ transition-colors cursor-pointer' %>
+
+ <% end %>
<% end %>
-
+
-
- <%= f.submit 'Salvar', class: 'px-4 py-2.5 rounded-xl text-sm font-semibold bg-orange-500 text-black hover:bg-orange-400 cursor-pointer' %>
- <%= link_to 'Cancelar', admin_eventos_path, data: { turbo: false }, class: 'text-gray-400 hover:text-white text-sm' %>
+ <%# OpçÔes %>
+
+
+
+ <%= f.label :notificar_envolvido, 'Avisar também a pessoa envolvida no fato',
+ class: 'block text-sm font-medium text-gray-300' %>
+
+ Ex.: o motorista dono do pagamento, além dos grupos. Só se aplica aos gatilhos de
+ consolidação/pagamento/valor.
+
+
+
+ <%= f.check_box :notificar_envolvido, class: 'sr-only peer' %>
+
+
+
+
+
+
+ <%= f.label :ativo, 'Evento ativo', class: 'block text-sm font-medium text-gray-300' %>
+
Evento inativo nĂŁo dispara nada
+
+
+ <%= f.check_box :ativo, class: 'sr-only peer' %>
+
+
+
+
+
+
+ <%= link_to 'Cancelar', admin_eventos_path, data: { turbo: false },
+ class: 'px-6 py-3 text-gray-400 hover:text-white border border-white/10
+ hover:border-white/20 rounded-xl transition-colors' %>
+ <%= f.submit evento.new_record? ? 'Criar Evento' : 'Salvar AlteraçÔes',
+ class: 'px-8 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold
+ rounded-xl transition-colors cursor-pointer min-h-[48px]' %>
<% end %>
diff --git a/app/views/admin/eventos_notificacao/edit.html.erb b/app/views/admin/eventos_notificacao/edit.html.erb
index c77367a..1af5e2b 100644
--- a/app/views/admin/eventos_notificacao/edit.html.erb
+++ b/app/views/admin/eventos_notificacao/edit.html.erb
@@ -1,4 +1,8 @@
-
-
Editar evento
- <%= render "form", evento: @evento %>
+
+
+
Editar: <%= @evento.nome %>
+
Atualize o gatilho, o agendamento e os grupos assinantes
+
+ <%= render 'shared/flash' %>
+ <%= render 'form', evento: @evento %>
diff --git a/app/views/admin/eventos_notificacao/index.html.erb b/app/views/admin/eventos_notificacao/index.html.erb
index 962cd2e..ac20768 100644
--- a/app/views/admin/eventos_notificacao/index.html.erb
+++ b/app/views/admin/eventos_notificacao/index.html.erb
@@ -1,90 +1,157 @@
+<%
+ input = 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ placeholder-gray-600 focus:outline-none focus:border-[#f97316] focus:ring-1
+ focus:ring-[#f97316] transition-colors text-sm'
+ pill = 'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-bold'
+%>
+
-
Eventos de notificação
+
+ <%= icone :diario, espaco: false %> Eventos de notificação
+
O que dispara mensagem e para quais grupos. VocĂȘ cria quantos eventos quiser â
o gatilho sai de uma lista fixa, porque gatilho Ă© cĂłdigo.
- <%= link_to 'Novo evento', new_admin_evento_path, data: { turbo: false },
- class: 'px-4 py-2.5 rounded-xl text-sm font-semibold bg-orange-500 text-black hover:bg-orange-400' %>
+ <%= link_to new_admin_evento_path, data: { turbo: false },
+ class: 'inline-flex items-center gap-2 px-5 py-3 bg-[#f97316] hover:bg-orange-500
+ text-white font-semibold rounded-xl transition-colors min-h-[48px] whitespace-nowrap' do %>
+ <%= icone :adicionar, cor: nil, tamanho: 'w-5 h-5', espaco: false %> Novo evento
+ <% end %>
-
+ <%= render 'shared/flash' %>
+
+
<% @eventos.each do |evento| %>
-
-
-
-
- <%= evento.nome %>
- <%= evento.gatilho_label %>
+ <%# Evento desativado fica esmaecido: a lista mistura ativo e inativo e sem
+ isso os dois pesam igual na varredura visual. %>
+
+
+
+
+
<%= evento.nome %>
+
+ <%= evento.gatilho_label %>
+
<% if evento.sistema? %>
-
sistema
+
sistema
<% end %>
<% unless evento.ativo? %>
-
desativado
+
desativado
<% end %>
-
-
<%= evento.descricao.presence || 'â' %>
-
+
+
+
<%= evento.descricao.presence || 'â' %>
+
+
<% assinantes = evento.assinaturas.select(&:ativo) %>
<% if assinantes.any? %>
- Grupos: <%= assinantes.map { |a| "#{a.grupo_contato.nome} (#{a.canal_label})" }.join(' · ') %>
+
+ Grupos
+ <% assinantes.each do |a| %>
+
+ <%= a.grupo_contato.nome %>
+ · <%= a.canal_label %>
+
+ <% end %>
+
<% else %>
-
Nenhum grupo assina â este evento nĂŁo manda nada.
+
+ <%= icone :alerta, cor: nil, tamanho: 'w-3.5 h-3.5', espaco: false %>
+ Nenhum grupo assina â este evento nĂŁo manda nada.
+
<% end %>
+
<% if evento.agendado? %>
- · <%= evento.frequencia == 'semanal' ? "toda #{Date::DAYNAMES[evento.dia_semana.to_i]}" : 'todo dia' %> às <%= format('%02d:00', evento.hora.to_i) %>
+
+ <%= icone :relogio, cor: 'text-gray-500', tamanho: 'w-3.5 h-3.5', espaco: false %>
+ <%= evento.frequencia == 'semanal' ? "Toda #{Date::DAYNAMES[evento.dia_semana.to_i]}" : 'Todo dia' %>
+ Ă s <%= format('%02d:00', evento.hora.to_i) %>
+
<% end %>
-
+
-
- <%# Editor de blocos, um por canal. O â diz se jĂĄ existe mensagem
- montada â sem ela o evento usa o texto padrĂŁo do sistema. %>
- <% %w[whatsapp email].each do |canal| %>
- <% montado = evento.template_utilizavel(canal).present? %>
- <%= link_to "#{montado ? 'â ' : ''}#{canal == 'email' ? 'E-mail' : 'WhatsApp'}",
- template_admin_evento_path(evento, canal: canal), data: { turbo: false },
- title: montado ? 'Mensagem montada no editor' : 'Sem mensagem â usa o texto padrĂŁo',
- class: "text-sm #{montado ? 'text-green-400 hover:text-green-300' : 'text-gray-400 hover:text-orange-400'}" %>
- <% end %>
-
|
- <%= link_to 'Editar', edit_admin_evento_path(evento), data: { turbo: false },
- class: 'text-gray-300 hover:text-orange-400 text-sm' %>
- <% if evento.apagavel? %>
- <%= button_to 'Excluir', admin_evento_path(evento), method: :delete,
- form: { data: { turbo_confirm: "Excluir o evento #{evento.nome}?" } },
- class: 'text-red-400 hover:text-red-300 text-sm inline' %>
- <% end %>
+
+ <%# AçÔes: editor de mensagem por canal + editar/excluir %>
+
+
+ <%# O â diz se jĂĄ existe mensagem montada â sem ela o evento usa o
+ texto padrĂŁo do sistema. %>
+ <% %w[whatsapp email].each do |canal| %>
+ <% montado = evento.template_utilizavel(canal).present? %>
+ <%= link_to template_admin_evento_path(evento, canal: canal), data: { turbo: false },
+ title: montado ? 'Mensagem montada no editor' : 'Sem mensagem â usa o texto padrĂŁo',
+ class: "inline-flex items-center gap-1.5 px-3 py-2 rounded-xl text-sm border transition-colors #{
+ montado ? 'bg-green-500/10 border-green-500/30 text-green-300 hover:border-green-500'
+ : 'bg-[#0a0a0a] border-white/10 text-gray-400 hover:border-[#f97316] hover:text-white'}" do %>
+ <%= icone(montado ? :sucesso : (canal == 'email' ? :email : :telefone),
+ cor: nil, tamanho: 'w-4 h-4', espaco: false) %>
+ <%= canal == 'email' ? 'E-mail' : 'WhatsApp' %>
+ <% end %>
+ <% end %>
+
+
+
+ <%= link_to edit_admin_evento_path(evento), data: { turbo: false },
+ class: 'p-2 text-gray-400 hover:text-white hover:bg-white/10 rounded-lg transition-colors',
+ title: 'Editar' do %>
+ <%= icone :editar, cor: nil, tamanho: 'w-4 h-4', espaco: false %>
+ <% end %>
+ <% if evento.apagavel? %>
+ <%= button_to admin_evento_path(evento), method: :delete,
+ class: 'p-2 text-gray-600 hover:text-red-400 hover:bg-red-900/20 rounded-lg transition-colors',
+ title: 'Excluir',
+ form: { data: { turbo_confirm: "Excluir o evento #{evento.nome}?" } } do %>
+ <%= icone :excluir, cor: nil, tamanho: 'w-4 h-4', espaco: false %>
+ <% end %>
+ <% end %>
+
- <%# Disparo manual: manda mensagem DE VERDADE. O texto Ă© digitado aqui
- porque o editor de blocos ainda nĂŁo existe. %>
+ <%# Disparo manual: manda mensagem DE VERDADE. %>
<% if evento.manual? && evento.ativo? %>
-
- Disparar agora
+
+
+ <%= icone :expandir, cor: nil, tamanho: 'w-4 h-4', espaco: false %> Disparar agora
+
<% com_template = %w[whatsapp email].any? { |c| evento.template_utilizavel(c) } %>
<%= form_with url: disparar_admin_evento_path(evento), method: :post,
- data: { turbo: false }, class: 'mt-3 space-y-3' do |f| %>
+ data: { turbo: false }, class: 'mt-4 space-y-3' do |f| %>
<% if com_template %>
-
- Este evento tem mensagem montada no editor â ela serĂĄ usada nos canais configurados.
- O texto abaixo sĂł vale para o canal que ainda nĂŁo tem mensagem prĂłpria.
+
+ <%= icone :sucesso, cor: nil, tamanho: 'w-4 h-4', espaco: false, classe: 'mt-0.5' %>
+
+ Este evento tem mensagem montada no editor â ela serĂĄ usada nos canais configurados.
+ O texto abaixo sĂł vale para o canal que ainda nĂŁo tem mensagem prĂłpria.
+
<% end %>
- <%= f.text_field :assunto, placeholder: 'Assunto (sĂł e-mail)', value: evento.nome,
- class: 'w-full bg-[#141414] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
- <%= f.text_area :corpo, rows: 4, required: !com_template, placeholder: com_template ? 'Opcional â sĂł para o canal sem mensagem montada' : 'Mensagem que serĂĄ enviadaâŠ',
- class: 'w-full bg-[#141414] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
+ <%= f.text_field :assunto, placeholder: 'Assunto (sĂł e-mail)', value: evento.nome, class: input %>
+ <%= f.text_area :corpo, rows: 4, required: !com_template, class: input,
+ placeholder: com_template ? 'Opcional â sĂł para o canal sem mensagem montada' : 'Mensagem que serĂĄ enviadaâŠ' %>
<%= f.submit 'Enviar para os grupos assinantes',
- data: { turbo_confirm: 'Isso envia mensagem de verdade para todos os contatos dos grupos assinantes. Confirmar?' },
- class: 'px-4 py-2.5 rounded-xl text-sm font-semibold bg-orange-500 text-black hover:bg-orange-400 cursor-pointer' %>
+ data: { turbo_confirm: 'Isso envia mensagem de verdade para todos os contatos dos grupos assinantes. Confirmar?' },
+ class: 'px-6 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold
+ rounded-xl transition-colors cursor-pointer min-h-[48px]' %>
<% end %>
<% end %>
<% end %>
+
+ <% if @eventos.empty? %>
+
+
<%= icone :vazio, tamanho: 'w-10 h-10', cor: 'text-gray-600', espaco: false %>
+
Nenhum evento cadastrado.
+
+ <% end %>
diff --git a/app/views/admin/eventos_notificacao/new.html.erb b/app/views/admin/eventos_notificacao/new.html.erb
index e5353cf..9f2cc8f 100644
--- a/app/views/admin/eventos_notificacao/new.html.erb
+++ b/app/views/admin/eventos_notificacao/new.html.erb
@@ -1,4 +1,8 @@
-
-
Novo evento
- <%= render "form", evento: @evento %>
+
+
+
Novo evento
+
Defina o que dispara a mensagem e quem recebe
+
+ <%= render 'shared/flash' %>
+ <%= render 'form', evento: @evento %>
diff --git a/app/views/admin/grupos_contato/_form.html.erb b/app/views/admin/grupos_contato/_form.html.erb
index fb20ded..f8ecbd4 100644
--- a/app/views/admin/grupos_contato/_form.html.erb
+++ b/app/views/admin/grupos_contato/_form.html.erb
@@ -1,36 +1,63 @@
+<%
+ input = 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ placeholder-gray-600 focus:outline-none focus:border-[#f97316] focus:ring-1
+ focus:ring-[#f97316] transition-colors'
+ rotulo = 'block text-sm font-medium text-gray-300 mb-1.5'
+ toggle = "w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer
+ peer-checked:after:translate-x-full peer-checked:after:border-white
+ after:content-[''] after:absolute after:top-[2px] after:left-[2px]
+ after:bg-white after:border-gray-300 after:border after:rounded-full
+ after:h-5 after:w-5 after:transition-all peer-checked:bg-[#f97316]"
+%>
<%# URL explĂcita: a rota se chama `grupo`/`grupos` (ver routes.rb), entĂŁo a
rota polimĂłrfica de `model:` procuraria admin_grupo_contato_path e estouraria.
O `model:` continua valendo para nomear os campos (grupo_contato[...]). %>
<%= form_with model: [:admin, grupo],
url: (grupo.persisted? ? admin_grupo_path(grupo) : admin_grupos_path),
- data: { turbo: false }, class: 'space-y-5' do |f| %>
+ data: { turbo: false },
+ class: 'bg-[#1a1a1a] rounded-2xl border border-white/5 p-8 space-y-6' do |f| %>
+
<% if grupo.errors.any? %>
-
+
+
+ <%= pluralize(grupo.errors.count, 'erro', 'erros') %> encontrado<%= grupo.errors.count > 1 ? 's' : '' %>:
+
- <% grupo.errors.full_messages.each do |msg| %><%= msg %> <% end %>
+ <% grupo.errors.full_messages.each do |msg| %>
+ <%= msg %>
+ <% end %>
<% end %>
- <%= f.label :nome, class: 'block text-gray-400 text-sm mb-1' %>
- <%= f.text_field :nome, required: true, placeholder: 'Diretoria',
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
+ <%= f.label :nome, class: rotulo %>
+ <%= f.text_field :nome, required: true, placeholder: 'Diretoria', class: input %>
- <%= f.label :descricao, 'Descrição', class: 'block text-gray-400 text-sm mb-1' %>
- <%= f.text_field :descricao, placeholder: 'Quem acompanha o fechamento',
- class: 'w-full bg-[#1a1a1a] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
+ <%= f.label :descricao, 'Descrição', class: rotulo %>
+ <%= f.text_field :descricao, placeholder: 'Quem acompanha o fechamento', class: input %>
+
Opcional â ajuda a lembrar para que serve o grupo.
-
- <%= f.check_box :ativo, class: 'rounded border-white/20 bg-[#1a1a1a] text-orange-500' %>
- Ativo (grupo inativo nĂŁo recebe nada)
-
+
+
+ <%= f.label :ativo, 'Grupo ativo', class: 'block text-sm font-medium text-gray-300' %>
+
Grupo inativo nĂŁo recebe nada, mesmo assinando eventos
+
+
+ <%= f.check_box :ativo, class: 'sr-only peer' %>
+
+
+
-
- <%= f.submit 'Salvar', class: 'px-4 py-2.5 rounded-xl text-sm font-semibold bg-orange-500 text-black hover:bg-orange-400 cursor-pointer' %>
- <%= link_to 'Cancelar', admin_grupos_path, data: { turbo: false }, class: 'text-gray-400 hover:text-white text-sm' %>
+
+ <%= link_to 'Cancelar', admin_grupos_path, data: { turbo: false },
+ class: 'px-6 py-3 text-gray-400 hover:text-white border border-white/10
+ hover:border-white/20 rounded-xl transition-colors' %>
+ <%= f.submit grupo.new_record? ? 'Criar Grupo' : 'Salvar AlteraçÔes',
+ class: 'px-8 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold
+ rounded-xl transition-colors cursor-pointer min-h-[48px]' %>
<% end %>
diff --git a/app/views/admin/grupos_contato/edit.html.erb b/app/views/admin/grupos_contato/edit.html.erb
index 0c729a8..1942319 100644
--- a/app/views/admin/grupos_contato/edit.html.erb
+++ b/app/views/admin/grupos_contato/edit.html.erb
@@ -1,4 +1,8 @@
-
-
Editar grupo
+
+
+
Editar: <%= @grupo.nome %>
+
Atualize as informaçÔes do grupo
+
+ <%= render 'shared/flash' %>
<%= render 'form', grupo: @grupo %>
diff --git a/app/views/admin/grupos_contato/index.html.erb b/app/views/admin/grupos_contato/index.html.erb
index 13c7c28..646f108 100644
--- a/app/views/admin/grupos_contato/index.html.erb
+++ b/app/views/admin/grupos_contato/index.html.erb
@@ -1,65 +1,105 @@
+
-
Grupos de contato
+
+ <%= icone :tag, espaco: false %> Grupos de contato
+
Ă o grupo que assina os eventos â cadastrar alguĂ©m novo Ă© escolher o grupo,
não repetir a configuração pessoa por pessoa.
- <%= link_to 'Novo grupo', new_admin_grupo_path, data: { turbo: false },
- class: 'px-4 py-2.5 rounded-xl text-sm font-semibold bg-orange-500 text-black hover:bg-orange-400' %>
-
-
-
- <% if @grupos.empty? %>
-
- Nenhum grupo ainda. Crie um (ex.: Diretoria ,
- Motoristas ) e depois cadastre os contatos dentro dele.
-
- <% else %>
-
-
-
-
- Grupo
- Descrição
- Contatos
- Assina os eventos
- Situação
-
-
-
-
- <% @grupos.each do |grupo| %>
-
- <%= grupo.nome %>
- <%= grupo.descricao.presence || 'â' %>
-
- <%= link_to grupo.contatos.size, admin_contatos_path(grupo_id: grupo.id),
- data: { turbo: false }, class: 'text-orange-400 hover:text-orange-300 underline' %>
-
-
- <% nomes = grupo.assinaturas.select(&:ativo).map { |a| a.evento_notificacao.nome } %>
- <%= nomes.any? ? nomes.join(', ') : 'â nenhum' %>
-
-
-
- <%= grupo.ativo? ? 'Ativo' : 'Inativo' %>
-
-
-
- <%= link_to 'Editar', edit_admin_grupo_path(grupo), data: { turbo: false },
- class: 'text-gray-300 hover:text-orange-400 mr-3' %>
- <%= button_to 'Excluir', admin_grupo_path(grupo), method: :delete,
- form: { data: { turbo_confirm: "Excluir #{grupo.nome}? Os #{grupo.contatos.size} contato(s) ficam sem grupo e param de receber." } },
- class: 'text-red-400 hover:text-red-300 inline' %>
-
-
- <% end %>
-
-
-
+ <%= link_to new_admin_grupo_path, data: { turbo: false },
+ class: 'inline-flex items-center gap-2 px-5 py-3 bg-[#f97316] hover:bg-orange-500
+ text-white font-semibold rounded-xl transition-colors min-h-[48px] whitespace-nowrap' do %>
+ <%= icone :adicionar, cor: nil, tamanho: 'w-5 h-5', espaco: false %> Novo grupo
<% end %>
+
+ <%= render 'shared/flash' %>
+
+
+
+
+
+
+ Grupo
+ Descrição
+ Contatos
+ Assina os eventos
+ Situação
+ AçÔes
+
+
+
+ <% @grupos.each do |grupo| %>
+
+
+
+
+ <%= icone :tag, cor: nil, tamanho: 'w-4 h-4', espaco: false %>
+
+
<%= grupo.nome %>
+
+
+ <%= grupo.descricao.presence || 'â' %>
+
+ <%= link_to admin_contatos_path(grupo_id: grupo.id), data: { turbo: false },
+ class: 'inline-flex items-center px-2.5 py-1 rounded-full text-xs font-bold
+ bg-[#f97316]/15 border border-[#f97316]/30 text-[#f97316]
+ hover:bg-[#f97316]/25 transition-colors',
+ title: 'Ver os contatos deste grupo' do %>
+ <%= grupo.contatos.size %>
+ <% end %>
+
+
+ <% nomes = grupo.assinaturas.select(&:ativo).map { |a| a.evento_notificacao.nome } %>
+ <% if nomes.any? %>
+
+ <% nomes.each do |nome| %>
+ <%= nome %>
+ <% end %>
+
+ <% else %>
+ â nenhum
+ <% end %>
+
+ <%= badge_status_usuario(grupo.ativo?) %>
+
+
+ <%= link_to edit_admin_grupo_path(grupo), data: { turbo: false },
+ class: 'p-2 text-gray-400 hover:text-white hover:bg-white/10 rounded-lg transition-colors',
+ title: 'Editar' do %>
+ <%= icone :editar, cor: nil, tamanho: 'w-4 h-4', espaco: false %>
+ <% end %>
+ <%= button_to admin_grupo_path(grupo), method: :delete,
+ class: 'p-2 text-gray-600 hover:text-red-400 hover:bg-red-900/20 rounded-lg transition-colors',
+ title: 'Excluir',
+ form: { data: { turbo_confirm: "Excluir #{grupo.nome}? Os #{grupo.contatos.size} contato(s) ficam sem grupo e param de receber." } } do %>
+ <%= icone :excluir, cor: nil, tamanho: 'w-4 h-4', espaco: false %>
+ <% end %>
+
+
+
+ <% end %>
+
+ <% if @grupos.empty? %>
+
+
+ <%= icone :vazio, tamanho: 'w-10 h-10', cor: 'text-gray-600', espaco: false %>
+ Nenhum grupo ainda.
+
+ Crie um (ex.: Diretoria ,
+ Motoristas ) e depois cadastre os contatos dentro dele.
+
+
+
+ <% end %>
+
+
+
+
diff --git a/app/views/admin/grupos_contato/new.html.erb b/app/views/admin/grupos_contato/new.html.erb
index 19714bb..aba1218 100644
--- a/app/views/admin/grupos_contato/new.html.erb
+++ b/app/views/admin/grupos_contato/new.html.erb
@@ -1,4 +1,8 @@
-
-
Novo grupo de contato
+
+
+
Novo grupo de contato
+
Junte contatos que recebem os mesmos eventos
+
+ <%= render 'shared/flash' %>
<%= render 'form', grupo: @grupo %>
diff --git a/app/views/admin/mensagem_templates/edit.html.erb b/app/views/admin/mensagem_templates/edit.html.erb
index e5572ac..a6bf97a 100644
--- a/app/views/admin/mensagem_templates/edit.html.erb
+++ b/app/views/admin/mensagem_templates/edit.html.erb
@@ -6,27 +6,53 @@
-
-
-
Mensagem â <%= @evento.nome %>
-
- Canal: <%= @canal == 'email' ? 'E-mail' : 'WhatsApp' %>
- · Gatilho: <%= @evento.gatilho_label %>
-
+
+
+
+ <%= icone :editar, espaco: false %> Mensagem â <%= @evento.nome %>
+
+
+ <% wpp = @canal != 'email' %>
+
+ <%= icone(wpp ? :telefone : :email, cor: nil, tamanho: 'w-3.5 h-3.5', espaco: false) %>
+ <%= wpp ? 'WhatsApp' : 'E-mail' %>
+
+
+ <%= @evento.gatilho_label %>
+
+
-
- <%= link_to "Editar #{outro_canal == 'email' ? 'e-mail' : 'WhatsApp'}",
- template_admin_evento_path(@evento, canal: outro_canal), data: { turbo: false },
- class: 'px-4 py-2.5 rounded-xl text-sm bg-[#1a1a1a] text-gray-300 border border-white/10 hover:border-orange-500' %>
- <%= link_to 'Voltar', admin_eventos_path, data: { turbo: false },
- class: 'px-4 py-2.5 rounded-xl text-sm bg-[#1a1a1a] text-gray-300 border border-white/10 hover:border-orange-500' %>
+
+ <%= link_to template_admin_evento_path(@evento, canal: outro_canal), data: { turbo: false },
+ class: 'inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm border
+ border-white/10 text-gray-300 hover:border-white/20 hover:text-white
+ transition-colors whitespace-nowrap' do %>
+ <%= icone(outro_canal == 'email' ? :email : :telefone, cor: nil, tamanho: 'w-4 h-4', espaco: false) %>
+ Editar <%= outro_canal == 'email' ? 'e-mail' : 'WhatsApp' %>
+ <% end %>
+ <%= link_to admin_eventos_path, data: { turbo: false },
+ class: 'inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm border
+ border-white/10 text-gray-300 hover:border-white/20 hover:text-white
+ transition-colors whitespace-nowrap' do %>
+ <%= icone :voltar, cor: nil, tamanho: 'w-4 h-4', espaco: false %> Voltar
+ <% end %>
+ <%= render 'shared/flash' %>
+
<% if @template.errors.any? %>
-
+
+
+ <%= pluralize(@template.errors.count, 'erro', 'erros') %> encontrado<%= @template.errors.count > 1 ? 's' : '' %>:
+
- <% @template.errors.full_messages.each do |msg| %><%= msg %> <% end %>
+ <% @template.errors.full_messages.each do |msg| %>
+ <%= msg %>
+ <% end %>
<% end %>
@@ -40,11 +66,12 @@
<%# ââ Paleta ââââââââââââââââââââââââââââââââââââââââââââ %>
-
Blocos
+
Blocos
Arraste para a mensagem â ou clique.
<% Notificacao::Blocos::CATALOGO.each do |tipo, meta| %>
-
â ż
<%= meta[:rotulo] %>
@@ -56,14 +83,17 @@
<%# ââ VariĂĄveis âââââââââââââââââââââââââââââââââââââââ %>
-
VariĂĄveis
+
VariĂĄveis
Clique num campo da mensagem e depois numa variĂĄvel para inseri-la ali.
<% @variaveis.each do |nome, meta| %>
-
+
<%= "{{#{nome}}}" %>
<% end %>
@@ -75,34 +105,47 @@
<% if @canal == 'email' %>
- Assunto
- <%= text_field_tag :assunto, @template.assunto, id: 'assunto', class: 'campo-editavel w-full bg-[#141414] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
+ Assunto
+ <%= text_field_tag :assunto, @template.assunto, id: 'assunto',
+ class: 'campo-editavel w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl
+ text-white placeholder-gray-600 focus:outline-none focus:border-[#f97316]
+ focus:ring-1 focus:ring-[#f97316] transition-colors text-sm' %>
<% end %>
-
Mensagem
+
Mensagem
-
- Arraste um bloco para cĂĄ. Sem blocos, o evento continua usando o texto padrĂŁo do sistema.
-
+
+
<%= icone :vazio, tamanho: 'w-8 h-8', cor: 'text-gray-600', espaco: false %>
+
+ Arraste um bloco para cĂĄ. Sem blocos, o evento continua usando o texto padrĂŁo do sistema.
+
+
-
- <%= submit_tag 'Salvar mensagem', class: 'px-4 py-2.5 rounded-xl text-sm font-semibold bg-orange-500 text-black hover:bg-orange-400 cursor-pointer' %>
-
- <%= check_box_tag :ativo, '1', @template.ativo, class: 'rounded border-white/20 bg-[#1a1a1a] text-orange-500' %>
+
+ <%= submit_tag 'Salvar mensagem',
+ class: 'px-6 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold
+ rounded-xl transition-colors cursor-pointer min-h-[48px]' %>
+
+ <%= icone :restaurar, cor: nil, tamanho: 'w-4 h-4', espaco: false %> Atualizar preview
+
+
+ <%= check_box_tag :ativo, '1', @template.ativo,
+ class: 'w-4 h-4 rounded border-white/20 bg-[#0a0a0a] text-[#f97316]
+ focus:ring-[#f97316] focus:ring-offset-0 cursor-pointer' %>
Usar esta mensagem
-
- Atualizar preview
-
<%# ââ Preview âââââââââââââââââââââââââââââââââââââââââââ %>
-
Preview
+
Preview
Com valores de exemplo, renderizado pelo mesmo cĂłdigo do envio.
@@ -157,12 +200,12 @@
if (especie === 'multilinha') {
return '
' + rotulo + ' ' +
'
' +
+ ' class="campo-editavel campo-bloco w-full bg-[#0a0a0a] border border-white/10 rounded-xl text-white text-sm px-3 py-2 focus:outline-none focus:border-[#f97316]">' +
escapar(valor) + ' ';
}
return '
' + rotulo + ' ' +
'
';
+ ' class="campo-editavel campo-bloco w-full bg-[#0a0a0a] border border-white/10 rounded-xl text-white text-sm px-3 py-2 focus:outline-none focus:border-[#f97316]">';
}
function listaHtml(indice, linhas) {
@@ -170,9 +213,9 @@
(linhas || []).forEach(function (linha, j) {
html += '
' +
' ' +
+ ' class="campo-editavel campo-linha flex-1 bg-[#0a0a0a] border border-white/10 rounded-xl text-white text-sm px-3 py-2 focus:outline-none focus:border-[#f97316]">' +
' ' +
+ ' class="campo-editavel campo-linha flex-1 bg-[#0a0a0a] border border-white/10 rounded-xl text-white text-sm px-3 py-2 focus:outline-none focus:border-[#f97316]">' +
'â ' +
'
';
});
@@ -187,7 +230,7 @@
blocos.forEach(function (bloco, i) {
var meta = CATALOGO[bloco.tipo] || { rotulo: bloco.tipo, campos: {} };
var card = document.createElement('div');
- card.className = 'bg-[#141414] border border-white/10 rounded-xl p-3';
+ card.className = 'bg-[#0a0a0a] border border-white/10 rounded-xl p-3 hover:border-white/20 transition-colors';
card.dataset.indice = i;
var corpo = '
' +
diff --git a/app/views/admin/notificacao_envios/index.html.erb b/app/views/admin/notificacao_envios/index.html.erb
index 710a37e..a027a7e 100644
--- a/app/views/admin/notificacao_envios/index.html.erb
+++ b/app/views/admin/notificacao_envios/index.html.erb
@@ -1,95 +1,142 @@
<% ctx = { status: params[:status], canal: params[:canal] }.compact_blank %>
+<%
+ # PĂlula de filtro: uma sĂł definição para os dois grupos (situação e canal),
+ # senĂŁo o estilo do "ativo" fatalmente diverge entre eles com o tempo.
+ pilula = lambda do |ativo|
+ base = 'px-4 py-2 rounded-xl text-sm border transition-colors'
+ ativo ? "#{base} bg-[#f97316] text-white border-[#f97316] font-semibold"
+ : "#{base} bg-[#0a0a0a] text-gray-400 border-white/10 hover:border-white/20 hover:text-white"
+ end
+%>
+
-
Envios
+
+ <%= icone :historico, espaco: false %> Envios
+
O que saiu, para quem e se chegou. Uma falha de envio nunca trava o fechamento â
entĂŁo Ă© aqui que ela aparece.
-
- <% [['', 'Todos'], ['enviado', 'Enviados'], ['falhou', 'Falhados'], ['pendente', 'Pendentes']].each do |valor, rotulo| %>
- <% ativo = params[:status].to_s == valor %>
- <%= link_to rotulo, admin_envios_path(ctx.merge(status: valor.presence).compact), data: { turbo: false },
- class: "px-3 py-2 rounded-xl text-sm border #{ativo ? 'bg-orange-500 text-black border-orange-500 font-bold' : 'bg-[#1a1a1a] text-gray-300 border-white/10 hover:border-orange-500'}" %>
- <% end %>
-
|
- <% [['', 'Todos os canais'], ['whatsapp', 'WhatsApp'], ['email', 'E-mail']].each do |valor, rotulo| %>
- <% ativo = params[:canal].to_s == valor %>
- <%= link_to rotulo, admin_envios_path(ctx.merge(canal: valor.presence).compact), data: { turbo: false },
- class: "px-3 py-2 rounded-xl text-sm border #{ativo ? 'bg-orange-500 text-black border-orange-500 font-bold' : 'bg-[#1a1a1a] text-gray-300 border-white/10 hover:border-orange-500'}" %>
- <% end %>
+ <%= render 'shared/flash' %>
+
+ <%# Filtros %>
+
+
+ Situação
+ <% [['', 'Todos'], ['enviado', 'Enviados'], ['falhou', 'Falhados'], ['pendente', 'Pendentes']].each do |valor, rotulo| %>
+ <%= link_to rotulo, admin_envios_path(ctx.merge(status: valor.presence).compact),
+ data: { turbo: false }, class: pilula.call(params[:status].to_s == valor) %>
+ <% end %>
+
+
+
+
+
+ Canal
+ <% [['', 'Todos'], ['whatsapp', 'WhatsApp'], ['email', 'E-mail']].each do |valor, rotulo| %>
+ <%= link_to rotulo, admin_envios_path(ctx.merge(canal: valor.presence).compact),
+ data: { turbo: false }, class: pilula.call(params[:canal].to_s == valor) %>
+ <% end %>
+
+
<% if @total_falhas.positive? %>
-
<%= @total_falhas %> falha(s) neste filtro
+
+ <%= icone :erro, cor: nil, tamanho: 'w-3.5 h-3.5', espaco: false %>
+ <%= @total_falhas %> falha(s) neste filtro
+
<% end %>
+ <%# Tabela %>
- <% if @envios.empty? %>
-
Nenhum envio registrado ainda.
- <% else %>
-
-
-
-
- Quando
- Evento
- DestinatĂĄrio
- Canal
- Situação
- Mensagem
+
+
+
+
+ Quando
+ Evento
+ DestinatĂĄrio
+ Canal
+ Situação
+ Mensagem
+
+
+
+ <% @envios.each do |envio| %>
+
+
+ <%= envio.created_at.strftime('%d/%m/%Y') %>
+ <%= envio.created_at.strftime('%H:%M') %>
+
+ <%= envio.evento_notificacao&.nome || 'â' %>
+
+ <%= envio.contato&.nome || envio.user&.nome || 'â' %>
+ <%= envio.destino %>
+
+
+ <% wpp = envio.canal == 'whatsapp' %>
+
+ <%= icone(wpp ? :telefone : :email, cor: nil, tamanho: 'w-3.5 h-3.5', espaco: false) %>
+ <%= wpp ? 'WhatsApp' : 'E-mail' %>
+
+
+
+ <% cfg = case envio.status
+ when 'enviado' then { cor: 'bg-green-600 text-white', icone: :sucesso, label: 'Enviado' }
+ when 'falhou' then { cor: 'bg-red-600 text-white', icone: :erro, label: 'Falhou' }
+ else { cor: 'bg-yellow-500 text-black', icone: :relogio, label: envio.status.to_s.capitalize }
+ end %>
+ <%= badge_com_icone(cfg) %>
+ <% if envio.falhou? && envio.erro.present? %>
+ <%= envio.erro.truncate(160) %>
+ <% end %>
+
+
+ <%= envio.corpo.to_s.truncate(90) %>
+
-
-
- <% @envios.each do |envio| %>
-
- <%= envio.created_at.strftime('%d/%m/%Y %H:%M') %>
- <%= envio.evento_notificacao&.nome || 'â' %>
-
- <%= envio.contato&.nome || envio.user&.nome || 'â' %>
- <%= envio.destino %>
-
- <%= envio.canal == 'whatsapp' ? 'WhatsApp' : 'E-mail' %>
-
- <% cor = { 'enviado' => 'text-green-400', 'falhou' => 'text-red-400' }.fetch(envio.status, 'text-yellow-400') %>
- <%= envio.status.capitalize %>
- <% if envio.falhou? && envio.erro.present? %>
- <%= envio.erro.truncate(160) %>
- <% end %>
-
-
- <%= envio.corpo.to_s.truncate(90) %>
-
-
- <% end %>
-
-
-
+ <% end %>
- <% if @pagy.pages > 1 %>
- <% pag = ->(p) { admin_envios_path(ctx.merge(page: p)) } %>
-
- <% if @pagy.prev %>
- <%= link_to 'âč', pag.call(@pagy.prev), data: { turbo: false },
- class: 'min-w-[40px] min-h-[40px] flex items-center justify-center rounded-lg border border-[#2a2a2a] text-white hover:border-orange-500' %>
+ <% if @envios.empty? %>
+
+
+ <%= icone :vazio, tamanho: 'w-10 h-10', cor: 'text-gray-600', espaco: false %>
+ Nenhum envio registrado ainda.
+
+
<% end %>
- <% @pagy.series.each do |item| %>
- <% if item == :gap %>
- âŠ
- <% elsif item.is_a?(String) %>
- <%= item %>
- <% else %>
- <%= link_to item, pag.call(item), data: { turbo: false },
- class: 'min-w-[40px] min-h-[40px] flex items-center justify-center rounded-lg border border-[#2a2a2a] text-white hover:border-orange-500' %>
- <% end %>
+
+
+
+
+ <% if @envios.any? && @pagy.pages > 1 %>
+ <% pag = ->(p) { admin_envios_path(ctx.merge(page: p)) } %>
+
+ <% if @pagy.prev %>
+ <%= link_to 'âč', pag.call(@pagy.prev), data: { turbo: false },
+ class: 'min-w-[40px] min-h-[40px] flex items-center justify-center rounded-lg border border-white/10 text-white hover:border-[#f97316] transition-colors' %>
+ <% end %>
+ <% @pagy.series.each do |item| %>
+ <% if item == :gap %>
+ âŠ
+ <% elsif item.is_a?(String) %>
+ <%= item %>
+ <% else %>
+ <%= link_to item, pag.call(item), data: { turbo: false },
+ class: 'min-w-[40px] min-h-[40px] flex items-center justify-center rounded-lg border border-white/10 text-white hover:border-[#f97316] transition-colors' %>
<% end %>
- <% if @pagy.next %>
- <%= link_to 'âș', pag.call(@pagy.next), data: { turbo: false },
- class: 'min-w-[40px] min-h-[40px] flex items-center justify-center rounded-lg border border-[#2a2a2a] text-white hover:border-orange-500' %>
- <% end %>
-
- <% end %>
+ <% end %>
+ <% if @pagy.next %>
+ <%= link_to 'âș', pag.call(@pagy.next), data: { turbo: false },
+ class: 'min-w-[40px] min-h-[40px] flex items-center justify-center rounded-lg border border-white/10 text-white hover:border-[#f97316] transition-colors' %>
+ <% end %>
+
<% end %>
diff --git a/app/views/admin/whatsapp_sessoes/show.html.erb b/app/views/admin/whatsapp_sessoes/show.html.erb
index bd56113..76e2a8b 100644
--- a/app/views/admin/whatsapp_sessoes/show.html.erb
+++ b/app/views/admin/whatsapp_sessoes/show.html.erb
@@ -1,86 +1,117 @@
-
+
+
-
WhatsApp â conexĂŁo
+
+ <%= icone :telefone, espaco: false %> WhatsApp â conexĂŁo
+
Pareia o nĂșmero da empresa com este servidor lendo um QR code, como no WhatsApp Web.
+ <%= render 'shared/flash' %>
+
<%# O aviso fica na TELA, nĂŁo sĂł no cĂłdigo: quem parear precisa saber o que
estĂĄ aceitando. %>
-
-
â ïž Este canal nĂŁo Ă© oficial.
-
+
+
+ <%= icone :alerta, cor: nil, tamanho: 'w-5 h-5', espaco: false %> Este canal nĂŁo Ă© oficial.
+
+
A conexĂŁo por QR usa a porta do WhatsApp Web por engenharia reversa. Ă gratuita e ilimitada,
mas estĂĄ fora dos Termos do WhatsApp â a Meta pode banir o nĂșmero sem aviso.
Use um chip dedicado , nunca o nĂșmero principal da operação.
-
+
Disparo em rajada Ă© o que mais causa banimento. O intervalo entre mensagens fica em
<%= link_to 'ConfiguraçÔes â NotificaçÔes', admin_configuracao_notificacao_path,
- data: { turbo: false }, class: 'underline hover:text-white' %>
+ data: { turbo: false }, class: 'underline hover:text-white transition-colors' %>
(hoje: <%= @config.intervalo_envio %>s ).
-
+
<% if @estado['conectado'] %>
-
-
+
+ <%# Ponto pulsante: Ă distĂąncia, Ă© o que diz "estĂĄ no ar" sem ler o texto. %>
+
+
+
+
-
Conectado
-
+
Conectado
+
NĂșmero <%= @estado['numero'] %>
- <% if @config.whatsapp_conectado_em %> · desde <%= l @config.whatsapp_conectado_em, format: :short rescue @config.whatsapp_conectado_em %><% end %>
+ <% if @config.whatsapp_conectado_em %>
+ · desde <%= l @config.whatsapp_conectado_em, format: :short rescue @config.whatsapp_conectado_em %>
+ <% end %>
-
+
<%= form_with url: testar_admin_whatsapp_sessao_path, method: :post, data: { turbo: false },
class: 'flex flex-wrap items-end gap-3' do |f| %>
-
- <%= f.label :numero, 'Enviar teste para', class: 'block text-gray-400 text-sm mb-1' %>
+
+ <%= f.label :numero, 'Enviar teste para', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
<%= f.text_field :numero, placeholder: '(11) 92005-1157',
- class: 'bg-[#141414] border border-white/10 rounded-xl text-white text-sm px-3 py-2.5 focus:outline-none focus:border-orange-500' %>
+ class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ placeholder-gray-600 focus:outline-none focus:border-[#f97316] focus:ring-1
+ focus:ring-[#f97316] transition-colors' %>
<%= f.submit 'Enviar teste',
- class: 'px-4 py-2.5 rounded-xl text-sm font-semibold bg-orange-500 text-black hover:bg-orange-400 cursor-pointer' %>
+ class: 'px-6 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold
+ rounded-xl transition-colors cursor-pointer min-h-[48px]' %>
<% end %>
- <%= button_to 'Desconectar', desconectar_admin_whatsapp_sessao_path, method: :delete,
- form: { data: { turbo_confirm: 'Isso encerra a sessĂŁo e para TODOS os envios de WhatsApp atĂ© vocĂȘ ler o QR de novo. Confirmar?' } },
- class: 'text-red-400 hover:text-red-300 text-sm' %>
+
+ <%= button_to desconectar_admin_whatsapp_sessao_path, method: :delete,
+ form: { data: { turbo_confirm: 'Isso encerra a sessĂŁo e para TODOS os envios de WhatsApp atĂ© vocĂȘ ler o QR de novo. Confirmar?' } },
+ class: 'inline-flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm border
+ border-red-500/30 text-red-400 hover:bg-red-900/20 hover:border-red-500/50
+ transition-colors cursor-pointer' do %>
+ <%= icone :sair, cor: nil, tamanho: 'w-4 h-4', espaco: false %> Desconectar
+ <% end %>
+
<% elsif @estado['qr'].present? %>
-
Leia o QR code
-
- No celular do chip dedicado: WhatsApp â Aparelhos conectados â
- Conectar um aparelho â aponte para o cĂłdigo abaixo.
+
+ Aguardando leitura
+
+
Leia o QR code
+
+ No celular do chip dedicado: WhatsApp â Aparelhos conectados â
+ Conectar um aparelho â aponte para o cĂłdigo abaixo.
<%# O QR expira em ~20s e a ponte gera outro â o polling abaixo troca a
imagem sozinho, senĂŁo o operador ficaria lendo um cĂłdigo morto. %>
+ class="rounded-2xl bg-white p-3 shadow-lg" id="qr-whatsapp">
O cĂłdigo se renova sozinho a cada poucos segundos.
<% else %>
-
-
-
-
Desconectado
-
+
+
+
+
Desconectado
+
<%= @estado['erro'].presence || @estado['ultimo_erro'].presence ||
'A ponte ainda nĂŁo gerou um QR code. Aguarde alguns segundos e recarregue.' %>
<% unless @cliente.configurado? %>
-
- Falta configurar a ponte: defina WHATSAPP_TOKEN no .env
- (o mesmo valor do container whatsapp) e suba os containers.
-
+
+
+ Falta configurar a ponte: defina
+ WHATSAPP_TOKEN no
+ .env (o mesmo valor do container
+ whatsapp) e suba os containers.
+
+
<% end %>
From e272b96d0cb8877f07135457d9d2bebe8ecfe5f4d93c69127b68a366fc922206 Mon Sep 17 00:00:00 2001
From: victor
Date: Tue, 25 Aug 2026 01:06:19 -0300
Subject: [PATCH 10/40] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20do=20READ.me?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 175 insertions(+)
diff --git a/README.md b/README.md
index 42f9714..73657f4 100644
--- a/README.md
+++ b/README.md
@@ -2770,3 +2770,178 @@ docker compose exec app bin/rails db:migrate
```
+
+---
+
+
+đš Ponte desatualizada, nome do Baileys fora da tela e padronização visual (25/08/2026)
+
+> â ïž **STATUS: implementado, nĂŁo executado.** Sem migration. Sintaxe conferida nos 16 `.erb`
+> tocados e nos 2 `.rb`. **Testes nĂŁo rodados** â nĂŁo hĂĄ bundler na mĂĄquina de desenvolvimento,
+> sĂł no container.
+
+### 1. `rota desconhecida` ao buscar os grupos
+
+O botĂŁo "Buscar grupos" do cadastro de contato respondia **`rota desconhecida`**. NĂŁo era bug do
+Rails: Ă© o 404 da prĂłpria ponte (`whatsapp/server.js`, fallback de rota inexistente) repassado cru
+para a tela.
+
+A rota `GET /grupos` existe no cĂłdigo desde o commit `568f185`, mas **o container `whatsapp` do
+ambiente de teste rodava uma imagem construĂda antes dele**. O deploy subiu o `app` e reaproveitou
+a imagem em cache da ponte.
+
+**Correção no servidor:**
+```bash
+docker compose up -d --build whatsapp
+# se o cache ainda entregar a imagem velha:
+docker compose build --no-cache whatsapp && docker compose up -d whatsapp
+```
+O volume `whatsapp_auth` Ă© preservado â **nĂŁo precisa ler o QR de novo**.
+
+> đ **A armadilha que volta:** toda vez que `whatsapp/server.js` ganha rota nova, a ponte precisa
+> de rebuild próprio. `docker compose up -d --build` sem nomear o serviço pode reaproveitar a
+> camada em cache, e o sintoma é sempre este: a tela nova chama uma rota que a ponte em pé não
+> conhece. Vale conferir tambĂ©m que o `whatsapp/server.js` foi junto no merge para a `main` â o
+> recurso de grupos ainda **nĂŁo estĂĄ lĂĄ**.
+
+**Correção no código:** o 404 da ponte deixou de aparecer cru. `ClienteWhatsapp#mensagem_de_erro`
+agora traduz para *"Ponte do WhatsApp desatualizada (nĂŁo conhece esta rota) â refaça o build do
+container `whatsapp`"*. à cosmético e **não substitui o rebuild**; serve para a próxima vez o erro
+dizer o que fazer.
+
+### 2. O nome "Baileys" saiu da interface
+
+Nenhum texto que o usuĂĄrio lĂȘ cita mais a biblioteca. A env var mostrada na tela de configuração
+virou **`WHATSAPP_URL`**, e `baileys_url_efetiva` lĂȘ nesta ordem:
+
+```
+banco â ENV['WHATSAPP_URL'] â ENV['BAILEYS_URL'] â 'http://whatsapp:3001'
+```
+
+O nome antigo continua sendo lido de propósito: **nenhum `.env` jå em produção quebra**.
+
+> **Ficou de fora, de propósito:** as colunas `baileys_url` / `baileys_token` e os métodos
+> `baileys?`, `baileys_pronto?`, `BAILEYS_URL_PADRAO` mantĂȘm o nome no banco e no cĂłdigo.
+> Renomear exigiria migration + model + controller + service + specs, e **nada disso aparece na
+> tela** â os rĂłtulos que o usuĂĄrio lĂȘ jĂĄ eram "Endereço da ponte" e "Token da ponte". O valor
+> `'baileys'` do seletor de provedor tambĂ©m continua: Ă© chave interna, o rĂłtulo visĂvel Ă©
+> "QR code (grĂĄtis, nĂŁo oficial)".
+
+### 3. Padronização visual das telas de notificação
+
+As 9 telas do mĂłdulo nasceram com um dialeto prĂłprio e destoavam do resto do admin. O padrĂŁo de
+referĂȘncia Ă© **UsuĂĄrios** â a tela mais madura. O que mudou:
+
+| Antes (telas de notificação) | Agora (padrão do admin) |
+|---|---|
+| BotĂŁo `bg-orange-500 text-black`, `py-2.5` | `bg-[#f97316] text-white` com Ăcone, `min-h-[48px]` |
+| `thead` com `bg-white/5`, `py-3`, `font-medium` | `border-b border-white/10`, `py-4`, `font-semibold` |
+| AçÔes "Editar"/"Excluir" em texto | Ăcones com `opacity-0 group-hover:opacity-100` |
+| Vazio: `` solto fora da tabela | Linha na tabela, Ăcone `:vazio`, `py-16` |
+| Situação em texto colorido | `badge_status_usuario` / `badge_com_icone` |
+| Inputs `bg-[#1a1a1a]`, `py-2.5` | `bg-[#0a0a0a]`, `py-3`, `focus:ring` laranja |
+| Checkbox de ativo/inativo | Toggle switch, igual ao de UsuĂĄrios |
+| Formulårio solto na pågina | Card `rounded-2xl border` com seçÔes divididas |
+
+**CorreçÔes de comportamento que vieram junto:**
+
+- **`render 'shared/flash'` nas telas que nĂŁo tinham.** Mensagens de sucesso e erro simplesmente
+ **nĂŁo apareciam** em Contatos, Grupos, Eventos e Envios â a ação dava certo e a tela ficava muda.
+- **O aviso do "Buscar grupos" ganhou cor semĂąntica** â verde no sucesso, vermelho no erro. Antes
+ toda resposta saĂa em cinza, entĂŁo "5 grupos encontrados" e "nĂŁo foi possĂvel falar com a ponte"
+ tinham exatamente o mesmo peso visual.
+- O rĂłtulo do botĂŁo de busca virou um `` prĂłprio: trocar o `textContent` do botĂŁo inteiro
+ apagava o Ăcone junto.
+
+**Ganhos de leitura:** avatar distingue pessoa de grupo em Contatos; os filtros de Envios ficaram
+agrupados por "Situação" e "Canal" com pĂlulas; o ponto verde de "Conectado" pulsa; evento
+desativado fica esmaecido na lista.
+
+### đ Arquivos
+```
+app/models/configuracao_notificacao.rb (WHATSAPP_URL com fallback p/ BAILEYS_URL)
+app/services/notificacao/cliente_whatsapp.rb (404 da ponte vira mensagem acionĂĄvel)
+.env.example (WHATSAPP_URL; nome antigo documentado)
+app/views/admin/configuracao_notificacoes/show.html.erb (nome da lib fora da tela)
+app/views/admin/contatos/{index,_form,new,edit}.html.erb
+app/views/admin/grupos_contato/{index,_form,new,edit}.html.erb
+app/views/admin/eventos_notificacao/{index,_form,new,edit}.html.erb
+app/views/admin/notificacao_envios/index.html.erb
+app/views/admin/whatsapp_sessoes/show.html.erb
+app/views/admin/mensagem_templates/edit.html.erb
+```
+
+### âł Pendente
+```bash
+docker compose up -d --build whatsapp # â ïž o que resolve o "rota desconhecida"
+docker compose restart app # views mudaram â cache de views do Puma
+
+docker compose exec app bundle exec rspec
+
+# Conferir na tela: NotificaçÔes â Contatos â Novo contato
+# â Tipo "Grupo do WhatsApp" â "Buscar grupos" â a lista deve vir com nome,
+# nÂș de participantes e â ïž nos grupos restritos a admin.
+```
+
+### đ Achado nĂŁo corrigido
+`app/views/admin/mensagem_templates/edit.html.erb` carrega o **SortableJS de
+`cdn.jsdelivr.net`**. Se a CSP passar de `report_only` para enforcing (pendĂȘncia jĂĄ registrada
+neste README), o **arrastar-e-soltar dos blocos morre** â o clique na paleta sobrevive, porque o
+cĂłdigo tem `if (window.Sortable)`. O conserto Ă© baixar o arquivo para `public/`. NĂŁo foi feito por
+estar fora do escopo pedido.
+
+
+
+---
+
+
+đ§ O que falta: gatilhos pela tela e blocos ricos na mensagem â backlog
+
+Complementa o backlog do editor jĂĄ registrado acima. **Nada aqui estĂĄ implementado.**
+
+### A. Criação de gatilhos pela tela
+
+**Como Ă© hoje:** o ADM cria quantos **eventos** quiser, mas o **gatilho** sai de uma lista fixa de
+seis â `manual`, `consolidacao_finalizada`, `pagamento_efetuado`, `valor_alterado`,
+`operacao_alterada`, `agendado`. A tela diz isso ao usuĂĄrio: *"o gatilho sai de uma lista fixa,
+porque gatilho Ă© cĂłdigo"*. E Ă© verdade â cada gatilho tem uma chamada correspondente no cĂłdigo de
+negĂłcio (`Notificacao::Gatilhos`, `NotificacaoService`, controllers).
+
+**O que "criar gatilho pela tela" realmente significa** â trĂȘs nĂveis, do barato ao caro:
+
+| # | Ideia | Esforço | Observação |
+|---|---|---|---|
+| A1 | **Gatilho agendado com filtro** â "todo dia 08h **se houver consolidação pendente**". NĂŁo Ă© gatilho novo: Ă© o `agendado` com uma condição escolhida numa lista. | baixo | Cobre boa parte do que se pede como "gatilho novo". |
+| A2 | **Gatilho por limiar** â "quando o valor alterado passar de R$ X" ou "quando as notas fora da operação passarem de N". Campos: mĂ©trica (lista), operador, valor. | mĂ©dio | Reusa `Analytics::ResumoOperacao` e `OperacaoSnapshot`, que jĂĄ calculam as mĂ©tricas. |
+| A3 | **Gatilhos que faltam no domĂnio** â consolidação **arquivada**, contato **sem grupo** hĂĄ X dias, envio **falhado** (avisar o ADM que o WhatsApp caiu). | mĂ©dio | SĂŁo gatilhos de cĂłdigo mesmo: uma constante + a chamada no ponto certo. O caminho jĂĄ estĂĄ pavimentado por `disparar_gatilho`. |
+| A4 | **Condição livre por regra** (construtor de expressĂŁo na tela) | **alto** | Vira uma mini-linguagem: precisa de parser, sandbox e validação. **NĂŁo recomendado** â A1+A2 entregam quase tudo por uma fração do custo. |
+
+> â ïž **A armadilha aqui:** gatilho cadastrado na tela que **nĂŁo tem chamada no cĂłdigo** fica mudo
+> para sempre, sem erro nenhum â exatamente o bug nÂș 4 do deploy de 24/08. Qualquer caminho
+> escolhido precisa de uma trava: ou a lista continua vindo do cĂłdigo, ou a tela avisa que o
+> gatilho ainda nĂŁo estĂĄ ligado.
+
+### B. Mais opçÔes na criação da mensagem
+
+**Como Ă© hoje:** 7 blocos (`cabecalho`, `texto`, `tabela`, `aviso`, `botao`, `divisor`, `rodape`),
+todos **sĂł texto**, com cores fixas no renderizador.
+
+| # | Ideia | Esforço | Observação |
+|---|---|---|---|
+| B1 | **Seletor de emoji** nos campos de texto â hoje sĂł colando do sistema. Emoji Ă© o que dĂĄ cara de WhatsApp Ă mensagem. | baixo | Um picker pequeno, sem dependĂȘncia externa (a CSP Ă© restritiva). Inserir no cursor: a mecĂąnica jĂĄ existe, Ă© a mesma dos chips de variĂĄvel. |
+| B2 | **Bloco de imagem no e-mail** â ` ` com URL ou upload. | mĂ©dio | Cliente de e-mail bloqueia imagem remota por padrĂŁo â precisa de URL absoluta pĂșblica ou base64. |
+| B3 | **Bloco de imagem no WhatsApp** | **alto** | A ponte hoje sĂł faz `sendMessage` de **texto**. Exige `sendMessage(jid, { image })` no `server.js`, endpoint novo, envio multipart no `ClienteWhatsapp`, storage do arquivo e uma coluna no log de envios. **Ă o item mais caro da lista** â vale sĂł se a demanda for real. |
+| B4 | **Formatação do WhatsApp** (`*negrito*`, `_itĂĄlico_`, `~riscado~`, ``` `mono` ```) com botĂ”es â hoje o ADM precisa saber a sintaxe de cor. | baixo | No e-mail cada marca vira a tag equivalente. |
+| B5 | **Bloco de lista com bullets** â hoje sĂł existe tabela rĂłtulo/valor. | baixo | JĂĄ estava listado como B7 no backlog anterior. |
+| B6 | **Ocultar bloco com variĂĄvel vazia** â resolve o `"â ïž AlteraçÔes: "` sem nada depois. | baixo | JĂĄ listado como B1 antes; **continua sendo o melhor valor Ă· esforço do mĂłdulo inteiro**. |
+| B7 | **Cor de destaque configurĂĄvel** â o laranja estĂĄ fixo no `Renderizador`. | baixo | |
+| B8 | **Contador de caracteres no WhatsApp** â mensagem longa vira "ler mais", e o começo Ă© o que decide se abrem. | baixo | |
+| B9 | **Bloco de menção** (`@membro`) em grupo do WhatsApp | mĂ©dio | Baileys exige passar os JIDs em `mentions` alĂ©m do texto â nĂŁo basta escrever `@`. |
+
+### Se fosse escolher trĂȘs
+**B6** (mensagem sem buraco quando a variåvel vem vazia), **B1** (emoji) e **B4** (formatação do
+WhatsApp por botĂŁo). Os trĂȘs sĂŁo de esforço baixo e atacam o que mais se sente escrevendo mensagem
+no dia a dia. **B3 (imagem no WhatsApp) fica por Ășltimo** â Ă© o Ășnico que mexe na ponte, no cliente
+Ruby e no schema ao mesmo tempo.
+
+
From 8d24a575794c8193026ad829b971fc27d1a604522f47201103c44a66f44db35b Mon Sep 17 00:00:00 2001
From: victor
Date: Wed, 26 Aug 2026 10:56:07 -0300
Subject: [PATCH 11/40] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20do=20Para=20corre?=
=?UTF-8?q?=C3=A7=C3=A3o=20do=20valor=20do=20dash=20da=20opera=C3=A7=C3=A3?=
=?UTF-8?q?o?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../operacoes_dashboard_controller.rb | 34 +++++++++----------
app/services/analytics/operacao_metricas.rb | 16 +++++++++
app/views/operacoes_dashboard/index.html.erb | 20 +++++++++--
3 files changed, 50 insertions(+), 20 deletions(-)
diff --git a/app/controllers/operacoes_dashboard_controller.rb b/app/controllers/operacoes_dashboard_controller.rb
index ca48bd0..31c53a4 100644
--- a/app/controllers/operacoes_dashboard_controller.rb
+++ b/app/controllers/operacoes_dashboard_controller.rb
@@ -35,13 +35,19 @@ class OperacoesDashboardController < ApplicationController
@metricas_a = montar([@op_a]) if @op_a
@metricas_b = montar([@op_b]) if @op_b
else
- # Operação Ășnica. Sem datas na URL o recorte Ă© a operação inteira (o
- # perĂodo vem dos prĂłprios dados) â Ă© o que mantĂ©m utilizĂĄvel abrir uma
- # operação de meses atrĂĄs. Quando o operador ESCOLHE um perĂodo, ele passa
- # a valer aqui também: sem isso, comparar esta tela com o dashboard
- # financeiro "no mesmo perĂodo" era impossĂvel, porque a data era ignorada.
+ # Operação Ășnica: o recorte Ă© SEMPRE a operação inteira, mesmo com
+ # inicio/fim na URL. Ă o que confere com os documentos fĂsicos e com a
+ # planilha entregue ao cliente (Analytics::PlanilhaEntregas também não
+ # filtra data). Recortar pelo perĂodo derrubava as entregas que a operação
+ # executou fora do mĂȘs do nome dela â EMAD AGO 2026 rodou de 30/07 a
+ # 12/08, e "01/08 â hoje" mostrava 1146 das 2021 NFs.
+ #
+ # O perĂodo escolhido continua tendo função aqui: alimenta as "Notas fora
+ # da operação" e aparece no cabeçalho como REFERĂNCIA (quantas NFs da
+ # operação caĂram na faixa), para comparar com o dashboard financeiro sem
+ # mexer no nĂșmero que o cliente confere.
@operacao = Operacao.sanitizar([params[:operacao]]).first || @tabelas_validas.first
- @metricas = montar([@operacao], filtros: @filtros, data: @data_filtro, **faixa_ativa) if @operacao
+ @metricas = montar([@operacao], filtros: @filtros, data: @data_filtro) if @operacao
@fora_operacao = Analytics::NotasForaOperacao.new(inicio: @periodo_inicio, fim: @periodo_fim) if @periodo_explicito
end
end
@@ -63,8 +69,10 @@ class OperacoesDashboardController < ApplicationController
if @modo == 'global'
@metricas = montar(@tabelas_validas, inicio: @periodo_inicio, fim: @periodo_fim, filtros: @filtros, data: @data_filtro)
else
+ # Mesma regra do #index: operação = operação inteira (a planilha espelho
+ # tem que bater com a que Ă© entregue ao cliente).
@operacao = Operacao.sanitizar([params[:operacao]]).first || @tabelas_validas.first
- @metricas = montar([@operacao], filtros: @filtros, data: @data_filtro, **faixa_ativa) if @operacao
+ @metricas = montar([@operacao], filtros: @filtros, data: @data_filtro) if @operacao
end
montar_tabela_espelho if @metricas
@@ -108,20 +116,12 @@ class OperacoesDashboardController < ApplicationController
@espelho_linhas = linhas[(@espelho_pagina - 1) * ESPELHO_POR_PAGINA, ESPELHO_POR_PAGINA] || []
end
- # PerĂodo escolhido pelo operador (veio na URL) x default da tela. SĂł o
- # explĂcito Ă© aplicado no modo Operação â ver o comentĂĄrio no #index.
+ # PerĂodo escolhido pelo operador (veio na URL) x default da tela. No modo
+ # Operação ele nĂŁo recorta os KPIs â ver o comentĂĄrio no #index.
def periodo_explicito?
params[:inicio].present? || params[:fim].present?
end
- # Faixa a repassar para as métricas no modo Operação: vazia quando o operador
- # nĂŁo escolheu perĂodo (aĂ o recorte Ă© a operação inteira).
- def faixa_ativa
- return {} unless @periodo_explicito
-
- { inicio: @periodo_inicio, fim: @periodo_fim }
- end
-
def montar(tabelas, inicio: nil, fim: nil, filtros: {}, data: nil)
Analytics::OperacaoMetricas.new(tabelas: tabelas, inicio: inicio, fim: fim, filtros: filtros, data: data)
end
diff --git a/app/services/analytics/operacao_metricas.rb b/app/services/analytics/operacao_metricas.rb
index a2304b0..bef84b4 100644
--- a/app/services/analytics/operacao_metricas.rb
+++ b/app/services/analytics/operacao_metricas.rb
@@ -126,6 +126,22 @@ module Analytics
total - sucesso - recusas
end
+ # NFs da operação cujo resultado caiu dentro de uma faixa de datas â
+ # REFERĂNCIA para o cabeçalho da tela de operação Ășnica, onde o total
+ # continua sendo a operação inteira (é o que confere com a planilha do
+ # cliente). Mesma regra do filtro SQL: concluĂdas/falhas pela data real
+ # (checkout), pendentes pela data planejada.
+ def total_no_periodo(inicio, fim)
+ return total unless inicio && fim
+
+ ini = inicio.to_date
+ fim = fim.to_date
+ linhas.count do |r|
+ d = data_de(r['checkout']) || data_de(r['planned_date'])
+ d && d >= ini && d <= fim
+ end
+ end
+
# ââ Camada operacional (por VISITA, nĂŁo por NF) âââââââââââââââ
# Os KPIs acima contam NOTAS porque Ă© o que o cliente paga e confere. Estes
# contam IDAS AO LOCAL â Ă© o que o motorista recebe e o que o dashboard
diff --git a/app/views/operacoes_dashboard/index.html.erb b/app/views/operacoes_dashboard/index.html.erb
index d3e8abc..98d3638 100644
--- a/app/views/operacoes_dashboard/index.html.erb
+++ b/app/views/operacoes_dashboard/index.html.erb
@@ -57,7 +57,15 @@
<%= icone :comparar %> Comparação entre operaçÔes (cada uma no seu prĂłprio perĂodo)
<% elsif @metricas&.data_inicio %>
<%= @metricas.operacoes_label %> · <%= @metricas.data_inicio.strftime('%d/%m/%Y') %> â <%= @metricas.data_fim.strftime('%d/%m/%Y') %>
- <%= @periodo_explicito ? '(perĂodo filtrado)' : '(operação inteira)' %>
+ (operação inteira)
+ <%# O perĂodo escolhido NĂO recorta os KPIs desta tela (eles tĂȘm que
+ bater com a planilha entregue ao cliente); ele aparece aqui como
+ referĂȘncia para comparar com o dashboard financeiro. %>
+ <% if @periodo_explicito %>
+ ·
+ no perĂodo <%= @periodo_inicio.strftime('%d/%m') %>â<%= @periodo_fim.strftime('%d/%m/%Y') %>:
+ <%= @metricas.total_no_periodo(@periodo_inicio, @periodo_fim) %> NFs
+ <% end %>
<% elsif @operacao %>
<%= Operacao.label(@operacao) %>
<% end %>
@@ -75,6 +83,7 @@
<%= icone :calendario, espaco: false %>
@@ -89,9 +98,14 @@
data: { turbo: false },
class: "px-3 py-2.5 rounded-xl text-sm whitespace-nowrap border #{ativo ? 'bg-orange-500 text-black border-orange-500 font-bold' : 'bg-[#1a1a1a] text-gray-300 border-white/10 hover:border-orange-500'}" %>
<% end %>
- <%# Volta ao recorte natural da operação (sem faixa de datas na URL). %>
+ <%# Limpa a faixa de datas da URL (os KPIs jå são sempre da operação
+ inteira; sem perĂodo a tela tambĂ©m some com o painel de notas
+ fora da operação). %>
<% if @modo == 'operacao' %>
- <%= link_to 'Operação inteira',
+ <%# RĂłtulo Ă© "Limpar perĂodo" e nĂŁo "Operação inteira": o total jĂĄ Ă©
+ sempre o da operação inteira, o que este botão faz é tirar a
+ faixa de datas da URL. %>
+ <%= link_to 'Limpar perĂodo',
operacoes_dashboard_path(ctx_ops.merge(modo: 'operacao')),
data: { turbo: false },
class: "px-3 py-2.5 rounded-xl text-sm whitespace-nowrap border #{@periodo_explicito ? 'bg-[#1a1a1a] text-gray-300 border-white/10 hover:border-orange-500' : 'bg-orange-500 text-black border-orange-500 font-bold'}" %>
From cd99a225538fa93e5bf562c4d31f31b916e17f0618033a4d241618a44b543881 Mon Sep 17 00:00:00 2001
From: victor
Date: Wed, 26 Aug 2026 11:15:52 -0300
Subject: [PATCH 12/40] =?UTF-8?q?Cria=C3=A7=C3=A3o=20da=20vis=C3=A3o=20ger?=
=?UTF-8?q?al=20da=20consolida=C3=A7=C3=A3o=20por=20motorista?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/controllers/consolidacoes_controller.rb | 59 ++++++
.../motorista/dashboard_controller.rb | 52 +++++
.../analytics/totais_por_motorista.rb | 111 ++++++++++
app/services/pdf/totais_motorista_pdf.rb | 130 ++++++++++++
app/views/consolidacoes/index.html.erb | 2 +
app/views/consolidacoes/totais.html.erb | 191 ++++++++++++++++++
app/views/layouts/_navbar.html.erb | 2 +
app/views/motorista/dashboard/index.html.erb | 10 +-
app/views/motorista/dashboard/totais.html.erb | 95 +++++++++
config/routes.rb | 10 +
spec/requests/totais_por_motorista_spec.rb | 90 +++++++++
.../analytics/totais_por_motorista_spec.rb | 92 +++++++++
12 files changed, 841 insertions(+), 3 deletions(-)
create mode 100644 app/services/analytics/totais_por_motorista.rb
create mode 100644 app/services/pdf/totais_motorista_pdf.rb
create mode 100644 app/views/consolidacoes/totais.html.erb
create mode 100644 app/views/motorista/dashboard/totais.html.erb
create mode 100644 spec/requests/totais_por_motorista_spec.rb
create mode 100644 spec/services/analytics/totais_por_motorista_spec.rb
diff --git a/app/controllers/consolidacoes_controller.rb b/app/controllers/consolidacoes_controller.rb
index b8fe1cd..30a09fa 100644
--- a/app/controllers/consolidacoes_controller.rb
+++ b/app/controllers/consolidacoes_controller.rb
@@ -40,6 +40,47 @@ class ConsolidacoesController < ApplicationController
end
end
+ # GET /consolidacoes/totais?inicio=&fim=&motorista=&rascunhos=1
+ #
+ # "Quanto cada motorista somou no perĂodo". A lista de consolidaçÔes mostra um
+ # card por CONSOLIDAĂĂO, mas o pagamento Ă© por MOTORISTA: para saber quanto um
+ # motorista fechou no mĂȘs era preciso abrir consolidação por consolidação e
+ # somar Ă mĂŁo. Aqui o total vem pronto e, clicando no motorista (?motorista=),
+ # a tela abre as consolidaçÔes que compÔem aquele valor.
+ def totais
+ authorize Consolidacao, :index?
+
+ @inicio, @fim = periodo_totais
+ @incluir_rascunhos = params[:rascunhos] == '1'
+ @motorista = params[:motorista].presence
+ @totais = Analytics::TotaisPorMotorista.new(
+ inicio: @inicio, fim: @fim, incluir_rascunhos: @incluir_rascunhos
+ )
+ # Drill-down: as consolidaçÔes do motorista clicado (a tabela geral continua
+ # na tela, entĂŁo o service Ă© montado sem recorte de motorista).
+ @detalhe = @motorista ? @totais.consolidacoes_de(@motorista) : nil
+ end
+
+ # GET /consolidacoes/totais_pdf â o mesmo relatĂłrio em PDF. Com ?motorista=
+ # sai só o extrato daquele motorista (com espaço de assinatura).
+ def totais_pdf
+ authorize Consolidacao, :index?
+
+ inicio, fim = periodo_totais
+ totais = Analytics::TotaisPorMotorista.new(
+ inicio: inicio, fim: fim,
+ motorista: params[:motorista].presence,
+ incluir_rascunhos: params[:rascunhos] == '1'
+ )
+
+ nome = params[:motorista].presence&.parameterize(separator: '_')
+ arquivo = ['totais', nome, inicio.strftime('%Y%m%d'), fim.strftime('%Y%m%d')].compact.join('_')
+ send_data Pdf::TotaisMotoristaPdf.new(totais: totais, detalhar: params[:detalhar] != '0').render,
+ filename: "#{arquivo}.pdf",
+ type: 'application/pdf',
+ disposition: 'attachment'
+ end
+
# GET /consolidacoes/new
def new
authorize Consolidacao
@@ -449,6 +490,24 @@ class ConsolidacoesController < ApplicationController
private
+ # PerĂodo do relatĂłrio de totais. Default: o MĂS CORRENTE INTEIRO â o fim Ă© o
+ # Ășltimo dia do mĂȘs, nĂŁo "hoje", porque o recorte exige a consolidação inteira
+ # dentro da faixa (Consolidacao.no_periodo) e um fechamento que vai até 31/08
+ # sumiria da tela até o dia 31 chegar.
+ def periodo_totais
+ inicio = parse_data_totais(params[:inicio]) || Date.current.beginning_of_month
+ fim = parse_data_totais(params[:fim]) || Date.current.end_of_month
+ inicio, fim = fim, inicio if fim < inicio
+ [inicio, fim]
+ end
+
+ def parse_data_totais(str)
+ return nil if str.blank?
+ Date.parse(str)
+ rescue ArgumentError, TypeError
+ nil
+ end
+
# Acha o User motorista pelo nome para gerar o QR Code de acesso. O
# motorista_nome vem dos dados externos de entrega e o users.nome Ă© digitado
# no admin â entĂŁo toleramos diferenças de caixa e de espaços (extras/duplos).
diff --git a/app/controllers/motorista/dashboard_controller.rb b/app/controllers/motorista/dashboard_controller.rb
index 6a1e903..86bb26c 100644
--- a/app/controllers/motorista/dashboard_controller.rb
+++ b/app/controllers/motorista/dashboard_controller.rb
@@ -54,8 +54,60 @@ module Motorista
.includes(:consolidacao)
end
+ # GET /motorista/totais?inicio=&fim=
+ #
+ # "Quanto eu fechei neste perĂodo e de onde vem esse valor". O card do painel
+ # mostra sĂł o total acumulado; aqui o motorista escolhe o perĂodo, vĂȘ o total
+ # FECHADO (consolidaçÔes finalizadas) e a lista de consolidaçÔes que compÔem
+ # o valor â a mesma conta que o admin vĂȘ em /consolidacoes/totais, montada
+ # pelo mesmo service para os dois nĂșmeros nĂŁo poderem divergir.
+ def totais
+ @inicio, @fim = periodo_totais
+ @totais = totais_do_periodo
+ @consolidacoes = @totais.consolidacoes_de(current_user.nome)
+ end
+
+ # GET /motorista/totais/pdf â o extrato do perĂodo em PDF (com assinatura),
+ # para o motorista guardar ou mandar por WhatsApp.
+ def totais_pdf
+ inicio, fim = periodo_totais
+ totais = Analytics::TotaisPorMotorista.new(
+ inicio: inicio, fim: fim, motorista: current_user.nome
+ )
+
+ arquivo = ['meu_total', current_user.nome.parameterize(separator: '_'),
+ inicio.strftime('%Y%m%d'), fim.strftime('%Y%m%d')].join('_')
+ send_data Pdf::TotaisMotoristaPdf.new(totais: totais).render,
+ filename: "#{arquivo}.pdf",
+ type: 'application/pdf',
+ disposition: 'attachment'
+ end
+
private
+ def totais_do_periodo
+ Analytics::TotaisPorMotorista.new(
+ inicio: @inicio, fim: @fim, motorista: current_user.nome
+ )
+ end
+
+ # Default: mĂȘs corrente INTEIRO (fim = Ășltimo dia do mĂȘs). O recorte pede a
+ # consolidação inteira dentro da faixa, então terminar em "hoje" esconderia
+ # um fechamento que vai atĂ© o fim do mĂȘs. Mesma regra do relatĂłrio do admin.
+ def periodo_totais
+ inicio = parse_data(params[:inicio]) || Date.current.beginning_of_month
+ fim = parse_data(params[:fim]) || Date.current.end_of_month
+ inicio, fim = fim, inicio if fim < inicio
+ [inicio, fim]
+ end
+
+ def parse_data(str)
+ return nil if str.blank?
+ Date.parse(str)
+ rescue ArgumentError, TypeError
+ nil
+ end
+
def garantir_motorista
return if current_user.motorista?
redirect_to dashboard_path, alert: 'Ărea exclusiva dos motoristas.'
diff --git a/app/services/analytics/totais_por_motorista.rb b/app/services/analytics/totais_por_motorista.rb
new file mode 100644
index 0000000..3112401
--- /dev/null
+++ b/app/services/analytics/totais_por_motorista.rb
@@ -0,0 +1,111 @@
+# app/services/analytics/totais_por_motorista.rb
+#
+# Quanto cada motorista somou no perĂodo â e QUAIS consolidaçÔes formam esse
+# total. Ă a fonte Ășnica das duas telas de "total por motorista":
+#
+# âą admin â /consolidacoes/totais (todos os motoristas + drill-down)
+# âą motoristaâ /motorista/totais (sĂł ele, mesma conta, mesmo PDF)
+#
+# RECORTE (igual ao filtro da lista de consolidaçÔes): entram as consolidaçÔes
+# ATIVAS cujo perĂodo estĂĄ DENTRO da faixa escolhida (data_inicio >= inicio e
+# data_fim <= fim). NĂŁo Ă© sobreposição de propĂłsito â uma consolidação que
+# atravessa a borda do filtro entraria inteira e o "total do perĂodo" ficaria
+# maior que o perĂodo.
+#
+# SĂł motoristas ATIVOS (nĂŁo arquivados) contam: Ă© o mesmo conjunto que a
+# consolidação paga (ConsolidacaoMotorista.ativos), então o total desta tela
+# bate com a soma dos fechamentos.
+module Analytics
+ class TotaisPorMotorista
+ def initialize(inicio:, fim:, motorista: nil, incluir_rascunhos: false)
+ @inicio = inicio.to_date
+ @fim = fim.to_date
+ @motorista = motorista.presence
+ @incluir_rascunhos = incluir_rascunhos
+ end
+
+ attr_reader :inicio, :fim, :motorista
+
+ # ConsolidacaoMotorista do recorte, com a consolidação jå carregada (as duas
+ # telas mostram nome/perĂodo/status dela em seguida).
+ def registros
+ @registros ||= begin
+ base = Consolidacao.ativas.no_periodo(@inicio, @fim)
+ # SĂł o que jĂĄ foi FECHADO conta como total do perĂodo; rascunho ainda
+ # muda de valor. `merge` (e nĂŁo where(consolidacoes: {status: ...}))
+ # porque status Ă© enum: quem sabe traduzir :finalizada Ă© o prĂłprio model.
+ base = base.finalizada unless @incluir_rascunhos
+
+ escopo = ConsolidacaoMotorista.ativos
+ .joins(:consolidacao)
+ .merge(base)
+ .includes(:consolidacao)
+ escopo = escopo.where(motorista_nome: @motorista) if @motorista
+ escopo.to_a
+ end
+ end
+
+ # Uma linha por motorista, do maior total para o menor.
+ def linhas
+ @linhas ||= registros.group_by(&:motorista_nome).map do |nome, cms|
+ pagos = cms.select(&:pago?)
+ pendentes = cms.reject(&:pago?)
+ {
+ motorista: nome,
+ consolidacoes: cms.size,
+ lancamentos: lancamentos_por_motorista[nome].to_i,
+ valor_total: soma(cms),
+ valor_pago: soma(pagos),
+ valor_pendente: soma(pendentes),
+ pagas: pagos.size,
+ pendentes: pendentes.size
+ }
+ end.sort_by { |l| -l[:valor_total] }
+ end
+
+ # ConsolidaçÔes que compÔem o total de UM motorista (o drill-down), da mais
+ # recente para a mais antiga.
+ def consolidacoes_de(nome)
+ registros.select { |cm| cm.motorista_nome == nome }
+ .sort_by { |cm| [cm.consolidacao.data_fim, cm.consolidacao.data_inicio] }
+ .reverse
+ end
+
+ def motoristas
+ linhas.map { |l| l[:motorista] }
+ end
+
+ def total_geral = soma(registros)
+ def total_pago = soma(registros.select(&:pago?))
+ def total_pendente = soma(registros.reject(&:pago?))
+ def total_consolidacoes = registros.map(&:consolidacao_id).uniq.size
+ def vazio? = registros.empty?
+
+ def periodo_label
+ "#{@inicio.strftime('%d/%m/%Y')} a #{@fim.strftime('%d/%m/%Y')}"
+ end
+
+ private
+
+ def soma(cms)
+ cms.sum { |cm| cm.valor_total || 0 }
+ end
+
+ # Lançamentos classificados por motorista (soma da quantidade â um termo com
+ # quantidade 5 vale 5). Uma query só para todas as consolidaçÔes do recorte,
+ # em vez de uma por linha da tabela.
+ def lancamentos_por_motorista
+ @lancamentos_por_motorista ||= begin
+ ids = registros.map(&:consolidacao_id).uniq
+ if ids.empty?
+ {}
+ else
+ ConsolidacaoEntrega.where(consolidacao_id: ids,
+ motorista_nome: registros.map(&:motorista_nome).uniq)
+ .group(:motorista_nome)
+ .sum(:quantidade)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/pdf/totais_motorista_pdf.rb b/app/services/pdf/totais_motorista_pdf.rb
new file mode 100644
index 0000000..c9e44b8
--- /dev/null
+++ b/app/services/pdf/totais_motorista_pdf.rb
@@ -0,0 +1,130 @@
+# app/services/pdf/totais_motorista_pdf.rb
+#
+# RelatĂłrio "total por motorista no perĂodo" â o mesmo documento serve Ă s duas
+# telas, porque os nĂșmeros vĂȘm do mesmo Analytics::TotaisPorMotorista:
+#
+# ⹠admin (todos os motoristas): resumo por motorista + as consolidaçÔes que
+# compÔem o total de cada um;
+# âą motorista (um sĂł): o total fechado dele no perĂodo + as consolidaçÔes que
+# formam esse valor, com espaço de assinatura para servir de comprovante.
+#
+module Pdf
+ class TotaisMotoristaPdf < BasePdf
+ VERDE = '22C55E'
+ AMBAR = 'D97706'
+
+ # totais: Analytics::TotaisPorMotorista jĂĄ montado (ele conhece o recorte).
+ # detalhar: inclui, para cada motorista, a lista de consolidaçÔes. Sempre
+ # ligado quando o relatĂłrio Ă© de um motorista sĂł.
+ def initialize(totais:, detalhar: true, titulo: nil)
+ super()
+ @t = totais
+ @detalhar = detalhar || @t.motorista.present?
+ @titulo = titulo
+ end
+
+ private
+
+ def corpo
+ secao(@titulo || (@t.motorista ? 'MEU TOTAL NO PERĂODO' : 'TOTAL POR MOTORISTA'))
+
+ @pdf.text "PerĂodo: #{@t.periodo_label}", size: 11, style: :bold
+ @pdf.fill_color CINZA
+ @pdf.text(@t.motorista ? "Motorista: #{@t.motorista}" : "Motoristas: #{@t.linhas.size}", size: 10)
+ @pdf.text "ConsolidaçÔes no perĂodo: #{@t.total_consolidacoes}", size: 10
+ @pdf.fill_color '000000'
+
+ secao 'TOTAIS'
+ kpis
+
+ if @t.vazio?
+ @pdf.fill_color CINZA
+ @pdf.text 'Nenhuma consolidação fechada neste perĂodo.', size: 10
+ @pdf.fill_color '000000'
+ return
+ end
+
+ unless @t.motorista
+ secao 'RESUMO POR MOTORISTA'
+ tabela_resumo
+ end
+
+ return unless @detalhar
+
+ @t.linhas.each do |linha|
+ @pdf.start_new_page if @pdf.cursor < 160
+ secao "COMPOSIĂĂO â #{linha[:motorista].upcase}"
+ tabela_composicao(@t.consolidacoes_de(linha[:motorista]))
+ end
+
+ assinaturas(motorista: @t.motorista) if @t.motorista
+ end
+
+ def kpis
+ linhas = [
+ ['Total do perĂodo', moeda(@t.total_geral)],
+ ['Pago', moeda(@t.total_pago)],
+ ['A receber', moeda(@t.total_pendente)]
+ ]
+ @pdf.table(linhas, width: 300, cell_style: { size: 11, padding: [6, 10] }) do |t|
+ t.columns(0).font_style = :bold
+ t.columns(1).align = :right
+ t.row(1).text_color = VERDE
+ t.row(2).text_color = AMBAR
+ t.cells.borders = [:bottom]
+ t.cells.border_color = 'DDDDDD'
+ end
+ end
+
+ def tabela_resumo
+ linhas = [['Motorista', 'Consol.', 'Lanç.', 'Pago', 'A receber', 'Total']]
+ @t.linhas.each do |l|
+ linhas << [l[:motorista], l[:consolidacoes].to_s, l[:lancamentos].to_s,
+ moeda(l[:valor_pago]), moeda(l[:valor_pendente]), moeda(l[:valor_total])]
+ end
+ linhas << ['TOTAL', @t.total_consolidacoes.to_s, '',
+ moeda(@t.total_pago), moeda(@t.total_pendente), moeda(@t.total_geral)]
+
+ @pdf.table(linhas, header: true, width: @pdf.bounds.width,
+ column_widths: { 1 => 50, 2 => 50, 3 => 80, 4 => 80, 5 => 85 },
+ cell_style: { size: 8, padding: [4, 6] }) do |t|
+ t.row(0).background_color = PRETO
+ t.row(0).text_color = 'FFFFFF'
+ t.row(0).font_style = :bold
+ t.columns(1..5).align = :right
+ t.row(-1).font_style = :bold
+ t.row(1..-1).borders = [:bottom]
+ t.row(1..-1).border_color = 'DDDDDD'
+ end
+ end
+
+ # As consolidaçÔes que compĂ”em o valor â Ă© o "clicar no motorista" da tela,
+ # em papel.
+ def tabela_composicao(cms)
+ linhas = [['Consolidação', 'PerĂodo', 'Situação', 'Pago em', 'Valor']]
+ cms.each do |cm|
+ c = cm.consolidacao
+ linhas << [
+ c.nome,
+ "#{c.data_inicio.strftime('%d/%m/%y')}â#{c.data_fim.strftime('%d/%m/%y')}",
+ cm.pago? ? 'Pago' : 'A receber',
+ cm.pago? ? cm.pago_em.strftime('%d/%m/%Y') : 'â',
+ moeda(cm.valor_total)
+ ]
+ end
+ linhas << ['TOTAL', '', '', '', moeda(cms.sum { |cm| cm.valor_total || 0 })]
+
+ @pdf.table(linhas, header: true, width: @pdf.bounds.width,
+ column_widths: { 1 => 90, 2 => 70, 3 => 70, 4 => 80 },
+ cell_style: { size: 8, padding: [4, 6] }) do |t|
+ t.row(0).background_color = PRETO
+ t.row(0).text_color = 'FFFFFF'
+ t.row(0).font_style = :bold
+ t.columns(4).align = :right
+ t.row(-1).font_style = :bold
+ t.row(1..-1).borders = [:bottom]
+ t.row(1..-1).border_color = 'DDDDDD'
+ end
+ end
+ end
+end
diff --git a/app/views/consolidacoes/index.html.erb b/app/views/consolidacoes/index.html.erb
index 47b5202..e8eaf82 100644
--- a/app/views/consolidacoes/index.html.erb
+++ b/app/views/consolidacoes/index.html.erb
@@ -6,6 +6,8 @@
<%= icone :consolidacoes, espaco: false %> ConsolidaçÔes
+ <%= link_to rotulo(:motorista, 'Totais por motorista', cor: nil), totais_consolidacoes_path,
+ class: 'text-gray-400 hover:text-white border border-[#2a2a2a] px-4 py-3 rounded-lg min-h-[48px] flex items-center transition-colors' %>
<%= link_to rotulo(:arquivadas, 'Arquivadas', cor: nil), arquivadas_consolidacoes_path,
class: 'text-gray-400 hover:text-white border border-[#2a2a2a] px-4 py-3 rounded-lg min-h-[48px] flex items-center transition-colors' %>
<% if policy(Consolidacao).create? %>
diff --git a/app/views/consolidacoes/totais.html.erb b/app/views/consolidacoes/totais.html.erb
new file mode 100644
index 0000000..2e5ad16
--- /dev/null
+++ b/app/views/consolidacoes/totais.html.erb
@@ -0,0 +1,191 @@
+<%# app/views/consolidacoes/totais.html.erb â Total por motorista no perĂodo %>
+<% content_for :title, 'Totais por motorista' %>
+
+
+
+
<%= icone :motorista, espaco: false %> Totais por motorista
+
+ ConsolidaçÔes <%= @incluir_rascunhos ? 'de qualquer status' : 'finalizadas' %>
+ com perĂodo dentro de <%= l @inicio, format: :short %> â <%= l @fim, format: :short %>
+
+
+
+ <%= link_to rotulo(:consolidacoes, 'ConsolidaçÔes', cor: nil), consolidacoes_path,
+ class: 'text-gray-400 hover:text-white border border-[#2a2a2a] px-4 py-3 rounded-lg min-h-[48px] flex items-center transition-colors' %>
+ <%= link_to rotulo(:baixar, 'Baixar relatĂłrio (PDF)', cor: nil),
+ totais_pdf_consolidacoes_path(inicio: @inicio, fim: @fim, rascunhos: (@incluir_rascunhos ? '1' : nil), motorista: @motorista),
+ data: { turbo: false },
+ class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold px-5 py-3 rounded-lg min-h-[48px] flex items-center transition-colors' %>
+
+
+
+<%# ââ Filtros ââââââââââââââââââââââââââââââââââââââââââââ %>
+<%= form_with url: totais_consolidacoes_path, method: :get,
+ class: 'bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-4 mb-6 flex flex-wrap items-center gap-3' do |f| %>
+ <%# O motorista clicado viaja junto para o filtro nĂŁo fechar o drill-down. %>
+ <%= hidden_field_tag :motorista, @motorista if @motorista %>
+
+ <%= f.date_field :inicio, value: @inicio.strftime('%Y-%m-%d'),
+ class: 'flex-1 basis-36 max-w-[13rem] min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
+ <%= f.date_field :fim, value: @fim.strftime('%Y-%m-%d'),
+ class: 'flex-1 basis-36 max-w-[13rem] min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
+
+
+ <%= check_box_tag :rascunhos, '1', @incluir_rascunhos, class: 'accent-orange-500' %>
+ Incluir rascunhos
+
+
+
+ <%= f.submit 'Filtrar', class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold rounded-lg cursor-pointer px-5 py-2.5 whitespace-nowrap' %>
+ <%= link_to 'Limpar', totais_consolidacoes_path, class: 'px-4 py-2.5 text-gray-400 hover:text-white border border-[#2a2a2a] rounded-lg whitespace-nowrap' %>
+
+<% end %>
+
+<%# ââ KPIs do perĂodo ââââââââââââââââââââââââââââââââââââ %>
+
+
+
<%= icone :dinheiro %> Total do perĂodo
+
<%= moeda(@totais.total_geral) %>
+
<%= @totais.total_consolidacoes %> consolidação(Ôes) · <%= @totais.linhas.size %> motorista(s)
+
+
+
<%= icone :sucesso, cor: 'text-green-400' %> Pago
+
<%= moeda(@totais.total_pago) %>
+
+
+
<%= icone :parcial, cor: 'text-amber-400' %> A pagar
+
<%= moeda(@totais.total_pendente) %>
+
+
+
<%= icone :ticket %> Média por motorista
+
+ <%= moeda(@totais.linhas.any? ? @totais.total_geral / @totais.linhas.size : 0) %>
+
+
+
+
+<% if @totais.vazio? %>
+
+
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
+
Nenhuma consolidação <%= @incluir_rascunhos ? '' : 'finalizada ' %>com perĂodo dentro dessa faixa.
+
Amplie o perĂodo ou marque "Incluir rascunhos".
+
+<% else %>
+ <%# ââ Tabela: total por motorista (clique abre a composição) ââ %>
+
+
+
+
+
+ Motorista
+ ConsolidaçÔes
+ Lançamentos
+ Pago
+ A pagar
+ Total
+
+
+
+ <% @totais.linhas.each do |l| %>
+ <% aberto = @motorista == l[:motorista] %>
+
+ <%# O nome Ă© um LINK de verdade (e nĂŁo um ): abre/fecha
+ a composição daquele motorista e continua acessĂvel pelo teclado. %>
+
+ <%= link_to totais_consolidacoes_path(inicio: @inicio, fim: @fim,
+ rascunhos: (@incluir_rascunhos ? '1' : nil),
+ motorista: (aberto ? nil : l[:motorista])),
+ class: 'text-white font-semibold hover:text-orange-500 flex items-center gap-1' do %>
+ <%= icone(aberto ? :expandir : :avancar, cor: 'text-gray-500', espaco: false) %><%= l[:motorista] %>
+ <% end %>
+
+ <%= l[:consolidacoes] %>
+ <%= l[:lancamentos] %>
+ <%= moeda(l[:valor_pago]) %>
+ <%= moeda(l[:valor_pendente]) %>
+ <%= moeda(l[:valor_total]) %>
+
+ <% end %>
+
+
+
+ TOTAL
+ <%= @totais.total_consolidacoes %>
+
+ <%= moeda(@totais.total_pago) %>
+ <%= moeda(@totais.total_pendente) %>
+ <%= moeda(@totais.total_geral) %>
+
+
+
+
+
+
+ <%= icone :dica %> Clique num motorista para ver as consolidaçÔes que compÔem o total dele.
+
+ <%# PDF sem a composição de cada motorista â para perĂodos com muita gente,
+ quando sĂł o resumo interessa. %>
+ <%= link_to 'Baixar sĂł o resumo (PDF)',
+ totais_pdf_consolidacoes_path(inicio: @inicio, fim: @fim, rascunhos: (@incluir_rascunhos ? '1' : nil), detalhar: '0'),
+ data: { turbo: false },
+ class: 'text-gray-400 hover:text-orange-500 text-xs underline whitespace-nowrap' %>
+
+
+
+ <%# ââ Drill-down: as consolidaçÔes que compĂ”em o total ââ %>
+ <% if @detalhe %>
+
+
+
+
+ <%= icone :composicao, espaco: false %> Composição de <%= @motorista %>
+
+
+ <%= @detalhe.size %> consolidação(Ă”es) no perĂodo ·
+ <%= moeda(@detalhe.sum { |cm| cm.valor_total || 0 }) %>
+
+
+
+ <%= link_to rotulo(:baixar, 'PDF deste motorista', cor: nil),
+ totais_pdf_consolidacoes_path(inicio: @inicio, fim: @fim, rascunhos: (@incluir_rascunhos ? '1' : nil), motorista: @motorista),
+ data: { turbo: false },
+ class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold px-4 py-2.5 rounded-lg flex items-center' %>
+ <%= link_to rotulo(:fechar, 'Fechar', cor: nil),
+ totais_consolidacoes_path(inicio: @inicio, fim: @fim, rascunhos: (@incluir_rascunhos ? '1' : nil)),
+ class: 'text-gray-400 hover:text-white border border-[#2a2a2a] px-4 py-2.5 rounded-lg flex items-center' %>
+
+
+
+ <% if @detalhe.empty? %>
+
Nenhuma consolidação desse motorista neste perĂodo.
+ <% end %>
+
+
+ <% @detalhe.each do |cm| %>
+ <% c = cm.consolidacao %>
+ <%= link_to consolidacao_path(c),
+ class: 'block bg-[#0a0a0a] border border-[#2a2a2a] hover:border-orange-500 rounded-xl p-4 transition-colors' do %>
+
+
+
+ <%= c.nome %>
+ <%= badge_status(c.status) %>
+ <%= badge_pagamento(cm.pago? ? :pago : :pendente) %>
+
+
+ <%= icone :calendario %> <%= l c.data_inicio, format: :short %> â <%= l c.data_fim, format: :short %>
+ <% if cm.pago? %>
+ · <%= icone :dinheiro, cor: 'text-green-400' %> Pago em <%= l cm.pago_em.to_date, format: :short %>
+ <% if cm.forma_pagamento.present? %>via <%= cm.forma_pagamento.humanize %><% end %>
+ <% if cm.nota_fiscal.present? %>· NF <%= cm.nota_fiscal %><% end %>
+ <% end %>
+
+
+
<%= moeda(cm.valor_total) %>
+
+ <% end %>
+ <% end %>
+
+
+ <% end %>
+<% end %>
diff --git a/app/views/layouts/_navbar.html.erb b/app/views/layouts/_navbar.html.erb
index 5e6e8f7..3a2e6f3 100644
--- a/app/views/layouts/_navbar.html.erb
+++ b/app/views/layouts/_navbar.html.erb
@@ -43,12 +43,14 @@
<%# ConsolidaçÔes â sĂł quem pode consolidar (exclui o externo) %>
<% if current_user.pode_consolidar? %>
<%= nav_link_to 'ConsolidaçÔes', consolidacoes_path, icon: :consolidacoes %>
+ <%= nav_link_to 'Totais por motorista', totais_consolidacoes_path, icon: :motorista %>
<%= nav_link_to 'Arquivadas', arquivadas_consolidacoes_path, icon: :arquivadas %>
<% end %>
<%# Motorista %>
<% if current_user.motorista? %>
<%= nav_link_to 'Meu Painel', motorista_dashboard_path, icon: :painel %>
+ <%= nav_link_to 'Meu total do perĂodo', motorista_totais_path, icon: :dinheiro %>
<% end %>
<%# Admin/Gerente â ConfiguraçÔes %>
diff --git a/app/views/motorista/dashboard/index.html.erb b/app/views/motorista/dashboard/index.html.erb
index af8d8d3..bdde49c 100644
--- a/app/views/motorista/dashboard/index.html.erb
+++ b/app/views/motorista/dashboard/index.html.erb
@@ -51,14 +51,18 @@
- <%# Card 2 â Valor Consolidado (BRANCO) %>
-
+ <%# Card 2 â Valor Consolidado (BRANCO). ClicĂĄvel: leva ao total POR PERĂODO,
+ com as consolidaçÔes que compĂ”em o valor e o PDF do perĂodo. %>
+ <%= link_to motorista_totais_path, class: 'block bg-white rounded-2xl p-6 hover:ring-2 hover:ring-orange-500 transition' do %>
<%= icone :sucesso, cor: 'text-green-600', espaco: false %> Valor fechado para pagamento
<%= moeda(@valor_consolidado) %>
Soma dos pagamentos jĂĄ confirmados pela empresa
-
+
+ <%= icone :avancar, cor: 'text-orange-600' %> Ver por perĂodo e baixar o PDF
+
+ <% end %>
<%# ââ Lista de consolidaçÔes ââââââââââââââââââââââââââââ %>
diff --git a/app/views/motorista/dashboard/totais.html.erb b/app/views/motorista/dashboard/totais.html.erb
new file mode 100644
index 0000000..c161ca1
--- /dev/null
+++ b/app/views/motorista/dashboard/totais.html.erb
@@ -0,0 +1,95 @@
+<%# app/views/motorista/dashboard/totais.html.erb â Meu total no perĂodo %>
+<% content_for :title, 'Meu total no perĂodo' %>
+
+
+
+
+
Meu total no perĂodo
+ <%= link_to rotulo(:voltar, 'Voltar', cor: nil), motorista_dashboard_path,
+ class: 'text-gray-300 hover:text-white border border-[#2a2a2a] px-4 py-3 rounded-xl min-h-[48px] flex items-center' %>
+
+
+ <%# ââ PerĂodo ââââââââââââââââââââââââââââââââââââââââââââ %>
+ <%= form_with url: motorista_totais_path, method: :get,
+ class: 'bg-[#1a1a1a] border border-[#2a2a2a] rounded-2xl p-4 mb-5 flex flex-wrap items-end gap-3' do |f| %>
+
+ De
+ <%= f.date_field :inicio, value: @inicio.strftime('%Y-%m-%d'),
+ class: 'w-full bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-xl px-3 py-3 text-base' %>
+
+
+ Até
+ <%= f.date_field :fim, value: @fim.strftime('%Y-%m-%d'),
+ class: 'w-full bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-xl px-3 py-3 text-base' %>
+
+ <%= f.submit 'Ver', class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold rounded-xl cursor-pointer px-6 py-3 min-h-[48px]' %>
+ <% end %>
+
+ <%# ââ Total fechado ââââââââââââââââââââââââââââââââââââââ %>
+
+
+ <%= icone :sucesso, cor: 'text-green-600', espaco: false %> Total fechado no perĂodo
+
+
<%= icone :calendario, cor: 'text-gray-500' %> <%= l @inicio, format: :short %> a <%= l @fim, format: :short %>
+
<%= moeda(@totais.total_geral) %>
+
+ <%= icone :dinheiro, cor: 'text-green-700' %> JĂĄ pago: <%= moeda(@totais.total_pago) %>
+ <%= icone :parcial, cor: 'text-amber-700' %> A receber: <%= moeda(@totais.total_pendente) %>
+
+
+ Soma de <%= @consolidacoes.size %> fechamento(s) da empresa neste perĂodo.
+
+
+
+ <%= link_to rotulo(:baixar, 'Baixar relatĂłrio em PDF', cor: nil),
+ motorista_totais_pdf_path(inicio: @inicio, fim: @fim),
+ data: { turbo: false },
+ class: 'w-full bg-orange-500 hover:bg-orange-600 text-black font-bold text-base px-6 py-4 rounded-xl min-h-[52px] flex items-center justify-center mb-6' %>
+
+ <%# ââ O que compĂ”e o valor âââââââââââââââââââââââââââââââ %>
+
<%= icone :composicao, espaco: false %> O que forma esse valor
+
Cada fechamento abaixo entra na soma acima.
+
+ <% if @consolidacoes.any? %>
+
+ <% @consolidacoes.each do |cm| %>
+ <% c = cm.consolidacao %>
+
+
+
+
+
<%= c.nome %>
+ <%= badge_pagamento(cm.pago? ? :pago : :pendente) %>
+
+
+ <%= icone :calendario %> <%= l c.data_inicio, format: :short %> a <%= l c.data_fim, format: :short %>
+
+
<%= moeda(cm.valor_total) %>
+ <% if cm.pago? %>
+
+ <%= icone :dinheiro, cor: 'text-green-400' %> Pago em <%= l cm.pago_em.to_date, format: :short %>
+ <% if cm.forma_pagamento.present? %>via <%= cm.forma_pagamento.humanize %><% end %>
+
+ <% if cm.nota_fiscal.present? %>
+
<%= icone :nota_fiscal %> Nota Fiscal: <%= cm.nota_fiscal %>
+ <% end %>
+ <% else %>
+
<%= icone :parcial, cor: 'text-amber-400' %> Ainda nĂŁo pago
+ <% end %>
+
+ <%= link_to rotulo(:baixar, 'Extrato deste fechamento', cor: nil),
+ gerar_pdf_extrato_consolidacao_path(c, motorista: current_user.nome),
+ data: { turbo: false },
+ class: 'w-full md:w-auto bg-[#0a0a0a] border border-[#2a2a2a] hover:border-orange-500 text-white font-bold text-base px-5 py-4 rounded-xl min-h-[52px] flex items-center justify-center text-center' %>
+
+
+ <% end %>
+
+ <% else %>
+
+
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
+
Nenhum fechamento neste perĂodo.
+
Escolha outro perĂodo acima â o fechamento aparece aqui quando a empresa finaliza a consolidação.
+
+ <% end %>
+
diff --git a/config/routes.rb b/config/routes.rb
index ec50104..4c90e09 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -27,6 +27,10 @@ Rails.application.routes.draw do
# Painel motorista
get '/motorista', to: 'motorista/dashboard#index', as: :motorista_dashboard
+ # Total fechado do motorista num perĂodo + as consolidaçÔes que compĂ”em o
+ # valor (mesma conta do relatĂłrio do admin) e o PDF do extrato do perĂodo.
+ get '/motorista/totais', to: 'motorista/dashboard#totais', as: :motorista_totais
+ get '/motorista/totais/pdf', to: 'motorista/dashboard#totais_pdf', as: :motorista_totais_pdf
# Login motorista â PIN de 4 dĂgitos + acesso via QR Code do extrato
get '/motorista/login', to: 'motorista/sessoes#new', as: :motorista_login
@@ -42,6 +46,12 @@ Rails.application.routes.draw do
get :buscar_nf # JSON â busca a NF na rastreio (nota avulsa)
post :avulsa # cria consolidação avulsa a partir de uma NF
get :arquivadas # HistĂłrico â consolidaçÔes arquivadas
+
+ # RelatĂłrio "total por motorista no perĂodo": soma o que cada motorista
+ # fechou na faixa e, ao clicar num deles (?motorista=), mostra as
+ # consolidaçÔes que compÔem o valor. `totais_pdf` é o mesmo relatório em PDF.
+ get :totais
+ get :totais_pdf
end
member do
diff --git a/spec/requests/totais_por_motorista_spec.rb b/spec/requests/totais_por_motorista_spec.rb
new file mode 100644
index 0000000..0f9b151
--- /dev/null
+++ b/spec/requests/totais_por_motorista_spec.rb
@@ -0,0 +1,90 @@
+require 'rails_helper'
+
+# Rotas do relatĂłrio "total por motorista": quem entra, o que a tela mostra e o
+# PDF. O drill-down (?motorista=) Ă© o que responde "de onde vem esse valor".
+RSpec.describe 'Totais por motorista', type: :request do
+ def consolidacao_finalizada(motorista:, valor:, nome: 'Fechamento Junho')
+ c = create(:consolidacao, nome: nome, status: :finalizada,
+ data_inicio: Date.new(2026, 6, 1), data_fim: Date.new(2026, 6, 30))
+ create(:consolidacao_entrega, consolidacao: c, motorista_nome: motorista, valor_aplicado: valor)
+ create(:consolidacao_motorista, consolidacao: c, motorista_nome: motorista)
+ c
+ end
+
+ let(:periodo) { { inicio: '2026-06-01', fim: '2026-06-30' } }
+
+ describe 'admin â /consolidacoes/totais' do
+ it 'exige login' do
+ get totais_consolidacoes_path
+ expect(response).to redirect_to(new_user_session_path)
+ end
+
+ it 'bloqueia motorista (Pundit)' do
+ sign_in create(:motorista)
+ get totais_consolidacoes_path
+ expect(response).to redirect_to(root_path)
+ end
+
+ it 'mostra o total do motorista no perĂodo' do
+ consolidacao_finalizada(motorista: 'Carlos', valor: 120.0)
+ sign_in create(:gerente)
+
+ get totais_consolidacoes_path(periodo)
+
+ expect(response).to have_http_status(:ok)
+ expect(response.body).to include('Carlos')
+ end
+
+ it 'abre a composição do motorista clicado' do
+ consolidacao_finalizada(motorista: 'Carlos', valor: 120.0, nome: 'Fechamento Junho')
+ sign_in create(:gerente)
+
+ get totais_consolidacoes_path(periodo.merge(motorista: 'Carlos'))
+
+ expect(response.body).to include('Composição de Carlos')
+ expect(response.body).to include('Fechamento Junho')
+ end
+
+ it 'gera o PDF' do
+ consolidacao_finalizada(motorista: 'Carlos', valor: 120.0)
+ sign_in create(:gerente)
+
+ get totais_pdf_consolidacoes_path(periodo)
+
+ expect(response).to have_http_status(:ok)
+ expect(response.media_type).to eq('application/pdf')
+ end
+ end
+
+ describe 'motorista â /motorista/totais' do
+ it 'mostra só o total dele e as consolidaçÔes que o compÔem' do
+ motorista = create(:motorista, nome: 'Carlos')
+ consolidacao_finalizada(motorista: 'Carlos', valor: 120.0, nome: 'Fechamento Junho')
+ consolidacao_finalizada(motorista: 'Pedro', valor: 999.0, nome: 'Fechamento do Pedro')
+ sign_in motorista
+
+ get motorista_totais_path(periodo)
+
+ expect(response).to have_http_status(:ok)
+ expect(response.body).to include('Fechamento Junho')
+ expect(response.body).not_to include('Fechamento do Pedro')
+ end
+
+ it 'gera o PDF do perĂodo' do
+ motorista = create(:motorista, nome: 'Carlos')
+ consolidacao_finalizada(motorista: 'Carlos', valor: 120.0)
+ sign_in motorista
+
+ get motorista_totais_pdf_path(periodo)
+
+ expect(response).to have_http_status(:ok)
+ expect(response.media_type).to eq('application/pdf')
+ end
+
+ it 'nĂŁo Ă© acessĂvel por quem nĂŁo Ă© motorista' do
+ sign_in create(:gerente)
+ get motorista_totais_path
+ expect(response).to redirect_to(dashboard_path)
+ end
+ end
+end
diff --git a/spec/services/analytics/totais_por_motorista_spec.rb b/spec/services/analytics/totais_por_motorista_spec.rb
new file mode 100644
index 0000000..7b2e245
--- /dev/null
+++ b/spec/services/analytics/totais_por_motorista_spec.rb
@@ -0,0 +1,92 @@
+require 'rails_helper'
+
+# Trava a conta do relatĂłrio "total por motorista" â a mesma que alimenta
+# /consolidacoes/totais (admin) e /motorista/totais (motorista). As duas telas
+# usam ESTE service justamente para nĂŁo poderem divergir.
+RSpec.describe Analytics::TotaisPorMotorista do
+ let(:inicio) { Date.new(2026, 6, 1) }
+ let(:fim) { Date.new(2026, 6, 30) }
+
+ # Uma consolidação com um motorista e `qtd` lançamentos de `valor` cada. As
+ # entregas vĂȘm ANTES do ConsolidacaoMotorista porque Ă© o before_save dele que
+ # calcula valor_total a partir delas.
+ def consolidar(motorista:, valor: 100.0, qtd: 1, status: :finalizada,
+ data_inicio: Date.new(2026, 6, 1), data_fim: Date.new(2026, 6, 30),
+ pago: false, arquivado: false)
+ c = create(:consolidacao, status: status, data_inicio: data_inicio, data_fim: data_fim)
+ qtd.times { create(:consolidacao_entrega, consolidacao: c, motorista_nome: motorista, valor_aplicado: valor) }
+ cm = create(:consolidacao_motorista, consolidacao: c, motorista_nome: motorista)
+ cm.update!(pago_em: Time.current, forma_pagamento: 'pix') if pago
+ cm.arquivar!(nil) if arquivado
+ [c, cm]
+ end
+
+ subject(:totais) { described_class.new(inicio: inicio, fim: fim) }
+
+ it 'soma por motorista sĂł as consolidaçÔes finalizadas do perĂodo' do
+ consolidar(motorista: 'Carlos', valor: 100.0, qtd: 2) # 200
+ consolidar(motorista: 'Carlos', valor: 50.0, qtd: 1) # 50
+ consolidar(motorista: 'Pedro', valor: 30.0, qtd: 1) # 30
+ consolidar(motorista: 'Carlos', valor: 999.0, status: :rascunho) # fora (rascunho)
+ consolidar(motorista: 'Carlos', valor: 999.0, # fora (perĂodo)
+ data_inicio: Date.new(2026, 7, 1), data_fim: Date.new(2026, 7, 31))
+
+ expect(totais.linhas.map { |l| [l[:motorista], l[:valor_total]] })
+ .to eq([['Carlos', 250.0], ['Pedro', 30.0]])
+ expect(totais.total_geral).to eq(280.0)
+ expect(totais.total_consolidacoes).to eq(3)
+ end
+
+ it 'inclui rascunhos quando pedido' do
+ consolidar(motorista: 'Carlos', valor: 100.0)
+ consolidar(motorista: 'Carlos', valor: 40.0, status: :rascunho)
+
+ com_rascunho = described_class.new(inicio: inicio, fim: fim, incluir_rascunhos: true)
+ expect(totais.total_geral).to eq(100.0)
+ expect(com_rascunho.total_geral).to eq(140.0)
+ end
+
+ it 'separa pago de a receber' do
+ consolidar(motorista: 'Carlos', valor: 100.0, pago: true)
+ consolidar(motorista: 'Carlos', valor: 60.0)
+
+ linha = totais.linhas.first
+ expect(linha[:valor_pago]).to eq(100.0)
+ expect(linha[:valor_pendente]).to eq(60.0)
+ expect(totais.total_pago).to eq(100.0)
+ expect(totais.total_pendente).to eq(60.0)
+ end
+
+ it 'ignora motorista arquivado (mesmo conjunto que a consolidação paga)' do
+ consolidar(motorista: 'Carlos', valor: 100.0)
+ consolidar(motorista: 'Carlos', valor: 70.0, arquivado: true)
+
+ expect(totais.total_geral).to eq(100.0)
+ end
+
+ it 'lista as consolidaçÔes que compÔem o total do motorista' do
+ c1, = consolidar(motorista: 'Carlos', valor: 100.0)
+ c2, = consolidar(motorista: 'Carlos', valor: 60.0, data_fim: Date.new(2026, 6, 15))
+ consolidar(motorista: 'Pedro', valor: 30.0)
+
+ composicao = totais.consolidacoes_de('Carlos')
+ expect(composicao.map(&:consolidacao_id)).to match_array([c1.id, c2.id])
+ expect(composicao.sum(&:valor_total)).to eq(160.0)
+ end
+
+ it 'recortado por motorista, sĂł enxerga o dele (visĂŁo do painel do motorista)' do
+ consolidar(motorista: 'Carlos', valor: 100.0)
+ consolidar(motorista: 'Pedro', valor: 30.0)
+
+ meu = described_class.new(inicio: inicio, fim: fim, motorista: 'Carlos')
+ expect(meu.total_geral).to eq(100.0)
+ expect(meu.linhas.size).to eq(1)
+ end
+
+ it 'perĂodo sem fechamento fica vazio (e nĂŁo quebra)' do
+ vazio = described_class.new(inicio: Date.new(2026, 1, 1), fim: Date.new(2026, 1, 31))
+ expect(vazio).to be_vazio
+ expect(vazio.total_geral).to eq(0)
+ expect(vazio.linhas).to eq([])
+ end
+end
From 7a8ec1bc165e01e03e99ca3cdb08eaf1d116f8e8c9df2b96b3c795ca38906cac Mon Sep 17 00:00:00 2001
From: victor
Date: Wed, 26 Aug 2026 15:15:44 -0300
Subject: [PATCH 13/40] =?UTF-8?q?Cria=C3=A7=C3=A3o=20da=20vis=C3=A3o=20ger?=
=?UTF-8?q?al=20da=20consolida=C3=A7=C3=A3o=20por=20motorista=20e=20Estili?=
=?UTF-8?q?za=C3=A7=C3=A3o?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/controllers/consolidacoes_controller.rb | 62 +++---
.../motorista/dashboard_controller.rb | 130 ++++++------
.../analytics/totais_por_motorista.rb | 31 +--
app/views/consolidacoes/_lista.html.erb | 38 ++++
.../consolidacoes/_por_motorista.html.erb | 128 ++++++++++++
app/views/consolidacoes/index.html.erb | 92 ++++-----
app/views/consolidacoes/totais.html.erb | 191 ------------------
app/views/layouts/_navbar.html.erb | 2 -
app/views/motorista/dashboard/index.html.erb | 174 ++++++++++------
app/views/motorista/dashboard/totais.html.erb | 95 ---------
config/routes.rb | 12 +-
spec/requests/motorista_dashboard_spec.rb | 6 +-
spec/requests/totais_por_motorista_spec.rb | 52 +++--
.../analytics/totais_por_motorista_spec.rb | 20 +-
14 files changed, 508 insertions(+), 525 deletions(-)
create mode 100644 app/views/consolidacoes/_lista.html.erb
create mode 100644 app/views/consolidacoes/_por_motorista.html.erb
delete mode 100644 app/views/consolidacoes/totais.html.erb
delete mode 100644 app/views/motorista/dashboard/totais.html.erb
diff --git a/app/controllers/consolidacoes_controller.rb b/app/controllers/consolidacoes_controller.rb
index 30a09fa..c988e79 100644
--- a/app/controllers/consolidacoes_controller.rb
+++ b/app/controllers/consolidacoes_controller.rb
@@ -13,10 +13,21 @@ class ConsolidacoesController < ApplicationController
# Formas de pagamento aceitas (o select da UI usa esta mesma lista).
FORMAS_PAGAMENTO = %w[pix transferencia dinheiro].freeze
- # GET /consolidacoes â lista com filtros
+ # GET /consolidacoes?visao=lista|motoristas â a MESMA tela em dois modos de
+ # visualização, com os mesmos filtros:
+ #
+ # lista â um card por CONSOLIDAĂĂO (o de sempre);
+ # motoristas â um card por MOTORISTA, com o total dele no perĂodo e, ao
+ # clicar, as consolidaçÔes que compÔem aquele valor.
+ #
+ # São duas leituras do mesmo recorte: o trabalho é organizado por consolidação,
+ # mas o pagamento Ă© por motorista â antes, para saber quanto um motorista tinha
+ # fechado no mĂȘs, era preciso abrir consolidação por consolidação e somar Ă mĂŁo.
def index
authorize Consolidacao
+ @visao = params[:visao] == 'motoristas' ? 'motoristas' : 'lista'
+
@consolidacoes = Consolidacao.ativas.recentes.includes(:criador)
# Filtros
@@ -38,42 +49,25 @@ class ConsolidacoesController < ApplicationController
if %w[pago pendente parcial].include?(params[:pagamento])
@consolidacoes = @consolidacoes.public_send("pagamento_#{params[:pagamento]}")
end
+
+ montar_totais if @visao == 'motoristas'
end
- # GET /consolidacoes/totais?inicio=&fim=&motorista=&rascunhos=1
- #
- # "Quanto cada motorista somou no perĂodo". A lista de consolidaçÔes mostra um
- # card por CONSOLIDAĂĂO, mas o pagamento Ă© por MOTORISTA: para saber quanto um
- # motorista fechou no mĂȘs era preciso abrir consolidação por consolidação e
- # somar Ă mĂŁo. Aqui o total vem pronto e, clicando no motorista (?motorista=),
- # a tela abre as consolidaçÔes que compÔem aquele valor.
- def totais
- authorize Consolidacao, :index?
-
- @inicio, @fim = periodo_totais
- @incluir_rascunhos = params[:rascunhos] == '1'
- @motorista = params[:motorista].presence
- @totais = Analytics::TotaisPorMotorista.new(
- inicio: @inicio, fim: @fim, incluir_rascunhos: @incluir_rascunhos
- )
- # Drill-down: as consolidaçÔes do motorista clicado (a tabela geral continua
- # na tela, entĂŁo o service Ă© montado sem recorte de motorista).
- @detalhe = @motorista ? @totais.consolidacoes_de(@motorista) : nil
- end
-
- # GET /consolidacoes/totais_pdf â o mesmo relatĂłrio em PDF. Com ?motorista=
- # sai só o extrato daquele motorista (com espaço de assinatura).
+ # GET /consolidacoes/totais_pdf â o modo "Por motorista" em PDF, com os mesmos
+ # filtros da tela. Com ?detalhe=NOME sai sĂł o extrato daquele motorista (com
+ # espaço de assinatura).
def totais_pdf
authorize Consolidacao, :index?
inicio, fim = periodo_totais
totais = Analytics::TotaisPorMotorista.new(
inicio: inicio, fim: fim,
- motorista: params[:motorista].presence,
- incluir_rascunhos: params[:rascunhos] == '1'
+ motorista: params[:detalhe].presence,
+ status: params[:status].presence || 'todos',
+ busca: params[:motorista].presence
)
- nome = params[:motorista].presence&.parameterize(separator: '_')
+ nome = params[:detalhe].presence&.parameterize(separator: '_')
arquivo = ['totais', nome, inicio.strftime('%Y%m%d'), fim.strftime('%Y%m%d')].compact.join('_')
send_data Pdf::TotaisMotoristaPdf.new(totais: totais, detalhar: params[:detalhar] != '0').render,
filename: "#{arquivo}.pdf",
@@ -490,6 +484,20 @@ class ConsolidacoesController < ApplicationController
private
+ # Modo "Por motorista": os totais saem do MESMO recorte da lista (perĂodo,
+ # status e busca por motorista), para as duas visÔes nunca contarem coisas
+ # diferentes. `detalhe` Ă© o motorista aberto (o clique na linha).
+ def montar_totais
+ @inicio, @fim = periodo_totais
+ @totais = Analytics::TotaisPorMotorista.new(
+ inicio: @inicio, fim: @fim,
+ status: params[:status].presence || 'todos',
+ busca: params[:motorista].presence
+ )
+ @detalhe_motorista = params[:detalhe].presence
+ @detalhe = @detalhe_motorista ? @totais.consolidacoes_de(@detalhe_motorista) : nil
+ end
+
# PerĂodo do relatĂłrio de totais. Default: o MĂS CORRENTE INTEIRO â o fim Ă© o
# Ășltimo dia do mĂȘs, nĂŁo "hoje", porque o recorte exige a consolidação inteira
# dentro da faixa (Consolidacao.no_periodo) e um fechamento que vai até 31/08
diff --git a/app/controllers/motorista/dashboard_controller.rb b/app/controllers/motorista/dashboard_controller.rb
index 86bb26c..828a024 100644
--- a/app/controllers/motorista/dashboard_controller.rb
+++ b/app/controllers/motorista/dashboard_controller.rb
@@ -3,47 +3,24 @@ module Motorista
class DashboardController < ApplicationController
before_action :garantir_motorista
- # GET /motorista
+ # GET /motorista?inicio=&fim=
+ #
+ # UMA tela sĂł: o motorista escolhe o perĂodo e vĂȘ, para aquele recorte, o que
+ # jĂĄ foi FECHADO pela empresa, o que ainda Ă© ESTIMATIVA e de onde vem cada
+ # centavo (os fechamentos que compÔem o total). Antes o total ficava numa
+ # tela e a composição em outra â e nenhuma das duas tinha perĂodo.
def index
nome = current_user.nome
- # Card 1 â Valor ESTIMADO (mĂȘs atual): entregas ATENDIDAS Ă preco_entrega.
- #
- # Atendida = o motorista FOI ao local e fechou a visita, com sucesso
- # ('completed') OU insucesso ('failed') â no insucesso o deslocamento
- # aconteceu e teve custo, entĂŁo a empresa paga os dois. Ă o MESMO recorte do
- # dashboard administrativo e da consolidação (ver Entrega::STATUS_ATENDIDO e
- # Entrega.contar_atendidas): usava `pagas` (sĂł concluĂdas) e o motorista via
- # um valor MENOR que o do fechamento, sem entender de onde vinha a diferença.
- #
- # â ïž O eixo Ă© o CHECKOUT (data real da visita), nĂŁo planned_date: falhada TEM
- # checkout, e uma entrega planejada em 31/07 mas fechada em 01/08 pertence a
- # agosto â igual ao que a consolidação considera elegĂvel.
- @estimado_inicio = Date.current.beginning_of_month
- @estimado_fim = Date.current
- atendidas = Entrega.atendidas.da_conta_gade
- .do_motorista(nome)
- .no_periodo_checkout(@estimado_inicio, @estimado_fim)
- @entregas_concluidas = atendidas.concluidas.count
- @entregas_falhadas = atendidas.falhadas.count
- @entregas_mes = @entregas_concluidas + @entregas_falhadas
- @valor_estimado = @entregas_mes * Configuracao.preco_entrega
+ @inicio, @fim = periodo_selecionado
+ @faixa_total = faixa_completa(nome) # para o atalho "Tudo"
- # Card 2 â Valor CONSOLIDADO: soma das consolidaçÔes finalizadas
- @minhas_consolidacoes = Consolidacao.ativas
- .joins(:consolidacao_motoristas)
- .where(consolidacao_motoristas: { motorista_nome: nome, arquivado_em: nil })
- .recentes
- .distinct
-
- @valor_consolidado = ConsolidacaoMotorista.ativos
- .joins(:consolidacao)
- .where(motorista_nome: nome,
- consolidacoes: { status: :finalizada, deleted_at: nil })
- .sum(:valor_total)
+ carregar_estimado(nome)
+ carregar_fechado(nome)
# Aviso in-app: pagamentos confirmados nos Ășltimos 7 dias (notificação na
- # própria pågina do motorista, além de WhatsApp/email).
+ # própria pågina do motorista, além de WhatsApp/email). Independe do
+ # perĂodo escolhido â Ă© um aviso, nĂŁo um relatĂłrio.
@pagamentos_recentes = ConsolidacaoMotorista
.joins(:consolidacao)
.where(motorista_nome: nome,
@@ -54,23 +31,11 @@ module Motorista
.includes(:consolidacao)
end
- # GET /motorista/totais?inicio=&fim=
- #
- # "Quanto eu fechei neste perĂodo e de onde vem esse valor". O card do painel
- # mostra sĂł o total acumulado; aqui o motorista escolhe o perĂodo, vĂȘ o total
- # FECHADO (consolidaçÔes finalizadas) e a lista de consolidaçÔes que compÔem
- # o valor â a mesma conta que o admin vĂȘ em /consolidacoes/totais, montada
- # pelo mesmo service para os dois nĂșmeros nĂŁo poderem divergir.
- def totais
- @inicio, @fim = periodo_totais
- @totais = totais_do_periodo
- @consolidacoes = @totais.consolidacoes_de(current_user.nome)
- end
-
- # GET /motorista/totais/pdf â o extrato do perĂodo em PDF (com assinatura),
- # para o motorista guardar ou mandar por WhatsApp.
+ # GET /motorista/totais/pdf?inicio=&fim= â o extrato do perĂodo (total,
+ # composição e assinaturas), para guardar ou mandar por WhatsApp. Mesmo
+ # service da tela, entĂŁo o papel e o painel nunca divergem.
def totais_pdf
- inicio, fim = periodo_totais
+ inicio, fim = periodo_selecionado
totais = Analytics::TotaisPorMotorista.new(
inicio: inicio, fim: fim, motorista: current_user.nome
)
@@ -85,16 +50,65 @@ module Motorista
private
- def totais_do_periodo
- Analytics::TotaisPorMotorista.new(
- inicio: @inicio, fim: @fim, motorista: current_user.nome
- )
+ # Card ESTIMADO: entregas ATENDIDAS Ă preco_entrega dentro do perĂodo.
+ #
+ # Atendida = o motorista FOI ao local e fechou a visita, com sucesso
+ # ('completed') OU insucesso ('failed') â no insucesso o deslocamento
+ # aconteceu e teve custo, entĂŁo a empresa paga os dois. Ă o MESMO recorte do
+ # dashboard administrativo e da consolidação (ver Entrega::STATUS_ATENDIDO e
+ # Entrega.contar_atendidas): usava `pagas` (sĂł concluĂdas) e o motorista via
+ # um valor MENOR que o do fechamento, sem entender de onde vinha a diferença.
+ #
+ # â ïž O eixo Ă© o CHECKOUT (data real da visita), nĂŁo planned_date: falhada TEM
+ # checkout, e uma entrega planejada em 31/07 mas fechada em 01/08 pertence a
+ # agosto â igual ao que a consolidação considera elegĂvel.
+ #
+ # O fim Ă© limitado a HOJE: estimar entregas de dias que ainda nĂŁo
+ # aconteceram nĂŁo faz sentido (o perĂodo pode ir atĂ© o fim do mĂȘs).
+ def carregar_estimado(nome)
+ @estimado_inicio = @inicio
+ @estimado_fim = [@fim, Date.current].min
+
+ if @estimado_fim < @estimado_inicio
+ @entregas_concluidas = @entregas_falhadas = @entregas_mes = 0
+ @valor_estimado = 0
+ return
+ end
+
+ atendidas = Entrega.atendidas.da_conta_gade
+ .do_motorista(nome)
+ .no_periodo_checkout(@estimado_inicio, @estimado_fim)
+ @entregas_concluidas = atendidas.concluidas.count
+ @entregas_falhadas = atendidas.falhadas.count
+ @entregas_mes = @entregas_concluidas + @entregas_falhadas
+ @valor_estimado = @entregas_mes * Configuracao.preco_entrega
end
- # Default: mĂȘs corrente INTEIRO (fim = Ășltimo dia do mĂȘs). O recorte pede a
+ # Card FECHADO + composição: o mesmo service do modo "Por motorista" do
+ # admin, recortado neste motorista â os dois lados mostram o mesmo nĂșmero.
+ def carregar_fechado(nome)
+ @totais = Analytics::TotaisPorMotorista.new(inicio: @inicio, fim: @fim, motorista: nome)
+ @consolidacoes = @totais.consolidacoes_de(nome)
+ @valor_consolidado = @totais.total_geral
+ end
+
+ # Primeira e Ășltima data de fechamento do motorista â alimenta o atalho
+ # "Tudo" com datas reais em vez de uma faixa inventada.
+ def faixa_completa(nome)
+ escopo = Consolidacao.ativas.finalizada
+ .joins(:consolidacao_motoristas)
+ .where(consolidacao_motoristas: { motorista_nome: nome, arquivado_em: nil })
+ inicio = escopo.minimum(:data_inicio)
+ fim = escopo.maximum(:data_fim)
+ return nil if inicio.nil? || fim.nil?
+
+ [inicio.to_date, fim.to_date]
+ end
+
+ # Default: mĂȘs corrente INTEIRO (fim = Ășltimo dia do mĂȘs). O recorte exige a
# consolidação inteira dentro da faixa, então terminar em "hoje" esconderia
- # um fechamento que vai atĂ© o fim do mĂȘs. Mesma regra do relatĂłrio do admin.
- def periodo_totais
+ # um fechamento que vai atĂ© o fim do mĂȘs. Mesma regra do lado do admin.
+ def periodo_selecionado
inicio = parse_data(params[:inicio]) || Date.current.beginning_of_month
fim = parse_data(params[:fim]) || Date.current.end_of_month
inicio, fim = fim, inicio if fim < inicio
diff --git a/app/services/analytics/totais_por_motorista.rb b/app/services/analytics/totais_por_motorista.rb
index 3112401..dc1b1ec 100644
--- a/app/services/analytics/totais_por_motorista.rb
+++ b/app/services/analytics/totais_por_motorista.rb
@@ -3,8 +3,9 @@
# Quanto cada motorista somou no perĂodo â e QUAIS consolidaçÔes formam esse
# total. Ă a fonte Ășnica das duas telas de "total por motorista":
#
-# âą admin â /consolidacoes/totais (todos os motoristas + drill-down)
-# âą motoristaâ /motorista/totais (sĂł ele, mesma conta, mesmo PDF)
+# âą admin â /consolidacoes?visao=motoristas (modo de visualização da prĂłpria
+# lista, com drill-down no motorista clicado)
+# âą motoristaâ /motorista (o painel, jĂĄ recortado nele â mesma conta, mesmo PDF)
#
# RECORTE (igual ao filtro da lista de consolidaçÔes): entram as consolidaçÔes
# ATIVAS cujo perĂodo estĂĄ DENTRO da faixa escolhida (data_inicio >= inicio e
@@ -17,30 +18,36 @@
# bate com a soma dos fechamentos.
module Analytics
class TotaisPorMotorista
- def initialize(inicio:, fim:, motorista: nil, incluir_rascunhos: false)
- @inicio = inicio.to_date
- @fim = fim.to_date
- @motorista = motorista.presence
- @incluir_rascunhos = incluir_rascunhos
+ # status: 'finalizada' (padrĂŁo â sĂł o que jĂĄ fechou), 'rascunho' ou
+ # 'todos'. à o MESMO select de status da lista de consolidaçÔes.
+ # motorista: recorte exato (drill-down / painel do motorista).
+ # busca: filtro parcial por nome, o campo de busca da lista.
+ def initialize(inicio:, fim:, motorista: nil, status: 'finalizada', busca: nil)
+ @inicio = inicio.to_date
+ @fim = fim.to_date
+ @motorista = motorista.presence
+ @status = status.presence || 'finalizada'
+ @busca = busca.presence
end
- attr_reader :inicio, :fim, :motorista
+ attr_reader :inicio, :fim, :motorista, :status
# ConsolidacaoMotorista do recorte, com a consolidação jå carregada (as duas
# telas mostram nome/perĂodo/status dela em seguida).
def registros
@registros ||= begin
base = Consolidacao.ativas.no_periodo(@inicio, @fim)
- # SĂł o que jĂĄ foi FECHADO conta como total do perĂodo; rascunho ainda
- # muda de valor. `merge` (e nĂŁo where(consolidacoes: {status: ...}))
- # porque status Ă© enum: quem sabe traduzir :finalizada Ă© o prĂłprio model.
- base = base.finalizada unless @incluir_rascunhos
+ # Por padrĂŁo sĂł o que jĂĄ foi FECHADO conta como total do perĂodo â
+ # rascunho ainda muda de valor. `merge` (e nĂŁo where(consolidacoes:
+ # {status: ...})) porque status Ă© enum: quem traduz :finalizada Ă© o model.
+ base = base.public_send(@status) if %w[finalizada rascunho].include?(@status)
escopo = ConsolidacaoMotorista.ativos
.joins(:consolidacao)
.merge(base)
.includes(:consolidacao)
escopo = escopo.where(motorista_nome: @motorista) if @motorista
+ escopo = escopo.where('motorista_nome ILIKE ?', "%#{@busca}%") if @busca
escopo.to_a
end
end
diff --git a/app/views/consolidacoes/_lista.html.erb b/app/views/consolidacoes/_lista.html.erb
new file mode 100644
index 0000000..a4c4307
--- /dev/null
+++ b/app/views/consolidacoes/_lista.html.erb
@@ -0,0 +1,38 @@
+<%# app/views/consolidacoes/_lista.html.erb â visĂŁo por CONSOLIDAĂĂO %>
+<% if @consolidacoes.any? %>
+
+ <% @consolidacoes.each do |c| %>
+ <%= link_to consolidacao_path(c),
+ class: 'block bg-[#1a1a1a] border border-[#2a2a2a] hover:border-orange-500 rounded-xl p-5 transition-colors' do %>
+
+
+
+
<%= c.nome %>
+ <%= badge_status(c.status) %>
+ <% if c.finalizada? %><%= badge_pagamento(c.status_pagamento) %><% end %>
+
+
+ <%= icone :calendario %> <%= l c.data_inicio, format: :short %> â <%= l c.data_fim, format: :short %>
+ · <%= icone :pessoa %> <%= c.consolidacao_motoristas.count %> motorista(s)
+ · Criada por <%= c.criador.nome_display %>
+
+ <% if c.operacoes.present? %>
+
<%= icone :pasta %> <%= c.operacoes_label.join(' · ') %>
+ <% end %>
+
+
+
<%= moeda(c.valor_total) %>
+
<%= c.consolidacao_entregas.count %> entregas classificadas
+
+
+ <% end %>
+ <% end %>
+
+<% else %>
+
+
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
+
Nenhuma consolidação encontrada.
+ <%= link_to 'Criar primeira consolidação', new_consolidacao_path,
+ class: 'inline-block bg-orange-500 hover:bg-orange-600 text-black font-bold px-5 py-3 rounded-lg' %>
+
+<% end %>
diff --git a/app/views/consolidacoes/_por_motorista.html.erb b/app/views/consolidacoes/_por_motorista.html.erb
new file mode 100644
index 0000000..27408cd
--- /dev/null
+++ b/app/views/consolidacoes/_por_motorista.html.erb
@@ -0,0 +1,128 @@
+<%# app/views/consolidacoes/_por_motorista.html.erb â visĂŁo por MOTORISTA
+ Mesmo recorte da lista, somado por quem recebe. Clicar num motorista abre,
+ ali mesmo (sem sair da tela), as consolidaçÔes que compÔem o valor dele. %>
+
+<%# Link desta mesma tela preservando filtros â usado pelas linhas e pelos PDFs. %>
+<% ctx = { nome: params[:nome], motorista: params[:motorista], status: params[:status],
+ inicio: @inicio.strftime('%Y-%m-%d'), fim: @fim.strftime('%Y-%m-%d'),
+ visao: 'motoristas' }.compact_blank %>
+
+<%# ââ Resumo do perĂodo ââââââââââââââââââââââââââââââââââ %>
+
+
+
<%= icone :dinheiro %> Total do perĂodo
+
<%= moeda(@totais.total_geral) %>
+
<%= @totais.total_consolidacoes %> consolidação(Ôes) · <%= @totais.linhas.size %> motorista(s)
+
+
+
<%= icone :sucesso, cor: 'text-green-400' %> Pago
+
<%= moeda(@totais.total_pago) %>
+
+
+
<%= icone :parcial, cor: 'text-amber-400' %> A pagar
+
<%= moeda(@totais.total_pendente) %>
+
+
+
<%= icone :documento %> RelatĂłrio
+
+ <%= link_to rotulo(:baixar, 'PDF completo', cor: nil),
+ totais_pdf_consolidacoes_path(ctx.except(:visao)),
+ data: { turbo: false },
+ class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold text-sm px-3 py-2 rounded-lg flex items-center justify-center' %>
+ <%= link_to 'SĂł o resumo',
+ totais_pdf_consolidacoes_path(ctx.except(:visao).merge(detalhar: '0')),
+ data: { turbo: false },
+ class: 'text-gray-400 hover:text-orange-500 text-xs underline text-center' %>
+
+
+
+
+<% if @totais.vazio? %>
+
+
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
+
Nenhuma consolidação com perĂodo dentro de <%= l @inicio, format: :short %> â <%= l @fim, format: :short %>.
+
Ajuste as datas acima â o recorte pega a consolidação inteira dentro da faixa.
+
+<% else %>
+
+ <% @totais.linhas.each do |linha| %>
+ <% aberto = @detalhe_motorista == linha[:motorista] %>
+
+
+ <%# Cabeçalho do motorista â o clique abre/fecha a composição %>
+ <%= link_to consolidacoes_path(ctx.merge(detalhe: (aberto ? nil : linha[:motorista])).compact_blank),
+ class: 'block p-5' do %>
+
+
+
+ <%= icone(aberto ? :expandir : :avancar, cor: 'text-gray-500', espaco: false) %>
+
<%= linha[:motorista] %>
+
+
+ <%= icone :consolidacoes %> <%= linha[:consolidacoes] %> consolidação(Ôes)
+ · <%= icone :caminhao %> <%= linha[:lancamentos] %> lançamentos
+ <% if linha[:pagas].positive? %>· <%= linha[:pagas] %> paga(s) <% end %>
+ <% if linha[:pendentes].positive? %>· <%= linha[:pendentes] %> a pagar <% end %>
+
+
+
+
<%= moeda(linha[:valor_total]) %>
+
+ <%= moeda(linha[:valor_pago]) %> pago
+ ·
+ <%= moeda(linha[:valor_pendente]) %> a pagar
+
+
+
+ <% end %>
+
+ <%# Composição â o que forma o valor daquele motorista %>
+ <% if aberto %>
+
+
+
+ <%= icone :composicao, espaco: false %> O que forma esse valor
+
+ <%= link_to rotulo(:baixar, 'PDF deste motorista', cor: nil),
+ totais_pdf_consolidacoes_path(ctx.except(:visao).merge(detalhe: linha[:motorista])),
+ data: { turbo: false },
+ class: 'text-gray-300 hover:text-orange-500 text-xs border border-[#2a2a2a] hover:border-orange-500 rounded-lg px-3 py-2 flex items-center' %>
+
+
+
+ <% @detalhe.each do |cm| %>
+ <% c = cm.consolidacao %>
+ <%= link_to consolidacao_path(c),
+ class: 'flex flex-col md:flex-row md:items-center md:justify-between gap-2 bg-[#1a1a1a] border border-[#2a2a2a] hover:border-orange-500 rounded-lg px-4 py-3 transition-colors' do %>
+
+
+ <%= c.nome %>
+ <%= badge_status(c.status) %>
+ <%= badge_pagamento(cm.pago? ? :pago : :pendente) %>
+
+
+ <%= icone :calendario %> <%= l c.data_inicio, format: :short %> â <%= l c.data_fim, format: :short %>
+ <% if cm.pago? %>
+ · <%= icone :dinheiro, cor: 'text-green-400' %> Pago em <%= l cm.pago_em.to_date, format: :short %>
+ <% if cm.forma_pagamento.present? %>via <%= cm.forma_pagamento.humanize %><% end %>
+ <% if cm.nota_fiscal.present? %>· NF <%= cm.nota_fiscal %><% end %>
+ <% end %>
+
+
+
<%= moeda(cm.valor_total) %>
+ <% end %>
+ <% end %>
+
+
+ <% end %>
+
+ <% end %>
+
+
+
+ <%= icone :dica %> Entram as consolidaçÔes
+ <%= params[:status].presence ? "com status #{params[:status]}" : 'de qualquer status' %>
+ cujo perĂodo estĂĄ dentro de <%= l @inicio, format: :short %> â <%= l @fim, format: :short %>.
+ Motoristas arquivados na consolidação não entram no total.
+
+<% end %>
diff --git a/app/views/consolidacoes/index.html.erb b/app/views/consolidacoes/index.html.erb
index e8eaf82..0dfb198 100644
--- a/app/views/consolidacoes/index.html.erb
+++ b/app/views/consolidacoes/index.html.erb
@@ -6,8 +6,6 @@
<%= icone :consolidacoes, espaco: false %> ConsolidaçÔes
- <%= link_to rotulo(:motorista, 'Totais por motorista', cor: nil), totais_consolidacoes_path,
- class: 'text-gray-400 hover:text-white border border-[#2a2a2a] px-4 py-3 rounded-lg min-h-[48px] flex items-center transition-colors' %>
<%= link_to rotulo(:arquivadas, 'Arquivadas', cor: nil), arquivadas_consolidacoes_path,
class: 'text-gray-400 hover:text-white border border-[#2a2a2a] px-4 py-3 rounded-lg min-h-[48px] flex items-center transition-colors' %>
<% if policy(Consolidacao).create? %>
@@ -72,6 +70,25 @@
<% end %>
+<%# ââ Modo de visualização ââââââââââââââââââââââââââââââ
+ A mesma tela lida de dois jeitos: por CONSOLIDAĂĂO (como o trabalho Ă©
+ organizado) e por MOTORISTA (como o pagamento sai). Os filtros abaixo valem
+ para os dois â por isso eles viajam junto no link da aba. %>
+<% ctx_filtros = { nome: params[:nome], motorista: params[:motorista], status: params[:status],
+ pagamento: params[:pagamento], vehicle: params[:vehicle],
+ inicio: params[:inicio], fim: params[:fim] }.compact_blank %>
+
+ <% [['lista', :consolidacoes, 'ConsolidaçÔes'], ['motoristas', :motorista, 'Por motorista']].each do |modo, ic, label| %>
+ <% ativo = @visao == modo %>
+ <%= link_to rotulo(ic, label, cor: (ativo ? nil : 'text-brand-laranja')),
+ consolidacoes_path(ctx_filtros.merge(visao: modo)),
+ class: "px-4 py-2.5 rounded-xl text-sm font-semibold whitespace-nowrap #{ativo ? 'bg-orange-500 text-black' : 'bg-[#1a1a1a] text-gray-300 border border-[#2a2a2a] hover:border-orange-500'}" %>
+ <% end %>
+
+ <%= @visao == 'motoristas' ? 'Quanto cada motorista fechou no perĂodo â clique num deles para ver de onde vem o valor' : 'Uma linha por consolidação' %>
+
+
+
<%# ââ Filtros ââââââââââââââââââââââââââââââââââââââââââââ %>
<%# Filtros em flex-wrap, NĂO em grade de 6 colunas fixas.
Motivo: os breakpoints do Tailwind (md:, lg:âŠ) enxergam a JANELA, mas o
@@ -82,66 +99,49 @@
tamanho legĂvel e a linha quebra sozinha conforme o espaço REAL. %>
<%= form_with url: consolidacoes_path, method: :get,
class: 'bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-4 mb-6 flex flex-wrap items-center gap-3' do |f| %>
- <%= f.text_field :nome, value: params[:nome], placeholder: 'Buscar por nomeâŠ',
- class: 'flex-1 basis-48 min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5 focus:border-orange-500 focus:outline-none' %>
+ <%# O modo de visualização é mantido ao filtrar (senão filtrar jogava o
+ operador de volta para a lista). %>
+ <%= hidden_field_tag :visao, @visao %>
+
+ <% if @visao == 'motoristas' %>
+ <%= f.text_field :motorista, value: params[:motorista], placeholder: 'Buscar motoristaâŠ',
+ class: 'flex-1 basis-48 min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5 focus:border-orange-500 focus:outline-none' %>
+ <% else %>
+ <%= f.text_field :nome, value: params[:nome], placeholder: 'Buscar por nomeâŠ',
+ class: 'flex-1 basis-48 min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5 focus:border-orange-500 focus:outline-none' %>
+ <% end %>
<%= f.select :status,
options_for_select([['Todos os status', ''], ['Rascunho', 'rascunho'], ['Finalizada', 'finalizada']], params[:status]),
{}, class: 'flex-1 basis-40 min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
- <%= f.select :pagamento,
- options_for_select([['Pagamento (todos)', ''], ['Pagas', 'pago'], ['Parciais', 'parcial'], ['Pendentes', 'pendente']], params[:pagamento]),
- {}, class: 'flex-1 basis-44 min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
+ <%# "Pagamento" Ă© status DA CONSOLIDAĂĂO (todos os motoristas dela); no modo
+ Por motorista cada linha jĂĄ mostra pago/a receber, entĂŁo o filtro sairia
+ confuso â some. %>
+ <% if @visao != 'motoristas' %>
+ <%= f.select :pagamento,
+ options_for_select([['Pagamento (todos)', ''], ['Pagas', 'pago'], ['Parciais', 'parcial'], ['Pendentes', 'pendente']], params[:pagamento]),
+ {}, class: 'flex-1 basis-44 min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
+ <% end %>
- <%= f.date_field :inicio, value: params[:inicio],
+ <%# No modo Por motorista o perĂodo SEMPRE vale (default: mĂȘs corrente
+ inteiro), entĂŁo os campos jĂĄ vĂȘm preenchidos com o que estĂĄ sendo somado. %>
+ <%= f.date_field :inicio, value: (@visao == 'motoristas' ? @inicio.strftime('%Y-%m-%d') : params[:inicio]),
class: 'flex-1 basis-36 max-w-[13rem] min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
- <%= f.date_field :fim, value: params[:fim],
+ <%= f.date_field :fim, value: (@visao == 'motoristas' ? @fim.strftime('%Y-%m-%d') : params[:fim]),
class: 'flex-1 basis-36 max-w-[13rem] min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
<%# shrink-0 + whitespace-nowrap: os botÔes nunca são espremidos a ponto de
cortar o texto â se faltar espaço, a linha quebra e eles descem inteiros. %>
<%= f.submit 'Filtrar', class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold rounded-lg cursor-pointer px-5 py-2.5 whitespace-nowrap' %>
- <%= link_to 'Limpar', consolidacoes_path, class: 'px-4 py-2.5 text-gray-400 hover:text-white border border-[#2a2a2a] rounded-lg whitespace-nowrap' %>
+ <%= link_to 'Limpar', consolidacoes_path(visao: @visao), class: 'px-4 py-2.5 text-gray-400 hover:text-white border border-[#2a2a2a] rounded-lg whitespace-nowrap' %>
<% end %>
-<%# ââ Lista ââââââââââââââââââââââââââââââââââââââââââââââ %>
-<% if @consolidacoes.any? %>
-
- <% @consolidacoes.each do |c| %>
- <%= link_to consolidacao_path(c),
- class: 'block bg-[#1a1a1a] border border-[#2a2a2a] hover:border-orange-500 rounded-xl p-5 transition-colors' do %>
-
-
-
-
<%= c.nome %>
- <%= badge_status(c.status) %>
- <% if c.finalizada? %><%= badge_pagamento(c.status_pagamento) %><% end %>
-
-
- <%= icone :calendario %> <%= l c.data_inicio, format: :short %> â <%= l c.data_fim, format: :short %>
- · <%= icone :pessoa %> <%= c.consolidacao_motoristas.count %> motorista(s)
- · Criada por <%= c.criador.nome_display %>
-
- <% if c.operacoes.present? %>
-
<%= icone :pasta %> <%= c.operacoes_label.join(' · ') %>
- <% end %>
-
-
-
<%= moeda(c.valor_total) %>
-
<%= c.consolidacao_entregas.count %> entregas classificadas
-
-
- <% end %>
- <% end %>
-
+<% if @visao == 'motoristas' %>
+ <%= render 'por_motorista' %>
<% else %>
-
-
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
-
Nenhuma consolidação encontrada.
- <%= link_to 'Criar primeira consolidação', new_consolidacao_path,
- class: 'inline-block bg-orange-500 hover:bg-orange-600 text-black font-bold px-5 py-3 rounded-lg' %>
-
+ <%= render 'lista' %>
<% end %>
diff --git a/app/views/consolidacoes/totais.html.erb b/app/views/consolidacoes/totais.html.erb
deleted file mode 100644
index 2e5ad16..0000000
--- a/app/views/consolidacoes/totais.html.erb
+++ /dev/null
@@ -1,191 +0,0 @@
-<%# app/views/consolidacoes/totais.html.erb â Total por motorista no perĂodo %>
-<% content_for :title, 'Totais por motorista' %>
-
-
-
-
<%= icone :motorista, espaco: false %> Totais por motorista
-
- ConsolidaçÔes <%= @incluir_rascunhos ? 'de qualquer status' : 'finalizadas' %>
- com perĂodo dentro de <%= l @inicio, format: :short %> â <%= l @fim, format: :short %>
-
-
-
- <%= link_to rotulo(:consolidacoes, 'ConsolidaçÔes', cor: nil), consolidacoes_path,
- class: 'text-gray-400 hover:text-white border border-[#2a2a2a] px-4 py-3 rounded-lg min-h-[48px] flex items-center transition-colors' %>
- <%= link_to rotulo(:baixar, 'Baixar relatĂłrio (PDF)', cor: nil),
- totais_pdf_consolidacoes_path(inicio: @inicio, fim: @fim, rascunhos: (@incluir_rascunhos ? '1' : nil), motorista: @motorista),
- data: { turbo: false },
- class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold px-5 py-3 rounded-lg min-h-[48px] flex items-center transition-colors' %>
-
-
-
-<%# ââ Filtros ââââââââââââââââââââââââââââââââââââââââââââ %>
-<%= form_with url: totais_consolidacoes_path, method: :get,
- class: 'bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-4 mb-6 flex flex-wrap items-center gap-3' do |f| %>
- <%# O motorista clicado viaja junto para o filtro nĂŁo fechar o drill-down. %>
- <%= hidden_field_tag :motorista, @motorista if @motorista %>
-
- <%= f.date_field :inicio, value: @inicio.strftime('%Y-%m-%d'),
- class: 'flex-1 basis-36 max-w-[13rem] min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
- <%= f.date_field :fim, value: @fim.strftime('%Y-%m-%d'),
- class: 'flex-1 basis-36 max-w-[13rem] min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
-
-
- <%= check_box_tag :rascunhos, '1', @incluir_rascunhos, class: 'accent-orange-500' %>
- Incluir rascunhos
-
-
-
- <%= f.submit 'Filtrar', class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold rounded-lg cursor-pointer px-5 py-2.5 whitespace-nowrap' %>
- <%= link_to 'Limpar', totais_consolidacoes_path, class: 'px-4 py-2.5 text-gray-400 hover:text-white border border-[#2a2a2a] rounded-lg whitespace-nowrap' %>
-
-<% end %>
-
-<%# ââ KPIs do perĂodo ââââââââââââââââââââââââââââââââââââ %>
-
-
-
<%= icone :dinheiro %> Total do perĂodo
-
<%= moeda(@totais.total_geral) %>
-
<%= @totais.total_consolidacoes %> consolidação(Ôes) · <%= @totais.linhas.size %> motorista(s)
-
-
-
<%= icone :sucesso, cor: 'text-green-400' %> Pago
-
<%= moeda(@totais.total_pago) %>
-
-
-
<%= icone :parcial, cor: 'text-amber-400' %> A pagar
-
<%= moeda(@totais.total_pendente) %>
-
-
-
<%= icone :ticket %> Média por motorista
-
- <%= moeda(@totais.linhas.any? ? @totais.total_geral / @totais.linhas.size : 0) %>
-
-
-
-
-<% if @totais.vazio? %>
-
-
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
-
Nenhuma consolidação <%= @incluir_rascunhos ? '' : 'finalizada ' %>com perĂodo dentro dessa faixa.
-
Amplie o perĂodo ou marque "Incluir rascunhos".
-
-<% else %>
- <%# ââ Tabela: total por motorista (clique abre a composição) ââ %>
-
-
-
-
-
- Motorista
- ConsolidaçÔes
- Lançamentos
- Pago
- A pagar
- Total
-
-
-
- <% @totais.linhas.each do |l| %>
- <% aberto = @motorista == l[:motorista] %>
-
- <%# O nome Ă© um LINK de verdade (e nĂŁo um ): abre/fecha
- a composição daquele motorista e continua acessĂvel pelo teclado. %>
-
- <%= link_to totais_consolidacoes_path(inicio: @inicio, fim: @fim,
- rascunhos: (@incluir_rascunhos ? '1' : nil),
- motorista: (aberto ? nil : l[:motorista])),
- class: 'text-white font-semibold hover:text-orange-500 flex items-center gap-1' do %>
- <%= icone(aberto ? :expandir : :avancar, cor: 'text-gray-500', espaco: false) %><%= l[:motorista] %>
- <% end %>
-
- <%= l[:consolidacoes] %>
- <%= l[:lancamentos] %>
- <%= moeda(l[:valor_pago]) %>
- <%= moeda(l[:valor_pendente]) %>
- <%= moeda(l[:valor_total]) %>
-
- <% end %>
-
-
-
- TOTAL
- <%= @totais.total_consolidacoes %>
-
- <%= moeda(@totais.total_pago) %>
- <%= moeda(@totais.total_pendente) %>
- <%= moeda(@totais.total_geral) %>
-
-
-
-
-
-
- <%= icone :dica %> Clique num motorista para ver as consolidaçÔes que compÔem o total dele.
-
- <%# PDF sem a composição de cada motorista â para perĂodos com muita gente,
- quando sĂł o resumo interessa. %>
- <%= link_to 'Baixar sĂł o resumo (PDF)',
- totais_pdf_consolidacoes_path(inicio: @inicio, fim: @fim, rascunhos: (@incluir_rascunhos ? '1' : nil), detalhar: '0'),
- data: { turbo: false },
- class: 'text-gray-400 hover:text-orange-500 text-xs underline whitespace-nowrap' %>
-
-
-
- <%# ââ Drill-down: as consolidaçÔes que compĂ”em o total ââ %>
- <% if @detalhe %>
-
-
-
-
- <%= icone :composicao, espaco: false %> Composição de <%= @motorista %>
-
-
- <%= @detalhe.size %> consolidação(Ă”es) no perĂodo ·
- <%= moeda(@detalhe.sum { |cm| cm.valor_total || 0 }) %>
-
-
-
- <%= link_to rotulo(:baixar, 'PDF deste motorista', cor: nil),
- totais_pdf_consolidacoes_path(inicio: @inicio, fim: @fim, rascunhos: (@incluir_rascunhos ? '1' : nil), motorista: @motorista),
- data: { turbo: false },
- class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold px-4 py-2.5 rounded-lg flex items-center' %>
- <%= link_to rotulo(:fechar, 'Fechar', cor: nil),
- totais_consolidacoes_path(inicio: @inicio, fim: @fim, rascunhos: (@incluir_rascunhos ? '1' : nil)),
- class: 'text-gray-400 hover:text-white border border-[#2a2a2a] px-4 py-2.5 rounded-lg flex items-center' %>
-
-
-
- <% if @detalhe.empty? %>
-
Nenhuma consolidação desse motorista neste perĂodo.
- <% end %>
-
-
- <% @detalhe.each do |cm| %>
- <% c = cm.consolidacao %>
- <%= link_to consolidacao_path(c),
- class: 'block bg-[#0a0a0a] border border-[#2a2a2a] hover:border-orange-500 rounded-xl p-4 transition-colors' do %>
-
-
-
- <%= c.nome %>
- <%= badge_status(c.status) %>
- <%= badge_pagamento(cm.pago? ? :pago : :pendente) %>
-
-
- <%= icone :calendario %> <%= l c.data_inicio, format: :short %> â <%= l c.data_fim, format: :short %>
- <% if cm.pago? %>
- · <%= icone :dinheiro, cor: 'text-green-400' %> Pago em <%= l cm.pago_em.to_date, format: :short %>
- <% if cm.forma_pagamento.present? %>via <%= cm.forma_pagamento.humanize %><% end %>
- <% if cm.nota_fiscal.present? %>· NF <%= cm.nota_fiscal %><% end %>
- <% end %>
-
-
-
<%= moeda(cm.valor_total) %>
-
- <% end %>
- <% end %>
-
-
- <% end %>
-<% end %>
diff --git a/app/views/layouts/_navbar.html.erb b/app/views/layouts/_navbar.html.erb
index 3a2e6f3..5e6e8f7 100644
--- a/app/views/layouts/_navbar.html.erb
+++ b/app/views/layouts/_navbar.html.erb
@@ -43,14 +43,12 @@
<%# ConsolidaçÔes â sĂł quem pode consolidar (exclui o externo) %>
<% if current_user.pode_consolidar? %>
<%= nav_link_to 'ConsolidaçÔes', consolidacoes_path, icon: :consolidacoes %>
- <%= nav_link_to 'Totais por motorista', totais_consolidacoes_path, icon: :motorista %>
<%= nav_link_to 'Arquivadas', arquivadas_consolidacoes_path, icon: :arquivadas %>
<% end %>
<%# Motorista %>
<% if current_user.motorista? %>
<%= nav_link_to 'Meu Painel', motorista_dashboard_path, icon: :painel %>
- <%= nav_link_to 'Meu total do perĂodo', motorista_totais_path, icon: :dinheiro %>
<% end %>
<%# Admin/Gerente â ConfiguraçÔes %>
diff --git a/app/views/motorista/dashboard/index.html.erb b/app/views/motorista/dashboard/index.html.erb
index bdde49c..5aae57c 100644
--- a/app/views/motorista/dashboard/index.html.erb
+++ b/app/views/motorista/dashboard/index.html.erb
@@ -1,14 +1,23 @@
-<%# app/views/motorista/dashboard/index.html.erb â Painel do Motorista %>
+<%# app/views/motorista/dashboard/index.html.erb â Painel do Motorista
+ Uma tela sĂł: perĂodo â quanto jĂĄ estĂĄ FECHADO, quanto ainda Ă© ESTIMATIVA e
+ de onde vem cada valor. Feita para celular: alvos grandes, nĂșmero em
+ destaque, sem tabela que precise de zoom. %>
<% content_for :title, 'Meu Painel' %>
+<% mes = Date.current.beginning_of_month..Date.current.end_of_month %>
+<% mes_ant = (Date.current - 1.month).beginning_of_month..(Date.current - 1.month).end_of_month %>
+<% tres_meses = (Date.current - 2.months).beginning_of_month..Date.current.end_of_month %>
+<% atalhos = { 'Este mĂȘs' => mes, 'MĂȘs passado' => mes_ant, '3 meses' => tres_meses } %>
+<% atalhos['Tudo'] = (@faixa_total.first..@faixa_total.last) if @faixa_total %>
+
OlĂĄ, <%= current_user.nome.split.first %>!
-
Aqui estĂŁo seus valores de entregas.
+
Escolha o perĂodo e veja quanto vocĂȘ tem a receber.
<%# ââ Aviso de pagamento recĂ©m-confirmado âââââââââââââââââ %>
<% if @pagamentos_recentes.any? %>
-
+
<%= icone :sucesso, cor: nil, espaco: false %> Pagamento confirmado!
<% @pagamentos_recentes.each do |cm| %>
@@ -28,65 +37,112 @@
<% end %>
- <%# ââ 2 Cards grandes âââââââââââââââââââââââââââââââââââ %>
-
-
- <%# Card 1 â Valor Estimado (LARANJA) %>
-
-
<%= icone :dinheiro, cor: nil, espaco: false %> Valor estimado deste mĂȘs
-
<%= icone :calendario, cor: nil %> <%= l @estimado_inicio, format: :short %> a <%= l @estimado_fim, format: :short %>
-
<%= moeda(@valor_estimado) %>
- <%# "Atendidas" = concluĂdas + sem sucesso. O rĂłtulo NĂO pode voltar a dizer
- "feitas e confirmadas": o valor acima inclui as visitas sem sucesso, que
- a empresa também paga (o motorista foi ao local). Sem a segunda linha o
- motorista nĂŁo consegue conferir a conta. %>
-
- <%= @entregas_mes %> entregas atendidas
-
-
- <%= @entregas_concluidas %> entregues · <%= @entregas_falhadas %> sem sucesso (pagas também)
-
-
- <%= icone :alerta, cor: nil %> Valor aproximado. O valor certo sai quando a empresa fechar o pagamento.
-
+ <%# ââ PerĂodo âââââââââââââââââââââââââââââââââââââââââââ
+ Chips grandes para o caso comum e um "outro perĂodo" escondido no
+
(sem JS) para a data exata. %>
+
+
+ <% atalhos.each do |label, faixa| %>
+ <% ativo = @inicio == faixa.first && @fim == faixa.last %>
+ <%= link_to label,
+ motorista_dashboard_path(inicio: faixa.first.strftime('%Y-%m-%d'), fim: faixa.last.strftime('%Y-%m-%d')),
+ class: "px-4 py-3 rounded-xl text-base font-bold min-h-[48px] flex items-center #{ativo ? 'bg-orange-500 text-black' : 'bg-[#0a0a0a] text-gray-300 border border-[#2a2a2a]'}" %>
+ <% end %>
- <%# Card 2 â Valor Consolidado (BRANCO). ClicĂĄvel: leva ao total POR PERĂODO,
- com as consolidaçÔes que compĂ”em o valor e o PDF do perĂodo. %>
- <%= link_to motorista_totais_path, class: 'block bg-white rounded-2xl p-6 hover:ring-2 hover:ring-orange-500 transition' do %>
-
<%= icone :sucesso, cor: 'text-green-600', espaco: false %> Valor fechado para pagamento
-
<%= moeda(@valor_consolidado) %>
-
- Soma dos pagamentos jĂĄ confirmados pela empresa
-
-
- <%= icone :avancar, cor: 'text-orange-600' %> Ver por perĂodo e baixar o PDF
+ >
+ <%= icone :calendario %> Outro perĂodo
+ <%= form_with url: motorista_dashboard_path, method: :get, class: 'flex flex-wrap items-end gap-3 mt-2' do |f| %>
+
+ De
+ <%= f.date_field :inicio, value: @inicio.strftime('%Y-%m-%d'),
+ class: 'w-full bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-xl px-3 py-3 text-base' %>
+
+
+ Até
+ <%= f.date_field :fim, value: @fim.strftime('%Y-%m-%d'),
+ class: 'w-full bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-xl px-3 py-3 text-base' %>
+
+ <%= f.submit 'Ver', class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold rounded-xl cursor-pointer px-6 py-3 min-h-[48px]' %>
+ <% end %>
+
+
+
+ <%# ââ Card principal: o que jĂĄ estĂĄ FECHADO ââââââââââââââ %>
+
+
+
+ <%= icone :sucesso, cor: 'text-green-600', espaco: false %> Fechado pela empresa
+
<%= l @inicio, format: :short %> a <%= l @fim, format: :short %>
+
+
+
<%= moeda(@valor_consolidado) %>
+
+ <%# As duas metades do total: o que jĂĄ caiu e o que ainda vem. %>
+
+
+
<%= icone :dinheiro, cor: 'text-green-700' %> JĂĄ recebido
+
<%= moeda(@totais.total_pago) %>
+
+
+
<%= icone :parcial, cor: 'text-amber-700' %> A receber
+
<%= moeda(@totais.total_pendente) %>
+
+
+
+
+ <%= @consolidacoes.size %> fechamento(s) no perĂodo.
+
+
+ <% if @consolidacoes.any? %>
+ <%= link_to rotulo(:baixar, 'Baixar meu relatĂłrio do perĂodo (PDF)', cor: nil),
+ motorista_totais_pdf_path(inicio: @inicio, fim: @fim),
+ data: { turbo: false },
+ class: 'w-full bg-orange-500 hover:bg-orange-600 text-black font-bold text-base px-6 py-4 rounded-xl min-h-[52px] flex items-center justify-center text-center mt-4' %>
<% end %>
- <%# ââ Lista de consolidaçÔes ââââââââââââââââââââââââââââ %>
- <%= icone :planilha, espaco: false %> Meus pagamentos
+ <%# ââ Card secundĂĄrio: estimativa (ainda nĂŁo fechada) ââââ %>
+
+
+
<%= icone :dinheiro, cor: nil, espaco: false %> Estimativa das entregas
+
<%= l @estimado_inicio, format: :short %> a <%= l @estimado_fim, format: :short %>
+
+
<%= moeda(@valor_estimado) %>
+ <%# "Atendidas" = concluĂdas + sem sucesso. O rĂłtulo NĂO pode voltar a dizer
+ "feitas e confirmadas": o valor acima inclui as visitas sem sucesso, que
+ a empresa também paga (o motorista foi ao local). Sem a segunda linha o
+ motorista nĂŁo consegue conferir a conta. %>
+
<%= @entregas_mes %> entregas atendidas
+
+ <%= @entregas_concluidas %> entregues · <%= @entregas_falhadas %> sem sucesso (pagas também)
+
+
+ <%= icone :alerta, cor: nil %> Valor aproximado. O valor certo Ă© o de cima, quando a empresa fecha o pagamento.
+
+
- <% if @minhas_consolidacoes.any? %>
+ <%# ââ Composição: de onde vem o valor fechado ââââââââââââ %>
+ <%= icone :composicao, espaco: false %> De onde vem esse valor
+ Cada fechamento abaixo entra na soma do card branco.
+
+ <% if @consolidacoes.any? %>
- <% @minhas_consolidacoes.each do |c| %>
- <% cm = c.consolidacao_motoristas.find_by(motorista_nome: current_user.nome) %>
+ <% @consolidacoes.each do |cm| %>
+ <% c = cm.consolidacao %>
-
+
<%= c.nome %>
- <%= badge_status(c.status) %>
- <% if c.finalizada? %>
- <%= badge_pagamento(cm&.pago? ? :pago : :pendente) %>
- <% end %>
+ <%= badge_pagamento(cm.pago? ? :pago : :pendente) %>
<%= icone :calendario %> <%= l c.data_inicio, format: :short %> a <%= l c.data_fim, format: :short %>
-
<%= moeda(cm&.valor_total || 0) %>
- <% if cm&.pago? %>
+
<%= moeda(cm.valor_total) %>
+ <% if cm.pago? %>
<%= icone :dinheiro, cor: 'text-green-400' %> Pago em <%= l cm.pago_em.to_date, format: :short %>
<% if cm.forma_pagamento.present? %>via <%= cm.forma_pagamento.humanize %><% end %>
@@ -94,18 +150,14 @@
<% if cm.nota_fiscal.present? %>
<%= icone :nota_fiscal %> Nota Fiscal: <%= cm.nota_fiscal %>
<% end %>
+ <% else %>
+
<%= icone :parcial, cor: 'text-amber-400' %> Ainda nĂŁo pago
<% end %>
- <% if c.finalizada? %>
- <%= link_to rotulo(:baixar, 'Baixar meu extrato', cor: nil),
- gerar_pdf_extrato_consolidacao_path(c, motorista: current_user.nome),
- data: { turbo: false },
- class: 'w-full md:w-auto bg-orange-500 hover:bg-orange-600 text-black font-bold text-base px-6 py-4 rounded-xl min-h-[52px] flex items-center justify-center text-center' %>
- <% else %>
-
- <%= icone :parcial, cor: 'text-amber-400' %> Aguardando fechamento
-
- <% end %>
+ <%= link_to rotulo(:baixar, 'Extrato deste fechamento', cor: nil),
+ gerar_pdf_extrato_consolidacao_path(c, motorista: current_user.nome),
+ data: { turbo: false },
+ class: 'w-full md:w-auto bg-[#0a0a0a] border border-[#2a2a2a] hover:border-orange-500 text-white font-bold text-base px-5 py-4 rounded-xl min-h-[52px] flex items-center justify-center text-center' %>
<% end %>
@@ -113,8 +165,14 @@
<% else %>
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
-
VocĂȘ ainda nĂŁo aparece em nenhum pagamento.
-
Quando a empresa fechar um perĂodo com suas entregas, ele aparece aqui.
+
Nenhum fechamento neste perĂodo.
+
+ <% if @faixa_total %>
+ Toque em Tudo para ver todos os seus fechamentos.
+ <% else %>
+ Quando a empresa fechar um perĂodo com suas entregas, ele aparece aqui.
+ <% end %>
+
<% end %>
diff --git a/app/views/motorista/dashboard/totais.html.erb b/app/views/motorista/dashboard/totais.html.erb
deleted file mode 100644
index c161ca1..0000000
--- a/app/views/motorista/dashboard/totais.html.erb
+++ /dev/null
@@ -1,95 +0,0 @@
-<%# app/views/motorista/dashboard/totais.html.erb â Meu total no perĂodo %>
-<% content_for :title, 'Meu total no perĂodo' %>
-
-
-
-
-
Meu total no perĂodo
- <%= link_to rotulo(:voltar, 'Voltar', cor: nil), motorista_dashboard_path,
- class: 'text-gray-300 hover:text-white border border-[#2a2a2a] px-4 py-3 rounded-xl min-h-[48px] flex items-center' %>
-
-
- <%# ââ PerĂodo ââââââââââââââââââââââââââââââââââââââââââââ %>
- <%= form_with url: motorista_totais_path, method: :get,
- class: 'bg-[#1a1a1a] border border-[#2a2a2a] rounded-2xl p-4 mb-5 flex flex-wrap items-end gap-3' do |f| %>
-
- De
- <%= f.date_field :inicio, value: @inicio.strftime('%Y-%m-%d'),
- class: 'w-full bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-xl px-3 py-3 text-base' %>
-
-
- Até
- <%= f.date_field :fim, value: @fim.strftime('%Y-%m-%d'),
- class: 'w-full bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-xl px-3 py-3 text-base' %>
-
- <%= f.submit 'Ver', class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold rounded-xl cursor-pointer px-6 py-3 min-h-[48px]' %>
- <% end %>
-
- <%# ââ Total fechado ââââââââââââââââââââââââââââââââââââââ %>
-
-
- <%= icone :sucesso, cor: 'text-green-600', espaco: false %> Total fechado no perĂodo
-
-
<%= icone :calendario, cor: 'text-gray-500' %> <%= l @inicio, format: :short %> a <%= l @fim, format: :short %>
-
<%= moeda(@totais.total_geral) %>
-
- <%= icone :dinheiro, cor: 'text-green-700' %> JĂĄ pago: <%= moeda(@totais.total_pago) %>
- <%= icone :parcial, cor: 'text-amber-700' %> A receber: <%= moeda(@totais.total_pendente) %>
-
-
- Soma de <%= @consolidacoes.size %> fechamento(s) da empresa neste perĂodo.
-
-
-
- <%= link_to rotulo(:baixar, 'Baixar relatĂłrio em PDF', cor: nil),
- motorista_totais_pdf_path(inicio: @inicio, fim: @fim),
- data: { turbo: false },
- class: 'w-full bg-orange-500 hover:bg-orange-600 text-black font-bold text-base px-6 py-4 rounded-xl min-h-[52px] flex items-center justify-center mb-6' %>
-
- <%# ââ O que compĂ”e o valor âââââââââââââââââââââââââââââââ %>
-
<%= icone :composicao, espaco: false %> O que forma esse valor
-
Cada fechamento abaixo entra na soma acima.
-
- <% if @consolidacoes.any? %>
-
- <% @consolidacoes.each do |cm| %>
- <% c = cm.consolidacao %>
-
-
-
-
-
<%= c.nome %>
- <%= badge_pagamento(cm.pago? ? :pago : :pendente) %>
-
-
- <%= icone :calendario %> <%= l c.data_inicio, format: :short %> a <%= l c.data_fim, format: :short %>
-
-
<%= moeda(cm.valor_total) %>
- <% if cm.pago? %>
-
- <%= icone :dinheiro, cor: 'text-green-400' %> Pago em <%= l cm.pago_em.to_date, format: :short %>
- <% if cm.forma_pagamento.present? %>via <%= cm.forma_pagamento.humanize %><% end %>
-
- <% if cm.nota_fiscal.present? %>
-
<%= icone :nota_fiscal %> Nota Fiscal: <%= cm.nota_fiscal %>
- <% end %>
- <% else %>
-
<%= icone :parcial, cor: 'text-amber-400' %> Ainda nĂŁo pago
- <% end %>
-
- <%= link_to rotulo(:baixar, 'Extrato deste fechamento', cor: nil),
- gerar_pdf_extrato_consolidacao_path(c, motorista: current_user.nome),
- data: { turbo: false },
- class: 'w-full md:w-auto bg-[#0a0a0a] border border-[#2a2a2a] hover:border-orange-500 text-white font-bold text-base px-5 py-4 rounded-xl min-h-[52px] flex items-center justify-center text-center' %>
-
-
- <% end %>
-
- <% else %>
-
-
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
-
Nenhum fechamento neste perĂodo.
-
Escolha outro perĂodo acima â o fechamento aparece aqui quando a empresa finaliza a consolidação.
-
- <% end %>
-
diff --git a/config/routes.rb b/config/routes.rb
index 4c90e09..3f28895 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -27,9 +27,8 @@ Rails.application.routes.draw do
# Painel motorista
get '/motorista', to: 'motorista/dashboard#index', as: :motorista_dashboard
- # Total fechado do motorista num perĂodo + as consolidaçÔes que compĂ”em o
- # valor (mesma conta do relatĂłrio do admin) e o PDF do extrato do perĂodo.
- get '/motorista/totais', to: 'motorista/dashboard#totais', as: :motorista_totais
+ # Extrato do perĂodo do motorista em PDF (o painel jĂĄ mostra o total e as
+ # consolidaçÔes que compÔem o valor na própria tela).
get '/motorista/totais/pdf', to: 'motorista/dashboard#totais_pdf', as: :motorista_totais_pdf
# Login motorista â PIN de 4 dĂgitos + acesso via QR Code do extrato
@@ -47,10 +46,9 @@ Rails.application.routes.draw do
post :avulsa # cria consolidação avulsa a partir de uma NF
get :arquivadas # HistĂłrico â consolidaçÔes arquivadas
- # RelatĂłrio "total por motorista no perĂodo": soma o que cada motorista
- # fechou na faixa e, ao clicar num deles (?motorista=), mostra as
- # consolidaçÔes que compÔem o valor. `totais_pdf` é o mesmo relatório em PDF.
- get :totais
+ # PDF do modo de visualização "Por motorista" (/consolidacoes?visao=
+ # motoristas): o total de cada um no perĂodo e as consolidaçÔes que
+ # compÔem o valor.
get :totais_pdf
end
diff --git a/spec/requests/motorista_dashboard_spec.rb b/spec/requests/motorista_dashboard_spec.rb
index 7f1cce2..2970419 100644
--- a/spec/requests/motorista_dashboard_spec.rb
+++ b/spec/requests/motorista_dashboard_spec.rb
@@ -14,9 +14,9 @@ RSpec.describe 'Painel do motorista â contabilização das entregas sem sucess
include ActiveSupport::Testing::TimeHelpers
include_context 'espelho de rastreio'
- # O painel nĂŁo aceita filtro de perĂodo (Ă© sempre "do dia 1Âș atĂ© hoje"), entĂŁo
- # congelamos a data: sem isso o spec quebraria sozinho ao rodar no dia 1Âș, com
- # as datas semeadas caindo fora do mĂȘs corrente.
+ # O painel abre no MĂS CORRENTE (a estimativa vai atĂ© hoje), entĂŁo congelamos a
+ # data: sem isso o spec quebraria sozinho ao rodar no dia 1Âș, com as datas
+ # semeadas caindo fora do mĂȘs corrente.
let(:hoje) { Date.new(2026, 8, 20) }
let(:preco) { 10.0 }
let(:nome) { 'JOSE DA SILVA' }
diff --git a/spec/requests/totais_por_motorista_spec.rb b/spec/requests/totais_por_motorista_spec.rb
index 0f9b151..ed5d5be 100644
--- a/spec/requests/totais_por_motorista_spec.rb
+++ b/spec/requests/totais_por_motorista_spec.rb
@@ -1,8 +1,9 @@
require 'rails_helper'
-# Rotas do relatĂłrio "total por motorista": quem entra, o que a tela mostra e o
-# PDF. O drill-down (?motorista=) Ă© o que responde "de onde vem esse valor".
-RSpec.describe 'Totais por motorista', type: :request do
+# Modo de visualização "Por motorista" (/consolidacoes?visao=motoristas) e o
+# painel do motorista: quem entra, o que a tela soma e o drill-down que responde
+# "de onde vem esse valor".
+RSpec.describe 'VisĂŁo por motorista', type: :request do
def consolidacao_finalizada(motorista:, valor:, nome: 'Fechamento Junho')
c = create(:consolidacao, nome: nome, status: :finalizada,
data_inicio: Date.new(2026, 6, 1), data_fim: Date.new(2026, 6, 30))
@@ -13,39 +14,49 @@ RSpec.describe 'Totais por motorista', type: :request do
let(:periodo) { { inicio: '2026-06-01', fim: '2026-06-30' } }
- describe 'admin â /consolidacoes/totais' do
- it 'exige login' do
- get totais_consolidacoes_path
- expect(response).to redirect_to(new_user_session_path)
- end
-
+ describe 'admin â /consolidacoes?visao=motoristas' do
it 'bloqueia motorista (Pundit)' do
sign_in create(:motorista)
- get totais_consolidacoes_path
+ get consolidacoes_path(visao: 'motoristas')
expect(response).to redirect_to(root_path)
end
- it 'mostra o total do motorista no perĂodo' do
+ it 'soma o total do motorista no perĂodo' do
consolidacao_finalizada(motorista: 'Carlos', valor: 120.0)
sign_in create(:gerente)
- get totais_consolidacoes_path(periodo)
+ get consolidacoes_path(periodo.merge(visao: 'motoristas'))
expect(response).to have_http_status(:ok)
expect(response.body).to include('Carlos')
+ expect(response.body).to include('R$ 120,00')
end
- it 'abre a composição do motorista clicado' do
+ it 'só abre a composição do motorista clicado' do
+ consolidacao_finalizada(motorista: 'Carlos', valor: 120.0, nome: 'Fechamento Junho')
+ consolidacao_finalizada(motorista: 'Pedro', valor: 90.0, nome: 'Fechamento do Pedro')
+ sign_in create(:gerente)
+
+ get consolidacoes_path(periodo.merge(visao: 'motoristas'))
+ expect(response.body).not_to include('O que forma esse valor')
+
+ get consolidacoes_path(periodo.merge(visao: 'motoristas', detalhe: 'Carlos'))
+ expect(response.body).to include('O que forma esse valor')
+ expect(response.body).to include('Fechamento Junho')
+ expect(response.body).not_to include('Fechamento do Pedro')
+ end
+
+ it 'a visĂŁo lista continua sendo o padrĂŁo' do
consolidacao_finalizada(motorista: 'Carlos', valor: 120.0, nome: 'Fechamento Junho')
sign_in create(:gerente)
- get totais_consolidacoes_path(periodo.merge(motorista: 'Carlos'))
+ get consolidacoes_path
- expect(response.body).to include('Composição de Carlos')
expect(response.body).to include('Fechamento Junho')
+ expect(response.body).not_to include('Total do perĂodo')
end
- it 'gera o PDF' do
+ it 'gera o PDF do perĂodo' do
consolidacao_finalizada(motorista: 'Carlos', valor: 120.0)
sign_in create(:gerente)
@@ -56,17 +67,18 @@ RSpec.describe 'Totais por motorista', type: :request do
end
end
- describe 'motorista â /motorista/totais' do
- it 'mostra só o total dele e as consolidaçÔes que o compÔem' do
+ describe 'motorista â painel' do
+ it 'mostra o total fechado do perĂodo e os fechamentos que o compĂ”em' do
motorista = create(:motorista, nome: 'Carlos')
consolidacao_finalizada(motorista: 'Carlos', valor: 120.0, nome: 'Fechamento Junho')
consolidacao_finalizada(motorista: 'Pedro', valor: 999.0, nome: 'Fechamento do Pedro')
sign_in motorista
- get motorista_totais_path(periodo)
+ get motorista_dashboard_path(periodo)
expect(response).to have_http_status(:ok)
expect(response.body).to include('Fechamento Junho')
+ expect(response.body).to include('R$ 120,00')
expect(response.body).not_to include('Fechamento do Pedro')
end
@@ -83,7 +95,7 @@ RSpec.describe 'Totais por motorista', type: :request do
it 'nĂŁo Ă© acessĂvel por quem nĂŁo Ă© motorista' do
sign_in create(:gerente)
- get motorista_totais_path
+ get motorista_totais_pdf_path(periodo)
expect(response).to redirect_to(dashboard_path)
end
end
diff --git a/spec/services/analytics/totais_por_motorista_spec.rb b/spec/services/analytics/totais_por_motorista_spec.rb
index 7b2e245..155a2b0 100644
--- a/spec/services/analytics/totais_por_motorista_spec.rb
+++ b/spec/services/analytics/totais_por_motorista_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
-# Trava a conta do relatĂłrio "total por motorista" â a mesma que alimenta
-# /consolidacoes/totais (admin) e /motorista/totais (motorista). As duas telas
+# Trava a conta do modo de visualização "Por motorista" â a mesma que alimenta
+# /consolidacoes?visao=motoristas (admin) e o painel do motorista. As duas telas
# usam ESTE service justamente para nĂŁo poderem divergir.
RSpec.describe Analytics::TotaisPorMotorista do
let(:inicio) { Date.new(2026, 6, 1) }
@@ -37,13 +37,21 @@ RSpec.describe Analytics::TotaisPorMotorista do
expect(totais.total_consolidacoes).to eq(3)
end
- it 'inclui rascunhos quando pedido' do
+ it 'respeita o filtro de status (o mesmo select da lista)' do
consolidar(motorista: 'Carlos', valor: 100.0)
consolidar(motorista: 'Carlos', valor: 40.0, status: :rascunho)
- com_rascunho = described_class.new(inicio: inicio, fim: fim, incluir_rascunhos: true)
- expect(totais.total_geral).to eq(100.0)
- expect(com_rascunho.total_geral).to eq(140.0)
+ expect(totais.total_geral).to eq(100.0) # default: sĂł finalizadas
+ expect(described_class.new(inicio: inicio, fim: fim, status: 'todos').total_geral).to eq(140.0)
+ expect(described_class.new(inicio: inicio, fim: fim, status: 'rascunho').total_geral).to eq(40.0)
+ end
+
+ it 'busca parcial por nome do motorista (campo de busca da tela)' do
+ consolidar(motorista: 'Carlos Matheus', valor: 100.0)
+ consolidar(motorista: 'Pedro Martins', valor: 30.0)
+
+ achou = described_class.new(inicio: inicio, fim: fim, busca: 'matheus')
+ expect(achou.linhas.map { |l| l[:motorista] }).to eq(['Carlos Matheus'])
end
it 'separa pago de a receber' do
From 6c78570658197b6c523311e80fadc222e44e5f698e89d1ded8215e546b1ed03f Mon Sep 17 00:00:00 2001
From: victor
Date: Wed, 26 Aug 2026 15:34:56 -0300
Subject: [PATCH 14/40] =?UTF-8?q?Modifica=C3=A7=C3=A3o=20na=20area=20do=20?=
=?UTF-8?q?motorista?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/controllers/consolidacoes_controller.rb | 33 +++++---
.../motorista/dashboard_controller.rb | 5 +-
.../controllers/carrossel_controller.js | 68 ++++++++++++++++
app/models/consolidacao.rb | 10 +++
.../analytics/totais_por_motorista.rb | 39 +++++++---
.../consolidacoes/_por_motorista.html.erb | 23 ++++--
app/views/consolidacoes/index.html.erb | 17 +++-
app/views/motorista/dashboard/index.html.erb | 77 ++++++++++++++-----
spec/requests/totais_por_motorista_spec.rb | 18 +++++
.../analytics/totais_por_motorista_spec.rb | 25 +++++-
10 files changed, 261 insertions(+), 54 deletions(-)
create mode 100644 app/javascript/controllers/carrossel_controller.js
diff --git a/app/controllers/consolidacoes_controller.rb b/app/controllers/consolidacoes_controller.rb
index c988e79..46cbaea 100644
--- a/app/controllers/consolidacoes_controller.rb
+++ b/app/controllers/consolidacoes_controller.rb
@@ -33,8 +33,17 @@ class ConsolidacoesController < ApplicationController
# Filtros
@consolidacoes = @consolidacoes.where(status: params[:status]) if params[:status].present?
@consolidacoes = @consolidacoes.where('nome ILIKE ?', "%#{params[:nome]}%") if params[:nome].present?
- @consolidacoes = @consolidacoes.where('data_inicio >= ?', params[:inicio]) if params[:inicio].present?
- @consolidacoes = @consolidacoes.where('data_fim <= ?', params[:fim]) if params[:fim].present?
+ # Datas: a consolidação entra se CRUZA a faixa â mesmo critĂ©rio do modo "Por
+ # motorista" e do dashboard financeiro. Com o critério antigo (caber inteira
+ # dentro do filtro), um fechamento de 01/08 a 31/08 sumia da lista ao filtrar
+ # 01/08â26/08 e as duas abas da MESMA tela contavam coisas diferentes.
+ if params[:inicio].present? && params[:fim].present?
+ @consolidacoes = @consolidacoes.cruzando_periodo(params[:inicio], params[:fim])
+ elsif params[:inicio].present?
+ @consolidacoes = @consolidacoes.where('data_fim >= ?', params[:inicio])
+ elsif params[:fim].present?
+ @consolidacoes = @consolidacoes.where('data_inicio <= ?', params[:fim])
+ end
if params[:motorista].present?
ids = ConsolidacaoMotorista.where('motorista_nome ILIKE ?', "%#{params[:motorista]}%")
@@ -63,7 +72,7 @@ class ConsolidacoesController < ApplicationController
totais = Analytics::TotaisPorMotorista.new(
inicio: inicio, fim: fim,
motorista: params[:detalhe].presence,
- status: params[:status].presence || 'todos',
+ status: params[:status].presence || 'finalizada',
busca: params[:motorista].presence
)
@@ -484,24 +493,26 @@ class ConsolidacoesController < ApplicationController
private
- # Modo "Por motorista": os totais saem do MESMO recorte da lista (perĂodo,
- # status e busca por motorista), para as duas visÔes nunca contarem coisas
- # diferentes. `detalhe` Ă© o motorista aberto (o clique na linha).
+ # Modo "Por motorista": mesmo perĂodo/busca da lista, mas o recorte de datas e
+ # o default de status sĂŁo os do DASHBOARD FINANCEIRO â Ă© com ele que o operador
+ # compara o nĂșmero (ver Analytics::TotaisPorMotorista). Sem status na URL vale
+ # "Consolidado" (sĂł finalizadas): "Geral" (com rascunhos) mostrava um valor
+ # maior que o do dashboard e parecia erro de conta.
+ # `detalhe` Ă© o motorista aberto (o clique na linha).
def montar_totais
@inicio, @fim = periodo_totais
+ @status_totais = params[:status].presence || 'finalizada'
@totais = Analytics::TotaisPorMotorista.new(
inicio: @inicio, fim: @fim,
- status: params[:status].presence || 'todos',
+ status: @status_totais,
busca: params[:motorista].presence
)
@detalhe_motorista = params[:detalhe].presence
@detalhe = @detalhe_motorista ? @totais.consolidacoes_de(@detalhe_motorista) : nil
end
- # PerĂodo do relatĂłrio de totais. Default: o MĂS CORRENTE INTEIRO â o fim Ă© o
- # Ășltimo dia do mĂȘs, nĂŁo "hoje", porque o recorte exige a consolidação inteira
- # dentro da faixa (Consolidacao.no_periodo) e um fechamento que vai até 31/08
- # sumiria da tela até o dia 31 chegar.
+ # PerĂodo do relatĂłrio de totais. Default: o MĂS CORRENTE INTEIRO (o mĂȘs Ă© a
+ # unidade de fechamento; "atĂ© hoje" recortaria o mĂȘs pela metade sem motivo).
def periodo_totais
inicio = parse_data_totais(params[:inicio]) || Date.current.beginning_of_month
fim = parse_data_totais(params[:fim]) || Date.current.end_of_month
diff --git a/app/controllers/motorista/dashboard_controller.rb b/app/controllers/motorista/dashboard_controller.rb
index 828a024..474b083 100644
--- a/app/controllers/motorista/dashboard_controller.rb
+++ b/app/controllers/motorista/dashboard_controller.rb
@@ -105,9 +105,8 @@ module Motorista
[inicio.to_date, fim.to_date]
end
- # Default: mĂȘs corrente INTEIRO (fim = Ășltimo dia do mĂȘs). O recorte exige a
- # consolidação inteira dentro da faixa, então terminar em "hoje" esconderia
- # um fechamento que vai atĂ© o fim do mĂȘs. Mesma regra do lado do admin.
+ # Default: mĂȘs corrente INTEIRO (fim = Ășltimo dia do mĂȘs) â o mĂȘs Ă© a unidade
+ # de fechamento. Mesma regra do lado do admin.
def periodo_selecionado
inicio = parse_data(params[:inicio]) || Date.current.beginning_of_month
fim = parse_data(params[:fim]) || Date.current.end_of_month
diff --git a/app/javascript/controllers/carrossel_controller.js b/app/javascript/controllers/carrossel_controller.js
new file mode 100644
index 0000000..74e6dbd
--- /dev/null
+++ b/app/javascript/controllers/carrossel_controller.js
@@ -0,0 +1,68 @@
+// app/javascript/controllers/carrossel_controller.js
+// Stimulus â paginação em "carrossel" para listas longas no celular (painel do
+// motorista). Os itens jĂĄ vĂȘm todos no HTML; aqui sĂł mostramos uma PĂGINA por
+// vez, com setas e o indicador "1 / 3".
+//
+// Por que no cliente e nĂŁo com ?pg= no servidor: a lista divide a tela com os
+// cards de valor e o filtro de perĂodo â recarregar a pĂĄgina a cada seta faria o
+// motorista perder a posição e piscar a tela inteira num 3G.
+//
+// Sem itens suficientes para duas pĂĄginas, os controles somem sozinhos (nada de
+// seta morta ocupando espaço).
+
+import { Controller } from "@hotwired/stimulus"
+
+export default class extends Controller {
+ static targets = ["item", "controles", "indicador", "anterior", "proximo"]
+ static values = { tamanho: { type: Number, default: 10 } }
+
+ connect() {
+ this.pagina = 0
+ this.render()
+ }
+
+ proxima() { this.ir(this.pagina + 1) }
+ anterior() { this.ir(this.pagina - 1) }
+
+ ir(destino) {
+ this.pagina = Math.min(Math.max(destino, 0), this.totalPaginas - 1)
+ this.render()
+ // Volta ao topo da lista: sem isso, na 2ÂȘ pĂĄgina o motorista continua vendo
+ // o fim da anterior e parece que nada mudou.
+ this.element.scrollIntoView({ behavior: "smooth", block: "start" })
+ }
+
+ get totalPaginas() {
+ return Math.max(Math.ceil(this.itemTargets.length / this.tamanhoValue), 1)
+ }
+
+ render() {
+ const inicio = this.pagina * this.tamanhoValue
+ const fim = inicio + this.tamanhoValue
+
+ this.itemTargets.forEach((item, i) => {
+ item.classList.toggle("hidden", i < inicio || i >= fim)
+ })
+
+ if (!this.hasControlesTarget) return
+
+ this.controlesTarget.classList.toggle("hidden", this.totalPaginas < 2)
+ if (this.hasIndicadorTarget) {
+ // "1â10 de 12" e nĂŁo "pĂĄgina 1 de 2": o motorista quer saber quantos
+ // fechamentos existem, nĂŁo quantas pĂĄginas a tela tem.
+ const total = this.itemTargets.length
+ this.indicadorTarget.textContent = `${inicio + 1}â${Math.min(fim, total)} de ${total}`
+ }
+ if (this.hasAnteriorTarget) this.alternar(this.anteriorTarget, this.pagina === 0)
+ if (this.hasProximoTarget) this.alternar(this.proximoTarget, this.pagina >= this.totalPaginas - 1)
+ }
+
+ // BotĂŁo de ponta: desabilitado de verdade (nĂŁo sĂł apagado), para o toque nĂŁo
+ // "funcionar sem fazer nada".
+ alternar(botao, desligado) {
+ if (!botao) return
+ botao.disabled = desligado
+ botao.classList.toggle("opacity-30", desligado)
+ botao.classList.toggle("pointer-events-none", desligado)
+ }
+}
diff --git a/app/models/consolidacao.rb b/app/models/consolidacao.rb
index bb0a9bb..96e057e 100644
--- a/app/models/consolidacao.rb
+++ b/app/models/consolidacao.rb
@@ -33,8 +33,18 @@ class Consolidacao < ApplicationRecord
# ââ Escopos âââââââââââââââââââââââââââââââââââââââââââââââââ
scope :recentes, -> { order(created_at: :desc) }
+ # â ïž `no_periodo` exige a consolidação INTEIRA dentro da faixa. NĂŁo Ă© o recorte
+ # das telas (elas usam `cruzando_periodo`, abaixo) â um fechamento de 01/08 a
+ # 31/08 sumia ao filtrar 01/08â26/08. SĂł use se o "caber dentro" for mesmo o
+ # que se quer.
scope :no_periodo, ->(i, f) { where('data_inicio >= ? AND data_fim <= ?', i, f) }
+ # ConsolidaçÔes que CRUZAM o perĂodo (nĂŁo precisam caber inteiras dentro dele).
+ # Ă o recorte do dashboard financeiro (DashboardController#carregar_dados_
+ # financeiro) e, por isso, o de qualquer tela que precise bater com ele: um
+ # fechamento de 01/08 a 31/08 tem que aparecer ao filtrar 01/08â26/08.
+ scope :cruzando_periodo, ->(i, f) { where('data_inicio <= ? AND data_fim >= ?', f, i) }
+
# ââ Filtro por status de pagamento (derivado dos motoristas) â
# pago_em NĂO-nulo conta como pago; COUNT(pago_em) ignora os NULL.
scope :pagamento_pago, -> {
diff --git a/app/services/analytics/totais_por_motorista.rb b/app/services/analytics/totais_por_motorista.rb
index dc1b1ec..fed4c8b 100644
--- a/app/services/analytics/totais_por_motorista.rb
+++ b/app/services/analytics/totais_por_motorista.rb
@@ -7,11 +7,18 @@
# lista, com drill-down no motorista clicado)
# âą motoristaâ /motorista (o painel, jĂĄ recortado nele â mesma conta, mesmo PDF)
#
-# RECORTE (igual ao filtro da lista de consolidaçÔes): entram as consolidaçÔes
-# ATIVAS cujo perĂodo estĂĄ DENTRO da faixa escolhida (data_inicio >= inicio e
-# data_fim <= fim). NĂŁo Ă© sobreposição de propĂłsito â uma consolidação que
-# atravessa a borda do filtro entraria inteira e o "total do perĂodo" ficaria
-# maior que o perĂodo.
+# RECORTE â Ă O MESMO DO DASHBOARD FINANCEIRO, de propĂłsito
+# (DashboardController#carregar_dados_financeiro). Os dois nĂșmeros sĂŁo lidos lado
+# a lado pelo operador; quando divergem, a leitura Ă© "o sistema estĂĄ errado".
+# Entram as consolidaçÔes ATIVAS que:
+# âą CRUZAM o perĂodo (data_inicio <= fim AND data_fim >= inicio) â um
+# fechamento de 01/08 a 31/08 aparece ao filtrar 01/08â26/08; OU
+# âą tiveram algum PAGAMENTO dentro do perĂodo (pagar em agosto um fechamento
+# de julho tem que aparecer em agosto).
+#
+# STATUS: por padrĂŁo sĂł as FINALIZADAS â Ă© o "Consolidado" do dashboard, o valor
+# que jĂĄ fechou. 'todos' inclui os rascunhos em andamento (a visĂŁo "Geral"), que
+# ainda mudam de valor.
#
# SĂł motoristas ATIVOS (nĂŁo arquivados) contam: Ă© o mesmo conjunto que a
# consolidação paga (ConsolidacaoMotorista.ativos), então o total desta tela
@@ -36,15 +43,12 @@ module Analytics
# telas mostram nome/perĂodo/status dela em seguida).
def registros
@registros ||= begin
- base = Consolidacao.ativas.no_periodo(@inicio, @fim)
- # Por padrĂŁo sĂł o que jĂĄ foi FECHADO conta como total do perĂodo â
- # rascunho ainda muda de valor. `merge` (e nĂŁo where(consolidacoes:
- # {status: ...})) porque status Ă© enum: quem traduz :finalizada Ă© o model.
+ base = Consolidacao.ativas
+ # `public_send` sobre o enum: quem traduz :finalizada/:rascunho Ă© o model.
base = base.public_send(@status) if %w[finalizada rascunho].include?(@status)
escopo = ConsolidacaoMotorista.ativos
- .joins(:consolidacao)
- .merge(base)
+ .where(consolidacao_id: ids_do_periodo(base))
.includes(:consolidacao)
escopo = escopo.where(motorista_nome: @motorista) if @motorista
escopo = escopo.where('motorista_nome ILIKE ?', "%#{@busca}%") if @busca
@@ -94,6 +98,19 @@ module Analytics
private
+ # As consolidaçÔes do perĂodo: as que cruzam a faixa + as que receberam
+ # pagamento dentro dela. Mesmo par de conjuntos do dashboard financeiro.
+ def ids_do_periodo(base)
+ intervalo = @inicio.beginning_of_day..@fim.end_of_day
+
+ cruzam = base.cruzando_periodo(@inicio, @fim).ids
+ pagas = ConsolidacaoMotorista.ativos
+ .where(pago_em: intervalo)
+ .where(consolidacao_id: base.select(:id))
+ .distinct.pluck(:consolidacao_id)
+ (cruzam + pagas).uniq
+ end
+
def soma(cms)
cms.sum { |cm| cm.valor_total || 0 }
end
diff --git a/app/views/consolidacoes/_por_motorista.html.erb b/app/views/consolidacoes/_por_motorista.html.erb
index 27408cd..bba2c91 100644
--- a/app/views/consolidacoes/_por_motorista.html.erb
+++ b/app/views/consolidacoes/_por_motorista.html.erb
@@ -3,14 +3,16 @@
ali mesmo (sem sair da tela), as consolidaçÔes que compÔem o valor dele. %>
<%# Link desta mesma tela preservando filtros â usado pelas linhas e pelos PDFs. %>
-<% ctx = { nome: params[:nome], motorista: params[:motorista], status: params[:status],
+<% ctx = { nome: params[:nome], motorista: params[:motorista], status: @status_totais,
inicio: @inicio.strftime('%Y-%m-%d'), fim: @fim.strftime('%Y-%m-%d'),
visao: 'motoristas' }.compact_blank %>
<%# ââ Resumo do perĂodo ââââââââââââââââââââââââââââââââââ %>
-
<%= icone :dinheiro %> Total do perĂodo
+
+ <%= icone :dinheiro %> <%= @status_totais == 'finalizada' ? 'Consolidado no perĂodo' : 'Total do perĂodo' %>
+
<%= moeda(@totais.total_geral) %>
<%= @totais.total_consolidacoes %> consolidação(Ôes) · <%= @totais.linhas.size %> motorista(s)
@@ -120,9 +122,18 @@
- <%= icone :dica %> Entram as consolidaçÔes
- <%= params[:status].presence ? "com status #{params[:status]}" : 'de qualquer status' %>
- cujo perĂodo estĂĄ dentro de <%= l @inicio, format: :short %> â <%= l @fim, format: :short %>.
- Motoristas arquivados na consolidação não entram no total.
+ <%= icone :dica %>
+ <% if @status_totais == 'finalizada' %>
+ Consolidado = sĂł consolidaçÔes finalizadas â
+ Ă© o mesmo nĂșmero do card "Consolidado / Pago" do Dashboard no mesmo perĂodo.
+ Troque para Geral para somar também os rascunhos em andamento.
+ <% elsif @status_totais == 'rascunho' %>
+ Mostrando sĂł rascunhos â valores ainda em aberto, que mudam atĂ© o fechamento.
+ <% else %>
+ Geral = finalizadas + rascunhos , por isso fica acima do
+ "Consolidado / Pago" do Dashboard. Troque para Consolidado para comparar com ele.
+ <% end %>
+ Entram as consolidaçÔes que cruzam <%= l @inicio, format: :short %> â <%= l @fim, format: :short %> e as pagas dentro do perĂodo;
+ motoristas arquivados nĂŁo entram no total.
<% end %>
diff --git a/app/views/consolidacoes/index.html.erb b/app/views/consolidacoes/index.html.erb
index 0dfb198..6bac899 100644
--- a/app/views/consolidacoes/index.html.erb
+++ b/app/views/consolidacoes/index.html.erb
@@ -111,9 +111,20 @@
class: 'flex-1 basis-48 min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5 focus:border-orange-500 focus:outline-none' %>
<% end %>
- <%= f.select :status,
- options_for_select([['Todos os status', ''], ['Rascunho', 'rascunho'], ['Finalizada', 'finalizada']], params[:status]),
- {}, class: 'flex-1 basis-40 min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
+ <% if @visao == 'motoristas' %>
+ <%# "Consolidado" x "Geral" â a pergunta que o nĂșmero responde muda com isso:
+ Consolidado Ă© o valor JĂ FECHADO (o mesmo do card "Consolidado / Pago" do
+ Dashboard); Geral soma também os rascunhos, que ainda mudam de valor. %>
+ <%= f.select :status,
+ options_for_select([['Consolidado (fechadas)', 'finalizada'],
+ ['Geral (com rascunhos)', 'todos'],
+ ['SĂł rascunhos', 'rascunho']], @status_totais),
+ {}, class: 'flex-1 basis-52 min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
+ <% else %>
+ <%= f.select :status,
+ options_for_select([['Todos os status', ''], ['Rascunho', 'rascunho'], ['Finalizada', 'finalizada']], params[:status]),
+ {}, class: 'flex-1 basis-40 min-w-0 bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
+ <% end %>
<%# "Pagamento" Ă© status DA CONSOLIDAĂĂO (todos os motoristas dela); no modo
Por motorista cada linha jĂĄ mostra pago/a receber, entĂŁo o filtro sairia
diff --git a/app/views/motorista/dashboard/index.html.erb b/app/views/motorista/dashboard/index.html.erb
index 5aae57c..1083ead 100644
--- a/app/views/motorista/dashboard/index.html.erb
+++ b/app/views/motorista/dashboard/index.html.erb
@@ -103,35 +103,57 @@
<% end %>
- <%# ââ Card secundĂĄrio: estimativa (ainda nĂŁo fechada) ââââ %>
-
-
-
<%= icone :dinheiro, cor: nil, espaco: false %> Estimativa das entregas
-
<%= l @estimado_inicio, format: :short %> a <%= l @estimado_fim, format: :short %>
+ <%# ââ Card secundĂĄrio: entregas do perĂodo ââââââââââââââ
+ REGRA: valor estimado e valor fechado NUNCA aparecem juntos.
+ Quando a empresa jĂĄ fechou o perĂodo, o nĂșmero certo Ă© o de cima; mostrar
+ ao lado uma estimativa (entregas à preço, sem descontos/bÎnus e sem as
+ regras de classificação) fazia o motorista ver dois valores diferentes para
+ o mesmo mĂȘs â e o estimado costuma ser MAIOR, o que vira cobrança.
+ Com fechamento: sĂł a CONTAGEM de entregas (o trabalho, sem prometer valor).
+ Sem fechamento: a prĂ©via em R$, que Ă© a Ășnica informação que ele tem. %>
+ <% if @consolidacoes.any? %>
+
+
+
<%= icone :caminhao, espaco: false %> Suas entregas no perĂodo
+
<%= l @estimado_inicio, format: :short %> a <%= l @estimado_fim, format: :short %>
+
+
<%= @entregas_mes %>
+ <%# "Atendidas" = concluĂdas + sem sucesso: a empresa paga as duas, porque
+ em ambas o motorista foi até o local. %>
+
+ <%= @entregas_concluidas %> entregues · <%= @entregas_falhadas %> sem sucesso (pagas também)
+
+
+ <%= icone :sucesso, cor: 'text-green-500' %> O valor deste perĂodo jĂĄ foi fechado pela empresa â Ă© o do card branco acima.
+
-
<%= moeda(@valor_estimado) %>
- <%# "Atendidas" = concluĂdas + sem sucesso. O rĂłtulo NĂO pode voltar a dizer
- "feitas e confirmadas": o valor acima inclui as visitas sem sucesso, que
- a empresa também paga (o motorista foi ao local). Sem a segunda linha o
- motorista nĂŁo consegue conferir a conta. %>
-
<%= @entregas_mes %> entregas atendidas
-
- <%= @entregas_concluidas %> entregues · <%= @entregas_falhadas %> sem sucesso (pagas também)
-
-
- <%= icone :alerta, cor: nil %> Valor aproximado. O valor certo Ă© o de cima, quando a empresa fecha o pagamento.
-
-
+ <% else %>
+
+
+
<%= icone :dinheiro, cor: nil, espaco: false %> Prévia (ainda não fechado)
+
<%= l @estimado_inicio, format: :short %> a <%= l @estimado_fim, format: :short %>
+
+
<%= moeda(@valor_estimado) %>
+
<%= @entregas_mes %> entregas atendidas
+
+ <%= @entregas_concluidas %> entregues · <%= @entregas_falhadas %> sem sucesso (pagas também)
+
+
+ <%= icone :alerta, cor: nil %> Conta simples (entregas Ă valor da entrega). O valor certo sai quando a empresa fechar o perĂodo â bĂŽnus, retiradas e descontos entram sĂł no fechamento.
+
+
+ <% end %>
<%# ââ Composição: de onde vem o valor fechado ââââââââââââ %>
<%= icone :composicao, espaco: false %> De onde vem esse valor
Cada fechamento abaixo entra na soma do card branco.
<% if @consolidacoes.any? %>
+
<% @consolidacoes.each do |cm| %>
<% c = cm.consolidacao %>
-
+
@@ -162,6 +184,23 @@
<% end %>
+
+ <%# Setas: alvos grandes (56px) porque a tela Ă© usada de luva/no trĂąnsito.
+ Somem sozinhas quando hĂĄ uma pĂĄgina sĂł (ver carrossel_controller.js). %>
+
+
+ <%= icone :voltar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
+
+
+
+ <%= icone :avancar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
+
+
+
<% else %>
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
diff --git a/spec/requests/totais_por_motorista_spec.rb b/spec/requests/totais_por_motorista_spec.rb
index ed5d5be..529d420 100644
--- a/spec/requests/totais_por_motorista_spec.rb
+++ b/spec/requests/totais_por_motorista_spec.rb
@@ -32,6 +32,24 @@ RSpec.describe 'VisĂŁo por motorista', type: :request do
expect(response.body).to include('R$ 120,00')
end
+ # O default tem que ser o MESMO recorte do dashboard ("Consolidado / Pago"),
+ # senĂŁo o operador compara as duas telas e vĂȘ valores diferentes.
+ it 'sem status na URL mostra o CONSOLIDADO (rascunho fica de fora)' do
+ consolidacao_finalizada(motorista: 'Carlos', valor: 120.0)
+ rascunho = create(:consolidacao, status: :rascunho,
+ data_inicio: Date.new(2026, 6, 1), data_fim: Date.new(2026, 6, 30))
+ create(:consolidacao_entrega, consolidacao: rascunho, motorista_nome: 'Carlos', valor_aplicado: 500.0)
+ create(:consolidacao_motorista, consolidacao: rascunho, motorista_nome: 'Carlos')
+ sign_in create(:gerente)
+
+ get consolidacoes_path(periodo.merge(visao: 'motoristas'))
+ expect(response.body).to include('R$ 120,00')
+ expect(response.body).not_to include('R$ 620,00')
+
+ get consolidacoes_path(periodo.merge(visao: 'motoristas', status: 'todos'))
+ expect(response.body).to include('R$ 620,00')
+ end
+
it 'só abre a composição do motorista clicado' do
consolidacao_finalizada(motorista: 'Carlos', valor: 120.0, nome: 'Fechamento Junho')
consolidacao_finalizada(motorista: 'Pedro', valor: 90.0, nome: 'Fechamento do Pedro')
diff --git a/spec/services/analytics/totais_por_motorista_spec.rb b/spec/services/analytics/totais_por_motorista_spec.rb
index 155a2b0..724c9e6 100644
--- a/spec/services/analytics/totais_por_motorista_spec.rb
+++ b/spec/services/analytics/totais_por_motorista_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Analytics::TotaisPorMotorista do
consolidar(motorista: 'Carlos', valor: 50.0, qtd: 1) # 50
consolidar(motorista: 'Pedro', valor: 30.0, qtd: 1) # 30
consolidar(motorista: 'Carlos', valor: 999.0, status: :rascunho) # fora (rascunho)
- consolidar(motorista: 'Carlos', valor: 999.0, # fora (perĂodo)
+ consolidar(motorista: 'Carlos', valor: 999.0, # fora (nĂŁo cruza)
data_inicio: Date.new(2026, 7, 1), data_fim: Date.new(2026, 7, 31))
expect(totais.linhas.map { |l| [l[:motorista], l[:valor_total]] })
@@ -37,6 +37,29 @@ RSpec.describe Analytics::TotaisPorMotorista do
expect(totais.total_consolidacoes).to eq(3)
end
+ # ââ Recorte de datas: o MESMO do dashboard financeiro âââââââââââââââââââ
+ # Quando estes dois casos divergiam do dashboard, o operador via dois valores
+ # diferentes para o mesmo motorista no mesmo perĂodo e lia como erro de conta.
+ it 'inclui a consolidação que CRUZA o perĂodo (nĂŁo precisa caber inteira)' do
+ # 20/05 a 20/06: começa antes do filtro, termina dentro.
+ consolidar(motorista: 'Carlos', valor: 100.0,
+ data_inicio: Date.new(2026, 5, 20), data_fim: Date.new(2026, 6, 20))
+ # 20/06 a 20/07: começa dentro, termina depois.
+ consolidar(motorista: 'Carlos', valor: 40.0,
+ data_inicio: Date.new(2026, 6, 20), data_fim: Date.new(2026, 7, 20))
+
+ expect(totais.total_geral).to eq(140.0)
+ end
+
+ it 'inclui a consolidação de outro perĂodo PAGA dentro do perĂodo' do
+ _, cm = consolidar(motorista: 'Carlos', valor: 80.0,
+ data_inicio: Date.new(2026, 4, 1), data_fim: Date.new(2026, 4, 30))
+ cm.update!(pago_em: Time.zone.local(2026, 6, 10, 12, 0), forma_pagamento: 'pix')
+
+ expect(totais.total_geral).to eq(80.0)
+ expect(totais.total_pago).to eq(80.0)
+ end
+
it 'respeita o filtro de status (o mesmo select da lista)' do
consolidar(motorista: 'Carlos', valor: 100.0)
consolidar(motorista: 'Carlos', valor: 40.0, status: :rascunho)
From e4b17653ef326ec55a9d01baf3b7a91797c9a845d26ce3a9f3976537bbf06cb6 Mon Sep 17 00:00:00 2001
From: victor
Date: Wed, 26 Aug 2026 15:40:50 -0300
Subject: [PATCH 15/40] =?UTF-8?q?Modifica=C3=A7=C3=A3o=20na=20area=20do=20?=
=?UTF-8?q?motorista?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../controllers/carrossel_controller.js | 82 +++++++++++++++----
app/views/consolidacoes/_lista.html.erb | 8 ++
.../consolidacoes/_por_motorista.html.erb | 11 ++-
app/views/motorista/dashboard/index.html.erb | 17 +---
.../shared/_carrossel_controles.html.erb | 24 ++++++
5 files changed, 108 insertions(+), 34 deletions(-)
create mode 100644 app/views/shared/_carrossel_controles.html.erb
diff --git a/app/javascript/controllers/carrossel_controller.js b/app/javascript/controllers/carrossel_controller.js
index 74e6dbd..2793498 100644
--- a/app/javascript/controllers/carrossel_controller.js
+++ b/app/javascript/controllers/carrossel_controller.js
@@ -1,42 +1,90 @@
// app/javascript/controllers/carrossel_controller.js
-// Stimulus â paginação em "carrossel" para listas longas no celular (painel do
-// motorista). Os itens jĂĄ vĂȘm todos no HTML; aqui sĂł mostramos uma PĂGINA por
-// vez, com setas e o indicador "1 / 3".
+// Stimulus â paginação em "carrossel" para listas longas: mostra N itens por vez
+// com setas grandes e o indicador "1â10 de 12".
//
-// Por que no cliente e nĂŁo com ?pg= no servidor: a lista divide a tela com os
-// cards de valor e o filtro de perĂodo â recarregar a pĂĄgina a cada seta faria o
-// motorista perder a posição e piscar a tela inteira num 3G.
+// PADRĂO EM TELA PEQUENA, LISTA INTEIRA EM TELA GRANDE. Em celular e monitor
+// pequeno, uma lista de 30 cards vira rolagem infinita e o rodapé/açÔes da tela
+// somem de vista; num monitor grande a mesma lista cabe e paginar sĂł atrapalha
+// (Ctrl+F do navegador nĂŁo acha o que estĂĄ escondido). O corte Ă© por largura de
+// janela â `maxLargura` â e Ă© reavaliado no resize/rotação da tela.
//
-// Sem itens suficientes para duas pĂĄginas, os controles somem sozinhos (nada de
-// seta morta ocupando espaço).
+// Os itens jĂĄ vĂȘm todos no HTML; aqui sĂł escondemos os de fora da pĂĄgina. Por
+// isso não serve para listas de centenas de itens (aà é paginação no servidor).
+//
+// Uso:
+// <- acima disso, mostra tudo
+//
âŠ
<- cada item
+// âŠ
+//
+//
+//
+//
+//
+//
+//
+// Um item com data-carrossel-inicial="true" (ex.: o motorista com a composição
+// aberta) define em qual pĂĄgina o carrossel abre â senĂŁo o clique dele "sumia".
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["item", "controles", "indicador", "anterior", "proximo"]
- static values = { tamanho: { type: Number, default: 10 } }
+ static values = {
+ tamanho: { type: Number, default: 10 },
+ maxLargura: { type: Number, default: 1366 }
+ }
connect() {
- this.pagina = 0
+ this.pagina = this.paginaInicial()
+ this._aoRedimensionar = () => this.render()
+ window.addEventListener("resize", this._aoRedimensionar)
this.render()
}
- proxima() { this.ir(this.pagina + 1) }
+ disconnect() {
+ window.removeEventListener("resize", this._aoRedimensionar)
+ }
+
+ proxima() { this.ir(this.pagina + 1) }
anterior() { this.ir(this.pagina - 1) }
ir(destino) {
this.pagina = Math.min(Math.max(destino, 0), this.totalPaginas - 1)
this.render()
- // Volta ao topo da lista: sem isso, na 2ÂȘ pĂĄgina o motorista continua vendo
- // o fim da anterior e parece que nada mudou.
+ // Volta ao topo da lista: sem isso, na 2ÂȘ pĂĄgina o leitor continua vendo o
+ // fim da anterior e parece que nada mudou.
this.element.scrollIntoView({ behavior: "smooth", block: "start" })
}
+ // Tela grande: sem paginação (a lista inteira aparece).
+ get paginando() {
+ return window.innerWidth <= this.maxLarguraValue &&
+ this.itemTargets.length > this.tamanhoValue
+ }
+
get totalPaginas() {
return Math.max(Math.ceil(this.itemTargets.length / this.tamanhoValue), 1)
}
+ paginaInicial() {
+ const i = this.itemTargets.findIndex((el) => el.dataset.carrosselInicial === "true")
+ return i < 0 ? 0 : Math.floor(i / this.tamanhoValue)
+ }
+
render() {
+ const total = this.itemTargets.length
+
+ if (!this.paginando) {
+ this.itemTargets.forEach((item) => item.classList.remove("hidden"))
+ if (this.hasControlesTarget) this.controlesTarget.classList.add("hidden")
+ return
+ }
+
+ // Uma tela menor pode ter reduzido o total de pĂĄginas desde a Ășltima vez.
+ this.pagina = Math.min(this.pagina, this.totalPaginas - 1)
+
const inicio = this.pagina * this.tamanhoValue
const fim = inicio + this.tamanhoValue
@@ -45,12 +93,11 @@ export default class extends Controller {
})
if (!this.hasControlesTarget) return
+ this.controlesTarget.classList.remove("hidden")
- this.controlesTarget.classList.toggle("hidden", this.totalPaginas < 2)
if (this.hasIndicadorTarget) {
- // "1â10 de 12" e nĂŁo "pĂĄgina 1 de 2": o motorista quer saber quantos
- // fechamentos existem, nĂŁo quantas pĂĄginas a tela tem.
- const total = this.itemTargets.length
+ // "1â10 de 12" e nĂŁo "pĂĄgina 1 de 2": quem lĂȘ quer saber quantos itens
+ // existem, nĂŁo quantas pĂĄginas a tela tem.
this.indicadorTarget.textContent = `${inicio + 1}â${Math.min(fim, total)} de ${total}`
}
if (this.hasAnteriorTarget) this.alternar(this.anteriorTarget, this.pagina === 0)
@@ -60,7 +107,6 @@ export default class extends Controller {
// BotĂŁo de ponta: desabilitado de verdade (nĂŁo sĂł apagado), para o toque nĂŁo
// "funcionar sem fazer nada".
alternar(botao, desligado) {
- if (!botao) return
botao.disabled = desligado
botao.classList.toggle("opacity-30", desligado)
botao.classList.toggle("pointer-events-none", desligado)
diff --git a/app/views/consolidacoes/_lista.html.erb b/app/views/consolidacoes/_lista.html.erb
index a4c4307..e359537 100644
--- a/app/views/consolidacoes/_lista.html.erb
+++ b/app/views/consolidacoes/_lista.html.erb
@@ -1,8 +1,12 @@
<%# app/views/consolidacoes/_lista.html.erb â visĂŁo por CONSOLIDAĂĂO %>
<% if @consolidacoes.any? %>
+ <%# Em celular/monitor pequeno a lista vira carrossel de 10 em 10; em tela
+ grande aparece inteira (ver carrossel_controller.js). %>
+
<% @consolidacoes.each do |c| %>
<%= link_to consolidacao_path(c),
+ data: { carrossel_target: 'item' },
class: 'block bg-[#1a1a1a] border border-[#2a2a2a] hover:border-orange-500 rounded-xl p-5 transition-colors' do %>
@@ -28,6 +32,10 @@
<% end %>
<% end %>
+
+ <%= render 'shared/carrossel_controles',
+ anterior: 'ConsolidaçÔes anteriores', proximo: 'Próximas consolidaçÔes' %>
+
<% else %>
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
diff --git a/app/views/consolidacoes/_por_motorista.html.erb b/app/views/consolidacoes/_por_motorista.html.erb
index bba2c91..06eaf02 100644
--- a/app/views/consolidacoes/_por_motorista.html.erb
+++ b/app/views/consolidacoes/_por_motorista.html.erb
@@ -46,10 +46,15 @@
Ajuste as datas acima â o recorte pega a consolidação inteira dentro da faixa.
<% else %>
+ <%# Em celular/monitor pequeno vira carrossel de 10 em 10; em tela grande sai a
+ lista inteira. O motorista com a composição aberta define a pågina inicial
+ (data-carrossel-inicial) â senĂŁo o clique dele cairia numa pĂĄgina escondida. %>
+
<% @totais.linhas.each do |linha| %>
<% aberto = @detalhe_motorista == linha[:motorista] %>
-
+
<%# Cabeçalho do motorista â o clique abre/fecha a composição %>
<%= link_to consolidacoes_path(ctx.merge(detalhe: (aberto ? nil : linha[:motorista])).compact_blank),
@@ -121,6 +126,10 @@
<% end %>
+ <%= render 'shared/carrossel_controles',
+ anterior: 'Motoristas anteriores', proximo: 'PrĂłximos motoristas' %>
+
+
<%= icone :dica %>
<% if @status_totais == 'finalizada' %>
diff --git a/app/views/motorista/dashboard/index.html.erb b/app/views/motorista/dashboard/index.html.erb
index 1083ead..ac30001 100644
--- a/app/views/motorista/dashboard/index.html.erb
+++ b/app/views/motorista/dashboard/index.html.erb
@@ -185,21 +185,8 @@
<% end %>
- <%# Setas: alvos grandes (56px) porque a tela Ă© usada de luva/no trĂąnsito.
- Somem sozinhas quando hĂĄ uma pĂĄgina sĂł (ver carrossel_controller.js). %>
-
-
- <%= icone :voltar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
-
-
-
- <%= icone :avancar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
-
-
+ <%= render 'shared/carrossel_controles',
+ anterior: 'Fechamentos anteriores', proximo: 'PrĂłximos fechamentos' %>
<% else %>
diff --git a/app/views/shared/_carrossel_controles.html.erb b/app/views/shared/_carrossel_controles.html.erb
new file mode 100644
index 0000000..8ab3b90
--- /dev/null
+++ b/app/views/shared/_carrossel_controles.html.erb
@@ -0,0 +1,24 @@
+<%# app/views/shared/_carrossel_controles.html.erb
+ Setas + indicador do carrossel (ver carrossel_controller.js). Nascem
+ escondidos: quem decide se esta tela Ă© pequena o bastante para paginar Ă© o
+ JS â em monitor grande a lista aparece inteira e estes controles nĂŁo voltam.
+
+ Alvos de 56px: a tela Ă© usada no celular, em movimento.
+
+ locals: anterior:/proximo: (rĂłtulos acessĂveis, opcionais). %>
+<% anterior = local_assigns.fetch(:anterior, 'Itens anteriores') %>
+<% proximo = local_assigns.fetch(:proximo, 'PrĂłximos itens') %>
+
+
+
+ <%= icone :voltar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
+
+
+
+ <%= icone :avancar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
+
+
From 044747a36b2b31bd239b005a01d8eb416635ff8e87436c8f65219df4c6e65e79 Mon Sep 17 00:00:00 2001
From: victor
Date: Wed, 26 Aug 2026 15:45:25 -0300
Subject: [PATCH 16/40] =?UTF-8?q?Modifica=C3=A7=C3=A3o=20na=20area=20do=20?=
=?UTF-8?q?motorista?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../controllers/carrossel_controller.js | 80 +++++++++++--------
1 file changed, 48 insertions(+), 32 deletions(-)
diff --git a/app/javascript/controllers/carrossel_controller.js b/app/javascript/controllers/carrossel_controller.js
index 2793498..a306cd7 100644
--- a/app/javascript/controllers/carrossel_controller.js
+++ b/app/javascript/controllers/carrossel_controller.js
@@ -1,27 +1,30 @@
// app/javascript/controllers/carrossel_controller.js
-// Stimulus â paginação em "carrossel" para listas longas: mostra N itens por vez
-// com setas grandes e o indicador "1â10 de 12".
+// Stimulus â paginação em "carrossel" para listas longas: mostra uma pĂĄgina de
+// itens por vez, com setas grandes e o indicador "1â5 de 12".
//
-// PADRĂO EM TELA PEQUENA, LISTA INTEIRA EM TELA GRANDE. Em celular e monitor
-// pequeno, uma lista de 30 cards vira rolagem infinita e o rodapé/açÔes da tela
-// somem de vista; num monitor grande a mesma lista cabe e paginar sĂł atrapalha
-// (Ctrl+F do navegador nĂŁo acha o que estĂĄ escondido). O corte Ă© por largura de
-// janela â `maxLargura` â e Ă© reavaliado no resize/rotação da tela.
+// TAMANHO DE PĂGINA POR TELA (Ă© o ponto do controller):
+// âą celular (atĂ© 640px) â 5 itens â cada card ocupa muita altura;
+// 10 jĂĄ viravam rolagem infinita;
+// âą atĂ© 1366px â 10 itens â tablet e notebook pequeno;
+// âą acima disso â lista INTEIRA, sem setas. Em monitor grande a
+// lista cabe e esconder item sĂł atrapalha (o
+// Ctrl+F do navegador nĂŁo acha o que estĂĄ oculto).
+//
+// O corte é reavaliado no resize/rotação: girar o celular ou encostar a janela
+// na lateral jĂĄ muda o comportamento, e o item que estava no topo continua na
+// tela (a posição Ă© guardada pelo ĂNDICE do item, nĂŁo pelo nĂșmero da pĂĄgina).
//
// Os itens jĂĄ vĂȘm todos no HTML; aqui sĂł escondemos os de fora da pĂĄgina. Por
// isso não serve para listas de centenas de itens (aà é paginação no servidor).
//
// Uso:
// <- acima disso, mostra tudo
-//
âŠ
<- cada item
-// âŠ
-//
-//
-//
-//
-//
+// data-carrossel-tamanho-value="10" <- itens por pĂĄgina
+// data-carrossel-tamanho-compacto-value="5" <- itens no celular
+// data-carrossel-largura-compacta-value="640" <- até aqui é "celular"
+// data-carrossel-max-largura-value="1366"> <- acima disso, mostra tudo
+//
âŠ
+// <%= render 'shared/carrossel_controles' %>
//
//
// Um item com data-carrossel-inicial="true" (ex.: o motorista com a composição
@@ -32,12 +35,17 @@ import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["item", "controles", "indicador", "anterior", "proximo"]
static values = {
- tamanho: { type: Number, default: 10 },
- maxLargura: { type: Number, default: 1366 }
+ tamanho: { type: Number, default: 10 },
+ tamanhoCompacto: { type: Number, default: 5 },
+ larguraCompacta: { type: Number, default: 640 },
+ maxLargura: { type: Number, default: 1366 }
}
connect() {
- this.pagina = this.paginaInicial()
+ // Ăncora = Ăndice do primeiro item da pĂĄgina atual. Guardar o ĂNDICE (e nĂŁo
+ // o nĂșmero da pĂĄgina) Ă© o que mantĂ©m o mesmo item na tela quando o tamanho
+ // da pĂĄgina muda junto com a largura.
+ this.ancora = this.indiceInicial()
this._aoRedimensionar = () => this.render()
window.addEventListener("resize", this._aoRedimensionar)
this.render()
@@ -51,26 +59,37 @@ export default class extends Controller {
anterior() { this.ir(this.pagina - 1) }
ir(destino) {
- this.pagina = Math.min(Math.max(destino, 0), this.totalPaginas - 1)
+ const pagina = Math.min(Math.max(destino, 0), this.totalPaginas - 1)
+ this.ancora = pagina * this.tamanho
this.render()
- // Volta ao topo da lista: sem isso, na 2ÂȘ pĂĄgina o leitor continua vendo o
- // fim da anterior e parece que nada mudou.
+ // Volta ao topo da lista: sem isso, na pĂĄgina seguinte o leitor continua
+ // vendo o fim da anterior e parece que nada mudou.
this.element.scrollIntoView({ behavior: "smooth", block: "start" })
}
+ get tamanho() {
+ return window.innerWidth <= this.larguraCompactaValue
+ ? this.tamanhoCompactoValue
+ : this.tamanhoValue
+ }
+
// Tela grande: sem paginação (a lista inteira aparece).
get paginando() {
return window.innerWidth <= this.maxLarguraValue &&
- this.itemTargets.length > this.tamanhoValue
+ this.itemTargets.length > this.tamanho
}
get totalPaginas() {
- return Math.max(Math.ceil(this.itemTargets.length / this.tamanhoValue), 1)
+ return Math.max(Math.ceil(this.itemTargets.length / this.tamanho), 1)
}
- paginaInicial() {
+ get pagina() {
+ return Math.min(Math.floor(this.ancora / this.tamanho), this.totalPaginas - 1)
+ }
+
+ indiceInicial() {
const i = this.itemTargets.findIndex((el) => el.dataset.carrosselInicial === "true")
- return i < 0 ? 0 : Math.floor(i / this.tamanhoValue)
+ return i < 0 ? 0 : i
}
render() {
@@ -82,11 +101,8 @@ export default class extends Controller {
return
}
- // Uma tela menor pode ter reduzido o total de pĂĄginas desde a Ășltima vez.
- this.pagina = Math.min(this.pagina, this.totalPaginas - 1)
-
- const inicio = this.pagina * this.tamanhoValue
- const fim = inicio + this.tamanhoValue
+ const inicio = this.pagina * this.tamanho
+ const fim = inicio + this.tamanho
this.itemTargets.forEach((item, i) => {
item.classList.toggle("hidden", i < inicio || i >= fim)
@@ -96,7 +112,7 @@ export default class extends Controller {
this.controlesTarget.classList.remove("hidden")
if (this.hasIndicadorTarget) {
- // "1â10 de 12" e nĂŁo "pĂĄgina 1 de 2": quem lĂȘ quer saber quantos itens
+ // "1â5 de 12" e nĂŁo "pĂĄgina 1 de 3": quem lĂȘ quer saber quantos itens
// existem, nĂŁo quantas pĂĄginas a tela tem.
this.indicadorTarget.textContent = `${inicio + 1}â${Math.min(fim, total)} de ${total}`
}
From 808c2c7545f2a0fcc733dcff2d6a2c54c7ebf833039b8029ac5ba2d87adb1a05 Mon Sep 17 00:00:00 2001
From: victor
Date: Wed, 26 Aug 2026 15:51:55 -0300
Subject: [PATCH 17/40] =?UTF-8?q?Modifica=C3=A7=C3=A3o=20na=20area=20do=20?=
=?UTF-8?q?motorista?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../controllers/carrossel_controller.js | 21 ++++---
app/views/consolidacoes/_lista.html.erb | 2 +-
.../consolidacoes/_por_motorista.html.erb | 2 +-
app/views/motorista/dashboard/index.html.erb | 2 +-
.../shared/_carrossel_controles.html.erb | 55 ++++++++++++++++---
5 files changed, 63 insertions(+), 19 deletions(-)
diff --git a/app/javascript/controllers/carrossel_controller.js b/app/javascript/controllers/carrossel_controller.js
index a306cd7..3a53ca0 100644
--- a/app/javascript/controllers/carrossel_controller.js
+++ b/app/javascript/controllers/carrossel_controller.js
@@ -97,7 +97,7 @@ export default class extends Controller {
if (!this.paginando) {
this.itemTargets.forEach((item) => item.classList.remove("hidden"))
- if (this.hasControlesTarget) this.controlesTarget.classList.add("hidden")
+ this.controlesTargets.forEach((el) => el.classList.add("hidden"))
return
}
@@ -109,15 +109,18 @@ export default class extends Controller {
})
if (!this.hasControlesTarget) return
- this.controlesTarget.classList.remove("hidden")
+ this.controlesTargets.forEach((el) => el.classList.remove("hidden"))
- if (this.hasIndicadorTarget) {
- // "1â5 de 12" e nĂŁo "pĂĄgina 1 de 3": quem lĂȘ quer saber quantos itens
- // existem, nĂŁo quantas pĂĄginas a tela tem.
- this.indicadorTarget.textContent = `${inicio + 1}â${Math.min(fim, total)} de ${total}`
- }
- if (this.hasAnteriorTarget) this.alternar(this.anteriorTarget, this.pagina === 0)
- if (this.hasProximoTarget) this.alternar(this.proximoTarget, this.pagina >= this.totalPaginas - 1)
+ // "1â5 de 12" e nĂŁo "pĂĄgina 1 de 3": quem lĂȘ quer saber quantos itens
+ // existem, nĂŁo quantas pĂĄginas a tela tem.
+ const rotulo = `${inicio + 1}â${Math.min(fim, total)} de ${total}`
+ this.indicadorTargets.forEach((el) => { el.textContent = rotulo })
+
+ // Plural: o mesmo controle aparece em dois layouts (setas laterais em tela
+ // com espaço, barra flutuante no rodapé no celular) e os dois precisam
+ // desabilitar juntos.
+ this.anteriorTargets.forEach((b) => this.alternar(b, this.pagina === 0))
+ this.proximoTargets.forEach((b) => this.alternar(b, this.pagina >= this.totalPaginas - 1))
}
// BotĂŁo de ponta: desabilitado de verdade (nĂŁo sĂł apagado), para o toque nĂŁo
diff --git a/app/views/consolidacoes/_lista.html.erb b/app/views/consolidacoes/_lista.html.erb
index e359537..5186478 100644
--- a/app/views/consolidacoes/_lista.html.erb
+++ b/app/views/consolidacoes/_lista.html.erb
@@ -2,7 +2,7 @@
<% if @consolidacoes.any? %>
<%# Em celular/monitor pequeno a lista vira carrossel de 10 em 10; em tela
grande aparece inteira (ver carrossel_controller.js). %>
-
+
<% @consolidacoes.each do |c| %>
<%= link_to consolidacao_path(c),
diff --git a/app/views/consolidacoes/_por_motorista.html.erb b/app/views/consolidacoes/_por_motorista.html.erb
index 06eaf02..d1ddd2a 100644
--- a/app/views/consolidacoes/_por_motorista.html.erb
+++ b/app/views/consolidacoes/_por_motorista.html.erb
@@ -49,7 +49,7 @@
<%# Em celular/monitor pequeno vira carrossel de 10 em 10; em tela grande sai a
lista inteira. O motorista com a composição aberta define a pågina inicial
(data-carrossel-inicial) â senĂŁo o clique dele cairia numa pĂĄgina escondida. %>
-
+
<% @totais.linhas.each do |linha| %>
<% aberto = @detalhe_motorista == linha[:motorista] %>
diff --git a/app/views/motorista/dashboard/index.html.erb b/app/views/motorista/dashboard/index.html.erb
index ac30001..15069dd 100644
--- a/app/views/motorista/dashboard/index.html.erb
+++ b/app/views/motorista/dashboard/index.html.erb
@@ -149,7 +149,7 @@
Cada fechamento abaixo entra na soma do card branco.
<% if @consolidacoes.any? %>
-
+
<% @consolidacoes.each do |cm| %>
<% c = cm.consolidacao %>
diff --git a/app/views/shared/_carrossel_controles.html.erb b/app/views/shared/_carrossel_controles.html.erb
index 8ab3b90..d51da24 100644
--- a/app/views/shared/_carrossel_controles.html.erb
+++ b/app/views/shared/_carrossel_controles.html.erb
@@ -1,7 +1,17 @@
<%# app/views/shared/_carrossel_controles.html.erb
- Setas + indicador do carrossel (ver carrossel_controller.js). Nascem
- escondidos: quem decide se esta tela Ă© pequena o bastante para paginar Ă© o
- JS â em monitor grande a lista aparece inteira e estes controles nĂŁo voltam.
+ Controles do carrossel (ver carrossel_controller.js) em DOIS layouts, porque
+ "seta flutuante na lateral" só funciona onde sobra espaço lateral:
+
+ ⹠tela com espaço (sm+): setas redondas flutuando FORA dos cards,
+ centralizadas na altura da lista â o container reserva a faixa com
+ `sm:px-16`, senĂŁo a seta passa por cima do texto do card;
+ ⹠celular: barra flutuante colada no rodapé (sticky), com as duas setas e
+ o contador no meio. Na largura de um celular nĂŁo hĂĄ faixa lateral livre:
+ a seta lateral cobriria o conteĂșdo, e a barra inferior ainda fica no
+ alcance do polegar.
+
+ â ïž O container do carrossel precisa de `relative sm:px-16` â ver os usos em
+ consolidacoes/_lista, _por_motorista e motorista/dashboard/index.
Alvos de 56px: a tela Ă© usada no celular, em movimento.
@@ -9,16 +19,47 @@
<% anterior = local_assigns.fetch(:anterior, 'Itens anteriores') %>
<% proximo = local_assigns.fetch(:proximo, 'PrĂłximos itens') %>
-
+<%# ââ Setas laterais (sm+) âââââââââââââââââââââââââââââââ %>
+
<%= icone :voltar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
-
+
<%= icone :avancar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
+
+ <%# Contador embaixo, centralizado (vale para os dois layouts em sm+). %>
+
+
+
+
+
+<%# ââ Barra flutuante no rodapĂ© (celular) ââââââââââââââââ %>
+
+
+
+ <%= icone :voltar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
+
+
+
+ <%= icone :avancar, cor: nil, tamanho: 'w-6 h-6', espaco: false %>
+
+
From 5bb5d74e22ce5eeeb02572a7c6c0dc68499071f7e141eb0e4a18709a4ccaf07c Mon Sep 17 00:00:00 2001
From: victor
Date: Wed, 26 Aug 2026 17:24:25 -0300
Subject: [PATCH 18/40] =?UTF-8?q?Implanda=C3=A7=C3=A3o=20da=20customiza?=
=?UTF-8?q?=C3=A7=C3=A3o=20das=20permiss=C3=B5es=20dos=20usuarios=20e=20da?=
=?UTF-8?q?s=20notifica=C3=A7oes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../admin/auditoria_logs_controller.rb | 5 +-
.../admin/mensagem_templates_controller.rb | 2 +-
.../admin/perfis_acesso_controller.rb | 110 ++++++++
app/controllers/admin/usuarios_controller.rb | 64 ++++-
.../variaveis_personalizadas_controller.rb | 74 ++++++
app/controllers/application_controller.rb | 30 ++-
.../consolidacao_entregas_controller.rb | 2 +-
app/controllers/consolidacoes_controller.rb | 22 +-
app/controllers/dashboard_controller.rb | 29 ++-
.../operacoes_dashboard_controller.rb | 4 +-
app/controllers/paginas_controller.rb | 12 +
app/controllers/users/sessions_controller.rb | 11 +-
app/models/evento_notificacao.rb | 8 +-
app/models/mensagem_template.rb | 2 +-
app/models/perfil_acesso.rb | 62 +++++
app/models/permissao.rb | 245 ++++++++++++++++++
app/models/user.rb | 108 +++++++-
app/models/variavel_personalizada.rb | 46 ++++
app/policies/application_policy.rb | 10 +-
app/policies/auditoria_log_policy.rb | 16 ++
.../configuracao_notificacao_policy.rb | 21 +-
app/policies/configuracao_policy.rb | 22 +-
app/policies/consolidacao_policy.rb | 46 +++-
app/policies/contato_policy.rb | 30 ++-
app/policies/dashboard_policy.rb | 24 +-
app/policies/edicao_lancamento_policy.rb | 19 +-
app/policies/evento_notificacao_policy.rb | 30 ++-
app/policies/notificacao_envio_policy.rb | 11 +-
app/policies/perfil_acesso_policy.rb | 24 ++
app/policies/planilha_simpli_route_policy.rb | 10 +-
app/policies/user_policy.rb | 49 ++--
app/policies/variavel_personalizada_policy.rb | 19 ++
app/policies/whatsapp_sessao_policy.rb | 14 +-
.../notificacao/catalogo_variaveis.rb | 196 ++++++++++++++
app/services/notificacao/despachante.rb | 20 +-
app/services/notificacao/formatador.rb | 31 +++
app/services/notificacao/gatilhos.rb | 11 +-
.../notificacao/resolvedor_variaveis.rb | 90 +++++++
app/services/notificacao_service.rb | 8 +-
.../admin/mensagem_templates/edit.html.erb | 81 ++++--
app/views/admin/perfis_acesso/_form.html.erb | 83 ++++++
app/views/admin/perfis_acesso/edit.html.erb | 10 +
app/views/admin/perfis_acesso/index.html.erb | 70 +++++
app/views/admin/perfis_acesso/new.html.erb | 8 +
app/views/admin/usuarios/_form.html.erb | 56 +++-
app/views/admin/usuarios/index.html.erb | 19 +-
.../variaveis_personalizadas/_form.html.erb | 55 ++++
.../variaveis_personalizadas/edit.html.erb | 8 +
.../variaveis_personalizadas/index.html.erb | 76 ++++++
.../variaveis_personalizadas/new.html.erb | 6 +
.../consolidacao_entregas/revisar.html.erb | 2 +-
.../consolidacao_entregas/validar.html.erb | 4 +-
app/views/consolidacoes/show.html.erb | 2 +-
app/views/consolidacoes/wizard.html.erb | 4 +-
app/views/dashboard/index.html.erb | 33 ++-
app/views/layouts/_navbar.html.erb | 65 +++--
app/views/paginas/sem_acesso.html.erb | 20 ++
config/initializers/inflections.rb | 3 +
config/locales/pt-BR.yml | 6 +
config/routes.rb | 13 +
.../20260826000001_create_perfis_acesso.rb | 31 +++
...260826000002_add_perfil_acesso_to_users.rb | 17 ++
.../20260826000003_semear_perfis_acesso.rb | 78 ++++++
...6000004_create_variaveis_personalizadas.rb | 22 ++
spec/factories.rb | 14 +
spec/models/perfil_acesso_spec.rb | 60 +++++
spec/models/permissao_spec.rb | 73 ++++++
spec/models/variavel_personalizada_spec.rb | 27 ++
spec/requests/admin/perfis_acesso_spec.rb | 101 ++++++++
spec/requests/controle_de_acesso_spec.rb | 89 +++++++
.../notificacao/resolvedor_variaveis_spec.rb | 83 ++++++
71 files changed, 2487 insertions(+), 269 deletions(-)
create mode 100644 app/controllers/admin/perfis_acesso_controller.rb
create mode 100644 app/controllers/admin/variaveis_personalizadas_controller.rb
create mode 100644 app/controllers/paginas_controller.rb
create mode 100644 app/models/perfil_acesso.rb
create mode 100644 app/models/permissao.rb
create mode 100644 app/models/variavel_personalizada.rb
create mode 100644 app/policies/auditoria_log_policy.rb
create mode 100644 app/policies/perfil_acesso_policy.rb
create mode 100644 app/policies/variavel_personalizada_policy.rb
create mode 100644 app/services/notificacao/catalogo_variaveis.rb
create mode 100644 app/services/notificacao/formatador.rb
create mode 100644 app/services/notificacao/resolvedor_variaveis.rb
create mode 100644 app/views/admin/perfis_acesso/_form.html.erb
create mode 100644 app/views/admin/perfis_acesso/edit.html.erb
create mode 100644 app/views/admin/perfis_acesso/index.html.erb
create mode 100644 app/views/admin/perfis_acesso/new.html.erb
create mode 100644 app/views/admin/variaveis_personalizadas/_form.html.erb
create mode 100644 app/views/admin/variaveis_personalizadas/edit.html.erb
create mode 100644 app/views/admin/variaveis_personalizadas/index.html.erb
create mode 100644 app/views/admin/variaveis_personalizadas/new.html.erb
create mode 100644 app/views/paginas/sem_acesso.html.erb
create mode 100644 db/migrate/20260826000001_create_perfis_acesso.rb
create mode 100644 db/migrate/20260826000002_add_perfil_acesso_to_users.rb
create mode 100644 db/migrate/20260826000003_semear_perfis_acesso.rb
create mode 100644 db/migrate/20260826000004_create_variaveis_personalizadas.rb
create mode 100644 spec/models/perfil_acesso_spec.rb
create mode 100644 spec/models/permissao_spec.rb
create mode 100644 spec/models/variavel_personalizada_spec.rb
create mode 100644 spec/requests/admin/perfis_acesso_spec.rb
create mode 100644 spec/requests/controle_de_acesso_spec.rb
create mode 100644 spec/services/notificacao/resolvedor_variaveis_spec.rb
diff --git a/app/controllers/admin/auditoria_logs_controller.rb b/app/controllers/admin/auditoria_logs_controller.rb
index f8a95a4..3c1d1d6 100644
--- a/app/controllers/admin/auditoria_logs_controller.rb
+++ b/app/controllers/admin/auditoria_logs_controller.rb
@@ -1,7 +1,10 @@
# app/controllers/admin/auditoria_logs_controller.rb
class Admin::AuditoriaLogsController < ApplicationController
def index
- authorize AuditoriaLog, policy_class: ApplicationPolicy
+ # Policy prĂłpria (AuditoriaLogPolicy). Com `policy_class: ApplicationPolicy`
+ # a régua era a de consolidação: quem podia consolidar abria a auditoria pela
+ # URL, mesmo sem o item no menu.
+ authorize AuditoriaLog
@logs = AuditoriaLog.recentes.limit(200)
@logs = @logs.por_user(params[:user_id]) if params[:user_id].present?
diff --git a/app/controllers/admin/mensagem_templates_controller.rb b/app/controllers/admin/mensagem_templates_controller.rb
index 5b8d63b..3e9e9d8 100644
--- a/app/controllers/admin/mensagem_templates_controller.rb
+++ b/app/controllers/admin/mensagem_templates_controller.rb
@@ -36,7 +36,7 @@ class Admin::MensagemTemplatesController < ApplicationController
assunto: params[:assunto], blocos: blocos_do_form, ativo: true)
provisorio.validate # dispara a limpeza dos blocos antes de renderizar
- dados = Notificacao::Variaveis.amostra(@evento.gatilho)
+ dados = Notificacao::CatalogoVariaveis.amostra(@evento.gatilho)
render json: { html: corpo_do_preview(provisorio, dados),
assunto: provisorio.renderizador(dados).interpolar(provisorio.assunto) }
end
diff --git a/app/controllers/admin/perfis_acesso_controller.rb b/app/controllers/admin/perfis_acesso_controller.rb
new file mode 100644
index 0000000..c8d737f
--- /dev/null
+++ b/app/controllers/admin/perfis_acesso_controller.rb
@@ -0,0 +1,110 @@
+# app/controllers/admin/perfis_acesso_controller.rb
+#
+# CRUD dos perfis de acesso. à aqui que o ADM decide o que cada função enxerga,
+# sem depender de deploy: o catĂĄlogo do que pode ser marcado Ă© cĂłdigo
+# (Permissao::TODAS), a escolha Ă© dado (PerfilAcesso#permissoes).
+module Admin
+ class PerfisAcessoController < ApplicationController
+ include Auditavel
+
+ before_action :set_perfil, only: %i[edit update destroy]
+
+ def index
+ authorize PerfilAcesso
+ @perfis = policy_scope(PerfilAcesso).ordenados.includes(:users)
+ end
+
+ def new
+ @perfil = PerfilAcesso.new(ativo: true, permissoes: [])
+ authorize @perfil
+ end
+
+ def create
+ @perfil = PerfilAcesso.new(perfil_params)
+ authorize @perfil
+
+ if @perfil.save
+ auditar!(:criar, @perfil, dados_novos: { nome: @perfil.nome, permissoes: @perfil.permissoes })
+ redirect_to admin_perfis_path, notice: "Perfil #{@perfil.nome} criado."
+ else
+ render :new, status: :unprocessable_entity
+ end
+ end
+
+ def edit; end
+
+ def update
+ anteriores = @perfil.permissoes
+
+ # Ordem importa: aplicamos os valores em memĂłria para a trava conferir o
+ # que o ADM ESTĂ salvando, nĂŁo o que estĂĄ no banco.
+ @perfil.assign_attributes(perfil_params)
+
+ if travaria_o_sistema?
+ @perfil.errors.add(:base, mensagem_da_trava)
+ return render(:edit, status: :unprocessable_entity)
+ end
+
+ if @perfil.save
+ auditar!(:editar, @perfil,
+ dados_anteriores: { permissoes: anteriores },
+ dados_novos: { permissoes: @perfil.permissoes, ativo: @perfil.ativo })
+ redirect_to admin_perfis_path, notice: "Perfil #{@perfil.nome} atualizado."
+ else
+ render :edit, status: :unprocessable_entity
+ end
+ end
+
+ def destroy
+ unless @perfil.apagavel?
+ return redirect_to(admin_perfis_path, alert: motivo_para_nao_apagar)
+ end
+
+ @perfil.destroy
+ auditar!(:excluir, @perfil, dados_anteriores: { nome: @perfil.nome })
+ redirect_to admin_perfis_path, notice: 'Perfil excluĂdo.'
+ end
+
+ private
+
+ def set_perfil
+ @perfil = PerfilAcesso.find(params[:id])
+ authorize @perfil
+ end
+
+ def perfil_params
+ dados = params.require(:perfil_acesso).permit(:nome, :descricao, :ativo, permissoes: [])
+ # O form manda um campo vazio para o caso de NENHUMA caixa marcada (senĂŁo o
+ # parĂąmetro some e "desmarcar tudo" viraria "nĂŁo mexer em nada").
+ dados[:permissoes] = Permissao.filtrar(dados[:permissoes])
+ dados
+ end
+
+ # ââ Trava do Ășltimo administrador ââââââââââââââââââââââââ
+ # Mesma regra da tela de usuĂĄrios (User.administradores_de_acesso): salvar um
+ # perfil que perde `admin.perfis`/`admin.usuarios_gerenciar` (ou Ă© desativado)
+ # nĂŁo pode deixar o sistema sem quem conserte.
+ def travaria_o_sistema?
+ return false if @perfil.administra_acesso? # continua podendo: nada a checar
+ return false unless @perfil.users.ativos.exists? # perfil vazio não tira acesso de ninguém
+
+ # Quem estå NESTE perfil perderå o acesso; sobra alguém fora dele?
+ # UsuĂĄrio sem perfil conta: cai no padrĂŁo do papel (admin legado administra).
+ restantes = User.administradores_de_acesso.reject { |u| u.perfil_acesso_id == @perfil.id }
+ restantes.empty?
+ end
+
+ def mensagem_da_trava
+ 'Esta alteração deixaria o sistema sem nenhum usuårio ativo capaz de gerenciar ' \
+ 'perfis e usuĂĄrios. Garanta que outra pessoa mantenha esse acesso antes de salvar.'
+ end
+
+ def motivo_para_nao_apagar
+ if @perfil.sistema?
+ 'Perfis que vĂȘm com o sistema nĂŁo podem ser excluĂdos â desative-o ou edite as permissĂ”es.'
+ else
+ "Este perfil estĂĄ em uso por #{@perfil.users.count} usuĂĄrio(s). Mova essas pessoas para outro perfil antes de excluir."
+ end
+ end
+ end
+end
diff --git a/app/controllers/admin/usuarios_controller.rb b/app/controllers/admin/usuarios_controller.rb
index 3366537..1203051 100644
--- a/app/controllers/admin/usuarios_controller.rb
+++ b/app/controllers/admin/usuarios_controller.rb
@@ -4,7 +4,7 @@ class Admin::UsuariosController < ApplicationController
def index
authorize User
- @usuarios = policy_scope(User).order(:nome).limit(200)
+ @usuarios = policy_scope(User).includes(:perfil_acesso).order(:nome).limit(200)
@roles = User.roles.keys
end
@@ -36,6 +36,13 @@ class Admin::UsuariosController < ApplicationController
def update
authorize @usuario
+ # Trava do Ășltimo administrador: mudar perfil ou desativar a Ășnica pessoa que
+ # ainda administra acesso deixaria o sistema sem quem conserte.
+ if desligaria_o_ultimo_administrador?
+ @usuario.errors.add(:base, MENSAGEM_ULTIMO_ADMIN)
+ return render(:edit, status: :unprocessable_entity)
+ end
+
if @usuario.update(usuario_params_update)
AuditoriaLog.registrar(user: current_user, acao: 'editar_usuario', entidade: 'User', dados_novos: { detalhes: "Editou usuĂĄrio #{@usuario.email}" }, request: request)
redirect_to admin_usuarios_path, notice: 'UsuĂĄrio atualizado.'
@@ -47,6 +54,10 @@ class Admin::UsuariosController < ApplicationController
def destroy
authorize @usuario
+ if User.ultimo_administrador?(@usuario)
+ return redirect_to(admin_usuarios_path, alert: MENSAGEM_ULTIMO_ADMIN)
+ end
+
if @usuario == current_user
redirect_to admin_usuarios_path, alert: 'VocĂȘ nĂŁo pode excluir sua prĂłpria conta.'
return
@@ -58,7 +69,11 @@ class Admin::UsuariosController < ApplicationController
end
def toggle_ativo
- authorize @usuario, :update?
+ authorize @usuario, :toggle_ativo?
+
+ if @usuario.ativo? && User.ultimo_administrador?(@usuario)
+ return redirect_to(admin_usuarios_path, alert: MENSAGEM_ULTIMO_ADMIN)
+ end
@usuario.update!(ativo: !@usuario.ativo)
status = @usuario.ativo? ? 'ativado' : 'desativado'
AuditoriaLog.registrar(user: current_user, acao: "usuario_#{status}", entidade: 'User', dados_novos: { detalhes: "#{@usuario.email} foi #{status}" }, request: request)
@@ -67,18 +82,53 @@ class Admin::UsuariosController < ApplicationController
private
+ MENSAGEM_ULTIMO_ADMIN = 'Esta alteração deixaria o sistema sem nenhum usuårio ativo capaz de ' \
+ 'gerenciar perfis e usuĂĄrios. DĂȘ esse acesso a outra pessoa antes.'.freeze
+
def set_usuario
@usuario = User.find(params[:id])
end
+ # Aplica as mudanças em memória e pergunta: sobra alguém administrando?
+ def desligaria_o_ultimo_administrador?
+ simulado = @usuario.dup
+ simulado.id = @usuario.id
+ simulado.assign_attributes(usuario_params_update)
+ simulado.recarregar_permissoes!
+
+ return false if simulado.ativo? && simulado.pode?('admin.perfis') && simulado.pode?('admin.usuarios_gerenciar')
+
+ User.ultimo_administrador?(@usuario)
+ end
+
+ # â ïž CAMPOS DE ACESSO SĂ PARA QUEM PODE MEXER NELES.
+ # `UserPolicy#update?` libera editar a PRĂPRIA ficha (nome, senha) â Ă© o que
+ # permite alguém trocar a própria senha. Sem o filtro abaixo, essa mesma
+ # permissão deixava qualquer usuårio logado enviar `user[role]=admin` na edição
+ # do prĂłprio cadastro e se promover. Quem decide papel e perfil Ă©
+ # `UserPolicy#alterar_acesso?` (nunca sobre si mesmo).
+ CAMPOS_DE_ACESSO = %i[role perfil_acesso_id ativo].freeze
+
def usuario_params
- params.require(:user).permit(:nome, :email, :password, :password_confirmation,
- :role, :pin_code, :ativo)
+ permitidos = [:nome, :email, :password, :password_confirmation, :pin_code]
+ permitidos += CAMPOS_DE_ACESSO if pode_alterar_acesso?
+ params.require(:user).permit(permitidos)
end
def usuario_params_update
- permitted = [:nome, :email, :role, :pin_code, :ativo]
- permitted += [:password, :password_confirmation] if params[:user][:password].present?
- params.require(:user).permit(permitted)
+ permitidos = [:nome, :email, :pin_code]
+ permitidos += CAMPOS_DE_ACESSO if pode_alterar_acesso?
+ permitidos += [:password, :password_confirmation] if params[:user][:password].present?
+ params.require(:user).permit(permitidos)
end
+
+ def pode_alterar_acesso?
+ if @usuario&.persisted?
+ policy(@usuario).alterar_acesso?
+ else
+ # Criação: não hå "si mesmo" para se promover, basta poder gerenciar.
+ current_user.pode?('admin.usuarios_gerenciar')
+ end
+ end
+ helper_method :pode_alterar_acesso?
end
diff --git a/app/controllers/admin/variaveis_personalizadas_controller.rb b/app/controllers/admin/variaveis_personalizadas_controller.rb
new file mode 100644
index 0000000..dfd235d
--- /dev/null
+++ b/app/controllers/admin/variaveis_personalizadas_controller.rb
@@ -0,0 +1,74 @@
+# app/controllers/admin/variaveis_personalizadas_controller.rb
+#
+# CRUD das {{variaveis}} que o ADM cria. Uma vez cadastradas, aparecem no editor
+# de mensagem ao lado das variĂĄveis do sistema e valem para qualquer evento.
+module Admin
+ class VariaveisPersonalizadasController < ApplicationController
+ include Auditavel
+
+ before_action :set_variavel, only: %i[edit update destroy]
+
+ def index
+ authorize VariavelPersonalizada
+ @variaveis = policy_scope(VariavelPersonalizada).ordenadas
+ # Mostrar o catĂĄlogo do sistema junto evita o erro mais comum: cadastrar
+ # {{data}} sem saber que jĂĄ existe (o model recusa, mas melhor avisar antes).
+ @do_sistema = Notificacao::CatalogoVariaveis.por_origem.except(:personalizada)
+ end
+
+ def new
+ @variavel = VariavelPersonalizada.new(ativo: true)
+ authorize @variavel
+ end
+
+ def create
+ @variavel = VariavelPersonalizada.new(variavel_params)
+ authorize @variavel
+
+ if @variavel.save
+ auditar!(:criar, @variavel, dados_novos: { chave: @variavel.chave, valor: @variavel.valor })
+ redirect_to admin_variaveis_path, notice: "VariĂĄvel #{@variavel.marcador} criada."
+ else
+ render :new, status: :unprocessable_entity
+ end
+ end
+
+ def edit; end
+
+ def update
+ anterior = @variavel.valor
+
+ if @variavel.update(variavel_params)
+ auditar!(:editar, @variavel,
+ dados_anteriores: { valor: anterior },
+ dados_novos: { valor: @variavel.valor })
+ redirect_to admin_variaveis_path, notice: "VariĂĄvel #{@variavel.marcador} atualizada."
+ else
+ render :edit, status: :unprocessable_entity
+ end
+ end
+
+ def destroy
+ marcador = @variavel.marcador
+ @variavel.destroy
+ auditar!(:excluir, @variavel, dados_anteriores: { chave: @variavel.chave })
+
+ # A variĂĄvel some, mas o texto que a usava continua lĂĄ: sem valor, o
+ # renderizador troca por vazio. Avisar Ă© melhor do que a mensagem sair com
+ # um buraco que ninguém relaciona a esta exclusão.
+ redirect_to admin_variaveis_path,
+ alert: "VariĂĄvel #{marcador} excluĂda. Mensagens que a usavam passam a mostrar espaço em branco no lugar."
+ end
+
+ private
+
+ def set_variavel
+ @variavel = VariavelPersonalizada.find(params[:id])
+ authorize @variavel
+ end
+
+ def variavel_params
+ params.require(:variavel_personalizada).permit(:chave, :valor, :descricao, :ativo)
+ end
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 76e68e7..b733145 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -7,14 +7,31 @@ class ApplicationController < ActionController::Base
before_action :set_tema
before_action :preparar_splash
- # Pundit: redireciona se nĂŁo autorizado
- rescue_from Pundit::NotAuthorizedError do |e|
- flash[:alert] = "VocĂȘ nĂŁo tem permissĂŁo para realizar esta ação."
- redirect_to root_path
+ # Pundit: redireciona se nĂŁo autorizado.
+ #
+ # â ïž NĂO redirecione para `root_path` aqui: a raiz Ă© o dashboard, e um perfil
+ # sem `dashboard.ver` entraria em loop (nega â raiz â nega). O destino Ă© a
+ # primeira tela que a PESSOA pode abrir; quem nĂŁo pode abrir nenhuma vai para a
+ # tela de sem-acesso, que explica a quem pedir.
+ rescue_from Pundit::NotAuthorizedError do |_e|
+ flash[:alert] = 'VocĂȘ nĂŁo tem permissĂŁo para acessar essa ĂĄrea.'
+ redirect_to destino_permitido
end
private
+ # Rota inicial deste usuĂĄrio (ver User#home_rota). `sem_acesso_path` Ă© o fim de
+ # linha: perfil sem nenhuma permissĂŁo.
+ def destino_permitido
+ rota = current_user&.home_rota
+ return sem_acesso_path if rota.nil?
+
+ # Evita loop quando a prĂłpria tela negada Ă© a home calculada.
+ destino = public_send(rota)
+ destino == request.path ? sem_acesso_path : destino
+ end
+ helper_method :destino_permitido
+
def set_tema
@tema = current_user&.tema_preferido || 'dark'
end
@@ -28,8 +45,11 @@ class ApplicationController < ActionController::Base
@mostrar_splash = session.delete(:mostrar_splash)
end
+ # Mesma regra do Users::SessionsController: o destino Ă© a primeira tela que o
+ # PERFIL da pessoa permite, nĂŁo uma rota fixa por papel.
def after_sign_in_path_for(resource)
session[:mostrar_splash] = true
- resource.motorista? ? motorista_dashboard_path : dashboard_path
+ rota = resource.home_rota
+ rota ? public_send(rota) : sem_acesso_path
end
end
diff --git a/app/controllers/consolidacao_entregas_controller.rb b/app/controllers/consolidacao_entregas_controller.rb
index df04cc8..ce980ab 100644
--- a/app/controllers/consolidacao_entregas_controller.rb
+++ b/app/controllers/consolidacao_entregas_controller.rb
@@ -433,7 +433,7 @@ class ConsolidacaoEntregasController < ApplicationController
end
def bloquear_se_finalizada
- return unless @consolidacao.finalizada? && current_user.operador?
+ return unless @consolidacao.finalizada? && !current_user.pode?('consolidacao.editar_finalizada')
redirect_to @consolidacao, alert: 'Consolidação finalizada â apenas admin/gerente podem editar.'
end
diff --git a/app/controllers/consolidacoes_controller.rb b/app/controllers/consolidacoes_controller.rb
index 46cbaea..740dfd4 100644
--- a/app/controllers/consolidacoes_controller.rb
+++ b/app/controllers/consolidacoes_controller.rb
@@ -66,7 +66,7 @@ class ConsolidacoesController < ApplicationController
# filtros da tela. Com ?detalhe=NOME sai sĂł o extrato daquele motorista (com
# espaço de assinatura).
def totais_pdf
- authorize Consolidacao, :index?
+ authorize Consolidacao, :exportar_pdf?
inicio, fim = periodo_totais
totais = Analytics::TotaisPorMotorista.new(
@@ -245,7 +245,7 @@ class ConsolidacoesController < ApplicationController
# POST /consolidacoes/:id/finalizar
def finalizar
- authorize @consolidacao, :update?
+ authorize @consolidacao, :finalizar?
if @consolidacao.finalizar!(current_user)
auditar!(:finalizar, @consolidacao)
@@ -262,7 +262,7 @@ class ConsolidacoesController < ApplicationController
# não då para consolidar (não 100% classificados) e finaliza a consolidação. Os
# veĂculos consolidados/prontos seguem no fechamento; os pendentes ficam de fora.
def finalizar_parcial
- authorize @consolidacao, :update?
+ authorize @consolidacao, :finalizar_parcial?
arquivados = @consolidacao.arquivar_veiculos_pendentes!(current_user)
if @consolidacao.finalizar!(current_user)
@@ -278,7 +278,7 @@ class ConsolidacoesController < ApplicationController
# POST /consolidacoes/:id/arquivar
def arquivar
- authorize @consolidacao, :destroy?
+ authorize @consolidacao, :arquivar?
@consolidacao.arquivar!(current_user)
auditar!(:arquivar, @consolidacao)
redirect_to consolidacoes_path, notice: 'Consolidação arquivada.'
@@ -292,7 +292,7 @@ class ConsolidacoesController < ApplicationController
# POST /consolidacoes/:id/reativar â restaura uma consolidação arquivada
def reativar
- authorize @consolidacao, :destroy?
+ authorize @consolidacao, :reativar?
@consolidacao.reativar!(current_user)
auditar!(:reativar, @consolidacao)
redirect_to consolidacoes_path, notice: 'Consolidação reativada.'
@@ -350,7 +350,7 @@ class ConsolidacoesController < ApplicationController
# consolidação (reversĂvel). Ele sai dos totais/pagamento/gate, mas os dados
# ficam guardados e podem ser restaurados. NĂŁo arquiva motorista jĂĄ pago.
def arquivar_motorista
- authorize @consolidacao, :update?
+ authorize @consolidacao, :gerir_motoristas?
cm = @consolidacao.consolidacao_motoristas.find(params[:consolidacao_motorista_id])
if cm.pago?
@@ -367,7 +367,7 @@ class ConsolidacoesController < ApplicationController
# POST /consolidacoes/:id/desarquivar_motorista â restaura um motorista arquivado.
def desarquivar_motorista
- authorize @consolidacao, :update?
+ authorize @consolidacao, :gerir_motoristas?
cm = @consolidacao.consolidacao_motoristas.find(params[:consolidacao_motorista_id])
cm.desarquivar!(current_user)
@@ -382,7 +382,7 @@ class ConsolidacoesController < ApplicationController
# como consolidacao_entregas se liga ao motorista por STRING (motorista_nome,
# sem FK/cascade), apagamos os lançamentos dele à mão antes de remover o registro.
def excluir_motorista
- authorize @consolidacao, :destroy?
+ authorize @consolidacao, :excluir_motorista?
cm = @consolidacao.consolidacao_motoristas.find(params[:consolidacao_motorista_id])
if cm.pago?
@@ -407,7 +407,7 @@ class ConsolidacoesController < ApplicationController
# consolidação (ou restaura, se estava arquivado). Ătil para refazer rĂĄpido
# depois de excluir/arquivar por erro de precificação.
def adicionar_motorista
- authorize @consolidacao, :update?
+ authorize @consolidacao, :gerir_motoristas?
nome = params[:motorista_nome].to_s.strip
if nome.blank?
@@ -435,7 +435,7 @@ class ConsolidacoesController < ApplicationController
# GET /consolidacoes/:id/gerar_pdf_relatorio?motorista=X
def gerar_pdf_relatorio
- authorize @consolidacao, :show?
+ authorize @consolidacao, :exportar_pdf?
motorista = params[:motorista]
pdf = Pdf::RelatorioMotoristaPdf.new(consolidacao: @consolidacao, motorista: motorista)
@@ -449,7 +449,7 @@ class ConsolidacoesController < ApplicationController
# GET /consolidacoes/:id/gerar_pdf_financeiro â relatĂłrio financeiro da consolidação
def gerar_pdf_financeiro
- authorize @consolidacao, :show?
+ authorize @consolidacao, :exportar_pdf?
pdf = Pdf::RelatorioFinanceiroConsolidacaoPdf.new(consolidacao: @consolidacao)
auditar!(:editar, @consolidacao, dados_novos: { pdf: 'financeiro' })
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 5a46284..ab41249 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -3,11 +3,16 @@ class DashboardController < ApplicationController
MAX_DIAS_GRAFICO = 92 # limita a granularidade diĂĄria do grĂĄfico (â 1 trimestre)
def index
- skip_authorization
-
# Motorista tem painel prĂłprio â nĂŁo enxerga o dashboard administrativo
return redirect_to(motorista_dashboard_path) if current_user.motorista?
+ authorize :dashboard, :ver?
+
+ # Custo, pagamento e ticket médio são permissão à parte: um perfil de
+ # acompanhamento vĂȘ a operação sem ver quanto ela custa. Antes o controller
+ # usava skip_authorization e o papel `externo` enxergava tudo.
+ @ver_financeiro = current_user.pode?('dashboard.financeiro')
+
@periodo_inicio, @periodo_fim = periodo_selecionado
carregar_dados_dashboard
end
@@ -15,9 +20,11 @@ class DashboardController < ApplicationController
# GET /dashboard/relatorio_financeiro?inicio=&fim=&operacoes[]=
# RelatĂłrio financeiro do perĂodo (PDF) â mesmos nĂșmeros da tela.
def relatorio_financeiro
- skip_authorization
return redirect_to(motorista_dashboard_path) if current_user.motorista?
+ authorize :dashboard, :financeiro?
+ @ver_financeiro = true
+
@periodo_inicio, @periodo_fim = periodo_selecionado
carregar_dados_dashboard
@@ -58,9 +65,10 @@ class DashboardController < ApplicationController
# PLANEJADA: Ă© o Ășnico recorte sem checkout, entĂŁo planned_date Ă© o eixo
# possĂvel. Mesmo recorte do card, para o total bater.
def pendentes
- skip_authorization
return redirect_to(motorista_dashboard_path) if current_user.motorista?
+ authorize :dashboard, :pendentes?
+
@periodo_inicio, @periodo_fim = periodo_selecionado
@operacoes_agrupadas = Operacao.agrupadas_por_mes
@operacao_filtro = Operacao.sanitizar(params[:operacoes])
@@ -179,12 +187,13 @@ class DashboardController < ApplicationController
@consolidacoes_abertas = @consolidacoes_mes.where(status: :rascunho).count
@consolidacoes_fechadas = @consolidacoes_mes.where(status: :finalizada).count
- # Pagamentos: pago vs pendente entre as consolidaçÔes FINALIZADAS do perĂodo
- # (medido por motorista â fonte: consolidacao_motoristas).
- carregar_dados_pagamentos
-
- # Visão financeira por operação (KPIs + gråficos)
- carregar_dados_financeiro
+ # Pagamentos e visĂŁo financeira sĂł sĂŁo consultados por quem pode vĂȘ-los: sem a
+ # permissĂŁo, o dado nĂŁo chega Ă pĂĄgina (a view esconde os blocos, mas o certo
+ # Ă© nem carregar â Ă© consulta cara e Ă© informação de custo).
+ if @ver_financeiro
+ carregar_dados_pagamentos
+ carregar_dados_financeiro
+ end
# HistĂłrico estimado mais recente
@historico_recente = HistoricoEstimado.order(created_at: :desc).limit(5)
diff --git a/app/controllers/operacoes_dashboard_controller.rb b/app/controllers/operacoes_dashboard_controller.rb
index 31c53a4..a6eb15e 100644
--- a/app/controllers/operacoes_dashboard_controller.rb
+++ b/app/controllers/operacoes_dashboard_controller.rb
@@ -82,7 +82,9 @@ class OperacoesDashboardController < ApplicationController
# Baixa a planilha Entregas (.xlsx) da operação jå preenchida com o resultado
# do rastreio â o modelo que Ă© entregue ao cliente.
def baixar_planilha
- authorize :dashboard, :operacoes?
+ # Chave prĂłpria: aqui o dado do cliente SAI do sistema (.xlsx), o que Ă©
+ # diferente de olhar os nĂșmeros na tela.
+ authorize :dashboard, :exportar?
tabela = params[:operacao].to_s
unless Operacao.valida?(tabela)
diff --git a/app/controllers/paginas_controller.rb b/app/controllers/paginas_controller.rb
new file mode 100644
index 0000000..e5eea05
--- /dev/null
+++ b/app/controllers/paginas_controller.rb
@@ -0,0 +1,12 @@
+# app/controllers/paginas_controller.rb
+#
+# Telas do sistema que nĂŁo pertencem a nenhum recurso.
+class PaginasController < ApplicationController
+ # Quem chega aqui estĂĄ autenticado, mas o perfil de acesso nĂŁo libera nenhuma
+ # tela. Sem esta pĂĄgina, o "acesso negado" mandaria de volta para a raiz â que
+ # tambĂ©m nega â e a pessoa ficaria num loop de redirecionamento.
+ def sem_acesso
+ skip_authorization
+ @perfil = current_user.perfil_acesso
+ end
+end
diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb
index 64a30bd..a79afb5 100644
--- a/app/controllers/users/sessions_controller.rb
+++ b/app/controllers/users/sessions_controller.rb
@@ -20,14 +20,13 @@ class Users::SessionsController < Devise::SessionsController
protected
+ # Para onde a pessoa vai depois de entrar. NĂO Ă© mais "motorista â painel,
+ # resto â dashboard": com perfis de acesso, alguĂ©m pode nĂŁo ter o dashboard e
+ # cairia direto numa tela negada. `home_rota` devolve a primeira tela permitida.
def after_sign_in_path_for(resource)
session[:mostrar_splash] = true # dispara a animação de abertura uma vez
- case resource.role
- when 'motorista'
- motorista_dashboard_path
- else
- dashboard_path
- end
+ rota = resource.home_rota
+ rota ? public_send(rota) : sem_acesso_path
end
def after_sign_out_path_for(resource_or_scope)
diff --git a/app/models/evento_notificacao.rb b/app/models/evento_notificacao.rb
index ec2634e..bccd9bf 100644
--- a/app/models/evento_notificacao.rb
+++ b/app/models/evento_notificacao.rb
@@ -78,7 +78,13 @@ class EventoNotificacao < ApplicationRecord
t&.utilizavel? ? t : nil
end
- def variaveis = Notificacao::Variaveis.para(gatilho)
+ # Todas as variĂĄveis do sistema, agrupadas por origem e jĂĄ marcadas com
+ # `aplicavel:` para este gatilho â a lista curta de antes obrigava deploy para
+ # o ADM escrever qualquer nĂșmero no texto.
+ def variaveis = Notificacao::CatalogoVariaveis.por_origem(gatilho)
+
+ # SĂł os nomes que ESTE gatilho preenche (usado pela invariante dos specs).
+ def variaveis_do_contexto = Notificacao::Variaveis.nomes(gatilho)
# Contatos que devem receber este evento, sem repetição â a mesma pessoa pode
# estar em dois grupos assinantes.
diff --git a/app/models/mensagem_template.rb b/app/models/mensagem_template.rb
index 1070ce9..199eeb9 100644
--- a/app/models/mensagem_template.rb
+++ b/app/models/mensagem_template.rb
@@ -39,7 +39,7 @@ class MensagemTemplate < ApplicationRecord
end
def preview(dados = nil)
- dados ||= Notificacao::Variaveis.amostra(evento_notificacao&.gatilho)
+ dados ||= Notificacao::CatalogoVariaveis.amostra(evento_notificacao&.gatilho)
r = renderizador(dados)
email? ? r.html : r.texto
end
diff --git a/app/models/perfil_acesso.rb b/app/models/perfil_acesso.rb
new file mode 100644
index 0000000..9de88d6
--- /dev/null
+++ b/app/models/perfil_acesso.rb
@@ -0,0 +1,62 @@
+# app/models/perfil_acesso.rb
+#
+# O conjunto de permissÔes que o ADM monta na tela e atribui a um usuårio.
+# O catĂĄlogo do que PODE ser marcado Ă© cĂłdigo (Permissao::TODAS); aqui fica sĂł a
+# escolha do ADM.
+class PerfilAcesso < ApplicationRecord
+ # Nome da tabela fixado: o Inflector pt-BR resolveria `perfil_acesso` como
+ # "perfil_acessos" (mesma pegadinha de ConsolidacaoMotorista e das tabelas de
+ # notificação). A tabela real é `perfis_acesso`.
+ self.table_name = 'perfis_acesso'
+
+ has_many :users, foreign_key: :perfil_acesso_id, dependent: :nullify, inverse_of: :perfil_acesso
+
+ validates :nome, presence: true
+ validates :nome, uniqueness: { case_sensitive: false }
+
+ before_validation :normalizar_permissoes
+
+ scope :ativos, -> { where(ativo: true) }
+ scope :ordenados, -> { order(Arel.sql('LOWER(nome)')) }
+ scope :de_sistema, -> { where(sistema: true) }
+
+ # Chaves que ainda existem no catĂĄlogo. Uma permissĂŁo removida do cĂłdigo nĂŁo
+ # pode travar um perfil salvo meses atrĂĄs â ela simplesmente deixa de valer.
+ def permissoes_validas
+ Permissao.filtrar(permissoes)
+ end
+
+ def pode?(chave)
+ permissoes_validas.include?(chave.to_s)
+ end
+
+ # Perfil de sistema Ă© editĂĄvel e desativĂĄvel, mas nĂŁo apagĂĄvel (apagar deixaria
+ # usuårios órfãos e a tela sem ponto de partida). Perfil em uso também não sai:
+ # o usuårio cairia no fallback do papel sem ninguém perceber.
+ def apagavel?
+ !sistema? && users.none?
+ end
+
+ # Este perfil ainda consegue reabrir a porta do sistema? Ă o que a trava do
+ # Ășltimo administrador pergunta antes de deixar salvar.
+ def administra_acesso?
+ ativo? && pode?('admin.perfis') && pode?('admin.usuarios_gerenciar')
+ end
+
+ def total_permissoes
+ permissoes_validas.size
+ end
+
+ # Um perfil que nĂŁo enxerga nada existe (Ă© uma forma de suspender acesso sem
+ # desativar a conta), mas a tela precisa avisar.
+ def vazio?
+ permissoes_validas.empty?
+ end
+
+ private
+
+ def normalizar_permissoes
+ self.nome = nome.to_s.strip
+ self.permissoes = Permissao.filtrar(permissoes)
+ end
+end
diff --git a/app/models/permissao.rb b/app/models/permissao.rb
new file mode 100644
index 0000000..ca9833b
--- /dev/null
+++ b/app/models/permissao.rb
@@ -0,0 +1,245 @@
+# 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.'
+ }
+ }.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
+ ].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
diff --git a/app/models/user.rb b/app/models/user.rb
index 19363f1..be5d55c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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
diff --git a/app/models/variavel_personalizada.rb b/app/models/variavel_personalizada.rb
new file mode 100644
index 0000000..30a9b3b
--- /dev/null
+++ b/app/models/variavel_personalizada.rb
@@ -0,0 +1,46 @@
+# app/models/variavel_personalizada.rb
+#
+# VariĂĄvel criada pelo ADM, com valor fixo, usĂĄvel em qualquer mensagem.
+class VariavelPersonalizada < ApplicationRecord
+ # Nome da tabela fixado: o Inflector pt-BR nĂŁo resolve variavel â variaveis.
+ self.table_name = 'variaveis_personalizadas'
+
+ # Mesmo formato que o renderizador aceita em {{...}}: letras minĂșsculas,
+ # nĂșmeros e _ . Sem isso, o ADM cadastraria "Telefone Suporte" e a variĂĄvel
+ # nunca seria substituĂda â falha muda dentro da mensagem enviada.
+ FORMATO_CHAVE = /\A[a-z][a-z0-9_]*\z/
+
+ before_validation :normalizar
+
+ validates :chave, presence: true, uniqueness: { case_sensitive: false },
+ format: { with: FORMATO_CHAVE,
+ message: 'use apenas letras minĂșsculas, nĂșmeros e _ (ex.: telefone_suporte)' }
+ validates :valor, presence: true
+ validate :nao_colidir_com_variavel_do_sistema
+
+ scope :ativas, -> { where(ativo: true) }
+ scope :ordenadas, -> { order(:chave) }
+
+ def marcador = "{{#{chave}}}"
+
+ private
+
+ def normalizar
+ self.chave = chave.to_s.strip.downcase.tr(' ', '_')
+ self.valor = valor.to_s.strip
+ end
+
+ # Deixar o ADM criar {{valor}} ou {{motorista}} faria a variĂĄvel dele
+ # sobrescrever (ou ser sobrescrita por) a do sistema, dependendo da ordem â
+ # exatamente o tipo de bug que nĂŁo aparece no preview e aparece no envio.
+ def nao_colidir_com_variavel_do_sistema
+ return if chave.blank?
+
+ reservadas = Notificacao::CatalogoVariaveis::SISTEMA.keys +
+ Notificacao::CatalogoVariaveis::CONTEXTO.keys +
+ Notificacao::CatalogoVariaveis::DADOS.keys
+ return unless reservadas.include?(chave)
+
+ errors.add(:chave, "jĂĄ existe no sistema â escolha outro nome (ex.: #{chave}_empresa)")
+ end
+end
diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb
index 348a51a..dd5b95b 100644
--- a/app/policies/application_policy.rb
+++ b/app/policies/application_policy.rb
@@ -15,7 +15,15 @@ class ApplicationPolicy
def edit? = update?
def destroy? = user.admin?
- # Helpers de papel disponĂveis para todas as policies (delegam para o User)
+ # Régua padrão de TODAS as policies: pergunta ao perfil de acesso do usuårio
+ # (User#pode?), nĂŁo ao papel dele. As chaves estĂŁo em Permissao::TODAS.
+ def pode?(chave)
+ user.pode?(chave)
+ end
+
+ # Helpers de papel â LEGADO. Continuam aqui porque `admin?` ainda distingue
+ # tipo de conta em pontos que nĂŁo sĂŁo permissĂŁo (ex.: escopo de listagem).
+ # Regra nova nĂŁo deve usĂĄ-los: use `pode?`.
def admin?
user.admin?
end
diff --git a/app/policies/auditoria_log_policy.rb b/app/policies/auditoria_log_policy.rb
new file mode 100644
index 0000000..417c968
--- /dev/null
+++ b/app/policies/auditoria_log_policy.rb
@@ -0,0 +1,16 @@
+# app/policies/auditoria_log_policy.rb
+#
+# Existe para a auditoria parar de cair no default genérico da ApplicationPolicy
+# (`policy_class: ApplicationPolicy` no controller): a régua de lå é a de
+# consolidação, e quem podia consolidar entrava na auditoria pela URL, embora o
+# menu nunca oferecesse.
+class AuditoriaLogPolicy < ApplicationPolicy
+ def index? = pode?('admin.auditoria')
+ def show? = index?
+
+ class Scope < Scope
+ def resolve
+ user.pode?('admin.auditoria') ? scope.all : scope.none
+ end
+ end
+end
diff --git a/app/policies/configuracao_notificacao_policy.rb b/app/policies/configuracao_notificacao_policy.rb
index 04b85c6..3f4133e 100644
--- a/app/policies/configuracao_notificacao_policy.rb
+++ b/app/policies/configuracao_notificacao_policy.rb
@@ -1,22 +1,7 @@
# app/policies/configuracao_notificacao_policy.rb
#
-# Mais restrito que ConfiguracaoPolicy (que libera index? para gerente): estas
-# sĂŁo credenciais de envio â senha de e-mail e token do Twilio â, entĂŁo sĂł ADM.
-#
-# â ïž Cada action nova do controller precisa de um mĂ©todo `?` aqui,
-# senĂŁo o `authorize` estoura.
+# Credenciais de SMTP/Twilio e limites de envio.
class ConfiguracaoNotificacaoPolicy < ApplicationPolicy
- def show?
- admin?
- end
-
- def update?
- admin?
- end
-
- class Scope < Scope
- def resolve
- scope.all
- end
- end
+ def show? = pode?('notificacao.credenciais')
+ def update? = show?
end
diff --git a/app/policies/configuracao_policy.rb b/app/policies/configuracao_policy.rb
index 5241764..76fe2c5 100644
--- a/app/policies/configuracao_policy.rb
+++ b/app/policies/configuracao_policy.rb
@@ -1,20 +1,10 @@
# app/policies/configuracao_policy.rb
class ConfiguracaoPolicy < ApplicationPolicy
- def index?
- admin_ou_gerente?
- end
+ def index? = pode?('admin.configuracoes')
+ def show? = index?
- def edit?
- admin?
- end
-
- def update?
- admin?
- end
-
- class Scope < Scope
- def resolve
- scope.all
- end
- end
+ # Alterar preço muda o valor de todo fechamento daqui para a frente â chave
+ # prĂłpria, separada de sĂł olhar a tabela.
+ def edit? = pode?('admin.configuracoes_editar')
+ def update? = edit?
end
diff --git a/app/policies/consolidacao_policy.rb b/app/policies/consolidacao_policy.rb
index 5104f43..90f2193 100644
--- a/app/policies/consolidacao_policy.rb
+++ b/app/policies/consolidacao_policy.rb
@@ -1,20 +1,42 @@
# app/policies/consolidacao_policy.rb
class ConsolidacaoPolicy < ApplicationPolicy
- def index? = user.pode_consolidar?
- def arquivadas? = user.pode_consolidar?
- def show? = user.pode_consolidar?
- def create? = user.pode_consolidar?
- def update? = user.pode_consolidar? && !record_finalizada_para_operador?
- def destroy? = user.admin? || user.gerente?
+ def index? = pode?('consolidacao.ver')
+ def arquivadas? = index?
+ def show? = index?
+ def create? = pode?('consolidacao.criar')
+ def new? = create?
- # Registrar/estornar pagamento: mesma régua de arquivar/excluir.
- def registrar_pagamento? = user.admin? || user.gerente?
- def cancelar_pagamento? = registrar_pagamento?
+ # Editar uma consolidação Jà FINALIZADA é outra permissão: mexer depois do
+ # fechamento muda o valor que o motorista vai receber. Antes isso era "operador
+ # nĂŁo pode, gerente pode" cravado no cĂłdigo.
+ def update?
+ pode?('consolidacao.editar') && (!record_finalizada? || pode?('consolidacao.editar_finalizada'))
+ end
+
+ def edit? = update?
+
+ def finalizar? = pode?('consolidacao.finalizar')
+ def finalizar_parcial? = finalizar?
+
+ def arquivar? = pode?('consolidacao.arquivar')
+ def reativar? = arquivar?
+
+ def destroy? = pode?('consolidacao.excluir')
+ def excluir_motorista? = destroy?
+
+ # Pagamento: registrar e estornar sĂŁo chaves distintas â estornar desfaz algo
+ # que jĂĄ foi comunicado ao motorista.
+ def registrar_pagamento? = pode?('consolidacao.registrar_pagamento')
+ def cancelar_pagamento? = pode?('consolidacao.cancelar_pagamento')
+
+ # Quem entra e quem sai do fechamento (sem excluir de vez).
+ def gerir_motoristas? = pode?('consolidacao.gerir_motoristas') && update?
+
+ def exportar_pdf? = pode?('consolidacao.exportar_pdf')
private
- # Operador não mexe em consolidação finalizada
- def record_finalizada_para_operador?
- record.respond_to?(:finalizada?) && record.finalizada? && user.operador?
+ def record_finalizada?
+ record.respond_to?(:finalizada?) && record.finalizada?
end
end
diff --git a/app/policies/contato_policy.rb b/app/policies/contato_policy.rb
index aa0adbb..7088c8f 100644
--- a/app/policies/contato_policy.rb
+++ b/app/policies/contato_policy.rb
@@ -1,20 +1,24 @@
# app/policies/contato_policy.rb
#
-# Contatos e grupos sĂŁo a lista de quem recebe mensagem em nome da empresa â
-# admin e gerente administram, operador nĂŁo.
-#
-# â ïž Cada action nova do controller precisa de um `?` aqui, senĂŁo o
-# `authorize` estoura.
+# Contatos e grupos (GrupoContatoPolicy herda esta) â quem RECEBE as mensagens.
class ContatoPolicy < ApplicationPolicy
- def index? = admin_ou_gerente?
- def show? = admin_ou_gerente?
- def create? = admin_ou_gerente?
- def new? = create?
- def update? = admin_ou_gerente?
- def edit? = update?
- def destroy? = admin?
+ def index? = pode?('notificacao.contatos')
+ def show? = index?
+
+ def create? = pode?('notificacao.contatos_gerenciar')
+ def new? = create?
+ def update? = create?
+ def edit? = update?
+
+ # Excluir destinatĂĄrio Ă© separado de editar: some do histĂłrico de quem recebe.
+ def destroy? = pode?('notificacao.contatos_excluir')
+
+ # Grupos do WhatsApp carregados sob demanda pelo form.
+ def grupos_whatsapp? = create?
class Scope < Scope
- def resolve = scope.all
+ def resolve
+ user.pode?('notificacao.contatos') ? scope.all : scope.none
+ end
end
end
diff --git a/app/policies/dashboard_policy.rb b/app/policies/dashboard_policy.rb
index d3591de..f3687c5 100644
--- a/app/policies/dashboard_policy.rb
+++ b/app/policies/dashboard_policy.rb
@@ -1,15 +1,17 @@
# app/policies/dashboard_policy.rb
-class DashboardPolicy < Struct.new(:user, :dashboard)
- def metricas?
- user.admin? || user.gerente? || user.operador?
- end
+#
+# Policy headless (`authorize :dashboard, :x?`). Deixou de ser Struct para herdar
+# o `pode?` da ApplicationPolicy.
+class DashboardPolicy < ApplicationPolicy
+ def ver? = pode?('dashboard.ver')
+ def index? = ver?
- def index?
- user.admin? || user.gerente?
- end
+ # Custos, pagamentos, relatĂłrio financeiro e entregas em aberto. Era o buraco
+ # do `externo`: o controller usava skip_authorization e ele via tudo.
+ def financeiro? = pode?('dashboard.financeiro')
+ def pendentes? = financeiro?
- # Dashboard de operaçÔes: todos menos motorista (mesma regra do dashboard HTML).
- def operacoes?
- !user.motorista?
- end
+ def operacoes? = pode?('dashboard.operacoes')
+ def exportar? = pode?('dashboard.exportar')
+ def metricas? = pode?('dashboard.metricas')
end
diff --git a/app/policies/edicao_lancamento_policy.rb b/app/policies/edicao_lancamento_policy.rb
index 5ab7ed7..c03dd32 100644
--- a/app/policies/edicao_lancamento_policy.rb
+++ b/app/policies/edicao_lancamento_policy.rb
@@ -1,16 +1,11 @@
# app/policies/edicao_lancamento_policy.rb
#
-# Correção de lançamento grava DIRETO na base de produção do SimpliRoute â
-# operação sensĂvel e irreversĂvel. Restrita a admin.
-# Policy "headless": autorizada com o sĂmbolo :edicao_lancamento (sem model).
+# Policy headless (`authorize :edicao_lancamento`). Escreve na API do SimpliRoute:
+# altera o dado de ORIGEM do rastreio, entĂŁo Ă© chave prĂłpria.
class EdicaoLancamentoPolicy < ApplicationPolicy
- # â ïž Uma entrada por action do controller: o Pundit chama "?" e, se o
- # mĂ©todo nĂŁo existir, levanta NoMethodError (500) â foi o que aconteceu ao
- # adicionar `carregar` sem passar por aqui. Ação nova no controller â linha
- # nova aqui.
- def show? = admin?
- def buscar? = admin?
- def carregar? = admin?
- def atualizar? = admin?
- def historico? = admin?
+ def show? = pode?('admin.edicao_lancamento')
+ def buscar? = show?
+ def carregar? = show?
+ def atualizar? = show?
+ def historico? = show?
end
diff --git a/app/policies/evento_notificacao_policy.rb b/app/policies/evento_notificacao_policy.rb
index f648b18..2e3bfd0 100644
--- a/app/policies/evento_notificacao_policy.rb
+++ b/app/policies/evento_notificacao_policy.rb
@@ -1,23 +1,25 @@
# app/policies/evento_notificacao_policy.rb
-#
-# Eventos definem o que a empresa dispara e para quem â inclusive o botĂŁo de
-# disparo manual, que manda mensagem de verdade. SĂł ADM.
class EventoNotificacaoPolicy < ApplicationPolicy
- def index? = admin?
- def show? = admin?
- def create? = admin?
- def new? = create?
- def update? = admin?
- def edit? = update?
- def disparar? = admin?
+ def index? = pode?('notificacao.eventos')
+ def show? = index?
- # Evento de sistema nĂŁo se apaga: o cĂłdigo dispara por `chave` e ficaria
- # chamando no vazio. A tela oferece desativar.
+ def create? = pode?('notificacao.eventos_gerenciar')
+ def new? = create?
+ def update? = create?
+ def edit? = update?
+
+ # Disparar manda mensagem DE VERDADE, em nome da empresa â chave prĂłpria,
+ # separada de configurar o evento.
+ def disparar? = pode?('notificacao.disparar_manual')
+
+ # Evento de sistema nĂŁo Ă© apagĂĄvel: o cĂłdigo dispara por ele.
def destroy?
- admin? && record.respond_to?(:apagavel?) && record.apagavel?
+ create? && record.respond_to?(:apagavel?) && record.apagavel?
end
class Scope < Scope
- def resolve = scope.all
+ def resolve
+ user.pode?('notificacao.eventos') ? scope.all : scope.none
+ end
end
end
diff --git a/app/policies/notificacao_envio_policy.rb b/app/policies/notificacao_envio_policy.rb
index 852fd1d..eb50e2a 100644
--- a/app/policies/notificacao_envio_policy.rb
+++ b/app/policies/notificacao_envio_policy.rb
@@ -1,12 +1,13 @@
# app/policies/notificacao_envio_policy.rb
#
-# Log de envios: o corpo das mensagens passa por aqui, entĂŁo leitura Ă© de
-# admin/gerente. Nada Ă© editĂĄvel â o log Ă© registro, nĂŁo cadastro.
+# HistĂłrico do que saiu (e do que falhou).
class NotificacaoEnvioPolicy < ApplicationPolicy
- def index? = admin_ou_gerente?
- def show? = admin_ou_gerente?
+ def index? = pode?('notificacao.envios')
+ def show? = index?
class Scope < Scope
- def resolve = scope.all
+ def resolve
+ user.pode?('notificacao.envios') ? scope.all : scope.none
+ end
end
end
diff --git a/app/policies/perfil_acesso_policy.rb b/app/policies/perfil_acesso_policy.rb
new file mode 100644
index 0000000..de040db
--- /dev/null
+++ b/app/policies/perfil_acesso_policy.rb
@@ -0,0 +1,24 @@
+# app/policies/perfil_acesso_policy.rb
+#
+# Quem monta perfil decide o acesso de todo mundo â Ă© a permissĂŁo mais sensĂvel
+# do sistema, por isso ela Ă© uma chave prĂłpria (`admin.perfis`) e nĂŁo vem junto
+# de "gerenciar usuĂĄrios".
+class PerfilAcessoPolicy < ApplicationPolicy
+ def index? = user.pode?('admin.perfis')
+ def show? = index?
+ def create? = index?
+ def new? = create?
+ def update? = index?
+ def edit? = update?
+
+ # Perfil de sistema ou em uso nĂŁo sai (ver PerfilAcesso#apagavel?).
+ def destroy?
+ index? && record.respond_to?(:apagavel?) && record.apagavel?
+ end
+
+ class Scope < Scope
+ def resolve
+ user.pode?('admin.perfis') ? scope.all : scope.none
+ end
+ end
+end
diff --git a/app/policies/planilha_simpli_route_policy.rb b/app/policies/planilha_simpli_route_policy.rb
index 69adf9b..d5d233a 100644
--- a/app/policies/planilha_simpli_route_policy.rb
+++ b/app/policies/planilha_simpli_route_policy.rb
@@ -1,9 +1,7 @@
-# app/policies/planilha_simpliroute_policy.rb
+# app/policies/planilha_simpli_route_policy.rb
#
-# Geração da planilha de carga do SimpliRoute â tarefa de preparação de operação.
-# Liberada para admin e gerente (quem prepara as cargas). Policy "headless"
-# (autorizada com o sĂmbolo :planilha_simpliroute).
+# Policy headless (`authorize :planilha_simpli_route`).
class PlanilhaSimpliRoutePolicy < ApplicationPolicy
- def show? = admin_ou_gerente?
- def baixar? = admin_ou_gerente?
+ def show? = pode?('admin.planilha_simpli_route')
+ def baixar? = show?
end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 09dcddd..8c54205 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -1,44 +1,35 @@
# app/policies/user_policy.rb
class UserPolicy < ApplicationPolicy
- def index?
- admin_ou_gerente?
- end
+ def index? = pode?('admin.usuarios')
- def show?
- admin? || record == user
- end
+ # Cada um enxerga a prĂłpria ficha (Ă© assim que a tela de perfil pessoal
+ # funciona), além de quem administra usuårios.
+ def show? = pode?('admin.usuarios') || record == user
- def create?
- admin?
- end
+ def create? = pode?('admin.usuarios_gerenciar')
+ def new? = create?
- def new?
- create?
- end
+ # â ïž `record == user` CONTINUA aqui de propĂłsito: Ă© o que permite alguĂ©m editar
+ # o prĂłprio cadastro (nome, senha). O que NĂO pode Ă© essa mesma pessoa mudar o
+ # prĂłprio papel/perfil e se promover â isso Ă© barrado em #alterar_acesso?, que
+ # o controller usa para decidir quais campos aceita.
+ def update? = pode?('admin.usuarios_gerenciar') || record == user
+ def edit? = update?
- def update?
- admin? || record == user
- end
+ def destroy? = create? && record != user
+ def toggle_ativo? = create? && record != user
- def edit?
- update?
- end
-
- def destroy?
- admin? && record != user
- end
-
- def toggle_ativo?
- admin?
- end
-
- # admin? e admin_ou_gerente? vĂȘm da ApplicationPolicy.
+ # Pode mexer no TIPO DE CONTA e no PERFIL DE ACESSO de alguém? Nunca no
+ # prĂłprio: senĂŁo qualquer usuĂĄrio se promove editando a prĂłpria ficha.
+ def alterar_acesso? = pode?('admin.usuarios_gerenciar') && record != user
class Scope < Scope
def resolve
+ # Continua por papel: filtrar por permissĂŁo exigiria carregar o perfil de
+ # cada linha (N+1) para responder algo que a policy jå cobre na ação.
if user.admin?
scope.all
- elsif user.gerente?
+ elsif user.pode?('admin.usuarios')
scope.where.not(role: :admin)
else
scope.where(id: user.id)
diff --git a/app/policies/variavel_personalizada_policy.rb b/app/policies/variavel_personalizada_policy.rb
new file mode 100644
index 0000000..a39b44c
--- /dev/null
+++ b/app/policies/variavel_personalizada_policy.rb
@@ -0,0 +1,19 @@
+# app/policies/variavel_personalizada_policy.rb
+#
+# VariĂĄvel personalizada entra no texto de mensagens enviadas em nome da empresa
+# â mesma rĂ©gua de quem configura evento.
+class VariavelPersonalizadaPolicy < ApplicationPolicy
+ def index? = pode?('notificacao.variaveis')
+ def show? = index?
+ def create? = index?
+ def new? = create?
+ def update? = index?
+ def edit? = update?
+ def destroy? = index?
+
+ class Scope < Scope
+ def resolve
+ user.pode?('notificacao.variaveis') ? scope.all : scope.none
+ end
+ end
+end
diff --git a/app/policies/whatsapp_sessao_policy.rb b/app/policies/whatsapp_sessao_policy.rb
index 43efe7a..571b7d0 100644
--- a/app/policies/whatsapp_sessao_policy.rb
+++ b/app/policies/whatsapp_sessao_policy.rb
@@ -1,13 +1,9 @@
# app/policies/whatsapp_sessao_policy.rb
#
-# Parear o WhatsApp por QR Ă© ligar o nĂșmero da empresa a este servidor, e
-# desconectar derruba o canal para todo mundo. SĂł ADM.
+# Policy headless (`authorize :whatsapp_sessao`). Parear o WhatsApp Ă© o que
+# habilita mandar mensagem em nome da empresa.
class WhatsappSessaoPolicy < ApplicationPolicy
- def show? = admin?
- def desconectar? = admin?
- def testar? = admin?
-
- class Scope < Scope
- def resolve = scope.all
- end
+ def show? = pode?('notificacao.whatsapp_sessao')
+ def desconectar? = show?
+ def testar? = show?
end
diff --git a/app/services/notificacao/catalogo_variaveis.rb b/app/services/notificacao/catalogo_variaveis.rb
new file mode 100644
index 0000000..af6306a
--- /dev/null
+++ b/app/services/notificacao/catalogo_variaveis.rb
@@ -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 01â14/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
diff --git a/app/services/notificacao/despachante.rb b/app/services/notificacao/despachante.rb
index 841cb27..9e3f80e 100644
--- a/app/services/notificacao/despachante.rb
+++ b/app/services/notificacao/despachante.rb
@@ -93,7 +93,25 @@ module Notificacao
# string â o custo por destinatĂĄrio Ă© irrelevante perto do envio em si.
def dados_para(contato, user)
nome = contato&.primeiro_nome.presence || user&.nome.to_s.split.first
- @dados.merge('contato' => nome.to_s)
+ dados_completos.merge('contato' => nome.to_s)
+ end
+
+ # O contexto do gatilho + as variĂĄveis que o TEMPLATE usa e o gatilho nĂŁo
+ # conhece ({{entregas_mes}}, {{telefone_suporte}}, {{hora}}âŠ).
+ #
+ # Resolvido UMA vez por disparo, nĂŁo por destinatĂĄrio: `{{entregas_mes}}` Ă©
+ # uma query, e o valor Ă© o mesmo para todo mundo â sĂł `{{contato}}` muda.
+ def dados_completos
+ @dados_completos ||= ResolvedorVariaveis.completar(textos_do_disparo, @dados)
+ end
+
+ # Tudo que serĂĄ interpolado: os blocos dos dois templates (o evento pode ter
+ # WhatsApp e e-mail com textos diferentes), o assunto e o corpo padrĂŁo.
+ def textos_do_disparo
+ [@assunto, @corpo,
+ @evento.template_utilizavel('whatsapp')&.blocos,
+ @evento.template_utilizavel('email')&.blocos,
+ @evento.template_utilizavel('email')&.assunto]
end
# O template do editor vence; sem template utilizĂĄvel, cai no texto que o
diff --git a/app/services/notificacao/formatador.rb b/app/services/notificacao/formatador.rb
new file mode 100644
index 0000000..96e9472
--- /dev/null
+++ b/app/services/notificacao/formatador.rb
@@ -0,0 +1,31 @@
+# app/services/notificacao/formatador.rb
+#
+# Formatação dos valores que entram numa mensagem. Existe porque o mesmo `moeda`
+# estava copiado em `Notificacao::Gatilhos` e em `NotificacaoService` â e agora o
+# catĂĄlogo de variĂĄveis tambĂ©m precisa dele, o que faria trĂȘs cĂłpias.
+#
+# Métodos de módulo (sem estado): é chamado de model, job, service e view.
+module Notificacao
+ module Formatador
+ module_function
+
+ # R$ 1.234,56 â separador de milhar incluĂdo (o `number_to_currency` do Rails
+ # depende de locale carregado, e isto roda também dentro de job).
+ def moeda(valor)
+ inteiro, decimais = format('%.2f', valor.to_f).split('.')
+ inteiro = inteiro.reverse.gsub(/(\d{3})(?=\d)/, '\1.').reverse
+ "R$ #{inteiro},#{decimais}"
+ end
+
+ def periodo(inicio, fim)
+ return '' if inicio.blank? || fim.blank?
+
+ "#{inicio.to_date.strftime('%d/%m/%Y')} a #{fim.to_date.strftime('%d/%m/%Y')}"
+ end
+
+ # NĂșmeros grandes ficam ilegĂveis sem separador numa mensagem de WhatsApp.
+ def numero(valor)
+ valor.to_i.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\1.').reverse
+ end
+ end
+end
diff --git a/app/services/notificacao/gatilhos.rb b/app/services/notificacao/gatilhos.rb
index bd89e21..7cf8f8a 100644
--- a/app/services/notificacao/gatilhos.rb
+++ b/app/services/notificacao/gatilhos.rb
@@ -121,13 +121,8 @@ module Notificacao
nil
end
- # Moeda BR com separador de milhar. Duplica o helper do NotificacaoService de
- # propĂłsito: este mĂłdulo Ă© chamado de dentro de models e jobs, onde puxar o
- # service inteiro sĂł para formatar nĂșmero seria acoplamento Ă toa.
- def moeda(valor)
- inteiro, decimais = format('%.2f', valor.to_f).split('.')
- inteiro = inteiro.reverse.gsub(/(\d{3})(?=\d)/, '\1.').reverse
- "R$ #{inteiro},#{decimais}"
- end
+ # Formatação vive em Notificacao::Formatador (era copiada aqui e no
+ # NotificacaoService; com o catĂĄlogo de variĂĄveis seriam trĂȘs cĂłpias).
+ def moeda(valor) = Formatador.moeda(valor)
end
end
diff --git a/app/services/notificacao/resolvedor_variaveis.rb b/app/services/notificacao/resolvedor_variaveis.rb
new file mode 100644
index 0000000..7fbc362
--- /dev/null
+++ b/app/services/notificacao/resolvedor_variaveis.rb
@@ -0,0 +1,90 @@
+# app/services/notificacao/resolvedor_variaveis.rb
+#
+# Completa o contexto de um disparo com as variĂĄveis que o template REALMENTE
+# usa. Ă o que permite escrever {{entregas_mes}} ou {{telefone_suporte}} numa
+# mensagem sem que o gatilho precise saber dessas variĂĄveis.
+#
+# SOB DEMANDA, de propĂłsito: `{{entregas_mes}}` Ă© uma query no espelho de
+# rastreio. Resolver o catĂĄlogo inteiro em todo disparo faria dezenas de
+# consultas para preencher variåveis que ninguém escreveu.
+#
+# PrecedĂȘncia (a primeira que responde vence):
+# 1. o que o gatilho jĂĄ mandou (contexto real do acontecimento);
+# 2. variĂĄvel de sistema (empresa, data, horaâŠ);
+# 3. variĂĄvel de dados (consulta ao banco);
+# 4. variĂĄvel personalizada do ADM.
+#
+# â ïž NADA aqui levanta. Uma variĂĄvel que falha vira string vazia â a mesma regra
+# do renderizador, e pelo mesmo motivo: mensagem com buraco Ă© ruim, disparo
+# derrubado por causa de um texto Ă© pior.
+module Notificacao
+ module ResolvedorVariaveis
+ MARCADOR = /\{\{\s*(\w+)\s*\}\}/
+
+ module_function
+
+ # textos: tudo que serĂĄ interpolado (blocos do template, assunto, corpo).
+ # dados: o contexto que o gatilho produziu.
+ def completar(textos, dados = {})
+ contexto = (dados || {}).transform_keys(&:to_s)
+ faltantes = usadas(textos) - contexto.keys
+
+ faltantes.each_with_object(contexto.dup) do |chave, resultado|
+ valor = resolver(chave)
+ resultado[chave] = valor unless valor.nil?
+ end
+ end
+
+ # Chaves {{assim}} encontradas em qualquer texto (String, Array ou Hash de
+ # blocos â o template Ă© jsonb aninhado).
+ def usadas(textos)
+ varrer(textos).scan(MARCADOR).flatten.uniq
+ end
+
+ def resolver(chave)
+ chave = chave.to_s
+ valor = valor_de_sistema(chave) || valor_de_dados(chave) || valor_personalizado(chave)
+ valor&.to_s
+ end
+
+ # ââ privados de mĂłdulo ââââââââââââââââââââââââââââââââââââââ
+ def varrer(objeto)
+ case objeto
+ when String then objeto
+ when Hash then objeto.values.map { |v| varrer(v) }.join(' ')
+ when Array then objeto.map { |v| varrer(v) }.join(' ')
+ else ''
+ end
+ end
+
+ def valor_de_sistema(chave)
+ meta = CatalogoVariaveis::SISTEMA[chave]
+ meta && executar(meta[:resolver], chave)
+ end
+
+ def valor_de_dados(chave)
+ meta = CatalogoVariaveis::DADOS[chave]
+ meta && executar(meta[:resolver], chave)
+ end
+
+ def valor_personalizado(chave)
+ return nil unless VariavelPersonalizada.table_exists?
+
+ VariavelPersonalizada.ativas.find_by(chave: chave)&.valor
+ rescue StandardError => e
+ Rails.logger.error("[Variaveis] personalizada #{chave}: #{e.class}: #{e.message}")
+ nil
+ end
+
+ def executar(bloco, chave)
+ return nil if bloco.nil?
+
+ resultado = bloco.call
+ # NĂșmero cru numa mensagem fica sem separador de milhar ("4977").
+ resultado.is_a?(Numeric) ? Formatador.numero(resultado) : resultado.to_s
+ rescue StandardError => e
+ Rails.logger.error("[Variaveis] #{chave}: #{e.class}: #{e.message}")
+ nil
+ end
+ end
+end
diff --git a/app/services/notificacao_service.rb b/app/services/notificacao_service.rb
index 67b72b4..88b1157 100644
--- a/app/services/notificacao_service.rb
+++ b/app/services/notificacao_service.rb
@@ -181,10 +181,6 @@ class NotificacaoService
"#{evt[:cta]} https://#{ENV.fetch('APP_HOST', 'localhost:3000')}/motorista"
end
- # Moeda BR com separador de milhar: R$ 1.234,56
- def moeda(valor)
- inteiro, decimais = format('%.2f', valor.to_f).split('.')
- inteiro = inteiro.reverse.gsub(/(\d{3})(?=\d)/, '\1.').reverse
- "R$ #{inteiro},#{decimais}"
- end
+ # Ver Notificacao::Formatador â implementação Ășnica.
+ def moeda(valor) = Notificacao::Formatador.moeda(valor)
end
diff --git a/app/views/admin/mensagem_templates/edit.html.erb b/app/views/admin/mensagem_templates/edit.html.erb
index a6bf97a..bccbf0a 100644
--- a/app/views/admin/mensagem_templates/edit.html.erb
+++ b/app/views/admin/mensagem_templates/edit.html.erb
@@ -82,22 +82,54 @@
<%# ââ VariĂĄveis âââââââââââââââââââââââââââââââââââââââ %>
+ <%# ââ EMOJIS âââââââââââââââââââââââââââââââââââââââââ
+ Digitar emoji no teclado do computador Ă© o passo que trava o ADM;
+ aqui ele clica. A lista Ă© curta e curada de propĂłsito â um seletor
+ completo viraria uma biblioteca externa (e CSP) para inserir 3
+ caracteres. %>
+
+
Emojis
+
+ <% %w[â
â â ïž đ đą đ° đ” đ§Ÿ đ
đ đ đŠ đ đ€ đ đ â đŽ đą đĄ âĄïž đ đ· đ âïž đ đ].each do |emoji| %>
+ <%= emoji %>
+ <% end %>
+
+
+
VariĂĄveis
Clique num campo da mensagem e depois numa variĂĄvel para inseri-la ali.
-
- <% @variaveis.each do |nome, meta| %>
-
- <%= "{{#{nome}}}" %>
-
- <% end %>
-
+
+ <%# Agrupadas por ORIGEM e sem esconder o que nĂŁo se aplica: a variĂĄvel
+ de outro gatilho aparece apagada, com o aviso de que sai em branco.
+ Antes, a tela sĂł listava as 6 do contexto â qualquer outro dado
+ exigia deploy. %>
+ <% @variaveis.each do |origem, itens| %>
+ <% next if itens.empty? %>
+
+ <%= Notificacao::CatalogoVariaveis::ORIGENS[origem] %>
+
+
+ <% itens.each do |v| %>
+
+ {{<%= v[:chave] %>}}<%= ' ·' unless v[:aplicavel] %>
+
+ <% end %>
+
+ <% end %>
+
+
+ As apagadas (·) não existem neste gatilho e sairiam em branco.
+ <%= link_to 'Criar variĂĄvel prĂłpria', admin_variaveis_path, class: 'text-orange-500 hover:underline' %>.
+
@@ -312,20 +344,29 @@
});
}
- // ââ VariĂĄveis: insere no cursor do Ășltimo campo focado âââââ
+ // ââ Inserção no cursor do Ășltimo campo focado ââââââââââââââ
+ // Serve para variĂĄvel e para emoji: a mecĂąnica Ă© a mesma (recortar o valor
+ // na posição do cursor e reemitir `input` para o JSON do editor atualizar).
+ function inserirNoCampo(texto) {
+ if (!ultimoCampo) { alert('Clique primeiro no campo onde o texto deve entrar.'); return; }
+ var ini = ultimoCampo.selectionStart != null ? ultimoCampo.selectionStart : ultimoCampo.value.length;
+ var fim = ultimoCampo.selectionEnd != null ? ultimoCampo.selectionEnd : ini;
+ ultimoCampo.value = ultimoCampo.value.slice(0, ini) + texto + ultimoCampo.value.slice(fim);
+ ultimoCampo.dispatchEvent(new Event('input', { bubbles: true }));
+ ultimoCampo.focus();
+ ultimoCampo.selectionStart = ultimoCampo.selectionEnd = ini + texto.length;
+ }
+
document.querySelectorAll('.chip-variavel').forEach(function (chip) {
chip.addEventListener('click', function () {
- var texto = '{{' + chip.dataset.variavel + '}}';
- if (!ultimoCampo) { alert('Clique primeiro no campo onde a variĂĄvel deve entrar.'); return; }
- var ini = ultimoCampo.selectionStart != null ? ultimoCampo.selectionStart : ultimoCampo.value.length;
- var fim = ultimoCampo.selectionEnd != null ? ultimoCampo.selectionEnd : ini;
- ultimoCampo.value = ultimoCampo.value.slice(0, ini) + texto + ultimoCampo.value.slice(fim);
- ultimoCampo.dispatchEvent(new Event('input', { bubbles: true }));
- ultimoCampo.focus();
- ultimoCampo.selectionStart = ultimoCampo.selectionEnd = ini + texto.length;
+ inserirNoCampo('{{' + chip.dataset.variavel + '}}');
});
});
+ document.querySelectorAll('.chip-emoji').forEach(function (chip) {
+ chip.addEventListener('click', function () { inserirNoCampo(chip.dataset.emoji); });
+ });
+
// ââ Preview: renderizado no servidor, pelo mesmo cĂłdigo do envio ââ
var token = document.querySelector('meta[name="csrf-token"]');
document.getElementById('btn-preview').addEventListener('click', function () {
diff --git a/app/views/admin/perfis_acesso/_form.html.erb b/app/views/admin/perfis_acesso/_form.html.erb
new file mode 100644
index 0000000..0ce7ff5
--- /dev/null
+++ b/app/views/admin/perfis_acesso/_form.html.erb
@@ -0,0 +1,83 @@
+<%# app/views/admin/perfis_acesso/_form.html.erb
+ O ADM marca o que o perfil enxerga. As caixas vĂȘm agrupadas por ĂĄrea do
+ catĂĄlogo (Permissao.por_grupo) e cada uma traz a descrição do EFEITO real â
+ "Registrar pagamento" sem a linha de baixo nĂŁo diz se inclui estornar. %>
+<%= form_with model: [:admin, perfil], url: (perfil.persisted? ? admin_perfil_path(perfil) : admin_perfis_path),
+ data: { turbo: false }, class: 'space-y-6' do |f| %>
+
+ <% if perfil.errors.any? %>
+
+
<%= icone :erro, cor: 'text-red-400' %> NĂŁo foi possĂvel salvar
+
+ <% perfil.errors.full_messages.each do |msg| %><%= msg %> <% end %>
+
+
+ <% end %>
+
+ <%# ââ Identificação ââââââââââââââââââââââââââââââââââââ %>
+
+
+ <%= f.label :nome, 'Nome do perfil', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
+ <%= f.text_field :nome, required: true, placeholder: 'Ex.: Gerente sem configuraçÔes',
+ class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ focus:outline-none focus:border-[#f97316]' %>
+
+
+
+ <%= f.label :descricao, 'Para que serve', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
+ <%= f.text_field :descricao, placeholder: 'Quem usa este perfil e por quĂȘ',
+ class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ focus:outline-none focus:border-[#f97316]' %>
+
+
+
+
+ <%= f.label :ativo, 'Perfil ativo', class: 'block text-sm font-medium text-gray-300' %>
+
Desativado, quem estiver nele perde os acessos marcados aqui.
+
+ <%= f.check_box :ativo, class: 'w-5 h-5 accent-orange-500' %>
+
+
+ <% if perfil.sistema? %>
+
+ <%= icone :alerta, cor: 'text-amber-400' %> Perfil que vem com o sistema: pode ser editado e desativado, mas nĂŁo excluĂdo.
+
+ <% end %>
+
+
+ <%# ââ PermissĂ”es âââââââââââââââââââââââââââââââââââââââââ
+ O campo vazio antes das caixas Ă© obrigatĂłrio: sem ele, desmarcar TUDO faria
+ o parĂąmetro sumir do POST e o controller entenderia "nĂŁo mexeu". %>
+ <%= hidden_field_tag 'perfil_acesso[permissoes][]', '' %>
+
+ <% marcadas = perfil.permissoes_validas %>
+ <% Permissao.por_grupo.each do |grupo, itens| %>
+
+
+
<%= Permissao.grupo_label(grupo) %>
+ <%= itens.count { |chave, _| marcadas.include?(chave) } %> de <%= itens.size %>
+
+
+
+ <% itens.each do |chave, cfg| %>
+
+ <%= check_box_tag 'perfil_acesso[permissoes][]', chave, marcadas.include?(chave),
+ id: "perm-#{chave.tr('.', '-')}", class: 'mt-1 w-5 h-5 accent-orange-500 shrink-0' %>
+
+ <%= cfg[:label] %>
+ <%= cfg[:descricao] %>
+
+
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= f.submit perfil.persisted? ? 'Salvar perfil' : 'Criar perfil',
+ class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold px-6 py-3.5 rounded-xl
+ min-h-[48px] cursor-pointer' %>
+ <%= link_to 'Cancelar', admin_perfis_path,
+ class: 'px-5 py-3.5 text-gray-300 hover:text-white border border-[#2a2a2a] rounded-xl min-h-[48px] flex items-center' %>
+
+<% end %>
diff --git a/app/views/admin/perfis_acesso/edit.html.erb b/app/views/admin/perfis_acesso/edit.html.erb
new file mode 100644
index 0000000..7452162
--- /dev/null
+++ b/app/views/admin/perfis_acesso/edit.html.erb
@@ -0,0 +1,10 @@
+<% content_for :title, "Perfil #{@perfil.nome}" %>
+
+
+ <%= icone :usuarios, espaco: false %> <%= @perfil.nome %>
+
+
+ <%= @perfil.users.count %> usuĂĄrio(s) neste perfil â o que vocĂȘ mudar aqui vale para todos eles.
+
+ <%= render 'form', perfil: @perfil %>
+
diff --git a/app/views/admin/perfis_acesso/index.html.erb b/app/views/admin/perfis_acesso/index.html.erb
new file mode 100644
index 0000000..215ce04
--- /dev/null
+++ b/app/views/admin/perfis_acesso/index.html.erb
@@ -0,0 +1,70 @@
+<%# app/views/admin/perfis_acesso/index.html.erb %>
+<% content_for :title, 'Perfis de acesso' %>
+
+
+
+
+ <%= icone :usuarios, espaco: false %> Perfis de acesso
+
+
+ O perfil define o que a pessoa enxerga e pode fazer. Mudou a regra, muda aqui â vale para todos do perfil.
+
+
+ <%= link_to new_admin_perfil_path, data: { turbo: false },
+ class: 'inline-flex items-center gap-2 px-5 py-3 bg-[#f97316] hover:bg-orange-500
+ text-white font-semibold rounded-xl transition-colors min-h-[48px] whitespace-nowrap' do %>
+ <%= icone :adicionar, cor: nil %> Novo perfil
+ <% end %>
+
+
+<% if @perfis.any? %>
+
+ <% @perfis.each do |perfil| %>
+
+
+
+
+
<%= perfil.nome %>
+ <% if perfil.sistema? %>
+ Do sistema
+ <% end %>
+ <% unless perfil.ativo? %>
+ Desativado
+ <% end %>
+ <% if perfil.vazio? %>
+ Sem nenhum acesso
+ <% end %>
+
+ <% if perfil.descricao.present? %>
+
<%= perfil.descricao %>
+ <% end %>
+
+ <%= icone :pessoa %> <%= perfil.users.size %> usuĂĄrio(s)
+ · <%= icone :chave %> <%= perfil.total_permissoes %> de <%= Permissao.chaves.size %> permissÔes
+
+
+
+
+ <%= link_to rotulo(:editar, 'Editar', cor: nil), edit_admin_perfil_path(perfil), data: { turbo: false },
+ class: 'px-4 py-3 text-gray-300 hover:text-white border border-[#2a2a2a] hover:border-orange-500 rounded-xl min-h-[48px] flex items-center' %>
+ <%# button_to com BLOCO: passando o rĂłtulo como primeiro argumento, o
+ Rails escapa o HTML e o Ăcone sai como texto cru na tela. %>
+ <% if policy(perfil).destroy? %>
+ <%= button_to admin_perfil_path(perfil), method: :delete,
+ class: 'px-4 py-3 text-red-400 hover:text-red-300 border border-[#2a2a2a] hover:border-red-500 rounded-xl min-h-[48px] flex items-center gap-2',
+ form: { data: { turbo_confirm: "Excluir o perfil #{perfil.nome}?" } } do %>
+ <%= icone :excluir, cor: nil, tamanho: 'w-4 h-4', espaco: false %> Excluir
+ <% end %>
+ <% end %>
+
+
+
+ <% end %>
+
+<% else %>
+
+
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
+
Nenhum perfil cadastrado ainda.
+
Enquanto nĂŁo houver perfil, cada pessoa acessa conforme o tipo de conta dela.
+
+<% end %>
diff --git a/app/views/admin/perfis_acesso/new.html.erb b/app/views/admin/perfis_acesso/new.html.erb
new file mode 100644
index 0000000..fb64b19
--- /dev/null
+++ b/app/views/admin/perfis_acesso/new.html.erb
@@ -0,0 +1,8 @@
+<% content_for :title, 'Novo perfil de acesso' %>
+
+
+ <%= icone :usuarios, espaco: false %> Novo perfil de acesso
+
+
Marque o que este perfil enxerga. VocĂȘ atribui o perfil a cada pessoa na tela de UsuĂĄrios.
+ <%= render 'form', perfil: @perfil %>
+
diff --git a/app/views/admin/usuarios/_form.html.erb b/app/views/admin/usuarios/_form.html.erb
index d0f8573..aeb2400 100644
--- a/app/views/admin/usuarios/_form.html.erb
+++ b/app/views/admin/usuarios/_form.html.erb
@@ -40,16 +40,46 @@
placeholder: 'JoĂŁo da Silva' %>
- <%# Perfil %>
-
- <%= f.label :role, 'Perfil de acesso', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
- <%= f.select :role,
- options_for_select(role_options_for_select, usuario.role),
- {},
- class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
- focus:outline-none focus:border-[#f97316] focus:ring-1 focus:ring-[#f97316]
- transition-colors cursor-pointer' %>
-
+ <%# TIPO DE CONTA x PERFIL DE ACESSO â sĂŁo coisas diferentes:
+ âą tipo de conta define COMO a pessoa entra e onde ela vive (motorista
+ entra por PIN e tem painel prĂłprio);
+ âą perfil de acesso define O QUE ela enxerga (Permissao::TODAS).
+ Antes este select se chamava "Perfil de acesso" e editava `role`.
+
+ Os dois sĂł aparecem para quem pode alterar acesso â e nunca na prĂłpria
+ ficha, senĂŁo qualquer usuĂĄrio se promoveria editando o prĂłprio cadastro
+ (o controller também filtra os campos, não só a tela). %>
+ <% if pode_alterar_acesso? %>
+
+ <%= f.label :role, 'Tipo de conta', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
+ <%= f.select :role,
+ options_for_select(role_options_for_select, usuario.role),
+ {},
+ class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ focus:outline-none focus:border-[#f97316] focus:ring-1 focus:ring-[#f97316]
+ transition-colors cursor-pointer' %>
+
+
+
+ <%= f.label :perfil_acesso_id, 'Perfil de acesso', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
+ <%= f.select :perfil_acesso_id,
+ options_from_collection_for_select(PerfilAcesso.ativos.ordenados, :id, :nome, usuario.perfil_acesso_id),
+ { include_blank: 'Sem perfil (usa o padrĂŁo do tipo de conta)' },
+ class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ focus:outline-none focus:border-[#f97316] focus:ring-1 focus:ring-[#f97316]
+ transition-colors cursor-pointer' %>
+
+ Define o que a pessoa enxerga.
+ <%= link_to 'Gerenciar perfis', admin_perfis_path, class: 'text-orange-500 hover:underline' %>
+
+
+ <% else %>
+
+
Tipo de conta
+
<%= usuario.role_label %>
+
SĂł quem administra usuĂĄrios altera tipo de conta e perfil de acesso.
+
+ <% end %>
<%# Ativo toggle %>
@@ -142,10 +172,16 @@
const fieldsPin = document.getElementById('fields-pin');
if (!roleSelect || !fieldsEmail || !fieldsPin) return;
+ // Motorista nĂŁo usa perfil de acesso: o que ele enxerga Ă© o painel prĂłprio,
+ // governado pelo tipo de conta. Deixar o select Ă mostra sugeriria que dĂĄ
+ // para liberar telas administrativas para ele.
+ const campoPerfil = document.getElementById('campo-perfil-acesso');
+
function toggleFields() {
const motorista = roleSelect.value === 'motorista';
fieldsEmail.classList.toggle('hidden', motorista);
fieldsPin.classList.toggle('hidden', !motorista);
+ if (campoPerfil) campoPerfil.classList.toggle('hidden', motorista);
}
roleSelect.addEventListener('change', toggleFields);
diff --git a/app/views/admin/usuarios/index.html.erb b/app/views/admin/usuarios/index.html.erb
index 1c0e520..d68d9dd 100644
--- a/app/views/admin/usuarios/index.html.erb
+++ b/app/views/admin/usuarios/index.html.erb
@@ -29,7 +29,8 @@
Nome
E-mail / PIN
- Perfil
+ Tipo de conta
+ Perfil de acesso
Status
AçÔes
@@ -56,6 +57,22 @@
<%= badge_role(usuario.role) %>
+ <%# Tipo de conta x perfil sĂŁo coisas diferentes: o primeiro diz como
+ a pessoa entra, o segundo o que ela enxerga. Sem perfil, vale o
+ padrĂŁo do tipo de conta â a tela precisa dizer isso, senĂŁo parece
+ que a pessoa estĂĄ sem acesso nenhum. %>
+
+ <% if usuario.motorista? %>
+ â
+ <% elsif usuario.perfil_acesso %>
+ <%= usuario.perfil_acesso.nome %>
+ <% unless usuario.perfil_acesso.ativo? %>
+ perfil desativado
+ <% end %>
+ <% else %>
+ PadrĂŁo do tipo de conta
+ <% end %>
+
<%= badge_status_usuario(usuario.ativo?) %>
diff --git a/app/views/admin/variaveis_personalizadas/_form.html.erb b/app/views/admin/variaveis_personalizadas/_form.html.erb
new file mode 100644
index 0000000..8e9275f
--- /dev/null
+++ b/app/views/admin/variaveis_personalizadas/_form.html.erb
@@ -0,0 +1,55 @@
+<%= form_with model: [:admin, variavel],
+ url: (variavel.persisted? ? admin_variavel_path(variavel) : admin_variaveis_path),
+ data: { turbo: false }, class: 'space-y-6' do |f| %>
+
+ <% if variavel.errors.any? %>
+
+
+ <% variavel.errors.full_messages.each do |msg| %><%= msg %> <% end %>
+
+
+ <% end %>
+
+
+
+ <%= f.label :chave, 'Nome da variĂĄvel', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
+ <%= f.text_field :chave, required: true, placeholder: 'telefone_suporte',
+ class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white font-mono
+ focus:outline-none focus:border-[#f97316]' %>
+
+ SĂł letras minĂșsculas, nĂșmeros e _ . Na mensagem vocĂȘ escreve
+ {{<%= variavel.chave.presence || 'telefone_suporte' %>}} .
+
+
+
+
+ <%= f.label :valor, 'Valor', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
+ <%= f.text_field :valor, required: true, placeholder: '(11) 4002-8922',
+ class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ focus:outline-none focus:border-[#f97316]' %>
+
Ă este texto que aparece no lugar da variĂĄvel, em toda mensagem que a usar.
+
+
+
+ <%= f.label :descricao, 'Para que serve', class: 'block text-sm font-medium text-gray-300 mb-1.5' %>
+ <%= f.text_field :descricao, placeholder: 'Telefone que atende o motorista',
+ class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
+ focus:outline-none focus:border-[#f97316]' %>
+
+
+
+
+ <%= f.label :ativo, 'VariĂĄvel ativa', class: 'block text-sm font-medium text-gray-300' %>
+
Desativada, some do editor e sai vazia nas mensagens que jĂĄ a usam.
+
+ <%= f.check_box :ativo, class: 'w-5 h-5 accent-orange-500' %>
+
+
+
+
+ <%= f.submit variavel.persisted? ? 'Salvar' : 'Criar variĂĄvel',
+ class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold px-6 py-3.5 rounded-xl min-h-[48px] cursor-pointer' %>
+ <%= link_to 'Cancelar', admin_variaveis_path,
+ class: 'px-5 py-3.5 text-gray-300 hover:text-white border border-[#2a2a2a] rounded-xl min-h-[48px] flex items-center' %>
+
+<% end %>
diff --git a/app/views/admin/variaveis_personalizadas/edit.html.erb b/app/views/admin/variaveis_personalizadas/edit.html.erb
new file mode 100644
index 0000000..89d6b46
--- /dev/null
+++ b/app/views/admin/variaveis_personalizadas/edit.html.erb
@@ -0,0 +1,8 @@
+<% content_for :title, "VariĂĄvel #{@variavel.chave}" %>
+
+
+ <%= icone :tag, espaco: false %> <%= @variavel.marcador %>
+
+
O novo valor passa a valer no prĂłximo disparo de toda mensagem que usa esta variĂĄvel.
+ <%= render 'form', variavel: @variavel %>
+
diff --git a/app/views/admin/variaveis_personalizadas/index.html.erb b/app/views/admin/variaveis_personalizadas/index.html.erb
new file mode 100644
index 0000000..4a582b6
--- /dev/null
+++ b/app/views/admin/variaveis_personalizadas/index.html.erb
@@ -0,0 +1,76 @@
+<% content_for :title, 'VariĂĄveis de mensagem' %>
+
+
+
+
<%= icone :tag, espaco: false %> VariĂĄveis de mensagem
+
+ Escreva {{assim}} no texto e o sistema troca pelo valor na hora do envio.
+
+
+ <%= link_to new_admin_variavel_path, data: { turbo: false },
+ class: 'inline-flex items-center gap-2 px-5 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold rounded-xl min-h-[48px] whitespace-nowrap' do %>
+ <%= icone :adicionar, cor: nil %> Nova variĂĄvel
+ <% end %>
+
+
+<%# ââ As suas âââââââââââââââââââââââââââââââââââââââââââ %>
+<% if @variaveis.any? %>
+
+ <% @variaveis.each do |v| %>
+
+
+
+ <%= v.marcador %>
+ <% unless v.ativo? %>
+ Desativada
+ <% end %>
+
+
<%= v.valor %>
+ <% if v.descricao.present? %>
+
<%= v.descricao %>
+ <% end %>
+
+
+ <%= link_to rotulo(:editar, 'Editar', cor: nil), edit_admin_variavel_path(v), data: { turbo: false },
+ class: 'px-4 py-3 text-gray-300 hover:text-white border border-[#2a2a2a] hover:border-orange-500 rounded-xl min-h-[48px] flex items-center' %>
+ <%= button_to admin_variavel_path(v), method: :delete,
+ class: 'px-4 py-3 text-red-400 hover:text-red-300 border border-[#2a2a2a] hover:border-red-500 rounded-xl min-h-[48px] flex items-center gap-2',
+ form: { data: { turbo_confirm: "Excluir #{v.marcador}? Mensagens que a usam passam a mostrar espaço em branco." } } do %>
+ <%= icone :excluir, cor: nil, tamanho: 'w-4 h-4', espaco: false %> Excluir
+ <% end %>
+
+
+ <% end %>
+
+<% else %>
+
+
<%= icone :vazio, tamanho: 'w-12 h-12', cor: 'text-gray-600', espaco: false %>
+
VocĂȘ ainda nĂŁo criou nenhuma variĂĄvel.
+
Boas primeiras: telefone do suporte, horårio de atendimento, endereço da base.
+
+<% end %>
+
+<%# ââ As que jĂĄ existem no sistema âââââââââââââââââââââ
+ Listadas aqui para o ADM nĂŁo tentar cadastrar uma que jĂĄ existe (o cadastro
+ recusaria) e para descobrir o que dĂĄ para usar sem criar nada. %>
+
JĂĄ disponĂveis no sistema
+
Use Ă vontade nas mensagens â nĂŁo precisam ser cadastradas.
+
+
+ <% @do_sistema.each do |origem, itens| %>
+ <% next if itens.empty? %>
+
+
+
<%= Notificacao::CatalogoVariaveis::ORIGENS[origem] %>
+
+
+ <% itens.each do |v| %>
+
+
{{<%= v[:chave] %>}}
+
<%= v[:descricao] %>
+
+ <% end %>
+
+
+ <% end %>
+
diff --git a/app/views/admin/variaveis_personalizadas/new.html.erb b/app/views/admin/variaveis_personalizadas/new.html.erb
new file mode 100644
index 0000000..eb750e0
--- /dev/null
+++ b/app/views/admin/variaveis_personalizadas/new.html.erb
@@ -0,0 +1,6 @@
+<% content_for :title, 'Nova variĂĄvel' %>
+
+
<%= icone :tag, espaco: false %> Nova variĂĄvel
+
Um texto fixo que vocĂȘ reaproveita em qualquer mensagem â mudou aqui, mudou em todas.
+ <%= render 'form', variavel: @variavel %>
+
diff --git a/app/views/consolidacao_entregas/revisar.html.erb b/app/views/consolidacao_entregas/revisar.html.erb
index b4c93d9..6fb1a2c 100644
--- a/app/views/consolidacao_entregas/revisar.html.erb
+++ b/app/views/consolidacao_entregas/revisar.html.erb
@@ -80,7 +80,7 @@
<%= c.desconto? ? '-' : '' %><%= moeda(c.valor_aplicado) %>
- <% if c.manual? && !(@consolidacao.finalizada? && current_user.operador?) %>
+ <% if c.manual? && !(@consolidacao.finalizada? && !current_user.pode?('consolidacao.editar_finalizada')) %>
<%= button_to remover_apontamento_consolidacao_consolidacao_entregas_path(@consolidacao, id: c.id, motorista: @motorista),
method: :delete,
form: { data: { turbo_confirm: 'Remover este apontamento manual?' } },
diff --git a/app/views/consolidacao_entregas/validar.html.erb b/app/views/consolidacao_entregas/validar.html.erb
index 31eb1b9..f019502 100644
--- a/app/views/consolidacao_entregas/validar.html.erb
+++ b/app/views/consolidacao_entregas/validar.html.erb
@@ -56,7 +56,7 @@
<%# Fechar por veĂculo â filtra a tela por carro, mostra o estado (pronto/
consolidado/arquivado) e permite CONSOLIDAR o veĂculo selecionado.
SĂł aparece quando o motorista usou mais de um veĂculo no perĂodo. %>
- <% pode_fechar_veiculo = !(@consolidacao.finalizada? && current_user.operador?) %>
+ <% pode_fechar_veiculo = !(@consolidacao.finalizada? && !current_user.pode?('consolidacao.editar_finalizada')) %>
<% if @veiculos_status.size > 1 %>
@@ -196,7 +196,7 @@
controllers existentes sĂŁo reaproveitados; sĂł o gatilho mudou.
Fica DENTRO da barra de açÔes (order-2) para não custar uma linha própria;
os modais sĂŁo `fixed`, entĂŁo nĂŁo afetam o layout da faixa. %>
- <% unless @consolidacao.finalizada? && current_user.operador? %>
+ <% unless @consolidacao.finalizada? && !current_user.pode?('consolidacao.editar_finalizada') %>
<% if @apontamentos.present? %>
- <% pode_editar = !(@consolidacao.finalizada? && current_user.operador?) %>
+ <% pode_editar = !(@consolidacao.finalizada? && !current_user.pode?('consolidacao.editar_finalizada')) %>
<%= icone :adicionar, espaco: false %> Apontamentos manuais
diff --git a/app/views/consolidacoes/wizard.html.erb b/app/views/consolidacoes/wizard.html.erb
index b5c50cd..2dbb1e3 100644
--- a/app/views/consolidacoes/wizard.html.erb
+++ b/app/views/consolidacoes/wizard.html.erb
@@ -33,7 +33,7 @@
<%# Caixa de ferramentas do Passo 1 â concentra "Apontamento manual" (busca pela
NF) e "Adicionar motorista" num Ășnico botĂŁo com menu suspenso. %>
- <% unless @consolidacao.finalizada? && current_user.operador? %>
+ <% unless @consolidacao.finalizada? && !current_user.pode?('consolidacao.editar_finalizada') %>
<%# Lista de motoristas %>
- <% pode_arquivar_motorista = !(@consolidacao.finalizada? && current_user.operador?) %>
+ <% pode_arquivar_motorista = !(@consolidacao.finalizada? && !current_user.pode?('consolidacao.editar_finalizada')) %>
<% @motoristas.each do |m| %>
<%# Container relativo: o card Ă© um link; o botĂŁo de arquivar fica FORA da
diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb
index 6d3aa67..18b88ed 100644
--- a/app/views/dashboard/index.html.erb
+++ b/app/views/dashboard/index.html.erb
@@ -106,7 +106,9 @@
<% end %>
- <%# Imprimir relatĂłrio financeiro do perĂodo/operação atuais %>
+ <%# Imprimir relatĂłrio financeiro do perĂodo/operação atuais â sĂł para quem
+ pode ver custo (permissĂŁo `dashboard.financeiro`). %>
+ <% if @ver_financeiro %>
<%= link_to rotulo(:imprimir, 'RelatĂłrio', cor: nil),
dashboard_relatorio_financeiro_path(
inicio: @periodo_inicio.strftime('%Y-%m-%d'),
@@ -114,6 +116,7 @@
operacoes: @operacao_filtro),
data: { turbo: false },
class: 'pl-4 pr-4 py-2.5 bg-orange-500 hover:bg-orange-600 text-black font-bold rounded-xl text-sm whitespace-nowrap' %>
+ <% end %>
@@ -147,6 +150,7 @@
<%# Card destacado: total consolidado e pago (consolidaçÔes finalizadas do perĂodo) %>
+ <% if @ver_financeiro %>
<%= icone :consolidado_pago, espaco: false %> Consolidado / Pago
<%= moeda(@fin_custo_total) %>
@@ -156,6 +160,7 @@
pago
+ <% end %>
<%# Card 2: Total de entregas %>
@@ -203,6 +208,7 @@
<%# KPIs FINANCEIROS (consolidaçÔes finalizadas, jå com filtro de operação) %>
+ <% if @ver_financeiro %>
<%= icone :dinheiro, espaco: false %> Custo total
@@ -225,6 +231,7 @@
pendente
+ <% end %>
<%# GRĂFICO + MOTORISTAS (lado a lado no desktop) %>
@@ -244,7 +251,9 @@
- <%# Pagamentos por dia (fluxo de caixa) â quando o dinheiro saiu de fato %>
+ <%# Pagamentos por dia (fluxo de caixa) â quando o dinheiro saiu de fato.
+ Ă valor pago: fica atrĂĄs da mesma permissĂŁo dos demais blocos de custo. %>
+ <% if @ver_financeiro %>
Pagamentos por dia
@@ -273,6 +282,7 @@
<% end %>
<% end %>
+ <% end %>
<%# Top motoristas â DUAS ABAS sobre o mesmo ranking:
@@ -320,6 +330,7 @@
+ <% if @ver_financeiro %>
<%# ââ FINANCEIRO POR OPERAĂĂO âââââââââââââââââââââââââââââ %>
@@ -472,7 +483,9 @@
- <%# Por operação %>
+ <% end %>
+
+ <%# Por operação (contagem de entregas â nĂŁo Ă© custo, fica para todos) %>
<% if @por_operacao.any? %>
<%= icone :operacao, espaco: false %> Entregas por Local
@@ -719,7 +732,9 @@
})();
-<%# Fluxo de pagamentos: barras (pago no dia) + linha (acumulado no perĂodo) %>
+<%# Fluxo de pagamentos: barras (pago no dia) + linha (acumulado no perĂodo).
+ Sem permissĂŁo de financeiro os dados nĂŁo sĂŁo carregados â o script sai fora. %>
+<% if @ver_financeiro %>
-<%# Rosca de pagamentos (pago x a pagar, por valor) %>
+<%# Rosca de pagamentos (pago x a pagar, por valor) â mesmo bloco de permissĂŁo. %>
-<%# ââ GrĂĄficos financeiros por operação âââââââââââââââââââââââ %>
+<% end %>
+
+<%# ââ GrĂĄficos financeiros por operação âââââââââââââââââââââââ
+ Sem a permissĂŁo de financeiro os dados nem chegam Ă pĂĄgina: o controller nĂŁo
+ carrega, e este script inteiro fica de fora do HTML. %>
+<% if @ver_financeiro %>
+<% end %>
diff --git a/app/views/layouts/_navbar.html.erb b/app/views/layouts/_navbar.html.erb
index 5e6e8f7..31f641b 100644
--- a/app/views/layouts/_navbar.html.erb
+++ b/app/views/layouts/_navbar.html.erb
@@ -34,14 +34,17 @@
<%# Navegação %>
- <%# Dashboard â admin/gerente/operador/externo (todos menos motorista) %>
- <% unless current_user.motorista? %>
+ <%# Cada item pergunta por PERMISSĂO, nĂŁo por papel: o menu Ă© o espelho do
+ perfil que o ADM montou (Permissao::TODAS). Item sem permissĂŁo SOME â nĂŁo
+ fica desabilitado, para nĂŁo anunciar o que a pessoa nĂŁo pode fazer. %>
+ <% if current_user.pode?('dashboard.ver') %>
<%= nav_link_to 'Dashboard', dashboard_path, icon: :dashboard %>
+ <% end %>
+ <% if current_user.pode?('dashboard.operacoes') %>
<%= nav_link_to 'OperaçÔes', operacoes_dashboard_path, icon: :operacoes %>
<% end %>
- <%# ConsolidaçÔes â sĂł quem pode consolidar (exclui o externo) %>
- <% if current_user.pode_consolidar? %>
+ <% if current_user.pode?('consolidacao.ver') %>
<%= nav_link_to 'ConsolidaçÔes', consolidacoes_path, icon: :consolidacoes %>
<%= nav_link_to 'Arquivadas', arquivadas_consolidacoes_path, icon: :arquivadas %>
<% end %>
@@ -51,29 +54,55 @@
<%= nav_link_to 'Meu Painel', motorista_dashboard_path, icon: :painel %>
<% end %>
- <%# Admin/Gerente â ConfiguraçÔes %>
- <% if current_user.pode_ver_config? %>
+ <%# Administração â a seção inteira some quando nenhum item dela estĂĄ liberado. %>
+ <% itens_admin = %w[admin.usuarios admin.perfis admin.configuracoes admin.auditoria
+ admin.planilha_simpli_route admin.edicao_lancamento] %>
+ <% if itens_admin.any? { |chave| current_user.pode?(chave) } %>
Administração
- <%= nav_link_to 'UsuĂĄrios', admin_usuarios_path, icon: :usuarios %>
- <%= nav_link_to 'ConfiguraçÔes', admin_configuracoes_path, icon: :configuracoes %>
- <%= nav_link_to 'Auditoria', admin_auditoria_logs_path, icon: :auditoria %>
- <%= nav_link_to 'Planilha SimpliRoute', admin_planilha_simpli_route_path, icon: :planilha %>
- <% if current_user.admin? %>
+ <% if current_user.pode?('admin.usuarios') %>
+ <%= nav_link_to 'UsuĂĄrios', admin_usuarios_path, icon: :usuarios %>
+ <% end %>
+ <% if current_user.pode?('admin.perfis') %>
+ <%= nav_link_to 'Perfis de acesso', admin_perfis_path, icon: :chave %>
+ <% end %>
+ <% if current_user.pode?('admin.configuracoes') %>
+ <%= nav_link_to 'ConfiguraçÔes', admin_configuracoes_path, icon: :configuracoes %>
+ <% end %>
+ <% if current_user.pode?('admin.auditoria') %>
+ <%= nav_link_to 'Auditoria', admin_auditoria_logs_path, icon: :auditoria %>
+ <% end %>
+ <% if current_user.pode?('admin.planilha_simpli_route') %>
+ <%= nav_link_to 'Planilha SimpliRoute', admin_planilha_simpli_route_path, icon: :planilha %>
+ <% end %>
+ <% if current_user.pode?('admin.edicao_lancamento') %>
<%= nav_link_to 'Editar Lançamento', admin_edicao_lancamento_path, icon: :editar_lancamento %>
<% end %>
- <%# NotificaçÔes: quem recebe (grupos/contatos) é admin+gerente; ligar o
- WhatsApp e criar eventos manda mensagem em nome da empresa â sĂł ADM.
- Mesmo recorte das policies. %>
+ <% end %>
+
+ <%# NotificaçÔes â cada item com sua chave: ver contatos Ă© uma coisa; ligar o
+ WhatsApp e disparar mensagem em nome da empresa Ă© outra. %>
+ <% itens_notif = %w[notificacao.contatos notificacao.envios notificacao.eventos
+ notificacao.variaveis notificacao.whatsapp_sessao] %>
+ <% if itens_notif.any? { |chave| current_user.pode?(chave) } %>
NotificaçÔes
- <%= nav_link_to 'Contatos', admin_contatos_path, icon: :usuarios %>
- <%= nav_link_to 'Grupos', admin_grupos_path, icon: :tag %>
- <%= nav_link_to 'Envios', admin_envios_path, icon: :historico %>
- <% if current_user.admin? %>
+ <% if current_user.pode?('notificacao.contatos') %>
+ <%= nav_link_to 'Contatos', admin_contatos_path, icon: :usuarios %>
+ <%= nav_link_to 'Grupos', admin_grupos_path, icon: :tag %>
+ <% end %>
+ <% if current_user.pode?('notificacao.envios') %>
+ <%= nav_link_to 'Envios', admin_envios_path, icon: :historico %>
+ <% end %>
+ <% if current_user.pode?('notificacao.eventos') %>
<%= nav_link_to 'Eventos', admin_eventos_path, icon: :diario %>
+ <% end %>
+ <% if current_user.pode?('notificacao.variaveis') %>
+ <%= nav_link_to 'VariĂĄveis', admin_variaveis_path, icon: :tag %>
+ <% end %>
+ <% if current_user.pode?('notificacao.whatsapp_sessao') %>
<%= nav_link_to 'WhatsApp', admin_whatsapp_sessao_path, icon: :telefone %>
<% end %>
diff --git a/app/views/paginas/sem_acesso.html.erb b/app/views/paginas/sem_acesso.html.erb
new file mode 100644
index 0000000..db003ce
--- /dev/null
+++ b/app/views/paginas/sem_acesso.html.erb
@@ -0,0 +1,20 @@
+<% content_for :title, 'Sem acesso' %>
+
+
+
<%= icone :travado, tamanho: 'w-14 h-14', cor: 'text-gray-600', espaco: false %>
+
VocĂȘ ainda nĂŁo tem acesso a nenhuma tela
+
+ OlĂĄ, <%= current_user.nome_display %>. Sua conta estĂĄ ativa, mas o perfil de acesso
+ <% if @perfil %>
+ (<%= @perfil.nome %> )
+ <% end %>
+ nĂŁo libera nenhuma ĂĄrea do sistema.
+
+
+ Peça a um administrador para revisar seu perfil em Administração âș Perfis de acesso .
+
+
+ <%= link_to rotulo(:sair, 'Sair', cor: nil), destroy_user_session_path,
+ data: { turbo_method: :delete },
+ class: 'inline-flex items-center mt-8 px-5 py-3 border border-[#2a2a2a] rounded-xl text-gray-300 hover:text-white min-h-[48px]' %>
+
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index f6cc57e..57f3ef6 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -6,4 +6,7 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
# Sem esta linha, ConfiguracaoNotificacao procuraria a tabela
# "configuracao_notificacaos" (o irregular acima sĂł casa no FIM da palavra).
inflect.irregular 'notificacao', 'notificacoes'
+ # Sem isto, "perfis".singularize vira "perfi" e as rotas de perfil de acesso
+ # gerariam helpers como `new_admin_perfi_path`.
+ inflect.irregular 'perfil', 'perfis'
end
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 1ada311..d43cf2a 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -7,3 +7,9 @@ pt-BR:
formats:
default: "%d/%m/%Y %H:%M"
short: "%d/%m %H:%M"
+
+ # Mensagem de login recusado para conta desativada (User#inactive_message).
+ # Sem esta chave, o Devise mostraria o sĂmbolo cru "conta_desativada".
+ devise:
+ failure:
+ conta_desativada: "Sua conta estĂĄ desativada. Fale com um administrador."
diff --git a/config/routes.rb b/config/routes.rb
index 3f28895..42ca49a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -12,6 +12,10 @@ Rails.application.routes.draw do
# Root â redireciona por role
root 'dashboard#index'
+ # Fim de linha para quem estĂĄ autenticado mas o perfil nĂŁo libera nenhuma tela.
+ # Existe para o "acesso negado" ter para onde ir sem cair em loop com a raiz.
+ get '/sem-acesso', to: 'paginas#sem_acesso', as: :sem_acesso
+
# Dashboard principal (admin/gerente/operador)
get '/dashboard', to: 'dashboard#index', as: :dashboard
get '/dashboard/relatorio_financeiro', to: 'dashboard#relatorio_financeiro', as: :dashboard_relatorio_financeiro
@@ -99,6 +103,12 @@ Rails.application.routes.draw do
end
resources :auditoria_logs, only: [:index]
+ # Perfis de acesso: o ADM monta o conjunto de permissÔes e atribui ao
+ # usuĂĄrio. Rota `perfis` (e nĂŁo `perfis_acesso`) pelo mesmo motivo de
+ # `grupos`/`eventos` mais abaixo â nome de rota curto evita o sufixo `_index`
+ # que o Inflector cria quando singular == plural.
+ resources :perfis, except: [:show], controller: 'perfis_acesso'
+
# SMTP e WhatsApp (Twilio) gravados no banco â sĂł admin.
# Sem rotas `testar_*`: os botÔes de teste são submits do próprio form
# (params[:acao]), para o teste usar exatamente o que estĂĄ na tela.
@@ -135,6 +145,9 @@ Rails.application.routes.draw do
# faria o cadastro de contato inteiro esperar o timeout.
collection { get :grupos_whatsapp }
end
+ # VariĂĄveis {{assim}} criadas pelo ADM, usĂĄveis em qualquer mensagem.
+ resources :variaveis, except: [:show], controller: 'variaveis_personalizadas'
+
resources :eventos, except: [:show], controller: 'eventos_notificacao' do
member do
post :disparar # gatilho `manual` â manda mensagem de verdade
diff --git a/db/migrate/20260826000001_create_perfis_acesso.rb b/db/migrate/20260826000001_create_perfis_acesso.rb
new file mode 100644
index 0000000..52ffa72
--- /dev/null
+++ b/db/migrate/20260826000001_create_perfis_acesso.rb
@@ -0,0 +1,31 @@
+# Perfil de acesso: o conjunto de permissÔes que o ADM monta na tela e atribui a
+# um usuĂĄrio.
+#
+# Antes, quem-vĂȘ-o-quĂȘ era o papel fixo em `users.role` e um punhado de `if
+# admin? || gerente?` espalhado pelas policies: tirar as ConfiguraçÔes de UM
+# gerente exigia editar cĂłdigo e fazer deploy.
+#
+# `permissoes` Ă© jsonb (lista de chaves de Permissao::TODAS) e nĂŁo tabela filha
+# de propĂłsito: Ă© um conjunto pequeno, sempre lido junto com o perfil, e nunca
+# consultado ao contrĂĄrio ("quem tem tal permissĂŁo?" se responde em memĂłria). A
+# validação de quais chaves existem fica no model â o catĂĄlogo Ă© cĂłdigo, muda com
+# deploy, e o banco nĂŁo pode travar um perfil salvo com uma chave que saiu.
+class CreatePerfisAcesso < ActiveRecord::Migration[7.1]
+ def change
+ create_table :perfis_acesso do |t|
+ t.string :nome, null: false
+ t.string :descricao
+ t.jsonb :permissoes, null: false, default: []
+ # sistema: nasce com o app (Administrador, Gerente, ...). EditĂĄvel e
+ # desativĂĄvel, mas nĂŁo apagĂĄvel â mesma regra de EventoNotificacao#apagavel?
+ t.boolean :sistema, null: false, default: false
+ t.boolean :ativo, null: false, default: true
+
+ t.timestamps
+ end
+
+ # Nome Ă© o que identifica o perfil na tela; duplicar sĂł gera erro humano.
+ add_index :perfis_acesso, 'LOWER(nome)', unique: true,
+ name: 'index_perfis_acesso_nome_unico'
+ end
+end
diff --git a/db/migrate/20260826000002_add_perfil_acesso_to_users.rb b/db/migrate/20260826000002_add_perfil_acesso_to_users.rb
new file mode 100644
index 0000000..1ae1675
--- /dev/null
+++ b/db/migrate/20260826000002_add_perfil_acesso_to_users.rb
@@ -0,0 +1,17 @@
+# Liga o usuĂĄrio ao perfil de acesso.
+#
+# NULO Ă VĂLIDO e nĂŁo Ă© descuido: motorista nĂŁo usa perfil (o que ele acessa Ă© a
+# ĂĄrea prĂłpria, governada pelo tipo de conta), e um usuĂĄrio sem perfil cai no
+# padrĂŁo do papel legado (Permissao::PADRAO_POR_ROLE) â Ă© o que mantĂ©m o sistema
+# funcionando entre o deploy do cĂłdigo e a migration de dados.
+class AddPerfilAcessoToUsers < ActiveRecord::Migration[7.1]
+ def change
+ # â ïž `to_table:` Ă© OBRIGATĂRIO. Com `foreign_key: true` o Rails pluraliza o
+ # nome da referĂȘncia para achar a tabela alvo, e o Inflector nĂŁo fala
+ # portuguĂȘs: `perfil_acesso` viraria "perfil_acessos". A tabela real Ă©
+ # `perfis_acesso` (plural no primeiro termo) â mesma pegadinha documentada em
+ # 20260824000002_create_notificacao_eventos.rb.
+ add_reference :users, :perfil_acesso, null: true,
+ foreign_key: { to_table: :perfis_acesso }
+ end
+end
diff --git a/db/migrate/20260826000003_semear_perfis_acesso.rb b/db/migrate/20260826000003_semear_perfis_acesso.rb
new file mode 100644
index 0000000..2e9ae03
--- /dev/null
+++ b/db/migrate/20260826000003_semear_perfis_acesso.rb
@@ -0,0 +1,78 @@
+# Cria os perfis pré-prontos e atribui a cada usuårio existente o perfil do papel
+# que ele jĂĄ tinha. Sem isso, o dia seguinte ao deploy seria "todo mundo no
+# fallback" â funciona, mas a tela de perfis nasceria vazia e o ADM nĂŁo teria de
+# onde partir para montar o dele.
+#
+# â ïž A lista de chaves estĂĄ DUPLICADA aqui e em Permissao::PADRAO_POR_ROLE, de
+# propĂłsito: migration nĂŁo pode depender de model. Se o catĂĄlogo mudar daqui a
+# seis meses, o histĂłrico do banco nĂŁo pode mudar junto â quem jĂĄ rodou esta
+# migration continua com o perfil que foi criado naquele dia.
+#
+# Idempotente (o teste e a produção migram em momentos diferentes, e a migration
+# pode encontrar um banco onde o ADM jĂĄ criou perfis Ă mĂŁo).
+class SemearPerfisAcesso < ActiveRecord::Migration[7.1]
+ TODAS = %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.contatos_excluir
+ notificacao.eventos notificacao.eventos_gerenciar notificacao.disparar_manual
+ notificacao.variaveis notificacao.envios notificacao.credenciais notificacao.whatsapp_sessao
+ admin.usuarios admin.usuarios_gerenciar admin.perfis
+ admin.configuracoes admin.configuracoes_editar admin.auditoria
+ admin.edicao_lancamento admin.planilha_simpli_route
+ ].freeze
+
+ 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
+ ].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
+
+ # nome, descrição, permissÔes, role legada que recebe este perfil
+ PERFIS = [
+ ['Administrador', 'Acesso total ao sistema.', :todas, 0],
+ ['Gerente', 'Opera e paga; administra usuårios, preços e destinatårios.', GERENTE, 1],
+ ['Operador', 'Monta e finaliza consolidaçÔes; não paga nem administra.', OPERADOR, 2],
+ ['Externo', 'Somente leitura dos indicadores de operação.', EXTERNO, 4]
+ ].freeze
+
+ def up
+ PERFIS.each do |nome, descricao, permissoes, role|
+ lista = (permissoes == :todas ? TODAS : permissoes)
+
+ id = select_value("SELECT id FROM perfis_acesso WHERE LOWER(nome) = #{quote(nome.downcase)}")
+ if id.nil?
+ execute(<<~SQL.squish)
+ INSERT INTO perfis_acesso (nome, descricao, permissoes, sistema, ativo, created_at, updated_at)
+ VALUES (#{quote(nome)}, #{quote(descricao)}, #{quote(lista.to_json)}::jsonb, true, true, NOW(), NOW())
+ SQL
+ id = select_value("SELECT id FROM perfis_acesso WHERE LOWER(nome) = #{quote(nome.downcase)}")
+ end
+
+ # Só quem ainda não tem perfil: se o ADM jå ajustou alguém à mão entre o
+ # deploy e a migration, a escolha dele vence.
+ execute("UPDATE users SET perfil_acesso_id = #{id}, updated_at = NOW() " \
+ "WHERE role = #{role} AND perfil_acesso_id IS NULL")
+ end
+ end
+
+ def down
+ execute('UPDATE users SET perfil_acesso_id = NULL')
+ execute('DELETE FROM perfis_acesso WHERE sistema = true')
+ end
+end
diff --git a/db/migrate/20260826000004_create_variaveis_personalizadas.rb b/db/migrate/20260826000004_create_variaveis_personalizadas.rb
new file mode 100644
index 0000000..6b17043
--- /dev/null
+++ b/db/migrate/20260826000004_create_variaveis_personalizadas.rb
@@ -0,0 +1,22 @@
+# VariĂĄveis que o ADM cria na tela e usa em qualquer mensagem: {{telefone_suporte}},
+# {{horario_atendimento}}, {{site}}âŠ
+#
+# Antes, toda informação fixa que precisasse aparecer numa mensagem era digitada
+# de novo em cada template â e mudar o telefone do suporte significava abrir um
+# por um. Aqui a chave Ă© Ășnica e o valor mora num lugar sĂł.
+class CreateVariaveisPersonalizadas < ActiveRecord::Migration[7.1]
+ def change
+ create_table :variaveis_personalizadas do |t|
+ t.string :chave, null: false # sem {{ }}: telefone_suporte
+ t.string :valor, null: false, default: ''
+ t.string :descricao # o que a tela mostra ao lado
+ t.boolean :ativo, null: false, default: true
+
+ t.timestamps
+ end
+
+ # A chave Ă© o que aparece no texto ({{chave}}); duas iguais tornariam o
+ # resultado imprevisĂvel.
+ add_index :variaveis_personalizadas, :chave, unique: true
+ end
+end
diff --git a/spec/factories.rb b/spec/factories.rb
index 2e8d06d..0a4d694 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -10,6 +10,8 @@ FactoryBot.define do
factory :gerente do role { :gerente } end
factory :operador do role { :operador } end
+ factory :externo do role { :externo } end
+
factory :motorista do
role { :motorista }
# email Ă© NOT NULL + Ășnico no banco; motorista real nĂŁo tem e-mail, entĂŁo
@@ -21,6 +23,12 @@ FactoryBot.define do
end
end
+ factory :perfil_acesso do
+ sequence(:nome) { |n| "Perfil #{n}" }
+ # Um conjunto pequeno e realista: ver e fechar, sem pagar nem administrar.
+ permissoes { %w[consolidacao.ver consolidacao.editar consolidacao.finalizar] }
+ end
+
factory :configuracao do
chave { 'empresa_nome' }
valor { 'Reem Transporte' }
@@ -53,6 +61,12 @@ FactoryBot.define do
valor_aplicado { 15.0 }
end
+ factory :variavel_personalizada do
+ sequence(:chave) { |n| "variavel_#{n}" }
+ valor { 'Valor de teste' }
+ ativo { true }
+ end
+
factory :grupo_contato do
sequence(:nome) { |n| "Grupo #{n}" }
ativo { true }
diff --git a/spec/models/perfil_acesso_spec.rb b/spec/models/perfil_acesso_spec.rb
new file mode 100644
index 0000000..c9bd1c6
--- /dev/null
+++ b/spec/models/perfil_acesso_spec.rb
@@ -0,0 +1,60 @@
+require 'rails_helper'
+
+RSpec.describe PerfilAcesso do
+ it 'descarta chave que nĂŁo existe mais no catĂĄlogo ao salvar' do
+ perfil = create(:perfil_acesso, permissoes: ['consolidacao.ver', 'permissao.aposentada'])
+
+ expect(perfil.permissoes).to eq(['consolidacao.ver'])
+ expect(perfil).to be_pode('consolidacao.ver')
+ expect(perfil).not_to be_pode('permissao.aposentada')
+ end
+
+ it 'nĂŁo aceita dois perfis com o mesmo nome' do
+ create(:perfil_acesso, nome: 'Financeiro')
+ expect(build(:perfil_acesso, nome: 'financeiro')).not_to be_valid
+ end
+
+ describe '#apagavel?' do
+ it 'perfil de sistema nĂŁo Ă© apagĂĄvel' do
+ expect(build(:perfil_acesso, sistema: true)).not_to be_apagavel
+ end
+
+ it 'perfil em uso nĂŁo Ă© apagĂĄvel (o usuĂĄrio cairia no fallback sem avisar)' do
+ perfil = create(:perfil_acesso)
+ create(:operador, perfil_acesso: perfil)
+
+ expect(perfil.reload).not_to be_apagavel
+ end
+
+ it 'perfil livre Ă© apagĂĄvel' do
+ expect(create(:perfil_acesso)).to be_apagavel
+ end
+ end
+
+ describe 'permissÔes do usuårio' do
+ it 'o perfil vence o padrĂŁo do papel' do
+ # Gerente por papel veria ConfiguraçÔes; o perfil tira só isso.
+ perfil = create(:perfil_acesso, permissoes: Permissao.do_role('gerente') - ['admin.configuracoes'])
+ usuario = create(:gerente, perfil_acesso: perfil)
+
+ expect(usuario.pode?('consolidacao.registrar_pagamento')).to be(true)
+ expect(usuario.pode?('admin.configuracoes')).to be(false)
+ expect(usuario.pode_ver_config?).to be(false)
+ end
+
+ it 'sem perfil, vale o padrĂŁo do papel (e nĂŁo consulta o banco)' do
+ usuario = build(:gerente) # nĂŁo persistido, como nos specs de policy
+
+ expect(usuario.pode_consolidar?).to be(true)
+ expect(usuario.pode_ver_config?).to be(true)
+ expect(usuario.pode?('admin.perfis')).to be(false)
+ end
+
+ it 'perfil vazio tira tudo sem precisar desativar a conta' do
+ usuario = create(:operador, perfil_acesso: create(:perfil_acesso, permissoes: []))
+
+ expect(usuario.pode?('consolidacao.ver')).to be(false)
+ expect(usuario.pode_consolidar?).to be(false)
+ end
+ end
+end
diff --git a/spec/models/permissao_spec.rb b/spec/models/permissao_spec.rb
new file mode 100644
index 0000000..0145dac
--- /dev/null
+++ b/spec/models/permissao_spec.rb
@@ -0,0 +1,73 @@
+require 'rails_helper'
+
+# O catĂĄlogo Ă© a fonte Ășnica de "o que dĂĄ para liberar". Estas travas existem
+# porque um erro aqui nĂŁo aparece na tela: uma chave escrita errado no
+# PADRAO_POR_ROLE simplesmente nĂŁo bate com nada e tira acesso de todo um papel
+# em silĂȘncio.
+RSpec.describe Permissao do
+ describe 'catĂĄlogo' do
+ it 'toda chave segue o padrĂŁo area.acao e pertence a um grupo declarado' do
+ described_class::TODAS.each do |chave, cfg|
+ expect(chave.count('.')).to eq(1), "#{chave} fora do padrĂŁo area.acao"
+ expect(described_class::GRUPOS).to have_key(cfg[:grupo]), "#{chave}: grupo #{cfg[:grupo]} nĂŁo existe"
+ expect(cfg[:label]).to be_present
+ expect(cfg[:descricao]).to be_present, "#{chave} sem descrição (Ă© o texto que o ADM lĂȘ na tela)"
+ end
+ end
+
+ it 'por_grupo cobre todas as chaves' do
+ total = described_class.por_grupo.values.sum(&:size)
+ expect(total).to eq(described_class.chaves.size)
+ end
+
+ it 'filtrar descarta chave inexistente e repetida' do
+ expect(described_class.filtrar(['consolidacao.ver', 'nao.existe', 'consolidacao.ver']))
+ .to eq(['consolidacao.ver'])
+ end
+ end
+
+ describe 'PADRAO_POR_ROLE' do
+ it 'sĂł usa chaves que existem no catĂĄlogo' do
+ described_class::PADRAO_POR_ROLE.each do |role, lista|
+ next if lista == :todas
+
+ lista.each do |chave|
+ expect(described_class).to be_existe(chave), "#{role} referencia chave inexistente: #{chave}"
+ end
+ end
+ end
+
+ it 'admin recebe o catĂĄlogo inteiro' do
+ expect(described_class.do_role('admin')).to match_array(described_class.chaves)
+ end
+
+ # O fallback é o que mantém o comportamento de hoje para quem ainda não tem
+ # perfil. Se estes pares mudarem, alguém perde acesso no dia do deploy.
+ it 'reproduz pode_consolidar? e pode_ver_config? de cada papel' do
+ {
+ 'admin' => [true, true],
+ 'gerente' => [true, true],
+ 'operador' => [true, false],
+ 'motorista' => [false, false],
+ 'externo' => [false, false]
+ }.each do |role, (consolidar, config)|
+ chaves = described_class.do_role(role)
+ expect(chaves.include?('consolidacao.ver')).to eq(consolidar), "#{role}: consolidar"
+ expect(chaves.include?('admin.configuracoes')).to eq(config), "#{role}: config"
+ end
+ end
+
+ # As duas mudanças de comportamento deliberadas desta entrega.
+ it 'externo nĂŁo enxerga o financeiro (era o furo do skip_authorization)' do
+ expect(described_class.do_role('externo')).not_to include('dashboard.financeiro')
+ end
+
+ it 'operador nĂŁo enxerga a auditoria (o menu nunca ofereceu)' do
+ expect(described_class.do_role('operador')).not_to include('admin.auditoria')
+ end
+
+ it 'motorista nĂŁo usa permissĂŁo â o acesso dele Ă© por tipo de conta' do
+ expect(described_class.do_role('motorista')).to be_empty
+ end
+ end
+end
diff --git a/spec/models/variavel_personalizada_spec.rb b/spec/models/variavel_personalizada_spec.rb
new file mode 100644
index 0000000..73245ce
--- /dev/null
+++ b/spec/models/variavel_personalizada_spec.rb
@@ -0,0 +1,27 @@
+require 'rails_helper'
+
+RSpec.describe VariavelPersonalizada do
+ it 'normaliza a chave (minĂșscula, sem espaço)' do
+ v = create(:variavel_personalizada, chave: ' Telefone Suporte ', valor: '4002-8922')
+ expect(v.chave).to eq('telefone_suporte')
+ expect(v.marcador).to eq('{{telefone_suporte}}')
+ end
+
+ it 'recusa chave com formato que o renderizador nĂŁo substituiria' do
+ expect(build(:variavel_personalizada, chave: '2telefone')).not_to be_valid
+ expect(build(:variavel_personalizada, chave: 'telefone-suporte')).not_to be_valid
+ end
+
+ # Deixar o ADM criar {{valor}} faria a variĂĄvel dele brigar com a do sistema â
+ # e o preview nĂŁo mostraria o problema, sĂł o envio real.
+ it 'recusa chave que jĂĄ existe no sistema' do
+ v = build(:variavel_personalizada, chave: 'valor')
+ expect(v).not_to be_valid
+ expect(v.errors[:chave].join).to include('jĂĄ existe no sistema')
+ end
+
+ it 'nĂŁo aceita duas variĂĄveis com a mesma chave' do
+ create(:variavel_personalizada, chave: 'site', valor: 'a')
+ expect(build(:variavel_personalizada, chave: 'SITE', valor: 'b')).not_to be_valid
+ end
+end
diff --git a/spec/requests/admin/perfis_acesso_spec.rb b/spec/requests/admin/perfis_acesso_spec.rb
new file mode 100644
index 0000000..9c3b669
--- /dev/null
+++ b/spec/requests/admin/perfis_acesso_spec.rb
@@ -0,0 +1,101 @@
+require 'rails_helper'
+
+# Perfis de acesso: quem entra na tela, e as travas que impedem o ADM de se
+# trancar para fora do prĂłprio sistema.
+RSpec.describe 'Admin::PerfisAcesso', type: :request do
+ # Perfil com acesso total, para o usuĂĄrio que administra nos testes.
+ let(:perfil_admin) { create(:perfil_acesso, nome: 'Administrador', permissoes: Permissao.chaves) }
+ let(:administrador) { create(:admin, perfil_acesso: perfil_admin) }
+
+ describe 'quem entra' do
+ it 'exige login' do
+ get admin_perfis_path
+ expect(response).to redirect_to(new_user_session_path)
+ end
+
+ it 'nega quem nĂŁo tem admin.perfis' do
+ sign_in create(:gerente, perfil_acesso: create(:perfil_acesso, permissoes: ['consolidacao.ver']))
+ get admin_perfis_path
+ expect(response).to have_http_status(:redirect)
+ end
+
+ it 'permite quem tem admin.perfis' do
+ sign_in administrador
+ get admin_perfis_path
+ expect(response).to have_http_status(:ok)
+ end
+ end
+
+ describe 'criar e editar' do
+ before { sign_in administrador }
+
+ it 'cria perfil sĂł com as chaves do catĂĄlogo' do
+ post admin_perfis_path, params: {
+ perfil_acesso: { nome: 'Financeiro', descricao: 'Paga',
+ ativo: '1', permissoes: ['consolidacao.ver', 'inventada.chave'] }
+ }
+
+ perfil = PerfilAcesso.find_by(nome: 'Financeiro')
+ expect(perfil.permissoes).to eq(['consolidacao.ver'])
+ end
+
+ it 'desmarcar tudo realmente zera as permissÔes' do
+ perfil = create(:perfil_acesso, permissoes: ['consolidacao.ver'])
+
+ # O form manda um valor vazio quando nenhuma caixa estĂĄ marcada; sem ele o
+ # parĂąmetro sumiria e "desmarcar tudo" viraria "nĂŁo mexeu".
+ patch admin_perfil_path(perfil), params: {
+ perfil_acesso: { nome: perfil.nome, ativo: '1', permissoes: [''] }
+ }
+
+ expect(perfil.reload.permissoes).to eq([])
+ end
+ end
+
+ describe 'trava do Ășltimo administrador' do
+ before { sign_in administrador }
+
+ it 'impede tirar admin.perfis do Ășnico perfil que administra' do
+ patch admin_perfil_path(perfil_admin), params: {
+ perfil_acesso: { nome: perfil_admin.nome, ativo: '1',
+ permissoes: Permissao.chaves - ['admin.perfis'] }
+ }
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(perfil_admin.reload.permissoes).to include('admin.perfis')
+ end
+
+ it 'libera quando outra pessoa mantém o acesso' do
+ outro = create(:perfil_acesso, nome: 'Admin 2', permissoes: Permissao.chaves)
+ create(:admin, perfil_acesso: outro)
+
+ patch admin_perfil_path(perfil_admin), params: {
+ perfil_acesso: { nome: perfil_admin.nome, ativo: '1',
+ permissoes: Permissao.chaves - ['admin.perfis'] }
+ }
+
+ expect(perfil_admin.reload.permissoes).not_to include('admin.perfis')
+ end
+ end
+
+ describe 'exclusĂŁo' do
+ before { sign_in administrador }
+
+ it 'nĂŁo exclui perfil de sistema' do
+ perfil = create(:perfil_acesso, sistema: true)
+ expect { delete admin_perfil_path(perfil) }.not_to change(PerfilAcesso, :count)
+ end
+
+ it 'nĂŁo exclui perfil em uso' do
+ perfil = create(:perfil_acesso)
+ create(:operador, perfil_acesso: perfil)
+
+ expect { delete admin_perfil_path(perfil) }.not_to change(PerfilAcesso, :count)
+ end
+
+ it 'exclui perfil livre' do
+ perfil = create(:perfil_acesso)
+ expect { delete admin_perfil_path(perfil) }.to change(PerfilAcesso, :count).by(-1)
+ end
+ end
+end
diff --git a/spec/requests/controle_de_acesso_spec.rb b/spec/requests/controle_de_acesso_spec.rb
new file mode 100644
index 0000000..6898339
--- /dev/null
+++ b/spec/requests/controle_de_acesso_spec.rb
@@ -0,0 +1,89 @@
+require 'rails_helper'
+
+# As trĂȘs falhas de acesso que existiam antes dos perfis. Cada exemplo aqui Ă© um
+# furo real que foi fechado â se algum voltar a passar "verde ao contrĂĄrio", o
+# sistema regrediu para o estado em que qualquer usuĂĄrio virava admin.
+RSpec.describe 'Controle de acesso', type: :request do
+ describe 'auto-promoção a administrador' do
+ it 'ignora role e perfil enviados por quem edita a PRĂPRIA ficha' do
+ operador = create(:operador)
+ perfil_total = create(:perfil_acesso, permissoes: Permissao.chaves)
+ sign_in operador
+
+ patch admin_usuario_path(operador), params: {
+ user: { nome: 'Nome Novo', role: 'admin', perfil_acesso_id: perfil_total.id }
+ }
+
+ operador.reload
+ expect(operador.nome).to eq('Nome Novo') # o que ele PODE mudar, mudou
+ expect(operador.role).to eq('operador') # o que ele nĂŁo pode, nĂŁo mudou
+ expect(operador.perfil_acesso_id).to be_nil
+ expect(operador.pode?('admin.usuarios_gerenciar')).to be(false)
+ end
+
+ it 'quem administra usuĂĄrios continua podendo trocar o perfil de outra pessoa' do
+ perfil_admin = create(:perfil_acesso, permissoes: Permissao.chaves)
+ admin = create(:admin, perfil_acesso: perfil_admin)
+ alvo = create(:operador)
+ novo = create(:perfil_acesso, nome: 'Financeiro', permissoes: ['consolidacao.ver'])
+ sign_in admin
+
+ patch admin_usuario_path(alvo), params: { user: { nome: alvo.nome, perfil_acesso_id: novo.id } }
+
+ expect(alvo.reload.perfil_acesso_id).to eq(novo.id)
+ end
+ end
+
+ describe 'papel externo no dashboard' do
+ it 'entra no dashboard mas nĂŁo vĂȘ custo nem pagamento' do
+ sign_in create(:externo)
+
+ get dashboard_path
+ expect(response).to have_http_status(:ok)
+ expect(response.body).not_to include('Consolidado / Pago')
+ expect(response.body).not_to include('Ticket médio')
+ end
+
+ it 'nĂŁo baixa o relatĂłrio financeiro nem abre as entregas em aberto' do
+ sign_in create(:externo)
+
+ get dashboard_relatorio_financeiro_path
+ expect(response).to have_http_status(:redirect)
+
+ get dashboard_pendentes_path
+ expect(response).to have_http_status(:redirect)
+ end
+
+ it 'gerente continua vendo o financeiro' do
+ sign_in create(:gerente)
+
+ get dashboard_path
+ expect(response.body).to include('Consolidado / Pago')
+ end
+ end
+
+ describe 'conta desativada' do
+ it 'nĂŁo faz login por e-mail e senha' do
+ usuario = create(:gerente, ativo: false, password: 'Gade@2026!')
+
+ post user_session_path, params: { user: { email: usuario.email, password: 'Gade@2026!' } }
+
+ expect(response).not_to redirect_to(dashboard_path)
+ expect(controller.current_user).to be_nil
+ end
+ end
+
+ describe 'sem nenhuma permissĂŁo' do
+ it 'cai na tela de sem-acesso em vez de entrar em loop com a raiz' do
+ usuario = create(:operador, perfil_acesso: create(:perfil_acesso, permissoes: []))
+ sign_in usuario
+
+ get dashboard_path
+ expect(response).to redirect_to(sem_acesso_path)
+
+ get sem_acesso_path
+ expect(response).to have_http_status(:ok)
+ expect(response.body).to include('nĂŁo tem acesso')
+ end
+ end
+end
diff --git a/spec/services/notificacao/resolvedor_variaveis_spec.rb b/spec/services/notificacao/resolvedor_variaveis_spec.rb
new file mode 100644
index 0000000..4be768a
--- /dev/null
+++ b/spec/services/notificacao/resolvedor_variaveis_spec.rb
@@ -0,0 +1,83 @@
+require 'rails_helper'
+
+# O resolvedor Ă© o que permite escrever qualquer variĂĄvel numa mensagem sem que o
+# gatilho conheça essa variåvel. As travas aqui protegem duas coisas: não
+# sobrescrever o contexto real do acontecimento, e nunca derrubar um disparo.
+RSpec.describe Notificacao::ResolvedorVariaveis do
+ describe '.usadas' do
+ it 'acha variĂĄveis dentro dos blocos aninhados do template' do
+ blocos = [
+ { 'tipo' => 'cabecalho', 'titulo' => 'OlĂĄ {{contato}}' },
+ { 'tipo' => 'tabela', 'linhas' => [{ 'rotulo' => 'Valor', 'valor' => '{{valor}}' }] }
+ ]
+
+ expect(described_class.usadas([blocos, 'Assunto {{empresa}}']))
+ .to match_array(%w[contato valor empresa])
+ end
+ end
+
+ describe '.completar' do
+ it 'nĂŁo sobrescreve o que o gatilho jĂĄ mandou' do
+ dados = described_class.completar(['{{empresa}} {{valor}}'],
+ { 'empresa' => 'Contexto Ltda', 'valor' => 'R$ 10,00' })
+
+ expect(dados['empresa']).to eq('Contexto Ltda')
+ expect(dados['valor']).to eq('R$ 10,00')
+ end
+
+ it 'resolve variĂĄvel de sistema' do
+ dados = described_class.completar(['{{data}}'], {})
+ expect(dados['data']).to eq(Date.current.strftime('%d/%m/%Y'))
+ end
+
+ it 'resolve variĂĄvel personalizada do ADM' do
+ create(:variavel_personalizada, chave: 'telefone_suporte', valor: '(11) 4002-8922')
+
+ dados = described_class.completar(['Ligue {{telefone_suporte}}'], {})
+ expect(dados['telefone_suporte']).to eq('(11) 4002-8922')
+ end
+
+ it 'ignora variĂĄvel desativada (a mensagem sai sem ela)' do
+ create(:variavel_personalizada, chave: 'promo', valor: 'X', ativo: false)
+
+ expect(described_class.completar(['{{promo}}'], {})).not_to have_key('promo')
+ end
+
+ it 'variĂĄvel inexistente nĂŁo vira valor inventado' do
+ expect(described_class.completar(['{{nao_existe}}'], {})).not_to have_key('nao_existe')
+ end
+
+ # Notificação não pode derrubar fechamento: se a consulta falhar, o disparo
+ # segue e a variĂĄvel sai vazia.
+ it 'engole erro de uma variĂĄvel de dados' do
+ allow(Entrega).to receive(:atendidas).and_raise(ActiveRecord::StatementInvalid, 'boom')
+
+ expect { described_class.completar(['{{entregas_mes}}'], {}) }.not_to raise_error
+ end
+
+ it 'sĂł consulta o banco para a variĂĄvel realmente escrita' do
+ expect(Entrega).not_to receive(:atendidas)
+
+ described_class.completar(['Sem nĂșmero aqui, sĂł {{empresa}}'], {})
+ end
+ end
+
+ describe Notificacao::CatalogoVariaveis do
+ it 'marca a variĂĄvel de outro gatilho como nĂŁo aplicĂĄvel (em vez de escondĂȘ-la)' do
+ expect(described_class.aplicavel?('valor', 'pagamento_efetuado')).to be(true)
+ expect(described_class.aplicavel?('valor', 'operacao_alterada')).to be(false)
+ end
+
+ it 'variĂĄvel de dados e de sistema valem em qualquer gatilho' do
+ expect(described_class.aplicavel?('entregas_mes', 'manual')).to be(true)
+ expect(described_class.aplicavel?('empresa', 'operacao_alterada')).to be(true)
+ end
+
+ it 'inclui as variĂĄveis criadas pelo ADM' do
+ create(:variavel_personalizada, chave: 'horario_atendimento', valor: '8h Ă s 18h')
+
+ expect(described_class.todas).to have_key('horario_atendimento')
+ expect(described_class.por_origem[:personalizada].map { |v| v[:chave] }).to include('horario_atendimento')
+ end
+ end
+end
From ed44d5fd85f896fb736a7b422d3234f6ed82976588ca86f4c876b1a86edf1f84 Mon Sep 17 00:00:00 2001
From: victor
Date: Wed, 26 Aug 2026 17:44:58 -0300
Subject: [PATCH 19/40] =?UTF-8?q?Corre=C3=A7=C3=A3o=20de=20vazamento=20de?=
=?UTF-8?q?=20dados=20nas=20p=C3=A1ginas=20de=20erro=20e=20nos=20logs?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
TrĂȘs problemas de exposição, todos com o mesmo efeito: dado interno saindo
para quem nĂŁo deveria ver.
1. Painel de debug do Rails na tela do usuĂĄrio
O servidor sobe com RAILS_ENV=development (docker-compose.yml), e
development tinha `consider_all_requests_local = true`. A pĂĄgina
"Action Controller: Exception caught" mostra parĂąmetros, SESSĂO
(session_id, _csrf_token, id do usuĂĄrio logado), cookies, IP do cliente,
caminho do servidor e o trace inteiro â para qualquer pessoa que
provocasse um erro. Agora o detalhe depende de ERROS_DETALHADOS=true,
que sĂł Ă© ligado na mĂĄquina de desenvolvimento.
2. Senha e PIN em texto puro no log
NĂŁo existia filter_parameter_logging.rb neste projeto: toda senha do
login, PIN de motorista e token do WhatsApp iam para log/ em texto
puro â e log vai junto em backup e em suporte. Era o pior dos trĂȘs,
porque é permanente e não aparece na tela de ninguém.
3. PĂĄginas de erro do usuĂĄrio final
Sem public/500.html, desligar o painel deixaria tela em branco. Criadas
500/404/422 em portuguĂȘs, sem CSS externo (precisam funcionar com a
aplicação fora do ar).
Também sai do versionamento a pasta Erros/, com 16 påginas de debug salvas
do navegador. NĂŁo hĂĄ cookie de sessĂŁo assinado nelas â ninguĂ©m entra no
sistema com o que estĂĄ ali â mas hĂĄ session_id, token CSRF, id de usuĂĄrio e
IPs, que nĂŁo tĂȘm por que estar no repositĂłrio.
Correção estrutural pendente: subir o servidor com RAILS_ENV=production
(production.rb jĂĄ tem consider_all_requests_local=false e force_ssl=true).
Co-Authored-By: Claude Opus 5
---
.env.example | 6 +
...tion caught erro holerite com nqrcode.html | 8856 -----------------
... caughtQuando clica na opção usuarios.html | 5441 ----------
...n caught_quando Cria uma consolidação.html | 3987 --------
...aught_quando aperta as configuraçoes .html | 4026 --------
...ght_quando aperta na parte de usuario.html | 4027 --------
...on caught_quando aperta nos usuarios .html | 5440 ----------
...perta o botĂŁo de relatĂłrio individual.html | 4148 --------
...ndo aperta para modificar os usuario .html | 6176 ------------
...tion caughtq_quando aperta para sair .html | 2130 ----
...ndo aperta o botçao de gerar holerite.html | 4148 --------
... termina de criar o usuario motorista.html | 276 -
Erros/Imagem colada (2).png | Bin 135171 -> 0 bytes
Erros/Imagem colada.png | Bin 94174 -> 0 bytes
.../holerite_carlos-matheus-pimentel_10-1.pdf | 2280 -----
Erros/relatorio_bruno-corgozinho_4-2.pdf | 3911 --------
... aperta a pre visualização do holerite.html | 252 -
config/environments/development.rb | 16 +-
.../initializers/filter_parameter_logging.rb | 19 +
public/404.html | 29 +
public/422.html | 29 +
public/500.html | 29 +
22 files changed, 127 insertions(+), 55099 deletions(-)
delete mode 100644 Erros/Action Controller_ Exception caught erro holerite com nqrcode.html
delete mode 100644 Erros/Action Controller_ Exception caughtQuando clica na opção usuarios.html
delete mode 100644 Erros/Action Controller_ Exception caught_quando Cria uma consolidação.html
delete mode 100644 Erros/Action Controller_ Exception caught_quando aperta as configuraçoes .html
delete mode 100644 Erros/Action Controller_ Exception caught_quando aperta na parte de usuario.html
delete mode 100644 Erros/Action Controller_ Exception caught_quando aperta nos usuarios .html
delete mode 100644 Erros/Action Controller_ Exception caught_quando aperta o botĂŁo de relatĂłrio individual.html
delete mode 100644 Erros/Action Controller_ Exception caught_quando aperta para modificar os usuario .html
delete mode 100644 Erros/Action Controller_ Exception caughtq_quando aperta para sair .html
delete mode 100644 Erros/Action Controller_ Exception caughtquando aperta o botçao de gerar holerite.html
delete mode 100644 Erros/Action Controller_ Exception caughtquando termina de criar o usuario motorista.html
delete mode 100644 Erros/Imagem colada (2).png
delete mode 100644 Erros/Imagem colada.png
delete mode 100644 Erros/holerite_carlos-matheus-pimentel_10-1.pdf
delete mode 100644 Erros/relatorio_bruno-corgozinho_4-2.pdf
delete mode 100644 Erros/teste _ Reem LogĂstica_quando aperta a pre visualização do holerite.html
create mode 100644 config/initializers/filter_parameter_logging.rb
create mode 100644 public/404.html
create mode 100644 public/422.html
create mode 100644 public/500.html
diff --git a/.env.example b/.env.example
index 35c17f0..5106d4c 100644
--- a/.env.example
+++ b/.env.example
@@ -87,3 +87,9 @@ WHATSAPP_TOKEN=troque_por_uma_string_aleatoria_longa
# do serviço no docker-compose.yml).
# (o nome antigo `BAILEYS_URL` ainda é aceito, para .env jå em produção)
WHATSAPP_URL=http://whatsapp:3001
+
+# ââ Erros na tela ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+# Mostra a pĂĄgina de debug do Rails (parĂąmetros, SESSĂO, cookies, IP, trace).
+# Deixe SEMPRE vazia/false em qualquer servidor: esse painel vai para a tela de
+# quem provocou o erro. Ligue sĂł na sua mĂĄquina, quando estiver depurando.
+ERROS_DETALHADOS=false
diff --git a/Erros/Action Controller_ Exception caught erro holerite com nqrcode.html b/Erros/Action Controller_ Exception caught erro holerite com nqrcode.html
deleted file mode 100644
index aa790c3..0000000
--- a/Erros/Action Controller_ Exception caught erro holerite com nqrcode.html
+++ /dev/null
@@ -1,8856 +0,0 @@
-
-
-
-
-
-
- Action Controller: Exception caught
-
-
-
-
-
-
-
-
- Prawn::Errors::IncompatibleStringEncoding
- in ConsolidacoesController#gerar_pdf_holerite
-
-
-
-
-
-
Your document includes text that's not compatible with the Windows-1252 character set.
- If you need full UTF-8 support, use external fonts instead of PDF's built-in fonts.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #146 ):
-
-
-
-
-
-
-144
-145
-146
-147
-148
-149
-
-
-
-
- ::Encoding::UndefinedConversionError
-
-
raise Prawn::Errors::IncompatibleStringEncoding,
-
"Your document includes text that's not compatible with the " \
-
"Windows-1252 character set.\n" \
-
'If you need full UTF-8 support, use external fonts instead of ' \
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- # @param text [String]
-
# @return [String]
-
def normalize_encoding(text)
-
text.encode('windows-1252')
-
rescue ::Encoding::InvalidByteSequenceError,
-
::Encoding::UndefinedConversionError
-
-
-
-
-
-
-
-
-
- Extracted source (around line #450 ):
-
-
-
-
-
-
-448
-449
-450
-451
-452
-453
-
-
-
-
- end
-
else
-
hash[:text] = @document.font.normalize_encoding (hash[:text])
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #444 ):
-
-
-
-
-
-
-442
-443
-444
-445
-446
-447
-
-
-
-
- end
-
-
formatted_text.each do |hash|
-
if hash[:font]
-
@document.font(hash[:font]) do
-
hash[:text] = @document.font.normalize_encoding(hash[:text])
-
-
-
-
-
-
-
-
-
- Extracted source (around line #444 ):
-
-
-
-
-
-
-442
-443
-444
-445
-446
-447
-
-
-
-
- end
-
-
formatted_text.each do |hash|
-
if hash[:font]
-
@document.font(hash[:font]) do
-
hash[:text] = @document.font.normalize_encoding(hash[:text])
-
-
-
-
-
-
-
-
-
- Extracted source (around line #422 ):
-
-
-
-
-
-
-420
-421
-422
-423
-424
-425
-
-
-
-
-
-
def normalized_text(flags)
-
text = normalize_encoding
-
-
text.each { |t| t.delete(:color) } if flags[:dry_run]
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- process_options
-
-
text = normalized_text (flags)
-
-
@document.font_size(@font_size) do
-
shrink_to_fit(text) if @overflow == :shrink_to_fit
-
-
-
-
-
-
-
-
-
- Extracted source (around line #231 ):
-
-
-
-
-
-
-229
-230
-231
-232
-233
-234
-
-
-
-
-
-
if text_rendering_mode == mode
-
yield
-
else
-
wrap_and_restore_text_rendering_mode(mode, &block)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #256 ):
-
-
-
-
-
-
-254
-255
-256
-257
-258
-259
-
-
-
-
- @document.save_font do
-
@document.character_spacing(@character_spacing) do
-
@document.text_rendering_mode (@mode) do
-
process_options
-
-
text = normalized_text(flags)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #260 ):
-
-
-
-
-
-
-258
-259
-260
-261
-262
-263
-
-
-
-
-
-
if character_spacing == amount
-
yield
-
else
-
wrap_and_restore_character_spacing(amount, &block)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #255 ):
-
-
-
-
-
-
-253
-254
-255
-256
-257
-258
-
-
-
-
-
-
@document.save_font do
-
@document.character_spacing (@character_spacing) do
-
@document.text_rendering_mode(@mode) do
-
process_options
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #260 ):
-
-
-
-
-
-
-258
-259
-260
-261
-262
-263
-
-
-
-
- original_size = @font_size
-
-
yield
-
ensure
-
set_font(original_font, original_size) if original_font
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- unprinted_text = []
-
-
@document.save_font do
-
@document.character_spacing(@character_spacing) do
-
@document.text_rendering_mode(@mode) do
-
process_options
-
-
-
-
-
-
-
-
-
- Extracted source (around line #187 ):
-
-
-
-
-
-
-185
-186
-187
-188
-189
-190
-
-
-
-
- # If not wide enough to print any text.
-
def render(flags = {})
-
leftover = super(flags)
-
leftover.map { |hash| hash[:text] }.join
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #97 ):
-
-
-
-
-
-
-95
-96
-97
-98
-99
-100
-
-
-
-
- end
-
-
box.render
-
end
-
-
# @group Experimental API
-
-
-
-
-
-
-
-
-
- Extracted source (around line #114 ):
-
-
-
-
-
-
-112
-113
-114
-115
-116
-117
-
-
-
-
- @pdf.fill_rounded_rectangle [0, @pdf.cursor], 250, 26, 5
-
@pdf.fill_color(pago ? 'FFFFFF' : '374151')
-
@pdf.text_box texto, at: [12, @pdf.cursor - 8], width: 238, size: 10, style: :bold
-
@pdf.fill_color '000000'
-
@pdf.move_down 34
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #43 ):
-
-
-
-
-
-
-41
-42
-43
-44
-45
-46
-
-
-
-
- end
-
-
desenhar_selo_pagamento
-
-
secao "PROVENTOS E DESCONTOS"
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
- def render
-
cabecalho
-
corpo
-
rodape
-
@pdf.render
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #230 ):
-
-
-
-
-
-
-228
-229
-230
-231
-232
-233
-
-
-
-
- auditar!(:editar, @consolidacao, dados_novos: { pdf: 'holerite', motorista: motorista })
-
-
send_data pdf.render ,
-
filename: "holerite_#{motorista.parameterize}_#{@consolidacao.id}.pdf",
-
type: 'application/pdf',
-
disposition: 'attachment'
-
-
-
-
-
-
-
-
-
- Extracted source (around line #6 ):
-
-
-
-
-
-
-4
-5
-6
-7
-8
-9
-
-
-
-
- module BasicImplicitRender # :nodoc:
-
def send_action(method, *args)
-
ret = super
-
default_render unless performed?
-
ret
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
- # which is *not* necessarily the same as the action name.
-
def process_action(...)
-
send_action (...)
-
end
-
-
# Actually call the method associated with the action. Override
-
-
-
-
-
-
-
-
-
- Extracted source (around line #165 ):
-
-
-
-
-
-
-163
-164
-165
-166
-167
-168
-
-
-
-
- def process_action(*) # :nodoc:
-
self.formats = request.formats.filter_map(&:ref)
-
super
-
end
-
-
def _process_variant(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- def process_action(...)
-
run_callbacks(:process_action) do
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def with_request_id(request_id)
-
old_request_id, self.current_request_id = self.current_request_id, request_id
-
yield
-
ensure
-
self.current_request_id = old_request_id
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #10 ):
-
-
-
-
-
-
-8
-9
-10
-11
-12
-
-
-
-
- private
-
def turbo_tracking_request_id(&block)
-
Turbo.with_request_id (request.headers["X-Turbo-Request-Id"], &block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- previous_renderer = self.renderer
-
self.renderer = renderer
-
yield
-
ensure
-
self.renderer = previous_renderer
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #69 ):
-
-
-
-
-
-
-67
-68
-69
-70
-71
-72
-
-
-
-
- ActiveSupport.on_load(base) do
-
around_action do |controller, action|
-
ActionText::Content.with_renderer (controller, &action)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #258 ):
-
-
-
-
-
-
-256
-257
-258
-259
-260
-261
-
-
-
-
- # <tt>process_action</tt> callbacks around the normal behavior.
-
def process_action(...)
-
run_callbacks (:process_action) do
-
super
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
- private
-
def process_action(*)
-
super
-
rescue Exception => exception
-
request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #74 ):
-
-
-
-
-
-
-72
-73
-74
-75
-76
-77
-
-
-
-
-
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
result
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #73 ):
-
-
-
-
-
-
-71
-72
-73
-74
-75
-76
-
-
-
-
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
-
-
ActiveSupport::Notifications.instrument ("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- def process_action(*)
-
_perform_parameter_wrapping if _wrapper_enabled?
-
super
-
end
-
-
# Returns the wrapper key which will be used to store wrapped parameters.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # and it won't be cleaned up by the method below.
-
ActiveRecord::RuntimeRegistry.reset
-
super
-
end
-
-
def cleanup_view_runtime
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- @_response_body = nil
-
-
process_action (action_name, *args)
-
end
-
ruby2_keywords(:process)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #40 ):
-
-
-
-
-
-
-38
-39
-40
-41
-42
-43
-
-
-
-
- def process(...) # :nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #227 ):
-
-
-
-
-
-
-225
-226
-227
-228
-229
-230
-
-
-
-
- set_request!(request)
-
set_response!(response)
-
process (name)
-
request.commit_flash
-
to_a
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #309 ):
-
-
-
-
-
-
-307
-308
-309
-310
-311
-312
-
-
-
-
- middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
-
else
-
new.dispatch (name, req, res)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #49 ):
-
-
-
-
-
-
-47
-48
-49
-50
-51
-52
-
-
-
-
-
-
def dispatch(controller, action, req, res)
-
controller.dispatch (action, req, res)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- controller = controller req
-
res = controller.make_response! req
-
dispatch (controller, params[:action], req, res)
-
rescue ActionController::RoutingError
-
if @raise_on_name_error
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #51 ):
-
-
-
-
-
-
-49
-50
-51
-52
-53
-54
-
-
-
-
- req.route_uri_pattern = route.path.spec.to_s
-
-
_, headers, _ = response = route.app.serve (req)
-
-
if "pass" == headers[Constants::X_CASCADE]
-
req.script_name = script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
-
}
-
yield [match_data, path_parameters, r]
-
}
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
-
-
def serve(req)
-
find_routes (req) do |match, parameters, route|
-
set_params = req.path_parameters
-
path_info = req.path_info
-
script_name = req.script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #882 ):
-
-
-
-
-
-
-880
-881
-882
-883
-884
-885
-
-
-
-
- req = make_request(env)
-
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
-
@router.serve (req)
-
end
-
-
def recognize_path(path, environment = {})
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
- result = catch(:warden) do
-
env['warden'].on_request
-
@app.call (env)
-
end
-
-
result ||= {}
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
-
-
begin
-
_, _, body = response = @app.call (env)
-
rescue Exception
-
env[RACK_TEMPFILES]&.each(&:close!)
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
status, headers, body = response = @app.call (env)
-
-
if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
-
body = body.to_ary
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- case env[REQUEST_METHOD]
-
when "GET", "HEAD"
-
status, headers, body = response = @app.call (env)
-
-
if status == 200 && fresh?(env, headers)
-
response[0] = 304
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
-
-
def call(env)
-
_, _, body = response = @app.call (env)
-
-
if env[REQUEST_METHOD] == HEAD
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
_, headers, _ = response = @app.call (env)
-
-
return response if policy_present?(headers)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
status, headers, _ = response = @app.call (env)
-
-
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
-
# CSP headers might not match nonces in the cached HTML.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #274 ):
-
-
-
-
-
-
-272
-273
-274
-275
-276
-277
-
-
-
-
- req = make_request env
-
prepare_session(req)
-
status, headers, body = app.call (req.env)
-
res = Rack::Response::Raw.new status, headers
-
commit_session(req, res)
-
[status, headers, body]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #268 ):
-
-
-
-
-
-
-266
-267
-268
-269
-270
-271
-
-
-
-
-
-
def call(env)
-
context (env)
-
end
-
-
def context(env, app = @app)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #689 ):
-
-
-
-
-
-
-687
-688
-689
-690
-691
-692
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
response = @app.call (env)
-
-
if request.have_cookie_jar?
-
cookie_jar = request.cookie_jar
-
-
-
-
-
-
-
-
-
- Extracted source (around line #655 ):
-
-
-
-
-
-
-653
-654
-655
-656
-657
-658
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- error = nil
-
result = run_callbacks :call do
-
@app.call (env)
-
rescue => error
-
end
-
raise error if error
-
-
-
-
-
-
-
-
-
- Extracted source (around line #101 ):
-
-
-
-
-
-
-99
-100
-101
-102
-103
-104
-
-
-
-
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def call(env)
-
error = nil
-
result = run_callbacks :call do
-
@app.call(env)
-
rescue => error
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #16 ):
-
-
-
-
-
-
-14
-15
-16
-17
-18
-19
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
return @app.call (env) unless actionable_request?(request)
-
-
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if headers[Constants::X_CASCADE] == "pass"
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #132 ):
-
-
-
-
-
-
-130
-131
-132
-133
-134
-135
-
-
-
-
-
-
def call_app(env)
-
@app.call (env)
-
rescue => e
-
throw :app_exception, e
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #19 ):
-
-
-
-
-
-
-17
-18
-19
-20
-21
-22
-
-
-
-
- app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app (env) unless request.permitted?
-
-
if id = id_for_repl_session_update(request)
-
return update_repl_session(id, request)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
-
-
def call(env)
-
@app.call (env)
-
rescue Exception => exception
-
request = ActionDispatch::Request.new env
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
-
-
logger.info { started_request_message(request) }
-
status, headers, body = response = @app.call (env)
-
body = ::Rack::BodyProxy.new(body, &handle.method(:finish))
-
-
if response.frozen?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged(*compute_tags(request)) { call_app (request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- def tagged(*tags)
-
pushed_count = tag_stack.push_tags(tags).size
-
yield self
-
ensure
-
pop_tags(pushed_count)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged (*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #241 ):
-
-
-
-
-
-
-239
-240
-241
-242
-243
-244
-
-
-
-
- super(name, *args, **kwargs, &block)
-
elsif loggers.one?
-
loggers.first.send (name, *args, **kwargs, &block)
-
else
-
loggers.map { |logger| logger.send(name, *args, **kwargs, &block) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged (*compute_tags(request)) { call_app(request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- ::Rails.logger.silence { @app.call(env) }
-
else
-
@app.call (env)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #92 ):
-
-
-
-
-
-
-90
-91
-92
-93
-94
-95
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.remote_ip = GetIp.new(req, check_ip, proxies)
-
@app.call (req.env)
-
end
-
-
# The GetIp class exists as a way to defer processing of the request data
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.request_id = make_request_id(req.headers[@header])
-
@app.call (env).tap { |_status, headers, _body| headers[@header] = req.request_id }
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
def method_override(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def call(env)
-
start_time = Utils.clock_time
-
_, headers, _ = response = @app.call (env)
-
-
request_time = Utils.clock_time - start_time
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- response = nil
-
events = @subscriber.collect_events do
-
response = @app.call (env)
-
end
-
-
headers = response[1]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- events = []
-
ActiveSupport::IsolatedExecutionState[KEY] = events
-
yield
-
events
-
ensure
-
ActiveSupport::IsolatedExecutionState.delete(KEY)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- def call(env)
-
response = nil
-
events = @subscriber.collect_events do
-
response = @app.call(env)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- end
-
else
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
-
-
def call(env)
-
@file_handler.attempt(env) || @app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if body.respond_to?(:to_path)
-
case type = variation(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
-
-
if skip_it || !config.enabled
-
status, headers, body = @app.call (env)
-
client_settings.disable_profiling = true
-
return client_settings.handle_cookie([status, headers, body])
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #536 ):
-
-
-
-
-
-
-534
-535
-536
-537
-538
-539
-
-
-
-
- def call(env)
-
req = build_request env
-
app.call req.env
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #279 ):
-
-
-
-
-
-
-277
-278
-279
-280
-281
-282
-
-
-
-
- def call(env)
-
env[Const::PUMA_CONFIG] = @config
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #99 ):
-
-
-
-
-
-
-97
-98
-99
-100
-101
-102
-
-
-
-
- if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call (env)
-
end
-
else
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
-
-
-
-
-
-
-
-
-
- Extracted source (around line #390 ):
-
-
-
-
-
-
-388
-389
-390
-391
-392
-393
-
-
-
-
- t[:with_force_shutdown] = true
-
end
-
yield
-
ensure
-
t[:with_force_shutdown] = false
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- begin
-
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call(env)
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #472 ):
-
-
-
-
-
-
-470
-471
-472
-473
-474
-475
-
-
-
-
- while true
-
@requests_count += 1
-
case handle_request (client, requests + 1)
-
when false
-
break
-
when :async
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- @status = :run
-
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
-
-
if @queue_requests
-
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
-
-
-
-
-
-
-
-
-
- Extracted source (around line #167 ):
-
-
-
-
-
-
-165
-166
-167
-168
-169
-170
-
-
-
-
-
-
begin
-
@out_of_band_pending = true if block.call (work)
-
rescue Exception => e
-
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
-
end
-
-
-
-
-
-
-
-
-
-Rails.root: /app
-
-
-
-
- Exception Causes
-
-
-
-
-
-
-
- Extracted source (around line #142 ):
-
-
-
-
-
-
-140
-141
-142
-143
-144
-145
-
-
-
-
- # @return [String]
-
def normalize_encoding(text)
-
text.encode ('windows-1252')
-
rescue ::Encoding::InvalidByteSequenceError,
-
::Encoding::UndefinedConversionError
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #142 ):
-
-
-
-
-
-
-140
-141
-142
-143
-144
-145
-
-
-
-
- # @return [String]
-
def normalize_encoding(text)
-
text.encode ('windows-1252')
-
rescue ::Encoding::InvalidByteSequenceError,
-
::Encoding::UndefinedConversionError
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #450 ):
-
-
-
-
-
-
-448
-449
-450
-451
-452
-453
-
-
-
-
- end
-
else
-
hash[:text] = @document.font.normalize_encoding (hash[:text])
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #444 ):
-
-
-
-
-
-
-442
-443
-444
-445
-446
-447
-
-
-
-
- end
-
-
formatted_text.each do |hash|
-
if hash[:font]
-
@document.font(hash[:font]) do
-
hash[:text] = @document.font.normalize_encoding(hash[:text])
-
-
-
-
-
-
-
-
-
- Extracted source (around line #444 ):
-
-
-
-
-
-
-442
-443
-444
-445
-446
-447
-
-
-
-
- end
-
-
formatted_text.each do |hash|
-
if hash[:font]
-
@document.font(hash[:font]) do
-
hash[:text] = @document.font.normalize_encoding(hash[:text])
-
-
-
-
-
-
-
-
-
- Extracted source (around line #422 ):
-
-
-
-
-
-
-420
-421
-422
-423
-424
-425
-
-
-
-
-
-
def normalized_text(flags)
-
text = normalize_encoding
-
-
text.each { |t| t.delete(:color) } if flags[:dry_run]
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- process_options
-
-
text = normalized_text (flags)
-
-
@document.font_size(@font_size) do
-
shrink_to_fit(text) if @overflow == :shrink_to_fit
-
-
-
-
-
-
-
-
-
- Extracted source (around line #231 ):
-
-
-
-
-
-
-229
-230
-231
-232
-233
-234
-
-
-
-
-
-
if text_rendering_mode == mode
-
yield
-
else
-
wrap_and_restore_text_rendering_mode(mode, &block)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #256 ):
-
-
-
-
-
-
-254
-255
-256
-257
-258
-259
-
-
-
-
- @document.save_font do
-
@document.character_spacing(@character_spacing) do
-
@document.text_rendering_mode (@mode) do
-
process_options
-
-
text = normalized_text(flags)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #260 ):
-
-
-
-
-
-
-258
-259
-260
-261
-262
-263
-
-
-
-
-
-
if character_spacing == amount
-
yield
-
else
-
wrap_and_restore_character_spacing(amount, &block)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #255 ):
-
-
-
-
-
-
-253
-254
-255
-256
-257
-258
-
-
-
-
-
-
@document.save_font do
-
@document.character_spacing (@character_spacing) do
-
@document.text_rendering_mode(@mode) do
-
process_options
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #260 ):
-
-
-
-
-
-
-258
-259
-260
-261
-262
-263
-
-
-
-
- original_size = @font_size
-
-
yield
-
ensure
-
set_font(original_font, original_size) if original_font
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- unprinted_text = []
-
-
@document.save_font do
-
@document.character_spacing(@character_spacing) do
-
@document.text_rendering_mode(@mode) do
-
process_options
-
-
-
-
-
-
-
-
-
- Extracted source (around line #187 ):
-
-
-
-
-
-
-185
-186
-187
-188
-189
-190
-
-
-
-
- # If not wide enough to print any text.
-
def render(flags = {})
-
leftover = super(flags)
-
leftover.map { |hash| hash[:text] }.join
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #97 ):
-
-
-
-
-
-
-95
-96
-97
-98
-99
-100
-
-
-
-
- end
-
-
box.render
-
end
-
-
# @group Experimental API
-
-
-
-
-
-
-
-
-
- Extracted source (around line #114 ):
-
-
-
-
-
-
-112
-113
-114
-115
-116
-117
-
-
-
-
- @pdf.fill_rounded_rectangle [0, @pdf.cursor], 250, 26, 5
-
@pdf.fill_color(pago ? 'FFFFFF' : '374151')
-
@pdf.text_box texto, at: [12, @pdf.cursor - 8], width: 238, size: 10, style: :bold
-
@pdf.fill_color '000000'
-
@pdf.move_down 34
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #43 ):
-
-
-
-
-
-
-41
-42
-43
-44
-45
-46
-
-
-
-
- end
-
-
desenhar_selo_pagamento
-
-
secao "PROVENTOS E DESCONTOS"
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
- def render
-
cabecalho
-
corpo
-
rodape
-
@pdf.render
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #230 ):
-
-
-
-
-
-
-228
-229
-230
-231
-232
-233
-
-
-
-
- auditar!(:editar, @consolidacao, dados_novos: { pdf: 'holerite', motorista: motorista })
-
-
send_data pdf.render ,
-
filename: "holerite_#{motorista.parameterize}_#{@consolidacao.id}.pdf",
-
type: 'application/pdf',
-
disposition: 'attachment'
-
-
-
-
-
-
-
-
-
- Extracted source (around line #6 ):
-
-
-
-
-
-
-4
-5
-6
-7
-8
-9
-
-
-
-
- module BasicImplicitRender # :nodoc:
-
def send_action(method, *args)
-
ret = super
-
default_render unless performed?
-
ret
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
- # which is *not* necessarily the same as the action name.
-
def process_action(...)
-
send_action (...)
-
end
-
-
# Actually call the method associated with the action. Override
-
-
-
-
-
-
-
-
-
- Extracted source (around line #165 ):
-
-
-
-
-
-
-163
-164
-165
-166
-167
-168
-
-
-
-
- def process_action(*) # :nodoc:
-
self.formats = request.formats.filter_map(&:ref)
-
super
-
end
-
-
def _process_variant(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- def process_action(...)
-
run_callbacks(:process_action) do
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def with_request_id(request_id)
-
old_request_id, self.current_request_id = self.current_request_id, request_id
-
yield
-
ensure
-
self.current_request_id = old_request_id
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #10 ):
-
-
-
-
-
-
-8
-9
-10
-11
-12
-
-
-
-
- private
-
def turbo_tracking_request_id(&block)
-
Turbo.with_request_id (request.headers["X-Turbo-Request-Id"], &block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- previous_renderer = self.renderer
-
self.renderer = renderer
-
yield
-
ensure
-
self.renderer = previous_renderer
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #69 ):
-
-
-
-
-
-
-67
-68
-69
-70
-71
-72
-
-
-
-
- ActiveSupport.on_load(base) do
-
around_action do |controller, action|
-
ActionText::Content.with_renderer (controller, &action)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #258 ):
-
-
-
-
-
-
-256
-257
-258
-259
-260
-261
-
-
-
-
- # <tt>process_action</tt> callbacks around the normal behavior.
-
def process_action(...)
-
run_callbacks (:process_action) do
-
super
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
- private
-
def process_action(*)
-
super
-
rescue Exception => exception
-
request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #74 ):
-
-
-
-
-
-
-72
-73
-74
-75
-76
-77
-
-
-
-
-
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
result
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #73 ):
-
-
-
-
-
-
-71
-72
-73
-74
-75
-76
-
-
-
-
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
-
-
ActiveSupport::Notifications.instrument ("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- def process_action(*)
-
_perform_parameter_wrapping if _wrapper_enabled?
-
super
-
end
-
-
# Returns the wrapper key which will be used to store wrapped parameters.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # and it won't be cleaned up by the method below.
-
ActiveRecord::RuntimeRegistry.reset
-
super
-
end
-
-
def cleanup_view_runtime
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- @_response_body = nil
-
-
process_action (action_name, *args)
-
end
-
ruby2_keywords(:process)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #40 ):
-
-
-
-
-
-
-38
-39
-40
-41
-42
-43
-
-
-
-
- def process(...) # :nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #227 ):
-
-
-
-
-
-
-225
-226
-227
-228
-229
-230
-
-
-
-
- set_request!(request)
-
set_response!(response)
-
process (name)
-
request.commit_flash
-
to_a
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #309 ):
-
-
-
-
-
-
-307
-308
-309
-310
-311
-312
-
-
-
-
- middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
-
else
-
new.dispatch (name, req, res)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #49 ):
-
-
-
-
-
-
-47
-48
-49
-50
-51
-52
-
-
-
-
-
-
def dispatch(controller, action, req, res)
-
controller.dispatch (action, req, res)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- controller = controller req
-
res = controller.make_response! req
-
dispatch (controller, params[:action], req, res)
-
rescue ActionController::RoutingError
-
if @raise_on_name_error
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #51 ):
-
-
-
-
-
-
-49
-50
-51
-52
-53
-54
-
-
-
-
- req.route_uri_pattern = route.path.spec.to_s
-
-
_, headers, _ = response = route.app.serve (req)
-
-
if "pass" == headers[Constants::X_CASCADE]
-
req.script_name = script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
-
}
-
yield [match_data, path_parameters, r]
-
}
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
-
-
def serve(req)
-
find_routes (req) do |match, parameters, route|
-
set_params = req.path_parameters
-
path_info = req.path_info
-
script_name = req.script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #882 ):
-
-
-
-
-
-
-880
-881
-882
-883
-884
-885
-
-
-
-
- req = make_request(env)
-
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
-
@router.serve (req)
-
end
-
-
def recognize_path(path, environment = {})
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
- result = catch(:warden) do
-
env['warden'].on_request
-
@app.call (env)
-
end
-
-
result ||= {}
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
-
-
begin
-
_, _, body = response = @app.call (env)
-
rescue Exception
-
env[RACK_TEMPFILES]&.each(&:close!)
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
status, headers, body = response = @app.call (env)
-
-
if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
-
body = body.to_ary
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- case env[REQUEST_METHOD]
-
when "GET", "HEAD"
-
status, headers, body = response = @app.call (env)
-
-
if status == 200 && fresh?(env, headers)
-
response[0] = 304
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
-
-
def call(env)
-
_, _, body = response = @app.call (env)
-
-
if env[REQUEST_METHOD] == HEAD
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
_, headers, _ = response = @app.call (env)
-
-
return response if policy_present?(headers)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
status, headers, _ = response = @app.call (env)
-
-
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
-
# CSP headers might not match nonces in the cached HTML.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #274 ):
-
-
-
-
-
-
-272
-273
-274
-275
-276
-277
-
-
-
-
- req = make_request env
-
prepare_session(req)
-
status, headers, body = app.call (req.env)
-
res = Rack::Response::Raw.new status, headers
-
commit_session(req, res)
-
[status, headers, body]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #268 ):
-
-
-
-
-
-
-266
-267
-268
-269
-270
-271
-
-
-
-
-
-
def call(env)
-
context (env)
-
end
-
-
def context(env, app = @app)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #689 ):
-
-
-
-
-
-
-687
-688
-689
-690
-691
-692
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
response = @app.call (env)
-
-
if request.have_cookie_jar?
-
cookie_jar = request.cookie_jar
-
-
-
-
-
-
-
-
-
- Extracted source (around line #655 ):
-
-
-
-
-
-
-653
-654
-655
-656
-657
-658
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- error = nil
-
result = run_callbacks :call do
-
@app.call (env)
-
rescue => error
-
end
-
raise error if error
-
-
-
-
-
-
-
-
-
- Extracted source (around line #101 ):
-
-
-
-
-
-
-99
-100
-101
-102
-103
-104
-
-
-
-
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def call(env)
-
error = nil
-
result = run_callbacks :call do
-
@app.call(env)
-
rescue => error
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #16 ):
-
-
-
-
-
-
-14
-15
-16
-17
-18
-19
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
return @app.call (env) unless actionable_request?(request)
-
-
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if headers[Constants::X_CASCADE] == "pass"
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #132 ):
-
-
-
-
-
-
-130
-131
-132
-133
-134
-135
-
-
-
-
-
-
def call_app(env)
-
@app.call (env)
-
rescue => e
-
throw :app_exception, e
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #19 ):
-
-
-
-
-
-
-17
-18
-19
-20
-21
-22
-
-
-
-
- app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app (env) unless request.permitted?
-
-
if id = id_for_repl_session_update(request)
-
return update_repl_session(id, request)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
-
-
def call(env)
-
@app.call (env)
-
rescue Exception => exception
-
request = ActionDispatch::Request.new env
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
-
-
logger.info { started_request_message(request) }
-
status, headers, body = response = @app.call (env)
-
body = ::Rack::BodyProxy.new(body, &handle.method(:finish))
-
-
if response.frozen?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged(*compute_tags(request)) { call_app (request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- def tagged(*tags)
-
pushed_count = tag_stack.push_tags(tags).size
-
yield self
-
ensure
-
pop_tags(pushed_count)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged (*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #241 ):
-
-
-
-
-
-
-239
-240
-241
-242
-243
-244
-
-
-
-
- super(name, *args, **kwargs, &block)
-
elsif loggers.one?
-
loggers.first.send (name, *args, **kwargs, &block)
-
else
-
loggers.map { |logger| logger.send(name, *args, **kwargs, &block) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged (*compute_tags(request)) { call_app(request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- ::Rails.logger.silence { @app.call(env) }
-
else
-
@app.call (env)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #92 ):
-
-
-
-
-
-
-90
-91
-92
-93
-94
-95
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.remote_ip = GetIp.new(req, check_ip, proxies)
-
@app.call (req.env)
-
end
-
-
# The GetIp class exists as a way to defer processing of the request data
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.request_id = make_request_id(req.headers[@header])
-
@app.call (env).tap { |_status, headers, _body| headers[@header] = req.request_id }
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
def method_override(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def call(env)
-
start_time = Utils.clock_time
-
_, headers, _ = response = @app.call (env)
-
-
request_time = Utils.clock_time - start_time
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- response = nil
-
events = @subscriber.collect_events do
-
response = @app.call (env)
-
end
-
-
headers = response[1]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- events = []
-
ActiveSupport::IsolatedExecutionState[KEY] = events
-
yield
-
events
-
ensure
-
ActiveSupport::IsolatedExecutionState.delete(KEY)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- def call(env)
-
response = nil
-
events = @subscriber.collect_events do
-
response = @app.call(env)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- end
-
else
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
-
-
def call(env)
-
@file_handler.attempt(env) || @app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if body.respond_to?(:to_path)
-
case type = variation(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
-
-
if skip_it || !config.enabled
-
status, headers, body = @app.call (env)
-
client_settings.disable_profiling = true
-
return client_settings.handle_cookie([status, headers, body])
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #536 ):
-
-
-
-
-
-
-534
-535
-536
-537
-538
-539
-
-
-
-
- def call(env)
-
req = build_request env
-
app.call req.env
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #279 ):
-
-
-
-
-
-
-277
-278
-279
-280
-281
-282
-
-
-
-
- def call(env)
-
env[Const::PUMA_CONFIG] = @config
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #99 ):
-
-
-
-
-
-
-97
-98
-99
-100
-101
-102
-
-
-
-
- if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call (env)
-
end
-
else
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
-
-
-
-
-
-
-
-
-
- Extracted source (around line #390 ):
-
-
-
-
-
-
-388
-389
-390
-391
-392
-393
-
-
-
-
- t[:with_force_shutdown] = true
-
end
-
yield
-
ensure
-
t[:with_force_shutdown] = false
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- begin
-
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call(env)
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #472 ):
-
-
-
-
-
-
-470
-471
-472
-473
-474
-475
-
-
-
-
- while true
-
@requests_count += 1
-
case handle_request (client, requests + 1)
-
when false
-
break
-
when :async
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- @status = :run
-
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
-
-
if @queue_requests
-
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
-
-
-
-
-
-
-
-
-
- Extracted source (around line #167 ):
-
-
-
-
-
-
-165
-166
-167
-168
-169
-170
-
-
-
-
-
-
begin
-
@out_of_band_pending = true if block.call (work)
-
rescue Exception => e
-
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
-
end
-
-
-
-
-
-
-
-
-
-
Rails.root: /app
-
-
-
-
-
- Request
- Parameters :
{"motorista"=>"Bruno Corgozinho", "id"=>"9"}
-
-
-
-
-
_csrf_token: "3uvfWx7owtIWLxm7TEUJHDuh2ufWpXl4uxC6PVXZhp0"
-session_id: "d89794e342c7fcb512eb4b9b1ebf9ae4"
-user_return_to: "/"
-warden.user.user.key: [[1], "$2a$12$nEAEta59SGXQbRb8EA.mYe"]
-
-
-
-
-
GATEWAY_INTERFACE: "CGI/1.2"
-HTTP_ACCEPT: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
-HTTP_ACCEPT_ENCODING: "gzip, deflate"
-HTTP_ACCEPT_LANGUAGE: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
-HTTP_VERSION: "HTTP/1.1"
-ORIGINAL_SCRIPT_NAME: ""
-REMOTE_ADDR: "192.168.144.1"
-SERVER_NAME: "100.75.222.23"
-SERVER_PROTOCOL: "HTTP/1.1"
-
-
-Response
-Headers :
None
-
-
-
-
-
-
diff --git a/Erros/Action Controller_ Exception caughtQuando clica na opção usuarios.html b/Erros/Action Controller_ Exception caughtQuando clica na opção usuarios.html
deleted file mode 100644
index 0aef099..0000000
--- a/Erros/Action Controller_ Exception caughtQuando clica na opção usuarios.html
+++ /dev/null
@@ -1,5441 +0,0 @@
-
-
-
-
-
-
- Action Controller: Exception caught
-
-
-
-
-
-
-
-
- NoMethodError in
- Admin::Usuarios#index
-
-
-
-
-
- Showing /app/app/views/admin/usuarios/index.html.erb where line #60 raised:
-
- undefined method `badge_status_usuario' for #<ActionView::Base:0x00000000011f80>
-
-
-
-
- Extracted source (around line #61 ):
-
-
-
-
-
-
-59
-60
-61
-62
-63
-64
-
-
-
-
- </td>
-
<td class="px-6 py-4">
-
'.freeze; @output_buffer.append=( badge_status_usuario (usuario.ativo?) ); @output_buffer.safe_append='
-
</td>
-
<td class="px-6 py-4">
-
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
-
-
-
-
-
-
-
-
-
- Extracted source (around line #1 ):
-
-
-
-
-
-
-1
-
-
-
-
-def to_xml(...); _ = records; _.to_xml(...);rescue NoMethodError => e; if _.nil? && e.name == :to_xml; raise DelegationError, "ActiveRecord::Delegation#to_xml delegated to records.to_xml, but records is nil: #{self.inspect}"; else; raise; end;end;def encode_with(...); _ = records; _.encode_with(...);rescue NoMethodError => e; if _.nil? && e.name == :encode_with; raise DelegationError, "ActiveRecord::Delegation#encode_with delegated to records.encode_with, but records is nil: #{self.inspect}"; else; raise; end;end;def length(...); _ = records; _.length(...);rescue NoMethodError => e; if _.nil? && e.name == :length; raise DelegationError, "ActiveRecord::Delegation#length delegated to records.length, but records is nil: #{self.inspect}"; else; raise; end;end;def each(...); _ = records; _.each (...);rescue NoMethodError => e; if _.nil? && e.name == :each; raise DelegationError, "ActiveRecord::Delegation#each delegated to records.each, but records is nil: #{self.inspect}"; else; raise; end;end;def join(...); _ = records; _.join(...);rescue NoMethodError => e; if _.nil? && e.name == :join; raise DelegationError, "ActiveRecord::Delegation#join delegated to records.join, but records is nil: #{self.inspect}"; else; raise; end;end;def intersect?(...); _ = records; _.intersect?(...);rescue NoMethodError => e; if _.nil? && e.name == :intersect?; raise DelegationError, "ActiveRecord::Delegation#intersect? delegated to records.intersect?, but records is nil: #{self.inspect}"; else; raise; end;end;def [](...); _ = records; _.[](...);rescue NoMethodError => e; if _.nil? && e.name == :[]; raise DelegationError, "ActiveRecord::Delegation#[] delegated to records.[], but records is nil: #{self.inspect}"; else; raise; end;end;def &(...); _ = records; _.&(...);rescue NoMethodError => e; if _.nil? && e.name == :&; raise DelegationError, "ActiveRecord::Delegation#& delegated to records.&, but records is nil: #{self.inspect}"; else; raise; end;end;def |(...); _ = records; _.|(...);rescue NoMethodError => e; if _.nil? && e.name == :|; raise DelegationError, "ActiveRecord::Delegation#| delegated to records.|, but records is nil: #{self.inspect}"; else; raise; end;end;def +(...); _ = records; _.+(...);rescue NoMethodError => e; if _.nil? && e.name == :+; raise DelegationError, "ActiveRecord::Delegation#+ delegated to records.+, but records is nil: #{self.inspect}"; else; raise; end;end;def -(...); _ = records; _.-(...);rescue NoMethodError => e; if _.nil? && e.name == :-; raise DelegationError, "ActiveRecord::Delegation#- delegated to records.-, but records is nil: #{self.inspect}"; else; raise; end;end;def sample(...); _ = records; _.sample(...);rescue NoMethodError => e; if _.nil? && e.name == :sample; raise DelegationError, "ActiveRecord::Delegation#sample delegated to records.sample, but records is nil: #{self.inspect}"; else; raise; end;end;def reverse(...); _ = records; _.reverse(...);rescue NoMethodError => e; if _.nil? && e.name == :reverse; raise DelegationError, "ActiveRecord::Delegation#reverse delegated to records.reverse, but records is nil: #{self.inspect}"; else; raise; end;end;def rotate(...); _ = records; _.rotate(...);rescue NoMethodError => e; if _.nil? && e.name == :rotate; raise DelegationError, "ActiveRecord::Delegation#rotate delegated to records.rotate, but records is nil: #{self.inspect}"; else; raise; end;end;def compact(...); _ = records; _.compact(...);rescue NoMethodError => e; if _.nil? && e.name == :compact; raise DelegationError, "ActiveRecord::Delegation#compact delegated to records.compact, but records is nil: #{self.inspect}"; else; raise; end;end;def in_groups(...); _ = records; _.in_groups(...);rescue NoMethodError => e; if _.nil? && e.name == :in_groups; raise DelegationError, "ActiveRecord::Delegation#in_groups delegated to records.in_groups, but records is nil: #{self.inspect}"; else; raise; end;end;def in_groups_of(...); _ = records; _.in_groups_of(...);rescue NoMethodError => e; if _.nil? && e.name == :in_groups_of; raise DelegationError, "ActiveRecord::Delegation#in_groups_of delegated to records.in_groups_of, but records is nil: #{self.inspect}"; else; raise; end;end;def to_sentence(...); _ = records; _.to_sentence(...);rescue NoMethodError => e; if _.nil? && e.name == :to_sentence; raise DelegationError, "ActiveRecord::Delegation#to_sentence delegated to records.to_sentence, but records is nil: #{self.inspect}"; else; raise; end;end;def to_fs(...); _ = records; _.to_fs(...);rescue NoMethodError => e; if _.nil? && e.name == :to_fs; raise DelegationError, "ActiveRecord::Delegation#to_fs delegated to records.to_fs, but records is nil: #{self.inspect}"; else; raise; end;end;def to_formatted_s(...); _ = records; _.to_formatted_s(...);rescue NoMethodError => e; if _.nil? && e.name == :to_formatted_s; raise DelegationError, "ActiveRecord::Delegation#to_formatted_s delegated to records.to_formatted_s, but records is nil: #{self.inspect}"; else; raise; end;end;def as_json(...); _ = records; _.as_json(...);rescue NoMethodError => e; if _.nil? && e.name == :as_json; raise DelegationError, "ActiveRecord::Delegation#as_json delegated to records.as_json, but records is nil: #{self.inspect}"; else; raise; end;end;def shuffle(...); _ = records; _.shuffle(...);rescue NoMethodError => e; if _.nil? && e.name == :shuffle; raise DelegationError, "ActiveRecord::Delegation#shuffle delegated to records.shuffle, but records is nil: #{self.inspect}"; else; raise; end;end;def split(...); _ = records; _.split(...);rescue NoMethodError => e; if _.nil? && e.name == :split; raise DelegationError, "ActiveRecord::Delegation#split delegated to records.split, but records is nil: #{self.inspect}"; else; raise; end;end;def slice(...); _ = records; _.slice(...);rescue NoMethodError => e; if _.nil? && e.name == :slice; raise DelegationError, "ActiveRecord::Delegation#slice delegated to records.slice, but records is nil: #{self.inspect}"; else; raise; end;end;def index(...); _ = records; _.index(...);rescue NoMethodError => e; if _.nil? && e.name == :index; raise DelegationError, "ActiveRecord::Delegation#index delegated to records.index, but records is nil: #{self.inspect}"; else; raise; end;end;def rindex(...); _ = records; _.rindex(...);rescue NoMethodError => e; if _.nil? && e.name == :rindex; raise DelegationError, "ActiveRecord::Delegation#rindex delegated to records.rindex, but records is nil: #{self.inspect}"; else; raise; end;end
-
-
-
-
-
-
-
-
- Extracted source (around line #1 ):
-
-
-
-
-
-
-1
-
-
-
-
-def to_xml(...); _ = records; _.to_xml(...);rescue NoMethodError => e; if _.nil? && e.name == :to_xml; raise DelegationError, "ActiveRecord::Delegation#to_xml delegated to records.to_xml, but records is nil: #{self.inspect}"; else; raise; end;end;def encode_with(...); _ = records; _.encode_with(...);rescue NoMethodError => e; if _.nil? && e.name == :encode_with; raise DelegationError, "ActiveRecord::Delegation#encode_with delegated to records.encode_with, but records is nil: #{self.inspect}"; else; raise; end;end;def length(...); _ = records; _.length(...);rescue NoMethodError => e; if _.nil? && e.name == :length; raise DelegationError, "ActiveRecord::Delegation#length delegated to records.length, but records is nil: #{self.inspect}"; else; raise; end;end;def each(...); _ = records; _.each (...);rescue NoMethodError => e; if _.nil? && e.name == :each; raise DelegationError, "ActiveRecord::Delegation#each delegated to records.each, but records is nil: #{self.inspect}"; else; raise; end;end;def join(...); _ = records; _.join(...);rescue NoMethodError => e; if _.nil? && e.name == :join; raise DelegationError, "ActiveRecord::Delegation#join delegated to records.join, but records is nil: #{self.inspect}"; else; raise; end;end;def intersect?(...); _ = records; _.intersect?(...);rescue NoMethodError => e; if _.nil? && e.name == :intersect?; raise DelegationError, "ActiveRecord::Delegation#intersect? delegated to records.intersect?, but records is nil: #{self.inspect}"; else; raise; end;end;def [](...); _ = records; _.[](...);rescue NoMethodError => e; if _.nil? && e.name == :[]; raise DelegationError, "ActiveRecord::Delegation#[] delegated to records.[], but records is nil: #{self.inspect}"; else; raise; end;end;def &(...); _ = records; _.&(...);rescue NoMethodError => e; if _.nil? && e.name == :&; raise DelegationError, "ActiveRecord::Delegation#& delegated to records.&, but records is nil: #{self.inspect}"; else; raise; end;end;def |(...); _ = records; _.|(...);rescue NoMethodError => e; if _.nil? && e.name == :|; raise DelegationError, "ActiveRecord::Delegation#| delegated to records.|, but records is nil: #{self.inspect}"; else; raise; end;end;def +(...); _ = records; _.+(...);rescue NoMethodError => e; if _.nil? && e.name == :+; raise DelegationError, "ActiveRecord::Delegation#+ delegated to records.+, but records is nil: #{self.inspect}"; else; raise; end;end;def -(...); _ = records; _.-(...);rescue NoMethodError => e; if _.nil? && e.name == :-; raise DelegationError, "ActiveRecord::Delegation#- delegated to records.-, but records is nil: #{self.inspect}"; else; raise; end;end;def sample(...); _ = records; _.sample(...);rescue NoMethodError => e; if _.nil? && e.name == :sample; raise DelegationError, "ActiveRecord::Delegation#sample delegated to records.sample, but records is nil: #{self.inspect}"; else; raise; end;end;def reverse(...); _ = records; _.reverse(...);rescue NoMethodError => e; if _.nil? && e.name == :reverse; raise DelegationError, "ActiveRecord::Delegation#reverse delegated to records.reverse, but records is nil: #{self.inspect}"; else; raise; end;end;def rotate(...); _ = records; _.rotate(...);rescue NoMethodError => e; if _.nil? && e.name == :rotate; raise DelegationError, "ActiveRecord::Delegation#rotate delegated to records.rotate, but records is nil: #{self.inspect}"; else; raise; end;end;def compact(...); _ = records; _.compact(...);rescue NoMethodError => e; if _.nil? && e.name == :compact; raise DelegationError, "ActiveRecord::Delegation#compact delegated to records.compact, but records is nil: #{self.inspect}"; else; raise; end;end;def in_groups(...); _ = records; _.in_groups(...);rescue NoMethodError => e; if _.nil? && e.name == :in_groups; raise DelegationError, "ActiveRecord::Delegation#in_groups delegated to records.in_groups, but records is nil: #{self.inspect}"; else; raise; end;end;def in_groups_of(...); _ = records; _.in_groups_of(...);rescue NoMethodError => e; if _.nil? && e.name == :in_groups_of; raise DelegationError, "ActiveRecord::Delegation#in_groups_of delegated to records.in_groups_of, but records is nil: #{self.inspect}"; else; raise; end;end;def to_sentence(...); _ = records; _.to_sentence(...);rescue NoMethodError => e; if _.nil? && e.name == :to_sentence; raise DelegationError, "ActiveRecord::Delegation#to_sentence delegated to records.to_sentence, but records is nil: #{self.inspect}"; else; raise; end;end;def to_fs(...); _ = records; _.to_fs(...);rescue NoMethodError => e; if _.nil? && e.name == :to_fs; raise DelegationError, "ActiveRecord::Delegation#to_fs delegated to records.to_fs, but records is nil: #{self.inspect}"; else; raise; end;end;def to_formatted_s(...); _ = records; _.to_formatted_s(...);rescue NoMethodError => e; if _.nil? && e.name == :to_formatted_s; raise DelegationError, "ActiveRecord::Delegation#to_formatted_s delegated to records.to_formatted_s, but records is nil: #{self.inspect}"; else; raise; end;end;def as_json(...); _ = records; _.as_json(...);rescue NoMethodError => e; if _.nil? && e.name == :as_json; raise DelegationError, "ActiveRecord::Delegation#as_json delegated to records.as_json, but records is nil: #{self.inspect}"; else; raise; end;end;def shuffle(...); _ = records; _.shuffle(...);rescue NoMethodError => e; if _.nil? && e.name == :shuffle; raise DelegationError, "ActiveRecord::Delegation#shuffle delegated to records.shuffle, but records is nil: #{self.inspect}"; else; raise; end;end;def split(...); _ = records; _.split(...);rescue NoMethodError => e; if _.nil? && e.name == :split; raise DelegationError, "ActiveRecord::Delegation#split delegated to records.split, but records is nil: #{self.inspect}"; else; raise; end;end;def slice(...); _ = records; _.slice(...);rescue NoMethodError => e; if _.nil? && e.name == :slice; raise DelegationError, "ActiveRecord::Delegation#slice delegated to records.slice, but records is nil: #{self.inspect}"; else; raise; end;end;def index(...); _ = records; _.index(...);rescue NoMethodError => e; if _.nil? && e.name == :index; raise DelegationError, "ActiveRecord::Delegation#index delegated to records.index, but records is nil: #{self.inspect}"; else; raise; end;end;def rindex(...); _ = records; _.rindex(...);rescue NoMethodError => e; if _.nil? && e.name == :rindex; raise DelegationError, "ActiveRecord::Delegation#rindex delegated to records.rindex, but records is nil: #{self.inspect}"; else; raise; end;end
-
-
-
-
-
-
-
-
- Extracted source (around line #38 ):
-
-
-
-
-
-
-36
-37
-38
-39
-40
-41
-
-
-
-
- </thead>
-
<tbody class="divide-y divide-white/5">
-
<% @usuarios.each do |usuario| %>
-
<tr class="hover:bg-white/5 transition-colors group">
-
<td class="px-6 py-4">
-
<div class="flex items-center gap-3">
-
-
-
-
-
-
-
-
-
- Extracted source (around line #263 ):
-
-
-
-
-
-
-261
-262
-263
-264
-265
-266
-
-
-
-
- end
-
else
-
public_send (method, locals, buffer, &block)
-
end
-
ensure
-
@output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
-
-
-
-
-
-
-
-
-
- Extracted source (around line #263 ):
-
-
-
-
-
-
-261
-262
-263
-264
-265
-266
-
-
-
-
- end
-
else
-
public_send (method, locals, buffer, &block)
-
end
-
ensure
-
@output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- nil
-
else
-
result = view._run (method_name, self, locals, OutputBuffer.new, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
-
result.is_a?(OutputBuffer) ? result.to_s : result
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #208 ):
-
-
-
-
-
-
-206
-207
-208
-209
-210
-211
-
-
-
-
- instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #556 ):
-
-
-
-
-
-
-554
-555
-556
-557
-558
-559
-
-
-
-
-
-
def instrument_render_template(&block)
-
ActiveSupport::Notifications.instrument ("!render_template.action_view", instrument_payload, &block)
-
end
-
-
def instrument_payload
-
-
-
-
-
-
-
-
-
- Extracted source (around line #249 ):
-
-
-
-
-
-
-247
-248
-249
-250
-251
-252
-
-
-
-
- # consume this in production. This is only slow if it's being listened to.
-
def render(view, locals, buffer = nil, implicit_locals: [], add_to_stack: true, &block)
-
instrument_render_template do
-
compile!(view)
-
-
if strict_locals? && @strict_local_keys && !implicit_locals.empty?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #66 ):
-
-
-
-
-
-
-64
-65
-66
-67
-68
-69
-
-
-
-
- locals: locals
-
) do
-
template.render (view, locals) { |*name| view._layout_for(*name) }
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #60 ):
-
-
-
-
-
-
-58
-59
-60
-61
-62
-63
-
-
-
-
- def render_template(view, template, layout_name, locals)
-
render_with_layout(view, template, layout_name, locals) do |layout|
-
ActiveSupport::Notifications.instrument (
-
"render_template.action_view",
-
identifier: template.identifier,
-
layout: layout && layout.virtual_path,
-
-
-
-
-
-
-
-
-
- Extracted source (around line #76 ):
-
-
-
-
-
-
-74
-75
-76
-77
-78
-79
-
-
-
-
- body = if layout
-
ActiveSupport::Notifications.instrument("render_layout.action_view", identifier: layout.identifier) do
-
view.view_flow.set(:layout, yield(layout))
-
layout.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #75 ):
-
-
-
-
-
-
-73
-74
-75
-76
-77
-78
-
-
-
-
-
-
body = if layout
-
ActiveSupport::Notifications.instrument ("render_layout.action_view", identifier: layout.identifier) do
-
view.view_flow.set(:layout, yield(layout))
-
layout.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- # supplied as well.
-
def render_template(view, template, layout_name, locals)
-
render_with_layout (view, template, layout_name, locals) do |layout|
-
ActiveSupport::Notifications.instrument(
-
"render_template.action_view",
-
identifier: template.identifier,
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- prepend_formats(template.format)
-
-
render_template (context, template, options[:layout], options[:locals] || {})
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #63 ):
-
-
-
-
-
-
-61
-62
-63
-64
-65
-66
-
-
-
-
-
-
def render_template_to_object(context, options) # :nodoc:
-
TemplateRenderer.new(@lookup_context).render (context, options)
-
end
-
-
def render_partial_to_object(context, options, &block) # :nodoc:
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- render_partial_to_object(context, options)
-
else
-
render_template_to_object (context, options)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #135 ):
-
-
-
-
-
-
-133
-134
-135
-136
-137
-138
-
-
-
-
-
-
rendered_template = context.in_rendering_context(options) do |renderer|
-
renderer.render_to_object (context, options)
-
end
-
-
rendered_format = rendered_template.format || lookup_context.formats.first
-
-
-
-
-
-
-
-
-
- Extracted source (around line #290 ):
-
-
-
-
-
-
-288
-289
-290
-291
-292
-293
-
-
-
-
- end
-
-
yield @view_renderer
-
ensure
-
@view_renderer = old_view_renderer
-
@lookup_context = old_lookup_context
-
-
-
-
-
-
-
-
-
- Extracted source (around line #134 ):
-
-
-
-
-
-
-132
-133
-134
-135
-136
-137
-
-
-
-
- lookup_context.variants = variant if variant
-
-
rendered_template = context.in_rendering_context (options) do |renderer|
-
renderer.render_to_object(context, options)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #256 ):
-
-
-
-
-
-
-254
-255
-256
-257
-258
-259
-
-
-
-
- Body.new view_renderer.render_body(view_context, options)
-
else
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- def render_to_body(options = {})
-
_process_options(options)
-
_render_template (options)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #158 ):
-
-
-
-
-
-
-156
-157
-158
-159
-160
-161
-
-
-
-
-
-
def render_to_body(options = {}) # :nodoc:
-
super || _render_in_priorities(options) || " "
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- # <tt>ActionView::Rendering.render_to_body</tt>, if present.
-
def render_to_body(options)
-
_render_to_body_with_renderer(options) || super
-
end
-
-
def _render_to_body_with_renderer(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #27 ):
-
-
-
-
-
-
-25
-26
-27
-28
-29
-30
-
-
-
-
- def render(*args, &block)
-
options = _normalize_render(*args, &block)
-
rendered_body = render_to_body (options)
-
if options[:html]
-
_set_html_content_type
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def render(*args)
-
raise ::AbstractController::DoubleRenderError if response_body
-
super
-
end
-
-
# Similar to #render, but only returns the rendered template as a string,
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- render_output = nil
-
self.view_runtime = cleanup_view_runtime do
-
Benchmark.ms { render_output = super }
-
end
-
render_output
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #324 ):
-
-
-
-
-
-
-322
-323
-324
-325
-326
-327
-
-
-
-
- def realtime # :yield:
-
r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
-
yield
-
Process.clock_gettime(Process::CLOCK_MONOTONIC) - r0
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-
-
-
-
- # # => 0.074
-
def ms(&block)
-
1000 * realtime (&block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- render_output = nil
-
self.view_runtime = cleanup_view_runtime do
-
Benchmark.ms { render_output = super }
-
end
-
render_output
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- # end
-
def cleanup_view_runtime # :doc:
-
yield
-
end
-
-
# Every time after an action is processed, this method is invoked
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- db_rt_before_render = ActiveRecord::RuntimeRegistry.reset
-
self.db_runtime = (db_runtime || 0) + db_rt_before_render
-
runtime = super
-
queries_rt = ActiveRecord::RuntimeRegistry.sql_runtime - ActiveRecord::RuntimeRegistry.async_sql_runtime
-
db_rt_after_render = ActiveRecord::RuntimeRegistry.reset
-
self.db_runtime += db_rt_after_render
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def render(*)
-
render_output = nil
-
self.view_runtime = cleanup_view_runtime do
-
Benchmark.ms { render_output = super }
-
end
-
render_output
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- def default_render
-
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
-
render
-
elsif any_templates?(action_name.to_s, _prefixes)
-
message = "#{self.class.name}\##{action_name} is missing a template " \
-
"for this request format and variant.\n" \
-
-
-
-
-
-
-
-
-
- Extracted source (around line #7 ):
-
-
-
-
-
-
-5
-6
-7
-8
-9
-10
-
-
-
-
- def send_action(method, *args)
-
ret = super
-
default_render unless performed?
-
ret
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
- # which is *not* necessarily the same as the action name.
-
def process_action(...)
-
send_action (...)
-
end
-
-
# Actually call the method associated with the action. Override
-
-
-
-
-
-
-
-
-
- Extracted source (around line #165 ):
-
-
-
-
-
-
-163
-164
-165
-166
-167
-168
-
-
-
-
- def process_action(*) # :nodoc:
-
self.formats = request.formats.filter_map(&:ref)
-
super
-
end
-
-
def _process_variant(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- def process_action(...)
-
run_callbacks(:process_action) do
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def with_request_id(request_id)
-
old_request_id, self.current_request_id = self.current_request_id, request_id
-
yield
-
ensure
-
self.current_request_id = old_request_id
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #10 ):
-
-
-
-
-
-
-8
-9
-10
-11
-12
-
-
-
-
- private
-
def turbo_tracking_request_id(&block)
-
Turbo.with_request_id (request.headers["X-Turbo-Request-Id"], &block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- previous_renderer = self.renderer
-
self.renderer = renderer
-
yield
-
ensure
-
self.renderer = previous_renderer
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #69 ):
-
-
-
-
-
-
-67
-68
-69
-70
-71
-72
-
-
-
-
- ActiveSupport.on_load(base) do
-
around_action do |controller, action|
-
ActionText::Content.with_renderer (controller, &action)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #258 ):
-
-
-
-
-
-
-256
-257
-258
-259
-260
-261
-
-
-
-
- # <tt>process_action</tt> callbacks around the normal behavior.
-
def process_action(...)
-
run_callbacks (:process_action) do
-
super
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
- private
-
def process_action(*)
-
super
-
rescue Exception => exception
-
request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #74 ):
-
-
-
-
-
-
-72
-73
-74
-75
-76
-77
-
-
-
-
-
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
result
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #73 ):
-
-
-
-
-
-
-71
-72
-73
-74
-75
-76
-
-
-
-
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
-
-
ActiveSupport::Notifications.instrument ("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- def process_action(*)
-
_perform_parameter_wrapping if _wrapper_enabled?
-
super
-
end
-
-
# Returns the wrapper key which will be used to store wrapped parameters.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # and it won't be cleaned up by the method below.
-
ActiveRecord::RuntimeRegistry.reset
-
super
-
end
-
-
def cleanup_view_runtime
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- @_response_body = nil
-
-
process_action (action_name, *args)
-
end
-
ruby2_keywords(:process)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #40 ):
-
-
-
-
-
-
-38
-39
-40
-41
-42
-43
-
-
-
-
- def process(...) # :nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #227 ):
-
-
-
-
-
-
-225
-226
-227
-228
-229
-230
-
-
-
-
- set_request!(request)
-
set_response!(response)
-
process (name)
-
request.commit_flash
-
to_a
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #309 ):
-
-
-
-
-
-
-307
-308
-309
-310
-311
-312
-
-
-
-
- middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
-
else
-
new.dispatch (name, req, res)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #49 ):
-
-
-
-
-
-
-47
-48
-49
-50
-51
-52
-
-
-
-
-
-
def dispatch(controller, action, req, res)
-
controller.dispatch (action, req, res)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- controller = controller req
-
res = controller.make_response! req
-
dispatch (controller, params[:action], req, res)
-
rescue ActionController::RoutingError
-
if @raise_on_name_error
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #51 ):
-
-
-
-
-
-
-49
-50
-51
-52
-53
-54
-
-
-
-
- req.route_uri_pattern = route.path.spec.to_s
-
-
_, headers, _ = response = route.app.serve (req)
-
-
if "pass" == headers[Constants::X_CASCADE]
-
req.script_name = script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
-
}
-
yield [match_data, path_parameters, r]
-
}
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
-
-
def serve(req)
-
find_routes (req) do |match, parameters, route|
-
set_params = req.path_parameters
-
path_info = req.path_info
-
script_name = req.script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #882 ):
-
-
-
-
-
-
-880
-881
-882
-883
-884
-885
-
-
-
-
- req = make_request(env)
-
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
-
@router.serve (req)
-
end
-
-
def recognize_path(path, environment = {})
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
- result = catch(:warden) do
-
env['warden'].on_request
-
@app.call (env)
-
end
-
-
result ||= {}
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
-
-
begin
-
_, _, body = response = @app.call (env)
-
rescue Exception
-
env[RACK_TEMPFILES]&.each(&:close!)
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
status, headers, body = response = @app.call (env)
-
-
if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
-
body = body.to_ary
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- case env[REQUEST_METHOD]
-
when "GET", "HEAD"
-
status, headers, body = response = @app.call (env)
-
-
if status == 200 && fresh?(env, headers)
-
response[0] = 304
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
-
-
def call(env)
-
_, _, body = response = @app.call (env)
-
-
if env[REQUEST_METHOD] == HEAD
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
_, headers, _ = response = @app.call (env)
-
-
return response if policy_present?(headers)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
status, headers, _ = response = @app.call (env)
-
-
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
-
# CSP headers might not match nonces in the cached HTML.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #274 ):
-
-
-
-
-
-
-272
-273
-274
-275
-276
-277
-
-
-
-
- req = make_request env
-
prepare_session(req)
-
status, headers, body = app.call (req.env)
-
res = Rack::Response::Raw.new status, headers
-
commit_session(req, res)
-
[status, headers, body]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #268 ):
-
-
-
-
-
-
-266
-267
-268
-269
-270
-271
-
-
-
-
-
-
def call(env)
-
context (env)
-
end
-
-
def context(env, app = @app)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #689 ):
-
-
-
-
-
-
-687
-688
-689
-690
-691
-692
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
response = @app.call (env)
-
-
if request.have_cookie_jar?
-
cookie_jar = request.cookie_jar
-
-
-
-
-
-
-
-
-
- Extracted source (around line #655 ):
-
-
-
-
-
-
-653
-654
-655
-656
-657
-658
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- error = nil
-
result = run_callbacks :call do
-
@app.call (env)
-
rescue => error
-
end
-
raise error if error
-
-
-
-
-
-
-
-
-
- Extracted source (around line #101 ):
-
-
-
-
-
-
-99
-100
-101
-102
-103
-104
-
-
-
-
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def call(env)
-
error = nil
-
result = run_callbacks :call do
-
@app.call(env)
-
rescue => error
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #16 ):
-
-
-
-
-
-
-14
-15
-16
-17
-18
-19
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
return @app.call (env) unless actionable_request?(request)
-
-
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if headers[Constants::X_CASCADE] == "pass"
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #132 ):
-
-
-
-
-
-
-130
-131
-132
-133
-134
-135
-
-
-
-
-
-
def call_app(env)
-
@app.call (env)
-
rescue => e
-
throw :app_exception, e
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #19 ):
-
-
-
-
-
-
-17
-18
-19
-20
-21
-22
-
-
-
-
- app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app (env) unless request.permitted?
-
-
if id = id_for_repl_session_update(request)
-
return update_repl_session(id, request)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
-
-
def call(env)
-
@app.call (env)
-
rescue Exception => exception
-
request = ActionDispatch::Request.new env
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
-
-
logger.info { started_request_message(request) }
-
status, headers, body = response = @app.call (env)
-
body = ::Rack::BodyProxy.new(body, &handle.method(:finish))
-
-
if response.frozen?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged(*compute_tags(request)) { call_app (request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- def tagged(*tags)
-
pushed_count = tag_stack.push_tags(tags).size
-
yield self
-
ensure
-
pop_tags(pushed_count)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged (*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #241 ):
-
-
-
-
-
-
-239
-240
-241
-242
-243
-244
-
-
-
-
- super(name, *args, **kwargs, &block)
-
elsif loggers.one?
-
loggers.first.send (name, *args, **kwargs, &block)
-
else
-
loggers.map { |logger| logger.send(name, *args, **kwargs, &block) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged (*compute_tags(request)) { call_app(request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- ::Rails.logger.silence { @app.call(env) }
-
else
-
@app.call (env)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #92 ):
-
-
-
-
-
-
-90
-91
-92
-93
-94
-95
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.remote_ip = GetIp.new(req, check_ip, proxies)
-
@app.call (req.env)
-
end
-
-
# The GetIp class exists as a way to defer processing of the request data
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.request_id = make_request_id(req.headers[@header])
-
@app.call (env).tap { |_status, headers, _body| headers[@header] = req.request_id }
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
def method_override(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def call(env)
-
start_time = Utils.clock_time
-
_, headers, _ = response = @app.call (env)
-
-
request_time = Utils.clock_time - start_time
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- response = nil
-
events = @subscriber.collect_events do
-
response = @app.call (env)
-
end
-
-
headers = response[1]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- events = []
-
ActiveSupport::IsolatedExecutionState[KEY] = events
-
yield
-
events
-
ensure
-
ActiveSupport::IsolatedExecutionState.delete(KEY)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- def call(env)
-
response = nil
-
events = @subscriber.collect_events do
-
response = @app.call(env)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- end
-
else
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
-
-
def call(env)
-
@file_handler.attempt(env) || @app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if body.respond_to?(:to_path)
-
case type = variation(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #334 ):
-
-
-
-
-
-
-332
-333
-334
-335
-336
-337
-
-
-
-
- status, headers, body = [200, { Rack::CONTENT_TYPE => 'text/html' }, [blank_page_html.dup]] # important to dup here!
-
else
-
status, headers, body = @app.call (env)
-
end
-
ensure
-
trace.disable if trace
-
-
-
-
-
-
-
-
-
- Extracted source (around line #536 ):
-
-
-
-
-
-
-534
-535
-536
-537
-538
-539
-
-
-
-
- def call(env)
-
req = build_request env
-
app.call req.env
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #279 ):
-
-
-
-
-
-
-277
-278
-279
-280
-281
-282
-
-
-
-
- def call(env)
-
env[Const::PUMA_CONFIG] = @config
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #99 ):
-
-
-
-
-
-
-97
-98
-99
-100
-101
-102
-
-
-
-
- if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call (env)
-
end
-
else
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
-
-
-
-
-
-
-
-
-
- Extracted source (around line #390 ):
-
-
-
-
-
-
-388
-389
-390
-391
-392
-393
-
-
-
-
- t[:with_force_shutdown] = true
-
end
-
yield
-
ensure
-
t[:with_force_shutdown] = false
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- begin
-
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call(env)
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #472 ):
-
-
-
-
-
-
-470
-471
-472
-473
-474
-475
-
-
-
-
- while true
-
@requests_count += 1
-
case handle_request (client, requests + 1)
-
when false
-
break
-
when :async
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- @status = :run
-
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
-
-
if @queue_requests
-
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
-
-
-
-
-
-
-
-
-
- Extracted source (around line #167 ):
-
-
-
-
-
-
-165
-166
-167
-168
-169
-170
-
-
-
-
-
-
begin
-
@out_of_band_pending = true if block.call (work)
-
rescue Exception => e
-
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
-
end
-
-
-
-
-
-
-
-
-
-
-
-
-Rails.root: /app
-
-
-
- Request
- Parameters :
None
-
-
-
-
_csrf_token: "EnAoldz5HlXoQhrcYXR4ocVI9hQFNDKJYwqzrcT0L0s"
-session_id: "9f993995f072ff0494437374b04b9a22"
-user_return_to: "/"
-warden.user.user.key: [[1], "$2a$12$OItHogL1JMQsM3o7sAh5SO"]
-
-
-
-
-
GATEWAY_INTERFACE: "CGI/1.2"
-HTTP_ACCEPT: "text/html, application/xhtml+xml"
-HTTP_ACCEPT_ENCODING: "gzip, deflate"
-HTTP_ACCEPT_LANGUAGE: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
-HTTP_VERSION: "HTTP/1.1"
-ORIGINAL_SCRIPT_NAME: ""
-REMOTE_ADDR: "192.168.112.1"
-SERVER_NAME: "100.75.222.23"
-SERVER_PROTOCOL: "HTTP/1.1"
-
-
-Response
-Headers :
None
-
-
-
-
-
-
diff --git a/Erros/Action Controller_ Exception caught_quando Cria uma consolidação.html b/Erros/Action Controller_ Exception caught_quando Cria uma consolidação.html
deleted file mode 100644
index 2d59e8f..0000000
--- a/Erros/Action Controller_ Exception caught_quando Cria uma consolidação.html
+++ /dev/null
@@ -1,3987 +0,0 @@
-
-
-
-
-
-
- Action Controller: Exception caught
-
-
-
-
-
-
-
-
- NoMethodError
- in ConsolidacoesController#new
-
-
-
-
-
-
undefined method `new?' for #<ConsolidacaoPolicy:0x00007f710ab493f8 @user=#<User id: 1, email: "admin@gade.com", nome: "Administrador Gade", telefone: nil, pin_code: nil, role: "admin", ativo: true, tema_preferido: "dark", created_at: "2026-06-11 23:00:11.605256000 -0300", updated_at: "2026-06-11 23:00:11.605256000 -0300", login_token: nil>, @record=Consolidacao(id: integer, nome: string, data_inicio: date, data_fim: date, status: integer, valor_total: decimal, created_by: integer, finalizado_por: integer, finalizado_em: datetime, deleted_at: datetime, created_at: datetime, updated_at: datetime, route_ids: jsonb, arquivado_por: integer, arquivado_em: datetime)>
-
-
-
-
-
-
-
-
- Extracted source (around line #70 ):
-
-
-
-
-
-
-68
-69
-70
-71
-72
-73
-
-
-
-
- end
-
-
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send (query)
-
-
record
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #70 ):
-
-
-
-
-
-
-68
-69
-70
-71
-72
-73
-
-
-
-
- end
-
-
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send (query)
-
-
record
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #94 ):
-
-
-
-
-
-
-92
-93
-94
-95
-96
-97
-
-
-
-
- @_pundit_policy_authorized = true
-
-
pundit.authorize (record, query: query, policy_class: policy_class)
-
end
-
-
# Allow this action not to perform authorization.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # GET /consolidacoes/new
-
def new
-
authorize Consolidacao
-
@consolidacao = Consolidacao.new(
-
data_inicio: Date.current.beginning_of_month,
-
data_fim: Date.current
-
-
-
-
-
-
-
-
-
- Extracted source (around line #6 ):
-
-
-
-
-
-
-4
-5
-6
-7
-8
-9
-
-
-
-
- module BasicImplicitRender # :nodoc:
-
def send_action(method, *args)
-
ret = super
-
default_render unless performed?
-
ret
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
- # which is *not* necessarily the same as the action name.
-
def process_action(...)
-
send_action (...)
-
end
-
-
# Actually call the method associated with the action. Override
-
-
-
-
-
-
-
-
-
- Extracted source (around line #165 ):
-
-
-
-
-
-
-163
-164
-165
-166
-167
-168
-
-
-
-
- def process_action(*) # :nodoc:
-
self.formats = request.formats.filter_map(&:ref)
-
super
-
end
-
-
def _process_variant(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- def process_action(...)
-
run_callbacks(:process_action) do
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def with_request_id(request_id)
-
old_request_id, self.current_request_id = self.current_request_id, request_id
-
yield
-
ensure
-
self.current_request_id = old_request_id
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #10 ):
-
-
-
-
-
-
-8
-9
-10
-11
-12
-
-
-
-
- private
-
def turbo_tracking_request_id(&block)
-
Turbo.with_request_id (request.headers["X-Turbo-Request-Id"], &block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- previous_renderer = self.renderer
-
self.renderer = renderer
-
yield
-
ensure
-
self.renderer = previous_renderer
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #69 ):
-
-
-
-
-
-
-67
-68
-69
-70
-71
-72
-
-
-
-
- ActiveSupport.on_load(base) do
-
around_action do |controller, action|
-
ActionText::Content.with_renderer (controller, &action)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #258 ):
-
-
-
-
-
-
-256
-257
-258
-259
-260
-261
-
-
-
-
- # <tt>process_action</tt> callbacks around the normal behavior.
-
def process_action(...)
-
run_callbacks (:process_action) do
-
super
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
- private
-
def process_action(*)
-
super
-
rescue Exception => exception
-
request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #74 ):
-
-
-
-
-
-
-72
-73
-74
-75
-76
-77
-
-
-
-
-
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
result
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #73 ):
-
-
-
-
-
-
-71
-72
-73
-74
-75
-76
-
-
-
-
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
-
-
ActiveSupport::Notifications.instrument ("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- def process_action(*)
-
_perform_parameter_wrapping if _wrapper_enabled?
-
super
-
end
-
-
# Returns the wrapper key which will be used to store wrapped parameters.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # and it won't be cleaned up by the method below.
-
ActiveRecord::RuntimeRegistry.reset
-
super
-
end
-
-
def cleanup_view_runtime
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- @_response_body = nil
-
-
process_action (action_name, *args)
-
end
-
ruby2_keywords(:process)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #40 ):
-
-
-
-
-
-
-38
-39
-40
-41
-42
-43
-
-
-
-
- def process(...) # :nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #227 ):
-
-
-
-
-
-
-225
-226
-227
-228
-229
-230
-
-
-
-
- set_request!(request)
-
set_response!(response)
-
process (name)
-
request.commit_flash
-
to_a
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #309 ):
-
-
-
-
-
-
-307
-308
-309
-310
-311
-312
-
-
-
-
- middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
-
else
-
new.dispatch (name, req, res)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #49 ):
-
-
-
-
-
-
-47
-48
-49
-50
-51
-52
-
-
-
-
-
-
def dispatch(controller, action, req, res)
-
controller.dispatch (action, req, res)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- controller = controller req
-
res = controller.make_response! req
-
dispatch (controller, params[:action], req, res)
-
rescue ActionController::RoutingError
-
if @raise_on_name_error
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #51 ):
-
-
-
-
-
-
-49
-50
-51
-52
-53
-54
-
-
-
-
- req.route_uri_pattern = route.path.spec.to_s
-
-
_, headers, _ = response = route.app.serve (req)
-
-
if "pass" == headers[Constants::X_CASCADE]
-
req.script_name = script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
-
}
-
yield [match_data, path_parameters, r]
-
}
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
-
-
def serve(req)
-
find_routes (req) do |match, parameters, route|
-
set_params = req.path_parameters
-
path_info = req.path_info
-
script_name = req.script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #882 ):
-
-
-
-
-
-
-880
-881
-882
-883
-884
-885
-
-
-
-
- req = make_request(env)
-
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
-
@router.serve (req)
-
end
-
-
def recognize_path(path, environment = {})
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
- result = catch(:warden) do
-
env['warden'].on_request
-
@app.call (env)
-
end
-
-
result ||= {}
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
-
-
begin
-
_, _, body = response = @app.call (env)
-
rescue Exception
-
env[RACK_TEMPFILES]&.each(&:close!)
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
status, headers, body = response = @app.call (env)
-
-
if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
-
body = body.to_ary
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- case env[REQUEST_METHOD]
-
when "GET", "HEAD"
-
status, headers, body = response = @app.call (env)
-
-
if status == 200 && fresh?(env, headers)
-
response[0] = 304
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
-
-
def call(env)
-
_, _, body = response = @app.call (env)
-
-
if env[REQUEST_METHOD] == HEAD
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
_, headers, _ = response = @app.call (env)
-
-
return response if policy_present?(headers)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
status, headers, _ = response = @app.call (env)
-
-
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
-
# CSP headers might not match nonces in the cached HTML.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #274 ):
-
-
-
-
-
-
-272
-273
-274
-275
-276
-277
-
-
-
-
- req = make_request env
-
prepare_session(req)
-
status, headers, body = app.call (req.env)
-
res = Rack::Response::Raw.new status, headers
-
commit_session(req, res)
-
[status, headers, body]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #268 ):
-
-
-
-
-
-
-266
-267
-268
-269
-270
-271
-
-
-
-
-
-
def call(env)
-
context (env)
-
end
-
-
def context(env, app = @app)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #689 ):
-
-
-
-
-
-
-687
-688
-689
-690
-691
-692
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
response = @app.call (env)
-
-
if request.have_cookie_jar?
-
cookie_jar = request.cookie_jar
-
-
-
-
-
-
-
-
-
- Extracted source (around line #655 ):
-
-
-
-
-
-
-653
-654
-655
-656
-657
-658
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- error = nil
-
result = run_callbacks :call do
-
@app.call (env)
-
rescue => error
-
end
-
raise error if error
-
-
-
-
-
-
-
-
-
- Extracted source (around line #101 ):
-
-
-
-
-
-
-99
-100
-101
-102
-103
-104
-
-
-
-
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def call(env)
-
error = nil
-
result = run_callbacks :call do
-
@app.call(env)
-
rescue => error
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #16 ):
-
-
-
-
-
-
-14
-15
-16
-17
-18
-19
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
return @app.call (env) unless actionable_request?(request)
-
-
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if headers[Constants::X_CASCADE] == "pass"
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #132 ):
-
-
-
-
-
-
-130
-131
-132
-133
-134
-135
-
-
-
-
-
-
def call_app(env)
-
@app.call (env)
-
rescue => e
-
throw :app_exception, e
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #19 ):
-
-
-
-
-
-
-17
-18
-19
-20
-21
-22
-
-
-
-
- app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app (env) unless request.permitted?
-
-
if id = id_for_repl_session_update(request)
-
return update_repl_session(id, request)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
-
-
def call(env)
-
@app.call (env)
-
rescue Exception => exception
-
request = ActionDispatch::Request.new env
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
-
-
logger.info { started_request_message(request) }
-
status, headers, body = response = @app.call (env)
-
body = ::Rack::BodyProxy.new(body, &handle.method(:finish))
-
-
if response.frozen?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged(*compute_tags(request)) { call_app (request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- def tagged(*tags)
-
pushed_count = tag_stack.push_tags(tags).size
-
yield self
-
ensure
-
pop_tags(pushed_count)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged (*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #241 ):
-
-
-
-
-
-
-239
-240
-241
-242
-243
-244
-
-
-
-
- super(name, *args, **kwargs, &block)
-
elsif loggers.one?
-
loggers.first.send (name, *args, **kwargs, &block)
-
else
-
loggers.map { |logger| logger.send(name, *args, **kwargs, &block) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged (*compute_tags(request)) { call_app(request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- ::Rails.logger.silence { @app.call(env) }
-
else
-
@app.call (env)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #92 ):
-
-
-
-
-
-
-90
-91
-92
-93
-94
-95
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.remote_ip = GetIp.new(req, check_ip, proxies)
-
@app.call (req.env)
-
end
-
-
# The GetIp class exists as a way to defer processing of the request data
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.request_id = make_request_id(req.headers[@header])
-
@app.call (env).tap { |_status, headers, _body| headers[@header] = req.request_id }
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
def method_override(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def call(env)
-
start_time = Utils.clock_time
-
_, headers, _ = response = @app.call (env)
-
-
request_time = Utils.clock_time - start_time
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- response = nil
-
events = @subscriber.collect_events do
-
response = @app.call (env)
-
end
-
-
headers = response[1]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- events = []
-
ActiveSupport::IsolatedExecutionState[KEY] = events
-
yield
-
events
-
ensure
-
ActiveSupport::IsolatedExecutionState.delete(KEY)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- def call(env)
-
response = nil
-
events = @subscriber.collect_events do
-
response = @app.call(env)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- end
-
else
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
-
-
def call(env)
-
@file_handler.attempt(env) || @app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if body.respond_to?(:to_path)
-
case type = variation(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #334 ):
-
-
-
-
-
-
-332
-333
-334
-335
-336
-337
-
-
-
-
- status, headers, body = [200, { Rack::CONTENT_TYPE => 'text/html' }, [blank_page_html.dup]] # important to dup here!
-
else
-
status, headers, body = @app.call (env)
-
end
-
ensure
-
trace.disable if trace
-
-
-
-
-
-
-
-
-
- Extracted source (around line #536 ):
-
-
-
-
-
-
-534
-535
-536
-537
-538
-539
-
-
-
-
- def call(env)
-
req = build_request env
-
app.call req.env
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #279 ):
-
-
-
-
-
-
-277
-278
-279
-280
-281
-282
-
-
-
-
- def call(env)
-
env[Const::PUMA_CONFIG] = @config
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #99 ):
-
-
-
-
-
-
-97
-98
-99
-100
-101
-102
-
-
-
-
- if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call (env)
-
end
-
else
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
-
-
-
-
-
-
-
-
-
- Extracted source (around line #390 ):
-
-
-
-
-
-
-388
-389
-390
-391
-392
-393
-
-
-
-
- t[:with_force_shutdown] = true
-
end
-
yield
-
ensure
-
t[:with_force_shutdown] = false
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- begin
-
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call(env)
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #472 ):
-
-
-
-
-
-
-470
-471
-472
-473
-474
-475
-
-
-
-
- while true
-
@requests_count += 1
-
case handle_request (client, requests + 1)
-
when false
-
break
-
when :async
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- @status = :run
-
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
-
-
if @queue_requests
-
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
-
-
-
-
-
-
-
-
-
- Extracted source (around line #167 ):
-
-
-
-
-
-
-165
-166
-167
-168
-169
-170
-
-
-
-
-
-
begin
-
@out_of_band_pending = true if block.call (work)
-
rescue Exception => e
-
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
-
end
-
-
-
-
-
-
-
-
-
-Rails.root: /app
-
-
-
-
-
-
- Request
- Parameters :
None
-
-
-
-
_csrf_token: "CDiW_RJyJcAlCPyrikFuKYkfdzBC-OW6YRsZDeWj9VQ"
-session_id: "ac99c889d2e6f9700a182c290873487b"
-user_return_to: "/"
-warden.user.user.key: [[1], "$2a$12$OItHogL1JMQsM3o7sAh5SO"]
-
-
-
-
-
GATEWAY_INTERFACE: "CGI/1.2"
-HTTP_ACCEPT: "text/html, application/xhtml+xml"
-HTTP_ACCEPT_ENCODING: "gzip, deflate"
-HTTP_ACCEPT_LANGUAGE: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
-HTTP_VERSION: "HTTP/1.1"
-ORIGINAL_SCRIPT_NAME: ""
-REMOTE_ADDR: "172.31.0.1"
-SERVER_NAME: "100.75.222.23"
-SERVER_PROTOCOL: "HTTP/1.1"
-
-
-Response
-Headers :
None
-
-
-
-
-
-
diff --git a/Erros/Action Controller_ Exception caught_quando aperta as configuraçoes .html b/Erros/Action Controller_ Exception caught_quando aperta as configuraçoes .html
deleted file mode 100644
index d75a32d..0000000
--- a/Erros/Action Controller_ Exception caught_quando aperta as configuraçoes .html
+++ /dev/null
@@ -1,4026 +0,0 @@
-
-
-
-
-
-
- Action Controller: Exception caught
-
-
-
-
-
-
-
-
- NoMethodError
- in Admin::ConfiguracoesController#index
-
-
-
-
-
-
undefined method `admin_ou_gerente?' for #<ConfiguracaoPolicy:0x00007efdbb16abb0 @user=#<User id: 1, email: "admin@gade.com", nome: "Administrador Gade", telefone: nil, pin_code: nil, role: "admin", ativo: true, tema_preferido: "dark", created_at: "2026-06-11 23:00:11.605256000 -0300", updated_at: "2026-06-11 23:00:11.605256000 -0300", login_token: nil>, @record=Configuracao(id: integer, chave: string, valor: string, descricao: string, updated_by: integer, created_at: datetime, updated_at: datetime)>
-
-
-
-
-
-
-
-
- Extracted source (around line #4 ):
-
-
-
-
-
-
-2
-3
-4
-5
-6
-7
-
-
-
-
-class ConfiguracaoPolicy < ApplicationPolicy
-
def index?
-
admin_ou_gerente?
-
end
-
-
def edit?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #70 ):
-
-
-
-
-
-
-68
-69
-70
-71
-72
-73
-
-
-
-
- end
-
-
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send (query)
-
-
record
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #70 ):
-
-
-
-
-
-
-68
-69
-70
-71
-72
-73
-
-
-
-
- end
-
-
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send (query)
-
-
record
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #94 ):
-
-
-
-
-
-
-92
-93
-94
-95
-96
-97
-
-
-
-
- @_pundit_policy_authorized = true
-
-
pundit.authorize (record, query: query, policy_class: policy_class)
-
end
-
-
# Allow this action not to perform authorization.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #6 ):
-
-
-
-
-
-
-4
-5
-6
-7
-8
-9
-
-
-
-
-
-
def index
-
authorize Configuracao
-
@configuracoes = Configuracao.all.order(:chave)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #6 ):
-
-
-
-
-
-
-4
-5
-6
-7
-8
-9
-
-
-
-
- module BasicImplicitRender # :nodoc:
-
def send_action(method, *args)
-
ret = super
-
default_render unless performed?
-
ret
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
- # which is *not* necessarily the same as the action name.
-
def process_action(...)
-
send_action (...)
-
end
-
-
# Actually call the method associated with the action. Override
-
-
-
-
-
-
-
-
-
- Extracted source (around line #165 ):
-
-
-
-
-
-
-163
-164
-165
-166
-167
-168
-
-
-
-
- def process_action(*) # :nodoc:
-
self.formats = request.formats.filter_map(&:ref)
-
super
-
end
-
-
def _process_variant(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- def process_action(...)
-
run_callbacks(:process_action) do
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def with_request_id(request_id)
-
old_request_id, self.current_request_id = self.current_request_id, request_id
-
yield
-
ensure
-
self.current_request_id = old_request_id
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #10 ):
-
-
-
-
-
-
-8
-9
-10
-11
-12
-
-
-
-
- private
-
def turbo_tracking_request_id(&block)
-
Turbo.with_request_id (request.headers["X-Turbo-Request-Id"], &block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- previous_renderer = self.renderer
-
self.renderer = renderer
-
yield
-
ensure
-
self.renderer = previous_renderer
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #69 ):
-
-
-
-
-
-
-67
-68
-69
-70
-71
-72
-
-
-
-
- ActiveSupport.on_load(base) do
-
around_action do |controller, action|
-
ActionText::Content.with_renderer (controller, &action)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #258 ):
-
-
-
-
-
-
-256
-257
-258
-259
-260
-261
-
-
-
-
- # <tt>process_action</tt> callbacks around the normal behavior.
-
def process_action(...)
-
run_callbacks (:process_action) do
-
super
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
- private
-
def process_action(*)
-
super
-
rescue Exception => exception
-
request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #74 ):
-
-
-
-
-
-
-72
-73
-74
-75
-76
-77
-
-
-
-
-
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
result
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #73 ):
-
-
-
-
-
-
-71
-72
-73
-74
-75
-76
-
-
-
-
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
-
-
ActiveSupport::Notifications.instrument ("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- def process_action(*)
-
_perform_parameter_wrapping if _wrapper_enabled?
-
super
-
end
-
-
# Returns the wrapper key which will be used to store wrapped parameters.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # and it won't be cleaned up by the method below.
-
ActiveRecord::RuntimeRegistry.reset
-
super
-
end
-
-
def cleanup_view_runtime
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- @_response_body = nil
-
-
process_action (action_name, *args)
-
end
-
ruby2_keywords(:process)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #40 ):
-
-
-
-
-
-
-38
-39
-40
-41
-42
-43
-
-
-
-
- def process(...) # :nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #227 ):
-
-
-
-
-
-
-225
-226
-227
-228
-229
-230
-
-
-
-
- set_request!(request)
-
set_response!(response)
-
process (name)
-
request.commit_flash
-
to_a
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #309 ):
-
-
-
-
-
-
-307
-308
-309
-310
-311
-312
-
-
-
-
- middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
-
else
-
new.dispatch (name, req, res)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #49 ):
-
-
-
-
-
-
-47
-48
-49
-50
-51
-52
-
-
-
-
-
-
def dispatch(controller, action, req, res)
-
controller.dispatch (action, req, res)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- controller = controller req
-
res = controller.make_response! req
-
dispatch (controller, params[:action], req, res)
-
rescue ActionController::RoutingError
-
if @raise_on_name_error
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #51 ):
-
-
-
-
-
-
-49
-50
-51
-52
-53
-54
-
-
-
-
- req.route_uri_pattern = route.path.spec.to_s
-
-
_, headers, _ = response = route.app.serve (req)
-
-
if "pass" == headers[Constants::X_CASCADE]
-
req.script_name = script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
-
}
-
yield [match_data, path_parameters, r]
-
}
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
-
-
def serve(req)
-
find_routes (req) do |match, parameters, route|
-
set_params = req.path_parameters
-
path_info = req.path_info
-
script_name = req.script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #882 ):
-
-
-
-
-
-
-880
-881
-882
-883
-884
-885
-
-
-
-
- req = make_request(env)
-
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
-
@router.serve (req)
-
end
-
-
def recognize_path(path, environment = {})
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
- result = catch(:warden) do
-
env['warden'].on_request
-
@app.call (env)
-
end
-
-
result ||= {}
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
-
-
begin
-
_, _, body = response = @app.call (env)
-
rescue Exception
-
env[RACK_TEMPFILES]&.each(&:close!)
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
status, headers, body = response = @app.call (env)
-
-
if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
-
body = body.to_ary
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- case env[REQUEST_METHOD]
-
when "GET", "HEAD"
-
status, headers, body = response = @app.call (env)
-
-
if status == 200 && fresh?(env, headers)
-
response[0] = 304
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
-
-
def call(env)
-
_, _, body = response = @app.call (env)
-
-
if env[REQUEST_METHOD] == HEAD
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
_, headers, _ = response = @app.call (env)
-
-
return response if policy_present?(headers)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
status, headers, _ = response = @app.call (env)
-
-
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
-
# CSP headers might not match nonces in the cached HTML.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #274 ):
-
-
-
-
-
-
-272
-273
-274
-275
-276
-277
-
-
-
-
- req = make_request env
-
prepare_session(req)
-
status, headers, body = app.call (req.env)
-
res = Rack::Response::Raw.new status, headers
-
commit_session(req, res)
-
[status, headers, body]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #268 ):
-
-
-
-
-
-
-266
-267
-268
-269
-270
-271
-
-
-
-
-
-
def call(env)
-
context (env)
-
end
-
-
def context(env, app = @app)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #689 ):
-
-
-
-
-
-
-687
-688
-689
-690
-691
-692
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
response = @app.call (env)
-
-
if request.have_cookie_jar?
-
cookie_jar = request.cookie_jar
-
-
-
-
-
-
-
-
-
- Extracted source (around line #655 ):
-
-
-
-
-
-
-653
-654
-655
-656
-657
-658
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- error = nil
-
result = run_callbacks :call do
-
@app.call (env)
-
rescue => error
-
end
-
raise error if error
-
-
-
-
-
-
-
-
-
- Extracted source (around line #101 ):
-
-
-
-
-
-
-99
-100
-101
-102
-103
-104
-
-
-
-
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def call(env)
-
error = nil
-
result = run_callbacks :call do
-
@app.call(env)
-
rescue => error
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #16 ):
-
-
-
-
-
-
-14
-15
-16
-17
-18
-19
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
return @app.call (env) unless actionable_request?(request)
-
-
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if headers[Constants::X_CASCADE] == "pass"
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #132 ):
-
-
-
-
-
-
-130
-131
-132
-133
-134
-135
-
-
-
-
-
-
def call_app(env)
-
@app.call (env)
-
rescue => e
-
throw :app_exception, e
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #19 ):
-
-
-
-
-
-
-17
-18
-19
-20
-21
-22
-
-
-
-
- app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app (env) unless request.permitted?
-
-
if id = id_for_repl_session_update(request)
-
return update_repl_session(id, request)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
-
-
def call(env)
-
@app.call (env)
-
rescue Exception => exception
-
request = ActionDispatch::Request.new env
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
-
-
logger.info { started_request_message(request) }
-
status, headers, body = response = @app.call (env)
-
body = ::Rack::BodyProxy.new(body, &handle.method(:finish))
-
-
if response.frozen?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged(*compute_tags(request)) { call_app (request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- def tagged(*tags)
-
pushed_count = tag_stack.push_tags(tags).size
-
yield self
-
ensure
-
pop_tags(pushed_count)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged (*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #241 ):
-
-
-
-
-
-
-239
-240
-241
-242
-243
-244
-
-
-
-
- super(name, *args, **kwargs, &block)
-
elsif loggers.one?
-
loggers.first.send (name, *args, **kwargs, &block)
-
else
-
loggers.map { |logger| logger.send(name, *args, **kwargs, &block) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged (*compute_tags(request)) { call_app(request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- ::Rails.logger.silence { @app.call(env) }
-
else
-
@app.call (env)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #92 ):
-
-
-
-
-
-
-90
-91
-92
-93
-94
-95
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.remote_ip = GetIp.new(req, check_ip, proxies)
-
@app.call (req.env)
-
end
-
-
# The GetIp class exists as a way to defer processing of the request data
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.request_id = make_request_id(req.headers[@header])
-
@app.call (env).tap { |_status, headers, _body| headers[@header] = req.request_id }
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
def method_override(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def call(env)
-
start_time = Utils.clock_time
-
_, headers, _ = response = @app.call (env)
-
-
request_time = Utils.clock_time - start_time
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- response = nil
-
events = @subscriber.collect_events do
-
response = @app.call (env)
-
end
-
-
headers = response[1]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- events = []
-
ActiveSupport::IsolatedExecutionState[KEY] = events
-
yield
-
events
-
ensure
-
ActiveSupport::IsolatedExecutionState.delete(KEY)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- def call(env)
-
response = nil
-
events = @subscriber.collect_events do
-
response = @app.call(env)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- end
-
else
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
-
-
def call(env)
-
@file_handler.attempt(env) || @app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if body.respond_to?(:to_path)
-
case type = variation(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #334 ):
-
-
-
-
-
-
-332
-333
-334
-335
-336
-337
-
-
-
-
- status, headers, body = [200, { Rack::CONTENT_TYPE => 'text/html' }, [blank_page_html.dup]] # important to dup here!
-
else
-
status, headers, body = @app.call (env)
-
end
-
ensure
-
trace.disable if trace
-
-
-
-
-
-
-
-
-
- Extracted source (around line #536 ):
-
-
-
-
-
-
-534
-535
-536
-537
-538
-539
-
-
-
-
- def call(env)
-
req = build_request env
-
app.call req.env
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #279 ):
-
-
-
-
-
-
-277
-278
-279
-280
-281
-282
-
-
-
-
- def call(env)
-
env[Const::PUMA_CONFIG] = @config
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #99 ):
-
-
-
-
-
-
-97
-98
-99
-100
-101
-102
-
-
-
-
- if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call (env)
-
end
-
else
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
-
-
-
-
-
-
-
-
-
- Extracted source (around line #390 ):
-
-
-
-
-
-
-388
-389
-390
-391
-392
-393
-
-
-
-
- t[:with_force_shutdown] = true
-
end
-
yield
-
ensure
-
t[:with_force_shutdown] = false
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- begin
-
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call(env)
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #472 ):
-
-
-
-
-
-
-470
-471
-472
-473
-474
-475
-
-
-
-
- while true
-
@requests_count += 1
-
case handle_request (client, requests + 1)
-
when false
-
break
-
when :async
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- @status = :run
-
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
-
-
if @queue_requests
-
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
-
-
-
-
-
-
-
-
-
- Extracted source (around line #167 ):
-
-
-
-
-
-
-165
-166
-167
-168
-169
-170
-
-
-
-
-
-
begin
-
@out_of_band_pending = true if block.call (work)
-
rescue Exception => e
-
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
-
end
-
-
-
-
-
-
-
-
-
-Rails.root: /app
-
-
-
-
-
-
- Request
- Parameters :
None
-
-
-
-
_csrf_token: "djDlIkXNu5qPodfxLYNNlTZAxaCJk-9sUsXjWESv56U"
-session_id: "b3988fe1be5909fede7f054b6028c72a"
-warden.user.user.key: [[1], "$2a$12$OItHogL1JMQsM3o7sAh5SO"]
-
-
-
-
-
GATEWAY_INTERFACE: "CGI/1.2"
-HTTP_ACCEPT: "text/html, application/xhtml+xml"
-HTTP_ACCEPT_ENCODING: "gzip, deflate"
-HTTP_ACCEPT_LANGUAGE: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
-HTTP_VERSION: "HTTP/1.1"
-ORIGINAL_SCRIPT_NAME: ""
-REMOTE_ADDR: "192.168.80.1"
-SERVER_NAME: "100.75.222.23"
-SERVER_PROTOCOL: "HTTP/1.1"
-
-
-Response
-Headers :
None
-
-
-
-
-
-
diff --git a/Erros/Action Controller_ Exception caught_quando aperta na parte de usuario.html b/Erros/Action Controller_ Exception caught_quando aperta na parte de usuario.html
deleted file mode 100644
index 0cc889a..0000000
--- a/Erros/Action Controller_ Exception caught_quando aperta na parte de usuario.html
+++ /dev/null
@@ -1,4027 +0,0 @@
-
-
-
-
-
-
- Action Controller: Exception caught
-
-
-
-
-
-
-
-
- NoMethodError
- in Admin::UsuariosController#index
-
-
-
-
-
-
undefined method `admin_ou_gerente?' for #<UserPolicy:0x00007f710b57d3d8 @user=#<User id: 1, email: "admin@gade.com", nome: "Administrador Gade", telefone: nil, pin_code: nil, role: "admin", ativo: true, tema_preferido: "dark", created_at: "2026-06-11 23:00:11.605256000 -0300", updated_at: "2026-06-11 23:00:11.605256000 -0300", login_token: nil>, @record=User(id: integer, email: string, encrypted_password: string, reset_password_token: string, reset_password_sent_at: datetime, remember_created_at: datetime, nome: string, telefone: string, pin_code: string, role: integer, ativo: boolean, tema_preferido: string, created_at: datetime, updated_at: datetime, login_token: string, sign_in_count: integer, current_sign_in_at: datetime, last_sign_in_at: datetime, current_sign_in_ip: string, last_sign_in_ip: string, failed_attempts: integer, unlock_token: string, locked_at: datetime)>
-
-
-
-
-
-
-
-
- Extracted source (around line #4 ):
-
-
-
-
-
-
-2
-3
-4
-5
-6
-7
-
-
-
-
-class UserPolicy < ApplicationPolicy
-
def index?
-
admin_ou_gerente?
-
end
-
-
def show?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #70 ):
-
-
-
-
-
-
-68
-69
-70
-71
-72
-73
-
-
-
-
- end
-
-
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send (query)
-
-
record
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #70 ):
-
-
-
-
-
-
-68
-69
-70
-71
-72
-73
-
-
-
-
- end
-
-
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send (query)
-
-
record
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #94 ):
-
-
-
-
-
-
-92
-93
-94
-95
-96
-97
-
-
-
-
- @_pundit_policy_authorized = true
-
-
pundit.authorize (record, query: query, policy_class: policy_class)
-
end
-
-
# Allow this action not to perform authorization.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #6 ):
-
-
-
-
-
-
-4
-5
-6
-7
-8
-9
-
-
-
-
-
-
def index
-
authorize User
-
@usuarios = policy_scope(User).order(:nome).limit(200)
-
@roles = User.roles.keys
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #6 ):
-
-
-
-
-
-
-4
-5
-6
-7
-8
-9
-
-
-
-
- module BasicImplicitRender # :nodoc:
-
def send_action(method, *args)
-
ret = super
-
default_render unless performed?
-
ret
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
- # which is *not* necessarily the same as the action name.
-
def process_action(...)
-
send_action (...)
-
end
-
-
# Actually call the method associated with the action. Override
-
-
-
-
-
-
-
-
-
- Extracted source (around line #165 ):
-
-
-
-
-
-
-163
-164
-165
-166
-167
-168
-
-
-
-
- def process_action(*) # :nodoc:
-
self.formats = request.formats.filter_map(&:ref)
-
super
-
end
-
-
def _process_variant(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- def process_action(...)
-
run_callbacks(:process_action) do
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def with_request_id(request_id)
-
old_request_id, self.current_request_id = self.current_request_id, request_id
-
yield
-
ensure
-
self.current_request_id = old_request_id
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #10 ):
-
-
-
-
-
-
-8
-9
-10
-11
-12
-
-
-
-
- private
-
def turbo_tracking_request_id(&block)
-
Turbo.with_request_id (request.headers["X-Turbo-Request-Id"], &block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- previous_renderer = self.renderer
-
self.renderer = renderer
-
yield
-
ensure
-
self.renderer = previous_renderer
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #69 ):
-
-
-
-
-
-
-67
-68
-69
-70
-71
-72
-
-
-
-
- ActiveSupport.on_load(base) do
-
around_action do |controller, action|
-
ActionText::Content.with_renderer (controller, &action)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #258 ):
-
-
-
-
-
-
-256
-257
-258
-259
-260
-261
-
-
-
-
- # <tt>process_action</tt> callbacks around the normal behavior.
-
def process_action(...)
-
run_callbacks (:process_action) do
-
super
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
- private
-
def process_action(*)
-
super
-
rescue Exception => exception
-
request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #74 ):
-
-
-
-
-
-
-72
-73
-74
-75
-76
-77
-
-
-
-
-
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
result
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #73 ):
-
-
-
-
-
-
-71
-72
-73
-74
-75
-76
-
-
-
-
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
-
-
ActiveSupport::Notifications.instrument ("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- def process_action(*)
-
_perform_parameter_wrapping if _wrapper_enabled?
-
super
-
end
-
-
# Returns the wrapper key which will be used to store wrapped parameters.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # and it won't be cleaned up by the method below.
-
ActiveRecord::RuntimeRegistry.reset
-
super
-
end
-
-
def cleanup_view_runtime
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- @_response_body = nil
-
-
process_action (action_name, *args)
-
end
-
ruby2_keywords(:process)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #40 ):
-
-
-
-
-
-
-38
-39
-40
-41
-42
-43
-
-
-
-
- def process(...) # :nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #227 ):
-
-
-
-
-
-
-225
-226
-227
-228
-229
-230
-
-
-
-
- set_request!(request)
-
set_response!(response)
-
process (name)
-
request.commit_flash
-
to_a
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #309 ):
-
-
-
-
-
-
-307
-308
-309
-310
-311
-312
-
-
-
-
- middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
-
else
-
new.dispatch (name, req, res)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #49 ):
-
-
-
-
-
-
-47
-48
-49
-50
-51
-52
-
-
-
-
-
-
def dispatch(controller, action, req, res)
-
controller.dispatch (action, req, res)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- controller = controller req
-
res = controller.make_response! req
-
dispatch (controller, params[:action], req, res)
-
rescue ActionController::RoutingError
-
if @raise_on_name_error
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #51 ):
-
-
-
-
-
-
-49
-50
-51
-52
-53
-54
-
-
-
-
- req.route_uri_pattern = route.path.spec.to_s
-
-
_, headers, _ = response = route.app.serve (req)
-
-
if "pass" == headers[Constants::X_CASCADE]
-
req.script_name = script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
-
}
-
yield [match_data, path_parameters, r]
-
}
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
-
-
def serve(req)
-
find_routes (req) do |match, parameters, route|
-
set_params = req.path_parameters
-
path_info = req.path_info
-
script_name = req.script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #882 ):
-
-
-
-
-
-
-880
-881
-882
-883
-884
-885
-
-
-
-
- req = make_request(env)
-
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
-
@router.serve (req)
-
end
-
-
def recognize_path(path, environment = {})
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
- result = catch(:warden) do
-
env['warden'].on_request
-
@app.call (env)
-
end
-
-
result ||= {}
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
-
-
begin
-
_, _, body = response = @app.call (env)
-
rescue Exception
-
env[RACK_TEMPFILES]&.each(&:close!)
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
status, headers, body = response = @app.call (env)
-
-
if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
-
body = body.to_ary
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- case env[REQUEST_METHOD]
-
when "GET", "HEAD"
-
status, headers, body = response = @app.call (env)
-
-
if status == 200 && fresh?(env, headers)
-
response[0] = 304
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
-
-
def call(env)
-
_, _, body = response = @app.call (env)
-
-
if env[REQUEST_METHOD] == HEAD
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
_, headers, _ = response = @app.call (env)
-
-
return response if policy_present?(headers)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
status, headers, _ = response = @app.call (env)
-
-
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
-
# CSP headers might not match nonces in the cached HTML.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #274 ):
-
-
-
-
-
-
-272
-273
-274
-275
-276
-277
-
-
-
-
- req = make_request env
-
prepare_session(req)
-
status, headers, body = app.call (req.env)
-
res = Rack::Response::Raw.new status, headers
-
commit_session(req, res)
-
[status, headers, body]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #268 ):
-
-
-
-
-
-
-266
-267
-268
-269
-270
-271
-
-
-
-
-
-
def call(env)
-
context (env)
-
end
-
-
def context(env, app = @app)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #689 ):
-
-
-
-
-
-
-687
-688
-689
-690
-691
-692
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
response = @app.call (env)
-
-
if request.have_cookie_jar?
-
cookie_jar = request.cookie_jar
-
-
-
-
-
-
-
-
-
- Extracted source (around line #655 ):
-
-
-
-
-
-
-653
-654
-655
-656
-657
-658
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- error = nil
-
result = run_callbacks :call do
-
@app.call (env)
-
rescue => error
-
end
-
raise error if error
-
-
-
-
-
-
-
-
-
- Extracted source (around line #101 ):
-
-
-
-
-
-
-99
-100
-101
-102
-103
-104
-
-
-
-
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def call(env)
-
error = nil
-
result = run_callbacks :call do
-
@app.call(env)
-
rescue => error
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #16 ):
-
-
-
-
-
-
-14
-15
-16
-17
-18
-19
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
return @app.call (env) unless actionable_request?(request)
-
-
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if headers[Constants::X_CASCADE] == "pass"
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #132 ):
-
-
-
-
-
-
-130
-131
-132
-133
-134
-135
-
-
-
-
-
-
def call_app(env)
-
@app.call (env)
-
rescue => e
-
throw :app_exception, e
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #19 ):
-
-
-
-
-
-
-17
-18
-19
-20
-21
-22
-
-
-
-
- app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app (env) unless request.permitted?
-
-
if id = id_for_repl_session_update(request)
-
return update_repl_session(id, request)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
-
-
def call(env)
-
@app.call (env)
-
rescue Exception => exception
-
request = ActionDispatch::Request.new env
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
-
-
logger.info { started_request_message(request) }
-
status, headers, body = response = @app.call (env)
-
body = ::Rack::BodyProxy.new(body, &handle.method(:finish))
-
-
if response.frozen?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged(*compute_tags(request)) { call_app (request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- def tagged(*tags)
-
pushed_count = tag_stack.push_tags(tags).size
-
yield self
-
ensure
-
pop_tags(pushed_count)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged (*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #241 ):
-
-
-
-
-
-
-239
-240
-241
-242
-243
-244
-
-
-
-
- super(name, *args, **kwargs, &block)
-
elsif loggers.one?
-
loggers.first.send (name, *args, **kwargs, &block)
-
else
-
loggers.map { |logger| logger.send(name, *args, **kwargs, &block) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged (*compute_tags(request)) { call_app(request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- ::Rails.logger.silence { @app.call(env) }
-
else
-
@app.call (env)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #92 ):
-
-
-
-
-
-
-90
-91
-92
-93
-94
-95
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.remote_ip = GetIp.new(req, check_ip, proxies)
-
@app.call (req.env)
-
end
-
-
# The GetIp class exists as a way to defer processing of the request data
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.request_id = make_request_id(req.headers[@header])
-
@app.call (env).tap { |_status, headers, _body| headers[@header] = req.request_id }
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
def method_override(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def call(env)
-
start_time = Utils.clock_time
-
_, headers, _ = response = @app.call (env)
-
-
request_time = Utils.clock_time - start_time
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- response = nil
-
events = @subscriber.collect_events do
-
response = @app.call (env)
-
end
-
-
headers = response[1]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- events = []
-
ActiveSupport::IsolatedExecutionState[KEY] = events
-
yield
-
events
-
ensure
-
ActiveSupport::IsolatedExecutionState.delete(KEY)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- def call(env)
-
response = nil
-
events = @subscriber.collect_events do
-
response = @app.call(env)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- end
-
else
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
-
-
def call(env)
-
@file_handler.attempt(env) || @app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if body.respond_to?(:to_path)
-
case type = variation(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #334 ):
-
-
-
-
-
-
-332
-333
-334
-335
-336
-337
-
-
-
-
- status, headers, body = [200, { Rack::CONTENT_TYPE => 'text/html' }, [blank_page_html.dup]] # important to dup here!
-
else
-
status, headers, body = @app.call (env)
-
end
-
ensure
-
trace.disable if trace
-
-
-
-
-
-
-
-
-
- Extracted source (around line #536 ):
-
-
-
-
-
-
-534
-535
-536
-537
-538
-539
-
-
-
-
- def call(env)
-
req = build_request env
-
app.call req.env
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #279 ):
-
-
-
-
-
-
-277
-278
-279
-280
-281
-282
-
-
-
-
- def call(env)
-
env[Const::PUMA_CONFIG] = @config
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #99 ):
-
-
-
-
-
-
-97
-98
-99
-100
-101
-102
-
-
-
-
- if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call (env)
-
end
-
else
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
-
-
-
-
-
-
-
-
-
- Extracted source (around line #390 ):
-
-
-
-
-
-
-388
-389
-390
-391
-392
-393
-
-
-
-
- t[:with_force_shutdown] = true
-
end
-
yield
-
ensure
-
t[:with_force_shutdown] = false
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- begin
-
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call(env)
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #472 ):
-
-
-
-
-
-
-470
-471
-472
-473
-474
-475
-
-
-
-
- while true
-
@requests_count += 1
-
case handle_request (client, requests + 1)
-
when false
-
break
-
when :async
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- @status = :run
-
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
-
-
if @queue_requests
-
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
-
-
-
-
-
-
-
-
-
- Extracted source (around line #167 ):
-
-
-
-
-
-
-165
-166
-167
-168
-169
-170
-
-
-
-
-
-
begin
-
@out_of_band_pending = true if block.call (work)
-
rescue Exception => e
-
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
-
end
-
-
-
-
-
-
-
-
-
-Rails.root: /app
-
-
-
-
-
-
- Request
- Parameters :
None
-
-
-
-
_csrf_token: "CDiW_RJyJcAlCPyrikFuKYkfdzBC-OW6YRsZDeWj9VQ"
-session_id: "ac99c889d2e6f9700a182c290873487b"
-user_return_to: "/"
-warden.user.user.key: [[1], "$2a$12$OItHogL1JMQsM3o7sAh5SO"]
-
-
-
-
-
GATEWAY_INTERFACE: "CGI/1.2"
-HTTP_ACCEPT: "text/html, application/xhtml+xml"
-HTTP_ACCEPT_ENCODING: "gzip, deflate"
-HTTP_ACCEPT_LANGUAGE: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
-HTTP_VERSION: "HTTP/1.1"
-ORIGINAL_SCRIPT_NAME: ""
-REMOTE_ADDR: "172.31.0.1"
-SERVER_NAME: "100.75.222.23"
-SERVER_PROTOCOL: "HTTP/1.1"
-
-
-Response
-Headers :
None
-
-
-
-
-
-
diff --git a/Erros/Action Controller_ Exception caught_quando aperta nos usuarios .html b/Erros/Action Controller_ Exception caught_quando aperta nos usuarios .html
deleted file mode 100644
index 3f57202..0000000
--- a/Erros/Action Controller_ Exception caught_quando aperta nos usuarios .html
+++ /dev/null
@@ -1,5440 +0,0 @@
-
-
-
-
-
-
- Action Controller: Exception caught
-
-
-
-
-
-
-
-
- NoMethodError in
- Admin::Usuarios#index
-
-
-
-
-
- Showing /app/app/views/admin/usuarios/index.html.erb where line #57 raised:
-
- undefined method `badge_role' for #<ActionView::Base:0x0000000000fc80>
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- @output_buffer.safe_append=' </td>
-
<td class="px-6 py-4">
-
'.freeze; @output_buffer.append=( badge_role (usuario.role) ); @output_buffer.safe_append='
-
</td>
-
<td class="px-6 py-4">
-
'.freeze; @output_buffer.append=( badge_status_usuario(usuario.ativo?) ); @output_buffer.safe_append='
-
-
-
-
-
-
-
-
-
- Extracted source (around line #1 ):
-
-
-
-
-
-
-1
-
-
-
-
-def to_xml(...); _ = records; _.to_xml(...);rescue NoMethodError => e; if _.nil? && e.name == :to_xml; raise DelegationError, "ActiveRecord::Delegation#to_xml delegated to records.to_xml, but records is nil: #{self.inspect}"; else; raise; end;end;def encode_with(...); _ = records; _.encode_with(...);rescue NoMethodError => e; if _.nil? && e.name == :encode_with; raise DelegationError, "ActiveRecord::Delegation#encode_with delegated to records.encode_with, but records is nil: #{self.inspect}"; else; raise; end;end;def length(...); _ = records; _.length(...);rescue NoMethodError => e; if _.nil? && e.name == :length; raise DelegationError, "ActiveRecord::Delegation#length delegated to records.length, but records is nil: #{self.inspect}"; else; raise; end;end;def each(...); _ = records; _.each (...);rescue NoMethodError => e; if _.nil? && e.name == :each; raise DelegationError, "ActiveRecord::Delegation#each delegated to records.each, but records is nil: #{self.inspect}"; else; raise; end;end;def join(...); _ = records; _.join(...);rescue NoMethodError => e; if _.nil? && e.name == :join; raise DelegationError, "ActiveRecord::Delegation#join delegated to records.join, but records is nil: #{self.inspect}"; else; raise; end;end;def intersect?(...); _ = records; _.intersect?(...);rescue NoMethodError => e; if _.nil? && e.name == :intersect?; raise DelegationError, "ActiveRecord::Delegation#intersect? delegated to records.intersect?, but records is nil: #{self.inspect}"; else; raise; end;end;def [](...); _ = records; _.[](...);rescue NoMethodError => e; if _.nil? && e.name == :[]; raise DelegationError, "ActiveRecord::Delegation#[] delegated to records.[], but records is nil: #{self.inspect}"; else; raise; end;end;def &(...); _ = records; _.&(...);rescue NoMethodError => e; if _.nil? && e.name == :&; raise DelegationError, "ActiveRecord::Delegation#& delegated to records.&, but records is nil: #{self.inspect}"; else; raise; end;end;def |(...); _ = records; _.|(...);rescue NoMethodError => e; if _.nil? && e.name == :|; raise DelegationError, "ActiveRecord::Delegation#| delegated to records.|, but records is nil: #{self.inspect}"; else; raise; end;end;def +(...); _ = records; _.+(...);rescue NoMethodError => e; if _.nil? && e.name == :+; raise DelegationError, "ActiveRecord::Delegation#+ delegated to records.+, but records is nil: #{self.inspect}"; else; raise; end;end;def -(...); _ = records; _.-(...);rescue NoMethodError => e; if _.nil? && e.name == :-; raise DelegationError, "ActiveRecord::Delegation#- delegated to records.-, but records is nil: #{self.inspect}"; else; raise; end;end;def sample(...); _ = records; _.sample(...);rescue NoMethodError => e; if _.nil? && e.name == :sample; raise DelegationError, "ActiveRecord::Delegation#sample delegated to records.sample, but records is nil: #{self.inspect}"; else; raise; end;end;def reverse(...); _ = records; _.reverse(...);rescue NoMethodError => e; if _.nil? && e.name == :reverse; raise DelegationError, "ActiveRecord::Delegation#reverse delegated to records.reverse, but records is nil: #{self.inspect}"; else; raise; end;end;def rotate(...); _ = records; _.rotate(...);rescue NoMethodError => e; if _.nil? && e.name == :rotate; raise DelegationError, "ActiveRecord::Delegation#rotate delegated to records.rotate, but records is nil: #{self.inspect}"; else; raise; end;end;def compact(...); _ = records; _.compact(...);rescue NoMethodError => e; if _.nil? && e.name == :compact; raise DelegationError, "ActiveRecord::Delegation#compact delegated to records.compact, but records is nil: #{self.inspect}"; else; raise; end;end;def in_groups(...); _ = records; _.in_groups(...);rescue NoMethodError => e; if _.nil? && e.name == :in_groups; raise DelegationError, "ActiveRecord::Delegation#in_groups delegated to records.in_groups, but records is nil: #{self.inspect}"; else; raise; end;end;def in_groups_of(...); _ = records; _.in_groups_of(...);rescue NoMethodError => e; if _.nil? && e.name == :in_groups_of; raise DelegationError, "ActiveRecord::Delegation#in_groups_of delegated to records.in_groups_of, but records is nil: #{self.inspect}"; else; raise; end;end;def to_sentence(...); _ = records; _.to_sentence(...);rescue NoMethodError => e; if _.nil? && e.name == :to_sentence; raise DelegationError, "ActiveRecord::Delegation#to_sentence delegated to records.to_sentence, but records is nil: #{self.inspect}"; else; raise; end;end;def to_fs(...); _ = records; _.to_fs(...);rescue NoMethodError => e; if _.nil? && e.name == :to_fs; raise DelegationError, "ActiveRecord::Delegation#to_fs delegated to records.to_fs, but records is nil: #{self.inspect}"; else; raise; end;end;def to_formatted_s(...); _ = records; _.to_formatted_s(...);rescue NoMethodError => e; if _.nil? && e.name == :to_formatted_s; raise DelegationError, "ActiveRecord::Delegation#to_formatted_s delegated to records.to_formatted_s, but records is nil: #{self.inspect}"; else; raise; end;end;def as_json(...); _ = records; _.as_json(...);rescue NoMethodError => e; if _.nil? && e.name == :as_json; raise DelegationError, "ActiveRecord::Delegation#as_json delegated to records.as_json, but records is nil: #{self.inspect}"; else; raise; end;end;def shuffle(...); _ = records; _.shuffle(...);rescue NoMethodError => e; if _.nil? && e.name == :shuffle; raise DelegationError, "ActiveRecord::Delegation#shuffle delegated to records.shuffle, but records is nil: #{self.inspect}"; else; raise; end;end;def split(...); _ = records; _.split(...);rescue NoMethodError => e; if _.nil? && e.name == :split; raise DelegationError, "ActiveRecord::Delegation#split delegated to records.split, but records is nil: #{self.inspect}"; else; raise; end;end;def slice(...); _ = records; _.slice(...);rescue NoMethodError => e; if _.nil? && e.name == :slice; raise DelegationError, "ActiveRecord::Delegation#slice delegated to records.slice, but records is nil: #{self.inspect}"; else; raise; end;end;def index(...); _ = records; _.index(...);rescue NoMethodError => e; if _.nil? && e.name == :index; raise DelegationError, "ActiveRecord::Delegation#index delegated to records.index, but records is nil: #{self.inspect}"; else; raise; end;end;def rindex(...); _ = records; _.rindex(...);rescue NoMethodError => e; if _.nil? && e.name == :rindex; raise DelegationError, "ActiveRecord::Delegation#rindex delegated to records.rindex, but records is nil: #{self.inspect}"; else; raise; end;end
-
-
-
-
-
-
-
-
- Extracted source (around line #1 ):
-
-
-
-
-
-
-1
-
-
-
-
-def to_xml(...); _ = records; _.to_xml(...);rescue NoMethodError => e; if _.nil? && e.name == :to_xml; raise DelegationError, "ActiveRecord::Delegation#to_xml delegated to records.to_xml, but records is nil: #{self.inspect}"; else; raise; end;end;def encode_with(...); _ = records; _.encode_with(...);rescue NoMethodError => e; if _.nil? && e.name == :encode_with; raise DelegationError, "ActiveRecord::Delegation#encode_with delegated to records.encode_with, but records is nil: #{self.inspect}"; else; raise; end;end;def length(...); _ = records; _.length(...);rescue NoMethodError => e; if _.nil? && e.name == :length; raise DelegationError, "ActiveRecord::Delegation#length delegated to records.length, but records is nil: #{self.inspect}"; else; raise; end;end;def each(...); _ = records; _.each (...);rescue NoMethodError => e; if _.nil? && e.name == :each; raise DelegationError, "ActiveRecord::Delegation#each delegated to records.each, but records is nil: #{self.inspect}"; else; raise; end;end;def join(...); _ = records; _.join(...);rescue NoMethodError => e; if _.nil? && e.name == :join; raise DelegationError, "ActiveRecord::Delegation#join delegated to records.join, but records is nil: #{self.inspect}"; else; raise; end;end;def intersect?(...); _ = records; _.intersect?(...);rescue NoMethodError => e; if _.nil? && e.name == :intersect?; raise DelegationError, "ActiveRecord::Delegation#intersect? delegated to records.intersect?, but records is nil: #{self.inspect}"; else; raise; end;end;def [](...); _ = records; _.[](...);rescue NoMethodError => e; if _.nil? && e.name == :[]; raise DelegationError, "ActiveRecord::Delegation#[] delegated to records.[], but records is nil: #{self.inspect}"; else; raise; end;end;def &(...); _ = records; _.&(...);rescue NoMethodError => e; if _.nil? && e.name == :&; raise DelegationError, "ActiveRecord::Delegation#& delegated to records.&, but records is nil: #{self.inspect}"; else; raise; end;end;def |(...); _ = records; _.|(...);rescue NoMethodError => e; if _.nil? && e.name == :|; raise DelegationError, "ActiveRecord::Delegation#| delegated to records.|, but records is nil: #{self.inspect}"; else; raise; end;end;def +(...); _ = records; _.+(...);rescue NoMethodError => e; if _.nil? && e.name == :+; raise DelegationError, "ActiveRecord::Delegation#+ delegated to records.+, but records is nil: #{self.inspect}"; else; raise; end;end;def -(...); _ = records; _.-(...);rescue NoMethodError => e; if _.nil? && e.name == :-; raise DelegationError, "ActiveRecord::Delegation#- delegated to records.-, but records is nil: #{self.inspect}"; else; raise; end;end;def sample(...); _ = records; _.sample(...);rescue NoMethodError => e; if _.nil? && e.name == :sample; raise DelegationError, "ActiveRecord::Delegation#sample delegated to records.sample, but records is nil: #{self.inspect}"; else; raise; end;end;def reverse(...); _ = records; _.reverse(...);rescue NoMethodError => e; if _.nil? && e.name == :reverse; raise DelegationError, "ActiveRecord::Delegation#reverse delegated to records.reverse, but records is nil: #{self.inspect}"; else; raise; end;end;def rotate(...); _ = records; _.rotate(...);rescue NoMethodError => e; if _.nil? && e.name == :rotate; raise DelegationError, "ActiveRecord::Delegation#rotate delegated to records.rotate, but records is nil: #{self.inspect}"; else; raise; end;end;def compact(...); _ = records; _.compact(...);rescue NoMethodError => e; if _.nil? && e.name == :compact; raise DelegationError, "ActiveRecord::Delegation#compact delegated to records.compact, but records is nil: #{self.inspect}"; else; raise; end;end;def in_groups(...); _ = records; _.in_groups(...);rescue NoMethodError => e; if _.nil? && e.name == :in_groups; raise DelegationError, "ActiveRecord::Delegation#in_groups delegated to records.in_groups, but records is nil: #{self.inspect}"; else; raise; end;end;def in_groups_of(...); _ = records; _.in_groups_of(...);rescue NoMethodError => e; if _.nil? && e.name == :in_groups_of; raise DelegationError, "ActiveRecord::Delegation#in_groups_of delegated to records.in_groups_of, but records is nil: #{self.inspect}"; else; raise; end;end;def to_sentence(...); _ = records; _.to_sentence(...);rescue NoMethodError => e; if _.nil? && e.name == :to_sentence; raise DelegationError, "ActiveRecord::Delegation#to_sentence delegated to records.to_sentence, but records is nil: #{self.inspect}"; else; raise; end;end;def to_fs(...); _ = records; _.to_fs(...);rescue NoMethodError => e; if _.nil? && e.name == :to_fs; raise DelegationError, "ActiveRecord::Delegation#to_fs delegated to records.to_fs, but records is nil: #{self.inspect}"; else; raise; end;end;def to_formatted_s(...); _ = records; _.to_formatted_s(...);rescue NoMethodError => e; if _.nil? && e.name == :to_formatted_s; raise DelegationError, "ActiveRecord::Delegation#to_formatted_s delegated to records.to_formatted_s, but records is nil: #{self.inspect}"; else; raise; end;end;def as_json(...); _ = records; _.as_json(...);rescue NoMethodError => e; if _.nil? && e.name == :as_json; raise DelegationError, "ActiveRecord::Delegation#as_json delegated to records.as_json, but records is nil: #{self.inspect}"; else; raise; end;end;def shuffle(...); _ = records; _.shuffle(...);rescue NoMethodError => e; if _.nil? && e.name == :shuffle; raise DelegationError, "ActiveRecord::Delegation#shuffle delegated to records.shuffle, but records is nil: #{self.inspect}"; else; raise; end;end;def split(...); _ = records; _.split(...);rescue NoMethodError => e; if _.nil? && e.name == :split; raise DelegationError, "ActiveRecord::Delegation#split delegated to records.split, but records is nil: #{self.inspect}"; else; raise; end;end;def slice(...); _ = records; _.slice(...);rescue NoMethodError => e; if _.nil? && e.name == :slice; raise DelegationError, "ActiveRecord::Delegation#slice delegated to records.slice, but records is nil: #{self.inspect}"; else; raise; end;end;def index(...); _ = records; _.index(...);rescue NoMethodError => e; if _.nil? && e.name == :index; raise DelegationError, "ActiveRecord::Delegation#index delegated to records.index, but records is nil: #{self.inspect}"; else; raise; end;end;def rindex(...); _ = records; _.rindex(...);rescue NoMethodError => e; if _.nil? && e.name == :rindex; raise DelegationError, "ActiveRecord::Delegation#rindex delegated to records.rindex, but records is nil: #{self.inspect}"; else; raise; end;end
-
-
-
-
-
-
-
-
- Extracted source (around line #38 ):
-
-
-
-
-
-
-36
-37
-38
-39
-40
-41
-
-
-
-
- </thead>
-
<tbody class="divide-y divide-white/5">
-
<% @usuarios.each do |usuario| %>
-
<tr class="hover:bg-white/5 transition-colors group">
-
<td class="px-6 py-4">
-
<div class="flex items-center gap-3">
-
-
-
-
-
-
-
-
-
- Extracted source (around line #263 ):
-
-
-
-
-
-
-261
-262
-263
-264
-265
-266
-
-
-
-
- end
-
else
-
public_send (method, locals, buffer, &block)
-
end
-
ensure
-
@output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
-
-
-
-
-
-
-
-
-
- Extracted source (around line #263 ):
-
-
-
-
-
-
-261
-262
-263
-264
-265
-266
-
-
-
-
- end
-
else
-
public_send (method, locals, buffer, &block)
-
end
-
ensure
-
@output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- nil
-
else
-
result = view._run (method_name, self, locals, OutputBuffer.new, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
-
result.is_a?(OutputBuffer) ? result.to_s : result
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #208 ):
-
-
-
-
-
-
-206
-207
-208
-209
-210
-211
-
-
-
-
- instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #556 ):
-
-
-
-
-
-
-554
-555
-556
-557
-558
-559
-
-
-
-
-
-
def instrument_render_template(&block)
-
ActiveSupport::Notifications.instrument ("!render_template.action_view", instrument_payload, &block)
-
end
-
-
def instrument_payload
-
-
-
-
-
-
-
-
-
- Extracted source (around line #249 ):
-
-
-
-
-
-
-247
-248
-249
-250
-251
-252
-
-
-
-
- # consume this in production. This is only slow if it's being listened to.
-
def render(view, locals, buffer = nil, implicit_locals: [], add_to_stack: true, &block)
-
instrument_render_template do
-
compile!(view)
-
-
if strict_locals? && @strict_local_keys && !implicit_locals.empty?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #66 ):
-
-
-
-
-
-
-64
-65
-66
-67
-68
-69
-
-
-
-
- locals: locals
-
) do
-
template.render (view, locals) { |*name| view._layout_for(*name) }
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #60 ):
-
-
-
-
-
-
-58
-59
-60
-61
-62
-63
-
-
-
-
- def render_template(view, template, layout_name, locals)
-
render_with_layout(view, template, layout_name, locals) do |layout|
-
ActiveSupport::Notifications.instrument (
-
"render_template.action_view",
-
identifier: template.identifier,
-
layout: layout && layout.virtual_path,
-
-
-
-
-
-
-
-
-
- Extracted source (around line #76 ):
-
-
-
-
-
-
-74
-75
-76
-77
-78
-79
-
-
-
-
- body = if layout
-
ActiveSupport::Notifications.instrument("render_layout.action_view", identifier: layout.identifier) do
-
view.view_flow.set(:layout, yield(layout))
-
layout.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #75 ):
-
-
-
-
-
-
-73
-74
-75
-76
-77
-78
-
-
-
-
-
-
body = if layout
-
ActiveSupport::Notifications.instrument ("render_layout.action_view", identifier: layout.identifier) do
-
view.view_flow.set(:layout, yield(layout))
-
layout.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- # supplied as well.
-
def render_template(view, template, layout_name, locals)
-
render_with_layout (view, template, layout_name, locals) do |layout|
-
ActiveSupport::Notifications.instrument(
-
"render_template.action_view",
-
identifier: template.identifier,
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- prepend_formats(template.format)
-
-
render_template (context, template, options[:layout], options[:locals] || {})
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #63 ):
-
-
-
-
-
-
-61
-62
-63
-64
-65
-66
-
-
-
-
-
-
def render_template_to_object(context, options) # :nodoc:
-
TemplateRenderer.new(@lookup_context).render (context, options)
-
end
-
-
def render_partial_to_object(context, options, &block) # :nodoc:
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- render_partial_to_object(context, options)
-
else
-
render_template_to_object (context, options)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #135 ):
-
-
-
-
-
-
-133
-134
-135
-136
-137
-138
-
-
-
-
-
-
rendered_template = context.in_rendering_context(options) do |renderer|
-
renderer.render_to_object (context, options)
-
end
-
-
rendered_format = rendered_template.format || lookup_context.formats.first
-
-
-
-
-
-
-
-
-
- Extracted source (around line #290 ):
-
-
-
-
-
-
-288
-289
-290
-291
-292
-293
-
-
-
-
- end
-
-
yield @view_renderer
-
ensure
-
@view_renderer = old_view_renderer
-
@lookup_context = old_lookup_context
-
-
-
-
-
-
-
-
-
- Extracted source (around line #134 ):
-
-
-
-
-
-
-132
-133
-134
-135
-136
-137
-
-
-
-
- lookup_context.variants = variant if variant
-
-
rendered_template = context.in_rendering_context (options) do |renderer|
-
renderer.render_to_object(context, options)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #256 ):
-
-
-
-
-
-
-254
-255
-256
-257
-258
-259
-
-
-
-
- Body.new view_renderer.render_body(view_context, options)
-
else
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- def render_to_body(options = {})
-
_process_options(options)
-
_render_template (options)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #158 ):
-
-
-
-
-
-
-156
-157
-158
-159
-160
-161
-
-
-
-
-
-
def render_to_body(options = {}) # :nodoc:
-
super || _render_in_priorities(options) || " "
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- # <tt>ActionView::Rendering.render_to_body</tt>, if present.
-
def render_to_body(options)
-
_render_to_body_with_renderer(options) || super
-
end
-
-
def _render_to_body_with_renderer(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #27 ):
-
-
-
-
-
-
-25
-26
-27
-28
-29
-30
-
-
-
-
- def render(*args, &block)
-
options = _normalize_render(*args, &block)
-
rendered_body = render_to_body (options)
-
if options[:html]
-
_set_html_content_type
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def render(*args)
-
raise ::AbstractController::DoubleRenderError if response_body
-
super
-
end
-
-
# Similar to #render, but only returns the rendered template as a string,
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- render_output = nil
-
self.view_runtime = cleanup_view_runtime do
-
Benchmark.ms { render_output = super }
-
end
-
render_output
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #324 ):
-
-
-
-
-
-
-322
-323
-324
-325
-326
-327
-
-
-
-
- def realtime # :yield:
-
r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
-
yield
-
Process.clock_gettime(Process::CLOCK_MONOTONIC) - r0
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-
-
-
-
- # # => 0.074
-
def ms(&block)
-
1000 * realtime (&block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- render_output = nil
-
self.view_runtime = cleanup_view_runtime do
-
Benchmark.ms { render_output = super }
-
end
-
render_output
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- # end
-
def cleanup_view_runtime # :doc:
-
yield
-
end
-
-
# Every time after an action is processed, this method is invoked
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- db_rt_before_render = ActiveRecord::RuntimeRegistry.reset
-
self.db_runtime = (db_runtime || 0) + db_rt_before_render
-
runtime = super
-
queries_rt = ActiveRecord::RuntimeRegistry.sql_runtime - ActiveRecord::RuntimeRegistry.async_sql_runtime
-
db_rt_after_render = ActiveRecord::RuntimeRegistry.reset
-
self.db_runtime += db_rt_after_render
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def render(*)
-
render_output = nil
-
self.view_runtime = cleanup_view_runtime do
-
Benchmark.ms { render_output = super }
-
end
-
render_output
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- def default_render
-
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
-
render
-
elsif any_templates?(action_name.to_s, _prefixes)
-
message = "#{self.class.name}\##{action_name} is missing a template " \
-
"for this request format and variant.\n" \
-
-
-
-
-
-
-
-
-
- Extracted source (around line #7 ):
-
-
-
-
-
-
-5
-6
-7
-8
-9
-10
-
-
-
-
- def send_action(method, *args)
-
ret = super
-
default_render unless performed?
-
ret
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
- # which is *not* necessarily the same as the action name.
-
def process_action(...)
-
send_action (...)
-
end
-
-
# Actually call the method associated with the action. Override
-
-
-
-
-
-
-
-
-
- Extracted source (around line #165 ):
-
-
-
-
-
-
-163
-164
-165
-166
-167
-168
-
-
-
-
- def process_action(*) # :nodoc:
-
self.formats = request.formats.filter_map(&:ref)
-
super
-
end
-
-
def _process_variant(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- def process_action(...)
-
run_callbacks(:process_action) do
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def with_request_id(request_id)
-
old_request_id, self.current_request_id = self.current_request_id, request_id
-
yield
-
ensure
-
self.current_request_id = old_request_id
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #10 ):
-
-
-
-
-
-
-8
-9
-10
-11
-12
-
-
-
-
- private
-
def turbo_tracking_request_id(&block)
-
Turbo.with_request_id (request.headers["X-Turbo-Request-Id"], &block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- previous_renderer = self.renderer
-
self.renderer = renderer
-
yield
-
ensure
-
self.renderer = previous_renderer
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #69 ):
-
-
-
-
-
-
-67
-68
-69
-70
-71
-72
-
-
-
-
- ActiveSupport.on_load(base) do
-
around_action do |controller, action|
-
ActionText::Content.with_renderer (controller, &action)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #258 ):
-
-
-
-
-
-
-256
-257
-258
-259
-260
-261
-
-
-
-
- # <tt>process_action</tt> callbacks around the normal behavior.
-
def process_action(...)
-
run_callbacks (:process_action) do
-
super
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
- private
-
def process_action(*)
-
super
-
rescue Exception => exception
-
request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #74 ):
-
-
-
-
-
-
-72
-73
-74
-75
-76
-77
-
-
-
-
-
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
result
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #73 ):
-
-
-
-
-
-
-71
-72
-73
-74
-75
-76
-
-
-
-
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
-
-
ActiveSupport::Notifications.instrument ("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- def process_action(*)
-
_perform_parameter_wrapping if _wrapper_enabled?
-
super
-
end
-
-
# Returns the wrapper key which will be used to store wrapped parameters.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # and it won't be cleaned up by the method below.
-
ActiveRecord::RuntimeRegistry.reset
-
super
-
end
-
-
def cleanup_view_runtime
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- @_response_body = nil
-
-
process_action (action_name, *args)
-
end
-
ruby2_keywords(:process)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #40 ):
-
-
-
-
-
-
-38
-39
-40
-41
-42
-43
-
-
-
-
- def process(...) # :nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #227 ):
-
-
-
-
-
-
-225
-226
-227
-228
-229
-230
-
-
-
-
- set_request!(request)
-
set_response!(response)
-
process (name)
-
request.commit_flash
-
to_a
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #309 ):
-
-
-
-
-
-
-307
-308
-309
-310
-311
-312
-
-
-
-
- middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
-
else
-
new.dispatch (name, req, res)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #49 ):
-
-
-
-
-
-
-47
-48
-49
-50
-51
-52
-
-
-
-
-
-
def dispatch(controller, action, req, res)
-
controller.dispatch (action, req, res)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- controller = controller req
-
res = controller.make_response! req
-
dispatch (controller, params[:action], req, res)
-
rescue ActionController::RoutingError
-
if @raise_on_name_error
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #51 ):
-
-
-
-
-
-
-49
-50
-51
-52
-53
-54
-
-
-
-
- req.route_uri_pattern = route.path.spec.to_s
-
-
_, headers, _ = response = route.app.serve (req)
-
-
if "pass" == headers[Constants::X_CASCADE]
-
req.script_name = script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
-
}
-
yield [match_data, path_parameters, r]
-
}
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
-
-
def serve(req)
-
find_routes (req) do |match, parameters, route|
-
set_params = req.path_parameters
-
path_info = req.path_info
-
script_name = req.script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #882 ):
-
-
-
-
-
-
-880
-881
-882
-883
-884
-885
-
-
-
-
- req = make_request(env)
-
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
-
@router.serve (req)
-
end
-
-
def recognize_path(path, environment = {})
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
- result = catch(:warden) do
-
env['warden'].on_request
-
@app.call (env)
-
end
-
-
result ||= {}
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
-
-
begin
-
_, _, body = response = @app.call (env)
-
rescue Exception
-
env[RACK_TEMPFILES]&.each(&:close!)
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
status, headers, body = response = @app.call (env)
-
-
if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
-
body = body.to_ary
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- case env[REQUEST_METHOD]
-
when "GET", "HEAD"
-
status, headers, body = response = @app.call (env)
-
-
if status == 200 && fresh?(env, headers)
-
response[0] = 304
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
-
-
def call(env)
-
_, _, body = response = @app.call (env)
-
-
if env[REQUEST_METHOD] == HEAD
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
_, headers, _ = response = @app.call (env)
-
-
return response if policy_present?(headers)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
status, headers, _ = response = @app.call (env)
-
-
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
-
# CSP headers might not match nonces in the cached HTML.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #274 ):
-
-
-
-
-
-
-272
-273
-274
-275
-276
-277
-
-
-
-
- req = make_request env
-
prepare_session(req)
-
status, headers, body = app.call (req.env)
-
res = Rack::Response::Raw.new status, headers
-
commit_session(req, res)
-
[status, headers, body]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #268 ):
-
-
-
-
-
-
-266
-267
-268
-269
-270
-271
-
-
-
-
-
-
def call(env)
-
context (env)
-
end
-
-
def context(env, app = @app)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #689 ):
-
-
-
-
-
-
-687
-688
-689
-690
-691
-692
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
response = @app.call (env)
-
-
if request.have_cookie_jar?
-
cookie_jar = request.cookie_jar
-
-
-
-
-
-
-
-
-
- Extracted source (around line #655 ):
-
-
-
-
-
-
-653
-654
-655
-656
-657
-658
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- error = nil
-
result = run_callbacks :call do
-
@app.call (env)
-
rescue => error
-
end
-
raise error if error
-
-
-
-
-
-
-
-
-
- Extracted source (around line #101 ):
-
-
-
-
-
-
-99
-100
-101
-102
-103
-104
-
-
-
-
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def call(env)
-
error = nil
-
result = run_callbacks :call do
-
@app.call(env)
-
rescue => error
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #16 ):
-
-
-
-
-
-
-14
-15
-16
-17
-18
-19
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
return @app.call (env) unless actionable_request?(request)
-
-
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if headers[Constants::X_CASCADE] == "pass"
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #132 ):
-
-
-
-
-
-
-130
-131
-132
-133
-134
-135
-
-
-
-
-
-
def call_app(env)
-
@app.call (env)
-
rescue => e
-
throw :app_exception, e
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #19 ):
-
-
-
-
-
-
-17
-18
-19
-20
-21
-22
-
-
-
-
- app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app (env) unless request.permitted?
-
-
if id = id_for_repl_session_update(request)
-
return update_repl_session(id, request)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
-
-
def call(env)
-
@app.call (env)
-
rescue Exception => exception
-
request = ActionDispatch::Request.new env
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
-
-
logger.info { started_request_message(request) }
-
status, headers, body = response = @app.call (env)
-
body = ::Rack::BodyProxy.new(body, &handle.method(:finish))
-
-
if response.frozen?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged(*compute_tags(request)) { call_app (request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- def tagged(*tags)
-
pushed_count = tag_stack.push_tags(tags).size
-
yield self
-
ensure
-
pop_tags(pushed_count)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged (*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #241 ):
-
-
-
-
-
-
-239
-240
-241
-242
-243
-244
-
-
-
-
- super(name, *args, **kwargs, &block)
-
elsif loggers.one?
-
loggers.first.send (name, *args, **kwargs, &block)
-
else
-
loggers.map { |logger| logger.send(name, *args, **kwargs, &block) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged (*compute_tags(request)) { call_app(request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- ::Rails.logger.silence { @app.call(env) }
-
else
-
@app.call (env)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #92 ):
-
-
-
-
-
-
-90
-91
-92
-93
-94
-95
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.remote_ip = GetIp.new(req, check_ip, proxies)
-
@app.call (req.env)
-
end
-
-
# The GetIp class exists as a way to defer processing of the request data
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.request_id = make_request_id(req.headers[@header])
-
@app.call (env).tap { |_status, headers, _body| headers[@header] = req.request_id }
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
def method_override(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def call(env)
-
start_time = Utils.clock_time
-
_, headers, _ = response = @app.call (env)
-
-
request_time = Utils.clock_time - start_time
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- response = nil
-
events = @subscriber.collect_events do
-
response = @app.call (env)
-
end
-
-
headers = response[1]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- events = []
-
ActiveSupport::IsolatedExecutionState[KEY] = events
-
yield
-
events
-
ensure
-
ActiveSupport::IsolatedExecutionState.delete(KEY)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- def call(env)
-
response = nil
-
events = @subscriber.collect_events do
-
response = @app.call(env)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- end
-
else
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
-
-
def call(env)
-
@file_handler.attempt(env) || @app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if body.respond_to?(:to_path)
-
case type = variation(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #334 ):
-
-
-
-
-
-
-332
-333
-334
-335
-336
-337
-
-
-
-
- status, headers, body = [200, { Rack::CONTENT_TYPE => 'text/html' }, [blank_page_html.dup]] # important to dup here!
-
else
-
status, headers, body = @app.call (env)
-
end
-
ensure
-
trace.disable if trace
-
-
-
-
-
-
-
-
-
- Extracted source (around line #536 ):
-
-
-
-
-
-
-534
-535
-536
-537
-538
-539
-
-
-
-
- def call(env)
-
req = build_request env
-
app.call req.env
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #279 ):
-
-
-
-
-
-
-277
-278
-279
-280
-281
-282
-
-
-
-
- def call(env)
-
env[Const::PUMA_CONFIG] = @config
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #99 ):
-
-
-
-
-
-
-97
-98
-99
-100
-101
-102
-
-
-
-
- if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call (env)
-
end
-
else
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
-
-
-
-
-
-
-
-
-
- Extracted source (around line #390 ):
-
-
-
-
-
-
-388
-389
-390
-391
-392
-393
-
-
-
-
- t[:with_force_shutdown] = true
-
end
-
yield
-
ensure
-
t[:with_force_shutdown] = false
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- begin
-
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call(env)
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #472 ):
-
-
-
-
-
-
-470
-471
-472
-473
-474
-475
-
-
-
-
- while true
-
@requests_count += 1
-
case handle_request (client, requests + 1)
-
when false
-
break
-
when :async
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- @status = :run
-
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
-
-
if @queue_requests
-
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
-
-
-
-
-
-
-
-
-
- Extracted source (around line #167 ):
-
-
-
-
-
-
-165
-166
-167
-168
-169
-170
-
-
-
-
-
-
begin
-
@out_of_band_pending = true if block.call (work)
-
rescue Exception => e
-
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
-
end
-
-
-
-
-
-
-
-
-
-
-
-
-Rails.root: /app
-
-
-
- Request
- Parameters :
None
-
-
-
-
_csrf_token: "djDlIkXNu5qPodfxLYNNlTZAxaCJk-9sUsXjWESv56U"
-session_id: "b3988fe1be5909fede7f054b6028c72a"
-warden.user.user.key: [[1], "$2a$12$OItHogL1JMQsM3o7sAh5SO"]
-
-
-
-
-
GATEWAY_INTERFACE: "CGI/1.2"
-HTTP_ACCEPT: "text/html, application/xhtml+xml"
-HTTP_ACCEPT_ENCODING: "gzip, deflate"
-HTTP_ACCEPT_LANGUAGE: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
-HTTP_VERSION: "HTTP/1.1"
-ORIGINAL_SCRIPT_NAME: ""
-REMOTE_ADDR: "192.168.80.1"
-SERVER_NAME: "100.75.222.23"
-SERVER_PROTOCOL: "HTTP/1.1"
-
-
-Response
-Headers :
None
-
-
-
-
-
-
diff --git a/Erros/Action Controller_ Exception caught_quando aperta o botĂŁo de relatĂłrio individual.html b/Erros/Action Controller_ Exception caught_quando aperta o botĂŁo de relatĂłrio individual.html
deleted file mode 100644
index d5cc393..0000000
--- a/Erros/Action Controller_ Exception caught_quando aperta o botĂŁo de relatĂłrio individual.html
+++ /dev/null
@@ -1,4148 +0,0 @@
-
-
-
-
-
-
- Action Controller: Exception caught
-
-
-
-
-
-
-
-
- Pundit::NotDefinedError
- in ConsolidacoesController#gerar_pdf_relatorio
-
-
-
-
-
-
unable to find policy `NilClassPolicy` for `nil`
-
-
-
-
-
-
-
-
- Extracted source (around line #70 ):
-
-
-
-
-
-
-68
-69
-70
-71
-72
-73
-
-
-
-
- # @since v0.1.0
-
def policy!
-
policy or raise NotDefinedError, "unable to find policy `#{find(object)}` for `#{object.inspect}`"
-
end
-
-
# @return [String] the name of the key this object would have in a params hash
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- def cached_find(record)
-
policy_cache.fetch(user: user, record: record) do
-
klass = yield policy_finder(record)
-
next unless klass
-
-
model = pundit_model(record)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- def fetch(user:, record:)
-
_ = user
-
@store[record] ||= yield
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #159 ):
-
-
-
-
-
-
-157
-158
-159
-160
-161
-162
-
-
-
-
- # @since v2.3.2
-
def cached_find(record)
-
policy_cache.fetch (user: user, record: record) do
-
klass = yield policy_finder(record)
-
next unless klass
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #95 ):
-
-
-
-
-
-
-93
-94
-95
-96
-97
-98
-
-
-
-
- # @since v2.3.2
-
def policy!(record)
-
cached_find (record, &:policy!)
-
end
-
-
# @!endgroup
-
-
-
-
-
-
-
-
-
- Extracted source (around line #67 ):
-
-
-
-
-
-
-65
-66
-67
-68
-69
-70
-
-
-
-
- policy_class.new(user, record)
-
else
-
policy! (possibly_namespaced_record)
-
end
-
-
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #94 ):
-
-
-
-
-
-
-92
-93
-94
-95
-96
-97
-
-
-
-
- @_pundit_policy_authorized = true
-
-
pundit.authorize (record, query: query, policy_class: policy_class)
-
end
-
-
# Allow this action not to perform authorization.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #116 ):
-
-
-
-
-
-
-114
-115
-116
-117
-118
-119
-
-
-
-
- # GET /consolidacoes/:id/gerar_pdf_relatorio?motorista=X
-
def gerar_pdf_relatorio
-
authorize @consolidacao, :show?
-
motorista = params[:motorista]
-
-
pdf = Pdf::RelatorioMotoristaPdf.new(consolidacao: @consolidacao, motorista: motorista)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #6 ):
-
-
-
-
-
-
-4
-5
-6
-7
-8
-9
-
-
-
-
- module BasicImplicitRender # :nodoc:
-
def send_action(method, *args)
-
ret = super
-
default_render unless performed?
-
ret
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
- # which is *not* necessarily the same as the action name.
-
def process_action(...)
-
send_action (...)
-
end
-
-
# Actually call the method associated with the action. Override
-
-
-
-
-
-
-
-
-
- Extracted source (around line #165 ):
-
-
-
-
-
-
-163
-164
-165
-166
-167
-168
-
-
-
-
- def process_action(*) # :nodoc:
-
self.formats = request.formats.filter_map(&:ref)
-
super
-
end
-
-
def _process_variant(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- def process_action(...)
-
run_callbacks(:process_action) do
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def with_request_id(request_id)
-
old_request_id, self.current_request_id = self.current_request_id, request_id
-
yield
-
ensure
-
self.current_request_id = old_request_id
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #10 ):
-
-
-
-
-
-
-8
-9
-10
-11
-12
-
-
-
-
- private
-
def turbo_tracking_request_id(&block)
-
Turbo.with_request_id (request.headers["X-Turbo-Request-Id"], &block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- previous_renderer = self.renderer
-
self.renderer = renderer
-
yield
-
ensure
-
self.renderer = previous_renderer
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #69 ):
-
-
-
-
-
-
-67
-68
-69
-70
-71
-72
-
-
-
-
- ActiveSupport.on_load(base) do
-
around_action do |controller, action|
-
ActionText::Content.with_renderer (controller, &action)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #258 ):
-
-
-
-
-
-
-256
-257
-258
-259
-260
-261
-
-
-
-
- # <tt>process_action</tt> callbacks around the normal behavior.
-
def process_action(...)
-
run_callbacks (:process_action) do
-
super
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
- private
-
def process_action(*)
-
super
-
rescue Exception => exception
-
request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #74 ):
-
-
-
-
-
-
-72
-73
-74
-75
-76
-77
-
-
-
-
-
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
result
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #73 ):
-
-
-
-
-
-
-71
-72
-73
-74
-75
-76
-
-
-
-
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
-
-
ActiveSupport::Notifications.instrument ("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- def process_action(*)
-
_perform_parameter_wrapping if _wrapper_enabled?
-
super
-
end
-
-
# Returns the wrapper key which will be used to store wrapped parameters.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # and it won't be cleaned up by the method below.
-
ActiveRecord::RuntimeRegistry.reset
-
super
-
end
-
-
def cleanup_view_runtime
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- @_response_body = nil
-
-
process_action (action_name, *args)
-
end
-
ruby2_keywords(:process)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #40 ):
-
-
-
-
-
-
-38
-39
-40
-41
-42
-43
-
-
-
-
- def process(...) # :nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #227 ):
-
-
-
-
-
-
-225
-226
-227
-228
-229
-230
-
-
-
-
- set_request!(request)
-
set_response!(response)
-
process (name)
-
request.commit_flash
-
to_a
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #309 ):
-
-
-
-
-
-
-307
-308
-309
-310
-311
-312
-
-
-
-
- middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
-
else
-
new.dispatch (name, req, res)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #49 ):
-
-
-
-
-
-
-47
-48
-49
-50
-51
-52
-
-
-
-
-
-
def dispatch(controller, action, req, res)
-
controller.dispatch (action, req, res)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- controller = controller req
-
res = controller.make_response! req
-
dispatch (controller, params[:action], req, res)
-
rescue ActionController::RoutingError
-
if @raise_on_name_error
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #51 ):
-
-
-
-
-
-
-49
-50
-51
-52
-53
-54
-
-
-
-
- req.route_uri_pattern = route.path.spec.to_s
-
-
_, headers, _ = response = route.app.serve (req)
-
-
if "pass" == headers[Constants::X_CASCADE]
-
req.script_name = script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
-
}
-
yield [match_data, path_parameters, r]
-
}
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
-
-
def serve(req)
-
find_routes (req) do |match, parameters, route|
-
set_params = req.path_parameters
-
path_info = req.path_info
-
script_name = req.script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #882 ):
-
-
-
-
-
-
-880
-881
-882
-883
-884
-885
-
-
-
-
- req = make_request(env)
-
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
-
@router.serve (req)
-
end
-
-
def recognize_path(path, environment = {})
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
- result = catch(:warden) do
-
env['warden'].on_request
-
@app.call (env)
-
end
-
-
result ||= {}
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
-
-
begin
-
_, _, body = response = @app.call (env)
-
rescue Exception
-
env[RACK_TEMPFILES]&.each(&:close!)
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
status, headers, body = response = @app.call (env)
-
-
if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
-
body = body.to_ary
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- case env[REQUEST_METHOD]
-
when "GET", "HEAD"
-
status, headers, body = response = @app.call (env)
-
-
if status == 200 && fresh?(env, headers)
-
response[0] = 304
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
-
-
def call(env)
-
_, _, body = response = @app.call (env)
-
-
if env[REQUEST_METHOD] == HEAD
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
_, headers, _ = response = @app.call (env)
-
-
return response if policy_present?(headers)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
status, headers, _ = response = @app.call (env)
-
-
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
-
# CSP headers might not match nonces in the cached HTML.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #274 ):
-
-
-
-
-
-
-272
-273
-274
-275
-276
-277
-
-
-
-
- req = make_request env
-
prepare_session(req)
-
status, headers, body = app.call (req.env)
-
res = Rack::Response::Raw.new status, headers
-
commit_session(req, res)
-
[status, headers, body]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #268 ):
-
-
-
-
-
-
-266
-267
-268
-269
-270
-271
-
-
-
-
-
-
def call(env)
-
context (env)
-
end
-
-
def context(env, app = @app)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #689 ):
-
-
-
-
-
-
-687
-688
-689
-690
-691
-692
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
response = @app.call (env)
-
-
if request.have_cookie_jar?
-
cookie_jar = request.cookie_jar
-
-
-
-
-
-
-
-
-
- Extracted source (around line #655 ):
-
-
-
-
-
-
-653
-654
-655
-656
-657
-658
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- error = nil
-
result = run_callbacks :call do
-
@app.call (env)
-
rescue => error
-
end
-
raise error if error
-
-
-
-
-
-
-
-
-
- Extracted source (around line #101 ):
-
-
-
-
-
-
-99
-100
-101
-102
-103
-104
-
-
-
-
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def call(env)
-
error = nil
-
result = run_callbacks :call do
-
@app.call(env)
-
rescue => error
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #16 ):
-
-
-
-
-
-
-14
-15
-16
-17
-18
-19
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
return @app.call (env) unless actionable_request?(request)
-
-
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if headers[Constants::X_CASCADE] == "pass"
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #132 ):
-
-
-
-
-
-
-130
-131
-132
-133
-134
-135
-
-
-
-
-
-
def call_app(env)
-
@app.call (env)
-
rescue => e
-
throw :app_exception, e
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #19 ):
-
-
-
-
-
-
-17
-18
-19
-20
-21
-22
-
-
-
-
- app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app (env) unless request.permitted?
-
-
if id = id_for_repl_session_update(request)
-
return update_repl_session(id, request)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
-
-
def call(env)
-
@app.call (env)
-
rescue Exception => exception
-
request = ActionDispatch::Request.new env
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
-
-
logger.info { started_request_message(request) }
-
status, headers, body = response = @app.call (env)
-
body = ::Rack::BodyProxy.new(body, &handle.method(:finish))
-
-
if response.frozen?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged(*compute_tags(request)) { call_app (request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- def tagged(*tags)
-
pushed_count = tag_stack.push_tags(tags).size
-
yield self
-
ensure
-
pop_tags(pushed_count)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged (*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #241 ):
-
-
-
-
-
-
-239
-240
-241
-242
-243
-244
-
-
-
-
- super(name, *args, **kwargs, &block)
-
elsif loggers.one?
-
loggers.first.send (name, *args, **kwargs, &block)
-
else
-
loggers.map { |logger| logger.send(name, *args, **kwargs, &block) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged (*compute_tags(request)) { call_app(request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- ::Rails.logger.silence { @app.call(env) }
-
else
-
@app.call (env)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #92 ):
-
-
-
-
-
-
-90
-91
-92
-93
-94
-95
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.remote_ip = GetIp.new(req, check_ip, proxies)
-
@app.call (req.env)
-
end
-
-
# The GetIp class exists as a way to defer processing of the request data
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.request_id = make_request_id(req.headers[@header])
-
@app.call (env).tap { |_status, headers, _body| headers[@header] = req.request_id }
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
def method_override(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def call(env)
-
start_time = Utils.clock_time
-
_, headers, _ = response = @app.call (env)
-
-
request_time = Utils.clock_time - start_time
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- response = nil
-
events = @subscriber.collect_events do
-
response = @app.call (env)
-
end
-
-
headers = response[1]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- events = []
-
ActiveSupport::IsolatedExecutionState[KEY] = events
-
yield
-
events
-
ensure
-
ActiveSupport::IsolatedExecutionState.delete(KEY)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- def call(env)
-
response = nil
-
events = @subscriber.collect_events do
-
response = @app.call(env)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- end
-
else
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
-
-
def call(env)
-
@file_handler.attempt(env) || @app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if body.respond_to?(:to_path)
-
case type = variation(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #334 ):
-
-
-
-
-
-
-332
-333
-334
-335
-336
-337
-
-
-
-
- status, headers, body = [200, { Rack::CONTENT_TYPE => 'text/html' }, [blank_page_html.dup]] # important to dup here!
-
else
-
status, headers, body = @app.call (env)
-
end
-
ensure
-
trace.disable if trace
-
-
-
-
-
-
-
-
-
- Extracted source (around line #536 ):
-
-
-
-
-
-
-534
-535
-536
-537
-538
-539
-
-
-
-
- def call(env)
-
req = build_request env
-
app.call req.env
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #279 ):
-
-
-
-
-
-
-277
-278
-279
-280
-281
-282
-
-
-
-
- def call(env)
-
env[Const::PUMA_CONFIG] = @config
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #99 ):
-
-
-
-
-
-
-97
-98
-99
-100
-101
-102
-
-
-
-
- if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call (env)
-
end
-
else
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
-
-
-
-
-
-
-
-
-
- Extracted source (around line #390 ):
-
-
-
-
-
-
-388
-389
-390
-391
-392
-393
-
-
-
-
- t[:with_force_shutdown] = true
-
end
-
yield
-
ensure
-
t[:with_force_shutdown] = false
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- begin
-
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call(env)
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #472 ):
-
-
-
-
-
-
-470
-471
-472
-473
-474
-475
-
-
-
-
- while true
-
@requests_count += 1
-
case handle_request (client, requests + 1)
-
when false
-
break
-
when :async
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- @status = :run
-
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
-
-
if @queue_requests
-
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
-
-
-
-
-
-
-
-
-
- Extracted source (around line #167 ):
-
-
-
-
-
-
-165
-166
-167
-168
-169
-170
-
-
-
-
-
-
begin
-
@out_of_band_pending = true if block.call (work)
-
rescue Exception => e
-
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
-
end
-
-
-
-
-
-
-
-
-
-Rails.root: /app
-
-
-
-
-
-
- Request
- Parameters :
{"motorista"=>"Bruno Corgozinho", "id"=>"3"}
-
-
-
-
-
_csrf_token: "5gp9GSGXtkcowZwum9G8lo4PfrqgU6k5QOTqGibFw2g"
-session_id: "5b30872b26fbe71a6e42fd31083d905e"
-user_return_to: "/"
-warden.user.user.key: [[1], "$2a$12$OItHogL1JMQsM3o7sAh5SO"]
-
-
-
-
-
GATEWAY_INTERFACE: "CGI/1.2"
-HTTP_ACCEPT: "text/html, application/xhtml+xml"
-HTTP_ACCEPT_ENCODING: "gzip, deflate"
-HTTP_ACCEPT_LANGUAGE: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
-HTTP_VERSION: "HTTP/1.1"
-ORIGINAL_SCRIPT_NAME: ""
-REMOTE_ADDR: "192.168.144.1"
-SERVER_NAME: "100.75.222.23"
-SERVER_PROTOCOL: "HTTP/1.1"
-
-
-Response
-Headers :
None
-
-
-
-
-
-
diff --git a/Erros/Action Controller_ Exception caught_quando aperta para modificar os usuario .html b/Erros/Action Controller_ Exception caught_quando aperta para modificar os usuario .html
deleted file mode 100644
index 9129811..0000000
--- a/Erros/Action Controller_ Exception caught_quando aperta para modificar os usuario .html
+++ /dev/null
@@ -1,6176 +0,0 @@
-
-
-
-
-
-
- Action Controller: Exception caught
-
-
-
-
-
-
-
-
- NameError in
- Admin::Usuarios#edit
-
-
-
-
-
- Showing /app/app/views/admin/usuarios/_form.html.erb where line #47 raised:
-
- undefined local variable or method `role_options_for_select' for #<ActionView::Base:0x0000000000fb68>
-
-
-
-
- Extracted source (around line #48 ):
-
-
-
-
-
-
-46
-47
-48
-49
-50
-51
-
-
-
-
- '.freeze; @output_buffer.append=( f.label :role, 'Perfil de acesso', class: 'block text-sm font-medium text-gray-300 mb-1.5' ); @output_buffer.safe_append='
-
'.freeze; @output_buffer.append=( f.select :role,
-
options_for_select(role_options_for_select , usuario.role),
-
{},
-
class: 'w-full px-4 py-3 bg-[#0a0a0a] border border-white/10 rounded-xl text-white
-
focus:outline-none focus:border-[#f97316] focus:ring-1 focus:ring-[#f97316]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #50 ):
-
-
-
-
-
-
-48
-49
-50
-51
-52
-53
-
-
-
-
- value = nil
-
@output_buffer ||= ActionView::OutputBuffer.new
-
buffer = @output_buffer.capture { value = yield(*args) }
-
-
string = if @output_buffer.equal?(value)
-
buffer
-
-
-
-
-
-
-
-
-
- Extracted source (around line #75 ):
-
-
-
-
-
-
-73
-74
-75
-76
-77
-78
-
-
-
-
- new_buffer = +""
-
old_buffer, @raw_buffer = @raw_buffer, new_buffer
-
yield(*args)
-
new_buffer.html_safe
-
ensure
-
@raw_buffer = old_buffer
-
-
-
-
-
-
-
-
-
- Extracted source (around line #50 ):
-
-
-
-
-
-
-48
-49
-50
-51
-52
-53
-
-
-
-
- value = nil
-
@output_buffer ||= ActionView::OutputBuffer.new
-
buffer = @output_buffer.capture { value = yield(*args) }
-
-
string = if @output_buffer.equal?(value)
-
buffer
-
-
-
-
-
-
-
-
-
- Extracted source (around line #773 ):
-
-
-
-
-
-
-771
-772
-773
-774
-775
-776
-
-
-
-
- if block_given?
-
builder = instantiate_builder(scope, model, options)
-
output = capture (builder, &block)
-
options[:multipart] ||= builder.multipart?
-
-
html_options = html_options_for_form_with(url, model, **options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
- <%= render 'shared/flash' %>
-
-
<%= form_with (model: usuario, url: usuario.new_record? ? admin_usuarios_path : admin_usuario_path(usuario),
-
method: usuario.new_record? ? :post : :patch,
-
class: 'bg-[#1a1a1a] rounded-2xl border border-white/5 p-8 space-y-6') do |f| %>
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #263 ):
-
-
-
-
-
-
-261
-262
-263
-264
-265
-266
-
-
-
-
- end
-
else
-
public_send (method, locals, buffer, &block)
-
end
-
ensure
-
@output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
-
-
-
-
-
-
-
-
-
- Extracted source (around line #263 ):
-
-
-
-
-
-
-261
-262
-263
-264
-265
-266
-
-
-
-
- end
-
else
-
public_send (method, locals, buffer, &block)
-
end
-
ensure
-
@output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- nil
-
else
-
result = view._run (method_name, self, locals, OutputBuffer.new, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
-
result.is_a?(OutputBuffer) ? result.to_s : result
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #208 ):
-
-
-
-
-
-
-206
-207
-208
-209
-210
-211
-
-
-
-
- instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #556 ):
-
-
-
-
-
-
-554
-555
-556
-557
-558
-559
-
-
-
-
-
-
def instrument_render_template(&block)
-
ActiveSupport::Notifications.instrument ("!render_template.action_view", instrument_payload, &block)
-
end
-
-
def instrument_payload
-
-
-
-
-
-
-
-
-
- Extracted source (around line #249 ):
-
-
-
-
-
-
-247
-248
-249
-250
-251
-252
-
-
-
-
- # consume this in production. This is only slow if it's being listened to.
-
def render(view, locals, buffer = nil, implicit_locals: [], add_to_stack: true, &block)
-
instrument_render_template do
-
compile!(view)
-
-
if strict_locals? && @strict_local_keys && !implicit_locals.empty?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #252 ):
-
-
-
-
-
-
-250
-251
-252
-253
-254
-255
-
-
-
-
- locals: locals
-
) do |payload|
-
content = template.render (view, locals, add_to_stack: !block) do |*name|
-
view._layout_for(*name, &block)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #246 ):
-
-
-
-
-
-
-244
-245
-246
-247
-248
-249
-
-
-
-
-
-
def render_partial_template(view, locals, template, layout, block)
-
ActiveSupport::Notifications.instrument (
-
"render_partial.action_view",
-
identifier: template.identifier,
-
layout: layout && layout.virtual_path,
-
-
-
-
-
-
-
-
-
- Extracted source (around line #237 ):
-
-
-
-
-
-
-235
-236
-237
-238
-239
-240
-
-
-
-
- end
-
-
render_partial_template (context, @locals, template, layout, block)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #83 ):
-
-
-
-
-
-
-81
-82
-83
-84
-85
-86
-
-
-
-
- # Partial
-
renderer = PartialRenderer.new(@lookup_context, options)
-
renderer.render (partial, context, block)
-
end
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #55 ):
-
-
-
-
-
-
-53
-54
-55
-56
-57
-58
-
-
-
-
- # Direct access to partial rendering.
-
def render_partial(context, options, &block) # :nodoc:
-
render_partial_to_object (context, options, &block).body
-
end
-
-
def cache_hits # :nodoc:
-
-
-
-
-
-
-
-
-
- Extracted source (around line #44 ):
-
-
-
-
-
-
-42
-43
-44
-45
-46
-47
-
-
-
-
- options.render_in(self, &block)
-
else
-
view_renderer.render_partial (self, partial: options, locals: locals, &block)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #2 ):
-
-
-
-
-
-
-1
-2
-
-
-
-
-<%# app/views/usuarios/edit.html.erb %>
-
<%= render 'form', usuario: @usuario %>
-
-
-
-
-
-
-
-
-
- Extracted source (around line #263 ):
-
-
-
-
-
-
-261
-262
-263
-264
-265
-266
-
-
-
-
- end
-
else
-
public_send (method, locals, buffer, &block)
-
end
-
ensure
-
@output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
-
-
-
-
-
-
-
-
-
- Extracted source (around line #263 ):
-
-
-
-
-
-
-261
-262
-263
-264
-265
-266
-
-
-
-
- end
-
else
-
public_send (method, locals, buffer, &block)
-
end
-
ensure
-
@output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- nil
-
else
-
result = view._run (method_name, self, locals, OutputBuffer.new, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block)
-
result.is_a?(OutputBuffer) ? result.to_s : result
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #208 ):
-
-
-
-
-
-
-206
-207
-208
-209
-210
-211
-
-
-
-
- instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #556 ):
-
-
-
-
-
-
-554
-555
-556
-557
-558
-559
-
-
-
-
-
-
def instrument_render_template(&block)
-
ActiveSupport::Notifications.instrument ("!render_template.action_view", instrument_payload, &block)
-
end
-
-
def instrument_payload
-
-
-
-
-
-
-
-
-
- Extracted source (around line #249 ):
-
-
-
-
-
-
-247
-248
-249
-250
-251
-252
-
-
-
-
- # consume this in production. This is only slow if it's being listened to.
-
def render(view, locals, buffer = nil, implicit_locals: [], add_to_stack: true, &block)
-
instrument_render_template do
-
compile!(view)
-
-
if strict_locals? && @strict_local_keys && !implicit_locals.empty?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #66 ):
-
-
-
-
-
-
-64
-65
-66
-67
-68
-69
-
-
-
-
- locals: locals
-
) do
-
template.render (view, locals) { |*name| view._layout_for(*name) }
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #60 ):
-
-
-
-
-
-
-58
-59
-60
-61
-62
-63
-
-
-
-
- def render_template(view, template, layout_name, locals)
-
render_with_layout(view, template, layout_name, locals) do |layout|
-
ActiveSupport::Notifications.instrument (
-
"render_template.action_view",
-
identifier: template.identifier,
-
layout: layout && layout.virtual_path,
-
-
-
-
-
-
-
-
-
- Extracted source (around line #76 ):
-
-
-
-
-
-
-74
-75
-76
-77
-78
-79
-
-
-
-
- body = if layout
-
ActiveSupport::Notifications.instrument("render_layout.action_view", identifier: layout.identifier) do
-
view.view_flow.set(:layout, yield(layout))
-
layout.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #75 ):
-
-
-
-
-
-
-73
-74
-75
-76
-77
-78
-
-
-
-
-
-
body = if layout
-
ActiveSupport::Notifications.instrument ("render_layout.action_view", identifier: layout.identifier) do
-
view.view_flow.set(:layout, yield(layout))
-
layout.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- # supplied as well.
-
def render_template(view, template, layout_name, locals)
-
render_with_layout (view, template, layout_name, locals) do |layout|
-
ActiveSupport::Notifications.instrument(
-
"render_template.action_view",
-
identifier: template.identifier,
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- prepend_formats(template.format)
-
-
render_template (context, template, options[:layout], options[:locals] || {})
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #63 ):
-
-
-
-
-
-
-61
-62
-63
-64
-65
-66
-
-
-
-
-
-
def render_template_to_object(context, options) # :nodoc:
-
TemplateRenderer.new(@lookup_context).render (context, options)
-
end
-
-
def render_partial_to_object(context, options, &block) # :nodoc:
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- render_partial_to_object(context, options)
-
else
-
render_template_to_object (context, options)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #135 ):
-
-
-
-
-
-
-133
-134
-135
-136
-137
-138
-
-
-
-
-
-
rendered_template = context.in_rendering_context(options) do |renderer|
-
renderer.render_to_object (context, options)
-
end
-
-
rendered_format = rendered_template.format || lookup_context.formats.first
-
-
-
-
-
-
-
-
-
- Extracted source (around line #290 ):
-
-
-
-
-
-
-288
-289
-290
-291
-292
-293
-
-
-
-
- end
-
-
yield @view_renderer
-
ensure
-
@view_renderer = old_view_renderer
-
@lookup_context = old_lookup_context
-
-
-
-
-
-
-
-
-
- Extracted source (around line #134 ):
-
-
-
-
-
-
-132
-133
-134
-135
-136
-137
-
-
-
-
- lookup_context.variants = variant if variant
-
-
rendered_template = context.in_rendering_context (options) do |renderer|
-
renderer.render_to_object(context, options)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #256 ):
-
-
-
-
-
-
-254
-255
-256
-257
-258
-259
-
-
-
-
- Body.new view_renderer.render_body(view_context, options)
-
else
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- def render_to_body(options = {})
-
_process_options(options)
-
_render_template (options)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #158 ):
-
-
-
-
-
-
-156
-157
-158
-159
-160
-161
-
-
-
-
-
-
def render_to_body(options = {}) # :nodoc:
-
super || _render_in_priorities(options) || " "
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- # <tt>ActionView::Rendering.render_to_body</tt>, if present.
-
def render_to_body(options)
-
_render_to_body_with_renderer(options) || super
-
end
-
-
def _render_to_body_with_renderer(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #27 ):
-
-
-
-
-
-
-25
-26
-27
-28
-29
-30
-
-
-
-
- def render(*args, &block)
-
options = _normalize_render(*args, &block)
-
rendered_body = render_to_body (options)
-
if options[:html]
-
_set_html_content_type
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def render(*args)
-
raise ::AbstractController::DoubleRenderError if response_body
-
super
-
end
-
-
# Similar to #render, but only returns the rendered template as a string,
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- render_output = nil
-
self.view_runtime = cleanup_view_runtime do
-
Benchmark.ms { render_output = super }
-
end
-
render_output
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #324 ):
-
-
-
-
-
-
-322
-323
-324
-325
-326
-327
-
-
-
-
- def realtime # :yield:
-
r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
-
yield
-
Process.clock_gettime(Process::CLOCK_MONOTONIC) - r0
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-
-
-
-
- # # => 0.074
-
def ms(&block)
-
1000 * realtime (&block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- render_output = nil
-
self.view_runtime = cleanup_view_runtime do
-
Benchmark.ms { render_output = super }
-
end
-
render_output
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- # end
-
def cleanup_view_runtime # :doc:
-
yield
-
end
-
-
# Every time after an action is processed, this method is invoked
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- db_rt_before_render = ActiveRecord::RuntimeRegistry.reset
-
self.db_runtime = (db_runtime || 0) + db_rt_before_render
-
runtime = super
-
queries_rt = ActiveRecord::RuntimeRegistry.sql_runtime - ActiveRecord::RuntimeRegistry.async_sql_runtime
-
db_rt_after_render = ActiveRecord::RuntimeRegistry.reset
-
self.db_runtime += db_rt_after_render
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def render(*)
-
render_output = nil
-
self.view_runtime = cleanup_view_runtime do
-
Benchmark.ms { render_output = super }
-
end
-
render_output
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- def default_render
-
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
-
render
-
elsif any_templates?(action_name.to_s, _prefixes)
-
message = "#{self.class.name}\##{action_name} is missing a template " \
-
"for this request format and variant.\n" \
-
-
-
-
-
-
-
-
-
- Extracted source (around line #7 ):
-
-
-
-
-
-
-5
-6
-7
-8
-9
-10
-
-
-
-
- def send_action(method, *args)
-
ret = super
-
default_render unless performed?
-
ret
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
- # which is *not* necessarily the same as the action name.
-
def process_action(...)
-
send_action (...)
-
end
-
-
# Actually call the method associated with the action. Override
-
-
-
-
-
-
-
-
-
- Extracted source (around line #165 ):
-
-
-
-
-
-
-163
-164
-165
-166
-167
-168
-
-
-
-
- def process_action(*) # :nodoc:
-
self.formats = request.formats.filter_map(&:ref)
-
super
-
end
-
-
def _process_variant(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- def process_action(...)
-
run_callbacks(:process_action) do
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def with_request_id(request_id)
-
old_request_id, self.current_request_id = self.current_request_id, request_id
-
yield
-
ensure
-
self.current_request_id = old_request_id
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #10 ):
-
-
-
-
-
-
-8
-9
-10
-11
-12
-
-
-
-
- private
-
def turbo_tracking_request_id(&block)
-
Turbo.with_request_id (request.headers["X-Turbo-Request-Id"], &block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- previous_renderer = self.renderer
-
self.renderer = renderer
-
yield
-
ensure
-
self.renderer = previous_renderer
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #69 ):
-
-
-
-
-
-
-67
-68
-69
-70
-71
-72
-
-
-
-
- ActiveSupport.on_load(base) do
-
around_action do |controller, action|
-
ActionText::Content.with_renderer (controller, &action)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #258 ):
-
-
-
-
-
-
-256
-257
-258
-259
-260
-261
-
-
-
-
- # <tt>process_action</tt> callbacks around the normal behavior.
-
def process_action(...)
-
run_callbacks (:process_action) do
-
super
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
- private
-
def process_action(*)
-
super
-
rescue Exception => exception
-
request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #74 ):
-
-
-
-
-
-
-72
-73
-74
-75
-76
-77
-
-
-
-
-
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
result
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #73 ):
-
-
-
-
-
-
-71
-72
-73
-74
-75
-76
-
-
-
-
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
-
-
ActiveSupport::Notifications.instrument ("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- def process_action(*)
-
_perform_parameter_wrapping if _wrapper_enabled?
-
super
-
end
-
-
# Returns the wrapper key which will be used to store wrapped parameters.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # and it won't be cleaned up by the method below.
-
ActiveRecord::RuntimeRegistry.reset
-
super
-
end
-
-
def cleanup_view_runtime
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- @_response_body = nil
-
-
process_action (action_name, *args)
-
end
-
ruby2_keywords(:process)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #40 ):
-
-
-
-
-
-
-38
-39
-40
-41
-42
-43
-
-
-
-
- def process(...) # :nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #227 ):
-
-
-
-
-
-
-225
-226
-227
-228
-229
-230
-
-
-
-
- set_request!(request)
-
set_response!(response)
-
process (name)
-
request.commit_flash
-
to_a
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #309 ):
-
-
-
-
-
-
-307
-308
-309
-310
-311
-312
-
-
-
-
- middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
-
else
-
new.dispatch (name, req, res)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #49 ):
-
-
-
-
-
-
-47
-48
-49
-50
-51
-52
-
-
-
-
-
-
def dispatch(controller, action, req, res)
-
controller.dispatch (action, req, res)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- controller = controller req
-
res = controller.make_response! req
-
dispatch (controller, params[:action], req, res)
-
rescue ActionController::RoutingError
-
if @raise_on_name_error
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #51 ):
-
-
-
-
-
-
-49
-50
-51
-52
-53
-54
-
-
-
-
- req.route_uri_pattern = route.path.spec.to_s
-
-
_, headers, _ = response = route.app.serve (req)
-
-
if "pass" == headers[Constants::X_CASCADE]
-
req.script_name = script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
-
}
-
yield [match_data, path_parameters, r]
-
}
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
-
-
def serve(req)
-
find_routes (req) do |match, parameters, route|
-
set_params = req.path_parameters
-
path_info = req.path_info
-
script_name = req.script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #882 ):
-
-
-
-
-
-
-880
-881
-882
-883
-884
-885
-
-
-
-
- req = make_request(env)
-
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
-
@router.serve (req)
-
end
-
-
def recognize_path(path, environment = {})
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
- result = catch(:warden) do
-
env['warden'].on_request
-
@app.call (env)
-
end
-
-
result ||= {}
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
-
-
begin
-
_, _, body = response = @app.call (env)
-
rescue Exception
-
env[RACK_TEMPFILES]&.each(&:close!)
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
status, headers, body = response = @app.call (env)
-
-
if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
-
body = body.to_ary
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- case env[REQUEST_METHOD]
-
when "GET", "HEAD"
-
status, headers, body = response = @app.call (env)
-
-
if status == 200 && fresh?(env, headers)
-
response[0] = 304
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
-
-
def call(env)
-
_, _, body = response = @app.call (env)
-
-
if env[REQUEST_METHOD] == HEAD
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
_, headers, _ = response = @app.call (env)
-
-
return response if policy_present?(headers)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
status, headers, _ = response = @app.call (env)
-
-
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
-
# CSP headers might not match nonces in the cached HTML.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #274 ):
-
-
-
-
-
-
-272
-273
-274
-275
-276
-277
-
-
-
-
- req = make_request env
-
prepare_session(req)
-
status, headers, body = app.call (req.env)
-
res = Rack::Response::Raw.new status, headers
-
commit_session(req, res)
-
[status, headers, body]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #268 ):
-
-
-
-
-
-
-266
-267
-268
-269
-270
-271
-
-
-
-
-
-
def call(env)
-
context (env)
-
end
-
-
def context(env, app = @app)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #689 ):
-
-
-
-
-
-
-687
-688
-689
-690
-691
-692
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
response = @app.call (env)
-
-
if request.have_cookie_jar?
-
cookie_jar = request.cookie_jar
-
-
-
-
-
-
-
-
-
- Extracted source (around line #655 ):
-
-
-
-
-
-
-653
-654
-655
-656
-657
-658
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- error = nil
-
result = run_callbacks :call do
-
@app.call (env)
-
rescue => error
-
end
-
raise error if error
-
-
-
-
-
-
-
-
-
- Extracted source (around line #101 ):
-
-
-
-
-
-
-99
-100
-101
-102
-103
-104
-
-
-
-
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def call(env)
-
error = nil
-
result = run_callbacks :call do
-
@app.call(env)
-
rescue => error
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #16 ):
-
-
-
-
-
-
-14
-15
-16
-17
-18
-19
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
return @app.call (env) unless actionable_request?(request)
-
-
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if headers[Constants::X_CASCADE] == "pass"
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #132 ):
-
-
-
-
-
-
-130
-131
-132
-133
-134
-135
-
-
-
-
-
-
def call_app(env)
-
@app.call (env)
-
rescue => e
-
throw :app_exception, e
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #19 ):
-
-
-
-
-
-
-17
-18
-19
-20
-21
-22
-
-
-
-
- app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app (env) unless request.permitted?
-
-
if id = id_for_repl_session_update(request)
-
return update_repl_session(id, request)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
-
-
def call(env)
-
@app.call (env)
-
rescue Exception => exception
-
request = ActionDispatch::Request.new env
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
-
-
logger.info { started_request_message(request) }
-
status, headers, body = response = @app.call (env)
-
body = ::Rack::BodyProxy.new(body, &handle.method(:finish))
-
-
if response.frozen?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged(*compute_tags(request)) { call_app (request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- def tagged(*tags)
-
pushed_count = tag_stack.push_tags(tags).size
-
yield self
-
ensure
-
pop_tags(pushed_count)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged (*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #241 ):
-
-
-
-
-
-
-239
-240
-241
-242
-243
-244
-
-
-
-
- super(name, *args, **kwargs, &block)
-
elsif loggers.one?
-
loggers.first.send (name, *args, **kwargs, &block)
-
else
-
loggers.map { |logger| logger.send(name, *args, **kwargs, &block) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged (*compute_tags(request)) { call_app(request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- ::Rails.logger.silence { @app.call(env) }
-
else
-
@app.call (env)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #92 ):
-
-
-
-
-
-
-90
-91
-92
-93
-94
-95
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.remote_ip = GetIp.new(req, check_ip, proxies)
-
@app.call (req.env)
-
end
-
-
# The GetIp class exists as a way to defer processing of the request data
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.request_id = make_request_id(req.headers[@header])
-
@app.call (env).tap { |_status, headers, _body| headers[@header] = req.request_id }
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
def method_override(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def call(env)
-
start_time = Utils.clock_time
-
_, headers, _ = response = @app.call (env)
-
-
request_time = Utils.clock_time - start_time
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- response = nil
-
events = @subscriber.collect_events do
-
response = @app.call (env)
-
end
-
-
headers = response[1]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- events = []
-
ActiveSupport::IsolatedExecutionState[KEY] = events
-
yield
-
events
-
ensure
-
ActiveSupport::IsolatedExecutionState.delete(KEY)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- def call(env)
-
response = nil
-
events = @subscriber.collect_events do
-
response = @app.call(env)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- end
-
else
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
-
-
def call(env)
-
@file_handler.attempt(env) || @app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if body.respond_to?(:to_path)
-
case type = variation(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #334 ):
-
-
-
-
-
-
-332
-333
-334
-335
-336
-337
-
-
-
-
- status, headers, body = [200, { Rack::CONTENT_TYPE => 'text/html' }, [blank_page_html.dup]] # important to dup here!
-
else
-
status, headers, body = @app.call (env)
-
end
-
ensure
-
trace.disable if trace
-
-
-
-
-
-
-
-
-
- Extracted source (around line #536 ):
-
-
-
-
-
-
-534
-535
-536
-537
-538
-539
-
-
-
-
- def call(env)
-
req = build_request env
-
app.call req.env
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #279 ):
-
-
-
-
-
-
-277
-278
-279
-280
-281
-282
-
-
-
-
- def call(env)
-
env[Const::PUMA_CONFIG] = @config
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #99 ):
-
-
-
-
-
-
-97
-98
-99
-100
-101
-102
-
-
-
-
- if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call (env)
-
end
-
else
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
-
-
-
-
-
-
-
-
-
- Extracted source (around line #390 ):
-
-
-
-
-
-
-388
-389
-390
-391
-392
-393
-
-
-
-
- t[:with_force_shutdown] = true
-
end
-
yield
-
ensure
-
t[:with_force_shutdown] = false
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- begin
-
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call(env)
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #472 ):
-
-
-
-
-
-
-470
-471
-472
-473
-474
-475
-
-
-
-
- while true
-
@requests_count += 1
-
case handle_request (client, requests + 1)
-
when false
-
break
-
when :async
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- @status = :run
-
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
-
-
if @queue_requests
-
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
-
-
-
-
-
-
-
-
-
- Extracted source (around line #167 ):
-
-
-
-
-
-
-165
-166
-167
-168
-169
-170
-
-
-
-
-
-
begin
-
@out_of_band_pending = true if block.call (work)
-
rescue Exception => e
-
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
-
end
-
-
-
-
-
-
-
-
-
- Trace of template inclusion: #<ActionView::Template app/views/admin/usuarios/edit.html.erb locals=[]>
-
-
-Rails.root: /app
-
-
-
- Request
- Parameters :
{"id"=>"1"}
-
-
-
-
-
_csrf_token: "5gp9GSGXtkcowZwum9G8lo4PfrqgU6k5QOTqGibFw2g"
-session_id: "5b30872b26fbe71a6e42fd31083d905e"
-user_return_to: "/"
-warden.user.user.key: [[1], "$2a$12$OItHogL1JMQsM3o7sAh5SO"]
-
-
-
-
-
GATEWAY_INTERFACE: "CGI/1.2"
-HTTP_ACCEPT: "text/html, application/xhtml+xml"
-HTTP_ACCEPT_ENCODING: "gzip, deflate"
-HTTP_ACCEPT_LANGUAGE: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
-HTTP_VERSION: "HTTP/1.1"
-ORIGINAL_SCRIPT_NAME: ""
-REMOTE_ADDR: "192.168.144.1"
-SERVER_NAME: "100.75.222.23"
-SERVER_PROTOCOL: "HTTP/1.1"
-
-
-Response
-Headers :
None
-
-
-
-
-
-
diff --git a/Erros/Action Controller_ Exception caughtq_quando aperta para sair .html b/Erros/Action Controller_ Exception caughtq_quando aperta para sair .html
deleted file mode 100644
index 9a90010..0000000
--- a/Erros/Action Controller_ Exception caughtq_quando aperta para sair .html
+++ /dev/null
@@ -1,2130 +0,0 @@
-
-
-
-
-
-
- Action Controller: Exception caught
-
-
-
-
-
-
-
-
- No route matches [GET] "/auth/logout"
-
-
-Rails.root: /app
-
-
-
-
-
- Routes
-
-
-
- Routes match in priority from top to bottom
-
-
-
-
-
-
- Helper
- (Path /
- Url )
-
- HTTP Verb
- Path
- Controller#Action
- Source Location
-
-
-
-
-
-
-
-
-
-
-
-
- new_user_session_path
-
-
- GET
-
-
- /auth/login(.:format)
-
-
- users/sessions#new
-
-
- devise (5.0.4) lib/devise/rails/routes.rb:378
-
-
-
-
- user_session_path
-
-
- POST
-
-
- /auth/login(.:format)
-
-
- users/sessions#create
-
-
- devise (5.0.4) lib/devise/rails/routes.rb:379
-
-
-
-
- destroy_user_session_path
-
-
- DELETE
-
-
- /auth/logout(.:format)
-
-
- users/sessions#destroy
-
-
- devise (5.0.4) lib/devise/rails/routes.rb:380
-
-
-
-
- new_user_password_path
-
-
- GET
-
-
- /auth/senha/new(.:format)
-
-
- devise/passwords#new
-
-
- devise (5.0.4) lib/devise/rails/routes.rb:385
-
-
-
-
- edit_user_password_path
-
-
- GET
-
-
- /auth/senha/edit(.:format)
-
-
- devise/passwords#edit
-
-
- devise (5.0.4) lib/devise/rails/routes.rb:385
-
-
-
-
- user_password_path
-
-
- PATCH
-
-
- /auth/senha(.:format)
-
-
- devise/passwords#update
-
-
- devise (5.0.4) lib/devise/rails/routes.rb:385
-
-
-
-
-
-
- PUT
-
-
- /auth/senha(.:format)
-
-
- devise/passwords#update
-
-
- devise (5.0.4) lib/devise/rails/routes.rb:385
-
-
-
-
-
-
- POST
-
-
- /auth/senha(.:format)
-
-
- devise/passwords#create
-
-
- devise (5.0.4) lib/devise/rails/routes.rb:385
-
-
-
-
- root_path
-
-
- GET
-
-
- /
-
-
- dashboard#index
-
-
- /app/config/routes.rb:13
-
-
-
-
- dashboard_path
-
-
- GET
-
-
- /dashboard(.:format)
-
-
- dashboard#index
-
-
- /app/config/routes.rb:16
-
-
-
-
- motorista_dashboard_path
-
-
- GET
-
-
- /motorista(.:format)
-
-
- motorista/dashboard#index
-
-
- /app/config/routes.rb:19
-
-
-
-
- motorista_login_path
-
-
- GET
-
-
- /motorista/login(.:format)
-
-
- motorista/sessoes#new
-
-
- /app/config/routes.rb:22
-
-
-
-
-
-
- POST
-
-
- /motorista/login(.:format)
-
-
- motorista/sessoes#create
-
-
- /app/config/routes.rb:23
-
-
-
-
- motorista_acesso_qr_path
-
-
- GET
-
-
- /motorista/acesso/:token(.:format)
-
-
- motorista/sessoes#acesso_qr
-
-
- /app/config/routes.rb:24
-
-
-
-
- wizard_consolidacao_path
-
-
- GET
-
-
- /consolidacoes/:id/wizard(.:format)
-
-
- consolidacoes#wizard
-
-
- /app/config/routes.rb:29
-
-
-
-
- finalizar_consolidacao_path
-
-
- POST
-
-
- /consolidacoes/:id/finalizar(.:format)
-
-
- consolidacoes#finalizar
-
-
- /app/config/routes.rb:30
-
-
-
-
- arquivar_consolidacao_path
-
-
- POST
-
-
- /consolidacoes/:id/arquivar(.:format)
-
-
- consolidacoes#arquivar
-
-
- /app/config/routes.rb:31
-
-
-
-
- preview_holerite_consolidacao_path
-
-
- GET
-
-
- /consolidacoes/:id/preview_holerite(.:format)
-
-
- consolidacoes#preview_holerite
-
-
- /app/config/routes.rb:32
-
-
-
-
- gerar_pdf_relatorio_consolidacao_path
-
-
- GET
-
-
- /consolidacoes/:id/gerar_pdf_relatorio(.:format)
-
-
- consolidacoes#gerar_pdf_relatorio
-
-
- /app/config/routes.rb:33
-
-
-
-
- gerar_pdf_holerite_consolidacao_path
-
-
- GET
-
-
- /consolidacoes/:id/gerar_pdf_holerite(.:format)
-
-
- consolidacoes#gerar_pdf_holerite
-
-
- /app/config/routes.rb:34
-
-
-
-
- validar_consolidacao_consolidacao_entregas_path
-
-
- GET
-
-
- /consolidacoes/:consolidacao_id/consolidacao_entregas/validar(.:format)
-
-
- consolidacao_entregas#validar
-
-
- /app/config/routes.rb:39
-
-
-
-
- classificar_consolidacao_consolidacao_entregas_path
-
-
- POST
-
-
- /consolidacoes/:consolidacao_id/consolidacao_entregas/classificar(.:format)
-
-
- consolidacao_entregas#classificar
-
-
- /app/config/routes.rb:40
-
-
-
-
- classificar_em_massa_consolidacao_consolidacao_entregas_path
-
-
- POST
-
-
- /consolidacoes/:consolidacao_id/consolidacao_entregas/classificar_em_massa(.:format)
-
-
- consolidacao_entregas#classificar_em_massa
-
-
- /app/config/routes.rb:41
-
-
-
-
- revisar_consolidacao_consolidacao_entregas_path
-
-
- GET
-
-
- /consolidacoes/:consolidacao_id/consolidacao_entregas/revisar(.:format)
-
-
- consolidacao_entregas#revisar
-
-
- /app/config/routes.rb:42
-
-
-
-
- consolidacoes_path
-
-
- GET
-
-
- /consolidacoes(.:format)
-
-
- consolidacoes#index
-
-
- /app/config/routes.rb:27
-
-
-
-
-
-
- POST
-
-
- /consolidacoes(.:format)
-
-
- consolidacoes#create
-
-
- /app/config/routes.rb:27
-
-
-
-
- new_consolidacao_path
-
-
- GET
-
-
- /consolidacoes/new(.:format)
-
-
- consolidacoes#new
-
-
- /app/config/routes.rb:27
-
-
-
-
- edit_consolidacao_path
-
-
- GET
-
-
- /consolidacoes/:id/edit(.:format)
-
-
- consolidacoes#edit
-
-
- /app/config/routes.rb:27
-
-
-
-
- consolidacao_path
-
-
- GET
-
-
- /consolidacoes/:id(.:format)
-
-
- consolidacoes#show
-
-
- /app/config/routes.rb:27
-
-
-
-
-
-
- PATCH
-
-
- /consolidacoes/:id(.:format)
-
-
- consolidacoes#update
-
-
- /app/config/routes.rb:27
-
-
-
-
-
-
- PUT
-
-
- /consolidacoes/:id(.:format)
-
-
- consolidacoes#update
-
-
- /app/config/routes.rb:27
-
-
-
-
-
-
- DELETE
-
-
- /consolidacoes/:id(.:format)
-
-
- consolidacoes#destroy
-
-
- /app/config/routes.rb:27
-
-
-
-
- toggle_ativo_admin_usuario_path
-
-
- PATCH
-
-
- /admin/usuarios/:id/toggle_ativo(.:format)
-
-
- admin/usuarios#toggle_ativo
-
-
- /app/config/routes.rb:50
-
-
-
-
- admin_usuarios_path
-
-
- GET
-
-
- /admin/usuarios(.:format)
-
-
- admin/usuarios#index
-
-
- /app/config/routes.rb:49
-
-
-
-
-
-
- POST
-
-
- /admin/usuarios(.:format)
-
-
- admin/usuarios#create
-
-
- /app/config/routes.rb:49
-
-
-
-
- new_admin_usuario_path
-
-
- GET
-
-
- /admin/usuarios/new(.:format)
-
-
- admin/usuarios#new
-
-
- /app/config/routes.rb:49
-
-
-
-
- edit_admin_usuario_path
-
-
- GET
-
-
- /admin/usuarios/:id/edit(.:format)
-
-
- admin/usuarios#edit
-
-
- /app/config/routes.rb:49
-
-
-
-
- admin_usuario_path
-
-
- PATCH
-
-
- /admin/usuarios/:id(.:format)
-
-
- admin/usuarios#update
-
-
- /app/config/routes.rb:49
-
-
-
-
-
-
- PUT
-
-
- /admin/usuarios/:id(.:format)
-
-
- admin/usuarios#update
-
-
- /app/config/routes.rb:49
-
-
-
-
-
-
- DELETE
-
-
- /admin/usuarios/:id(.:format)
-
-
- admin/usuarios#destroy
-
-
- /app/config/routes.rb:49
-
-
-
-
- toggle_tema_admin_configuracoes_path
-
-
- POST
-
-
- /admin/configuracoes/toggle_tema(.:format)
-
-
- admin/configuracoes#toggle_tema
-
-
- /app/config/routes.rb:53
-
-
-
-
- admin_configuracoes_path
-
-
- GET
-
-
- /admin/configuracoes(.:format)
-
-
- admin/configuracoes#index
-
-
- /app/config/routes.rb:52
-
-
-
-
- edit_admin_configuracao_path
-
-
- GET
-
-
- /admin/configuracoes/:id/edit(.:format)
-
-
- admin/configuracoes#edit
-
-
- /app/config/routes.rb:52
-
-
-
-
- admin_configuracao_path
-
-
- PATCH
-
-
- /admin/configuracoes/:id(.:format)
-
-
- admin/configuracoes#update
-
-
- /app/config/routes.rb:52
-
-
-
-
-
-
- PUT
-
-
- /admin/configuracoes/:id(.:format)
-
-
- admin/configuracoes#update
-
-
- /app/config/routes.rb:52
-
-
-
-
- admin_auditoria_logs_path
-
-
- GET
-
-
- /admin/auditoria_logs(.:format)
-
-
- admin/auditoria_logs#index
-
-
- /app/config/routes.rb:55
-
-
-
-
- api_v1_dashboard_metricas_path
-
-
- GET
-
-
- /api/v1/dashboard/metricas(.:format)
-
-
- api/v1/dashboard#metricas
-
-
- /app/config/routes.rb:61
-
-
-
-
- health_path
-
-
- GET
-
-
- /health(.:format)
-
-
- Inline handler (Proc/Lambda)
-
-
- /app/config/routes.rb:66
-
-
-
-
- turbo_recede_historical_location_path
-
-
- GET
-
-
- /recede_historical_location(.:format)
-
-
- turbo/native/navigation#recede
-
-
- turbo-rails (2.0.23) config/routes.rb:2
-
-
-
-
- turbo_resume_historical_location_path
-
-
- GET
-
-
- /resume_historical_location(.:format)
-
-
- turbo/native/navigation#resume
-
-
- turbo-rails (2.0.23) config/routes.rb:3
-
-
-
-
- turbo_refresh_historical_location_path
-
-
- GET
-
-
- /refresh_historical_location(.:format)
-
-
- turbo/native/navigation#refresh
-
-
- turbo-rails (2.0.23) config/routes.rb:4
-
-
-
-
- rails_postmark_inbound_emails_path
-
-
- POST
-
-
- /rails/action_mailbox/postmark/inbound_emails(.:format)
-
-
- action_mailbox/ingresses/postmark/inbound_emails#create
-
-
- actionmailbox (7.1.6) config/routes.rb:5
-
-
-
-
- rails_relay_inbound_emails_path
-
-
- POST
-
-
- /rails/action_mailbox/relay/inbound_emails(.:format)
-
-
- action_mailbox/ingresses/relay/inbound_emails#create
-
-
- actionmailbox (7.1.6) config/routes.rb:6
-
-
-
-
- rails_sendgrid_inbound_emails_path
-
-
- POST
-
-
- /rails/action_mailbox/sendgrid/inbound_emails(.:format)
-
-
- action_mailbox/ingresses/sendgrid/inbound_emails#create
-
-
- actionmailbox (7.1.6) config/routes.rb:7
-
-
-
-
- rails_mandrill_inbound_health_check_path
-
-
- GET
-
-
- /rails/action_mailbox/mandrill/inbound_emails(.:format)
-
-
- action_mailbox/ingresses/mandrill/inbound_emails#health_check
-
-
- actionmailbox (7.1.6) config/routes.rb:10
-
-
-
-
- rails_mandrill_inbound_emails_path
-
-
- POST
-
-
- /rails/action_mailbox/mandrill/inbound_emails(.:format)
-
-
- action_mailbox/ingresses/mandrill/inbound_emails#create
-
-
- actionmailbox (7.1.6) config/routes.rb:11
-
-
-
-
- rails_mailgun_inbound_emails_path
-
-
- POST
-
-
- /rails/action_mailbox/mailgun/inbound_emails/mime(.:format)
-
-
- action_mailbox/ingresses/mailgun/inbound_emails#create
-
-
- actionmailbox (7.1.6) config/routes.rb:14
-
-
-
-
- rails_conductor_inbound_emails_path
-
-
- GET
-
-
- /rails/conductor/action_mailbox/inbound_emails(.:format)
-
-
- rails/conductor/action_mailbox/inbound_emails#index
-
-
- actionmailbox (7.1.6) config/routes.rb:19
-
-
-
-
-
-
- POST
-
-
- /rails/conductor/action_mailbox/inbound_emails(.:format)
-
-
- rails/conductor/action_mailbox/inbound_emails#create
-
-
- actionmailbox (7.1.6) config/routes.rb:19
-
-
-
-
- new_rails_conductor_inbound_email_path
-
-
- GET
-
-
- /rails/conductor/action_mailbox/inbound_emails/new(.:format)
-
-
- rails/conductor/action_mailbox/inbound_emails#new
-
-
- actionmailbox (7.1.6) config/routes.rb:19
-
-
-
-
- rails_conductor_inbound_email_path
-
-
- GET
-
-
- /rails/conductor/action_mailbox/inbound_emails/:id(.:format)
-
-
- rails/conductor/action_mailbox/inbound_emails#show
-
-
- actionmailbox (7.1.6) config/routes.rb:19
-
-
-
-
- new_rails_conductor_inbound_email_source_path
-
-
- GET
-
-
- /rails/conductor/action_mailbox/inbound_emails/sources/new(.:format)
-
-
- rails/conductor/action_mailbox/inbound_emails/sources#new
-
-
- actionmailbox (7.1.6) config/routes.rb:20
-
-
-
-
- rails_conductor_inbound_email_sources_path
-
-
- POST
-
-
- /rails/conductor/action_mailbox/inbound_emails/sources(.:format)
-
-
- rails/conductor/action_mailbox/inbound_emails/sources#create
-
-
- actionmailbox (7.1.6) config/routes.rb:21
-
-
-
-
- rails_conductor_inbound_email_reroute_path
-
-
- POST
-
-
- /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format)
-
-
- rails/conductor/action_mailbox/reroutes#create
-
-
- actionmailbox (7.1.6) config/routes.rb:23
-
-
-
-
- rails_conductor_inbound_email_incinerate_path
-
-
- POST
-
-
- /rails/conductor/action_mailbox/:inbound_email_id/incinerate(.:format)
-
-
- rails/conductor/action_mailbox/incinerates#create
-
-
- actionmailbox (7.1.6) config/routes.rb:24
-
-
-
-
- rails_service_blob_path
-
-
- GET
-
-
- /rails/active_storage/blobs/redirect/:signed_id/*filename(.:format)
-
-
- active_storage/blobs/redirect#show
-
-
- activestorage (7.1.6) config/routes.rb:5
-
-
-
-
- rails_service_blob_proxy_path
-
-
- GET
-
-
- /rails/active_storage/blobs/proxy/:signed_id/*filename(.:format)
-
-
- active_storage/blobs/proxy#show
-
-
- activestorage (7.1.6) config/routes.rb:6
-
-
-
-
-
-
- GET
-
-
- /rails/active_storage/blobs/:signed_id/*filename(.:format)
-
-
- active_storage/blobs/redirect#show
-
-
- activestorage (7.1.6) config/routes.rb:7
-
-
-
-
- rails_blob_representation_path
-
-
- GET
-
-
- /rails/active_storage/representations/redirect/:signed_blob_id/:variation_key/*filename(.:format)
-
-
- active_storage/representations/redirect#show
-
-
- activestorage (7.1.6) config/routes.rb:9
-
-
-
-
- rails_blob_representation_proxy_path
-
-
- GET
-
-
- /rails/active_storage/representations/proxy/:signed_blob_id/:variation_key/*filename(.:format)
-
-
- active_storage/representations/proxy#show
-
-
- activestorage (7.1.6) config/routes.rb:10
-
-
-
-
-
-
- GET
-
-
- /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format)
-
-
- active_storage/representations/redirect#show
-
-
- activestorage (7.1.6) config/routes.rb:11
-
-
-
-
- rails_disk_service_path
-
-
- GET
-
-
- /rails/active_storage/disk/:encoded_key/*filename(.:format)
-
-
- active_storage/disk#show
-
-
- activestorage (7.1.6) config/routes.rb:13
-
-
-
-
- update_rails_disk_service_path
-
-
- PUT
-
-
- /rails/active_storage/disk/:encoded_token(.:format)
-
-
- active_storage/disk#update
-
-
- activestorage (7.1.6) config/routes.rb:14
-
-
-
-
- rails_direct_uploads_path
-
-
- POST
-
-
- /rails/active_storage/direct_uploads(.:format)
-
-
- active_storage/direct_uploads#create
-
-
- activestorage (7.1.6) config/routes.rb:15
-
-
-
-
-
-
-
-
-
- Request
- Parameters :
None
-
-
-
-
_csrf_token: "CDiW_RJyJcAlCPyrikFuKYkfdzBC-OW6YRsZDeWj9VQ"
-session_id: "ac99c889d2e6f9700a182c290873487b"
-user_return_to: "/"
-warden.user.user.key: [[1], "$2a$12$OItHogL1JMQsM3o7sAh5SO"]
-
-
-
-
-
GATEWAY_INTERFACE: "CGI/1.2"
-HTTP_ACCEPT: "text/html, application/xhtml+xml"
-HTTP_ACCEPT_ENCODING: "gzip, deflate"
-HTTP_ACCEPT_LANGUAGE: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
-HTTP_VERSION: "HTTP/1.1"
-ORIGINAL_SCRIPT_NAME: ""
-REMOTE_ADDR: "172.31.0.1"
-SERVER_NAME: "100.75.222.23"
-SERVER_PROTOCOL: "HTTP/1.1"
-
-
-Response
-Headers :
None
-
-
-
-
-
-
diff --git a/Erros/Action Controller_ Exception caughtquando aperta o botçao de gerar holerite.html b/Erros/Action Controller_ Exception caughtquando aperta o botçao de gerar holerite.html
deleted file mode 100644
index 58bb3d2..0000000
--- a/Erros/Action Controller_ Exception caughtquando aperta o botçao de gerar holerite.html
+++ /dev/null
@@ -1,4148 +0,0 @@
-
-
-
-
-
-
- Action Controller: Exception caught
-
-
-
-
-
-
-
-
- Pundit::NotDefinedError
- in ConsolidacoesController#gerar_pdf_holerite
-
-
-
-
-
-
unable to find policy `NilClassPolicy` for `nil`
-
-
-
-
-
-
-
-
- Extracted source (around line #70 ):
-
-
-
-
-
-
-68
-69
-70
-71
-72
-73
-
-
-
-
- # @since v0.1.0
-
def policy!
-
policy or raise NotDefinedError, "unable to find policy `#{find(object)}` for `#{object.inspect}`"
-
end
-
-
# @return [String] the name of the key this object would have in a params hash
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- def cached_find(record)
-
policy_cache.fetch(user: user, record: record) do
-
klass = yield policy_finder(record)
-
next unless klass
-
-
model = pundit_model(record)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- def fetch(user:, record:)
-
_ = user
-
@store[record] ||= yield
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #159 ):
-
-
-
-
-
-
-157
-158
-159
-160
-161
-162
-
-
-
-
- # @since v2.3.2
-
def cached_find(record)
-
policy_cache.fetch (user: user, record: record) do
-
klass = yield policy_finder(record)
-
next unless klass
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #95 ):
-
-
-
-
-
-
-93
-94
-95
-96
-97
-98
-
-
-
-
- # @since v2.3.2
-
def policy!(record)
-
cached_find (record, &:policy!)
-
end
-
-
# @!endgroup
-
-
-
-
-
-
-
-
-
- Extracted source (around line #67 ):
-
-
-
-
-
-
-65
-66
-67
-68
-69
-70
-
-
-
-
- policy_class.new(user, record)
-
else
-
policy! (possibly_namespaced_record)
-
end
-
-
raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #94 ):
-
-
-
-
-
-
-92
-93
-94
-95
-96
-97
-
-
-
-
- @_pundit_policy_authorized = true
-
-
pundit.authorize (record, query: query, policy_class: policy_class)
-
end
-
-
# Allow this action not to perform authorization.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- # GET /consolidacoes/:id/gerar_pdf_holerite?motorista=X
-
def gerar_pdf_holerite
-
authorize @consolidacao, :show?
-
motorista = params[:motorista]
-
user_mot = User.motorista.find_by('LOWER(nome) = ?', motorista.to_s.downcase)
-
user_mot&.regenerar_login_token! if user_mot&.login_token.blank?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #6 ):
-
-
-
-
-
-
-4
-5
-6
-7
-8
-9
-
-
-
-
- module BasicImplicitRender # :nodoc:
-
def send_action(method, *args)
-
ret = super
-
default_render unless performed?
-
ret
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #224 ):
-
-
-
-
-
-
-222
-223
-224
-225
-226
-227
-
-
-
-
- # which is *not* necessarily the same as the action name.
-
def process_action(...)
-
send_action (...)
-
end
-
-
# Actually call the method associated with the action. Override
-
-
-
-
-
-
-
-
-
- Extracted source (around line #165 ):
-
-
-
-
-
-
-163
-164
-165
-166
-167
-168
-
-
-
-
- def process_action(*) # :nodoc:
-
self.formats = request.formats.filter_map(&:ref)
-
super
-
end
-
-
def _process_variant(options)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #259 ):
-
-
-
-
-
-
-257
-258
-259
-260
-261
-262
-
-
-
-
- def process_action(...)
-
run_callbacks(:process_action) do
-
super
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #121 ):
-
-
-
-
-
-
-119
-120
-121
-122
-123
-124
-
-
-
-
- current.invoke_before(env)
-
if current.final?
-
env.value = !env.halted && (!block_given? || yield)
-
elsif current.skip?(env)
-
(skipped ||= []) << current
-
next_sequence = next_sequence.nested
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def with_request_id(request_id)
-
old_request_id, self.current_request_id = self.current_request_id, request_id
-
yield
-
ensure
-
self.current_request_id = old_request_id
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #10 ):
-
-
-
-
-
-
-8
-9
-10
-11
-12
-
-
-
-
- private
-
def turbo_tracking_request_id(&block)
-
Turbo.with_request_id (request.headers["X-Turbo-Request-Id"], &block)
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #23 ):
-
-
-
-
-
-
-21
-22
-23
-24
-25
-26
-
-
-
-
- previous_renderer = self.renderer
-
self.renderer = renderer
-
yield
-
ensure
-
self.renderer = previous_renderer
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #69 ):
-
-
-
-
-
-
-67
-68
-69
-70
-71
-72
-
-
-
-
- ActiveSupport.on_load(base) do
-
around_action do |controller, action|
-
ActionText::Content.with_renderer (controller, &action)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #130 ):
-
-
-
-
-
-
-128
-129
-130
-131
-132
-133
-
-
-
-
- begin
-
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
-
target.send (method, *arguments, &block)
-
ensure
-
next_sequence = current
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #141 ):
-
-
-
-
-
-
-139
-140
-141
-142
-143
-144
-
-
-
-
- end
-
-
invoke_sequence.call
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #258 ):
-
-
-
-
-
-
-256
-257
-258
-259
-260
-261
-
-
-
-
- # <tt>process_action</tt> callbacks around the normal behavior.
-
def process_action(...)
-
run_callbacks (:process_action) do
-
super
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
- private
-
def process_action(*)
-
super
-
rescue Exception => exception
-
request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #74 ):
-
-
-
-
-
-
-72
-73
-74
-75
-76
-77
-
-
-
-
-
-
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
result
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument(name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- handle.start
-
begin
-
yield payload if block_given?
-
rescue Exception => e
-
payload[:exception] = [e.class.name, e.message]
-
payload[:exception_object] = e
-
-
-
-
-
-
-
-
-
- Extracted source (around line #206 ):
-
-
-
-
-
-
-204
-205
-206
-207
-208
-209
-
-
-
-
- def instrument(name, payload = {})
-
if notifier.listening?(name)
-
instrumenter.instrument (name, payload) { yield payload if block_given? }
-
else
-
yield payload if block_given?
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #73 ):
-
-
-
-
-
-
-71
-72
-73
-74
-75
-76
-
-
-
-
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
-
-
ActiveSupport::Notifications.instrument ("process_action.action_controller", raw_payload) do |payload|
-
result = super
-
payload[:response] = response
-
payload[:status] = response.status
-
-
-
-
-
-
-
-
-
- Extracted source (around line #261 ):
-
-
-
-
-
-
-259
-260
-261
-262
-263
-264
-
-
-
-
- def process_action(*)
-
_perform_parameter_wrapping if _wrapper_enabled?
-
super
-
end
-
-
# Returns the wrapper key which will be used to store wrapped parameters.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- # and it won't be cleaned up by the method below.
-
ActiveRecord::RuntimeRegistry.reset
-
super
-
end
-
-
def cleanup_view_runtime
-
-
-
-
-
-
-
-
-
- Extracted source (around line #160 ):
-
-
-
-
-
-
-158
-159
-160
-161
-162
-163
-
-
-
-
- @_response_body = nil
-
-
process_action (action_name, *args)
-
end
-
ruby2_keywords(:process)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #40 ):
-
-
-
-
-
-
-38
-39
-40
-41
-42
-43
-
-
-
-
- def process(...) # :nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #227 ):
-
-
-
-
-
-
-225
-226
-227
-228
-229
-230
-
-
-
-
- set_request!(request)
-
set_response!(response)
-
process (name)
-
request.commit_flash
-
to_a
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #309 ):
-
-
-
-
-
-
-307
-308
-309
-310
-311
-312
-
-
-
-
- middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
-
else
-
new.dispatch (name, req, res)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #49 ):
-
-
-
-
-
-
-47
-48
-49
-50
-51
-52
-
-
-
-
-
-
def dispatch(controller, action, req, res)
-
controller.dispatch (action, req, res)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
- controller = controller req
-
res = controller.make_response! req
-
dispatch (controller, params[:action], req, res)
-
rescue ActionController::RoutingError
-
if @raise_on_name_error
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #51 ):
-
-
-
-
-
-
-49
-50
-51
-52
-53
-54
-
-
-
-
- req.route_uri_pattern = route.path.spec.to_s
-
-
_, headers, _ = response = route.app.serve (req)
-
-
if "pass" == headers[Constants::X_CASCADE]
-
req.script_name = script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
- path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
-
}
-
yield [match_data, path_parameters, r]
-
}
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #124 ):
-
-
-
-
-
-
-122
-123
-124
-125
-126
-127
-
-
-
-
- routes.sort_by!(&:precedence)
-
-
routes.each { |r|
-
match_data = r.path.match(path_info)
-
path_parameters = {}
-
match_data.names.each_with_index { |name, i|
-
-
-
-
-
-
-
-
-
- Extracted source (around line #32 ):
-
-
-
-
-
-
-30
-31
-32
-33
-34
-35
-
-
-
-
-
-
def serve(req)
-
find_routes (req) do |match, parameters, route|
-
set_params = req.path_parameters
-
path_info = req.path_info
-
script_name = req.script_name
-
-
-
-
-
-
-
-
-
- Extracted source (around line #882 ):
-
-
-
-
-
-
-880
-881
-882
-883
-884
-885
-
-
-
-
- req = make_request(env)
-
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
-
@router.serve (req)
-
end
-
-
def recognize_path(path, environment = {})
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
- result = catch(:warden) do
-
env['warden'].on_request
-
@app.call (env)
-
end
-
-
result ||= {}
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #34 ):
-
-
-
-
-
-
-32
-33
-34
-35
-36
-37
-
-
-
-
-
-
env['warden'] = Proxy.new(env, self)
-
result = catch (:warden) do
-
env['warden'].on_request
-
@app.call(env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #20 ):
-
-
-
-
-
-
-18
-19
-20
-21
-22
-23
-
-
-
-
-
-
begin
-
_, _, body = response = @app.call (env)
-
rescue Exception
-
env[RACK_TEMPFILES]&.each(&:close!)
-
raise
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
status, headers, body = response = @app.call (env)
-
-
if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
-
body = body.to_ary
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
- case env[REQUEST_METHOD]
-
when "GET", "HEAD"
-
status, headers, body = response = @app.call (env)
-
-
if status == 200 && fresh?(env, headers)
-
response[0] = 304
-
-
-
-
-
-
-
-
-
- Extracted source (around line #15 ):
-
-
-
-
-
-
-13
-14
-15
-16
-17
-18
-
-
-
-
-
-
def call(env)
-
_, _, body = response = @app.call (env)
-
-
if env[REQUEST_METHOD] == HEAD
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
_, headers, _ = response = @app.call (env)
-
-
return response if policy_present?(headers)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #36 ):
-
-
-
-
-
-
-34
-35
-36
-37
-38
-39
-
-
-
-
-
-
def call(env)
-
status, headers, _ = response = @app.call (env)
-
-
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
-
# CSP headers might not match nonces in the cached HTML.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #274 ):
-
-
-
-
-
-
-272
-273
-274
-275
-276
-277
-
-
-
-
- req = make_request env
-
prepare_session(req)
-
status, headers, body = app.call (req.env)
-
res = Rack::Response::Raw.new status, headers
-
commit_session(req, res)
-
[status, headers, body]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #268 ):
-
-
-
-
-
-
-266
-267
-268
-269
-270
-271
-
-
-
-
-
-
def call(env)
-
context (env)
-
end
-
-
def context(env, app = @app)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #689 ):
-
-
-
-
-
-
-687
-688
-689
-690
-691
-692
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
response = @app.call (env)
-
-
if request.have_cookie_jar?
-
cookie_jar = request.cookie_jar
-
-
-
-
-
-
-
-
-
- Extracted source (around line #655 ):
-
-
-
-
-
-
-653
-654
-655
-656
-657
-658
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
- error = nil
-
result = run_callbacks :call do
-
@app.call (env)
-
rescue => error
-
end
-
raise error if error
-
-
-
-
-
-
-
-
-
- Extracted source (around line #101 ):
-
-
-
-
-
-
-99
-100
-101
-102
-103
-104
-
-
-
-
-
-
if callbacks.empty?
-
yield if block_given?
-
else
-
env = Filters::Environment.new(self, false, nil)
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- def call(env)
-
error = nil
-
result = run_callbacks :call do
-
@app.call(env)
-
rescue => error
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #16 ):
-
-
-
-
-
-
-14
-15
-16
-17
-18
-19
-
-
-
-
- def call(env)
-
request = ActionDispatch::Request.new(env)
-
return @app.call (env) unless actionable_request?(request)
-
-
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #29 ):
-
-
-
-
-
-
-27
-28
-29
-30
-31
-32
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if headers[Constants::X_CASCADE] == "pass"
-
body.close if body.respond_to?(:close)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #132 ):
-
-
-
-
-
-
-130
-131
-132
-133
-134
-135
-
-
-
-
-
-
def call_app(env)
-
@app.call (env)
-
rescue => e
-
throw :app_exception, e
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #19 ):
-
-
-
-
-
-
-17
-18
-19
-20
-21
-22
-
-
-
-
- app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app (env) unless request.permitted?
-
-
if id = id_for_repl_session_update(request)
-
return update_repl_session(id, request)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #17 ):
-
-
-
-
-
-
-15
-16
-17
-18
-19
-20
-
-
-
-
-
-
def call(env)
-
app_exception = catch :app_exception do
-
request = create_regular_or_whiny_request(env)
-
return call_app(env) unless request.permitted?
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #31 ):
-
-
-
-
-
-
-29
-30
-31
-32
-33
-34
-
-
-
-
-
-
def call(env)
-
@app.call (env)
-
rescue Exception => exception
-
request = ActionDispatch::Request.new env
-
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
-
-
logger.info { started_request_message(request) }
-
status, headers, body = response = @app.call (env)
-
body = ::Rack::BodyProxy.new(body, &handle.method(:finish))
-
-
if response.frozen?
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged(*compute_tags(request)) { call_app (request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged(*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #39 ):
-
-
-
-
-
-
-37
-38
-39
-40
-41
-42
-
-
-
-
- def tagged(*tags)
-
pushed_count = tag_stack.push_tags(tags).size
-
yield self
-
ensure
-
pop_tags(pushed_count)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #139 ):
-
-
-
-
-
-
-137
-138
-139
-140
-141
-142
-
-
-
-
- def tagged(*tags)
-
if block_given?
-
formatter.tagged (*tags) { yield self }
-
else
-
logger = ActiveSupport::TaggedLogging.new(self)
-
logger.formatter.extend LocalTagStorage
-
-
-
-
-
-
-
-
-
- Extracted source (around line #241 ):
-
-
-
-
-
-
-239
-240
-241
-242
-243
-244
-
-
-
-
- super(name, *args, **kwargs, &block)
-
elsif loggers.one?
-
loggers.first.send (name, *args, **kwargs, &block)
-
else
-
loggers.map { |logger| logger.send(name, *args, **kwargs, &block) }
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
-
-
if logger.respond_to?(:tagged)
-
logger.tagged (*compute_tags(request)) { call_app(request, env) }
-
else
-
call_app(request, env)
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #11 ):
-
-
-
-
-
-
-9
-10
-11
-12
-13
-14
-
-
-
-
- ::Rails.logger.silence { @app.call(env) }
-
else
-
@app.call (env)
-
end
-
end
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #92 ):
-
-
-
-
-
-
-90
-91
-92
-93
-94
-95
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.remote_ip = GetIp.new(req, check_ip, proxies)
-
@app.call (req.env)
-
end
-
-
# The GetIp class exists as a way to defer processing of the request data
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- req = ActionDispatch::Request.new env
-
req.request_id = make_request_id(req.headers[@header])
-
@app.call (env).tap { |_status, headers, _body| headers[@header] = req.request_id }
-
end
-
-
private
-
-
-
-
-
-
-
-
-
- Extracted source (around line #28 ):
-
-
-
-
-
-
-26
-27
-28
-29
-30
-31
-
-
-
-
- end
-
-
@app.call (env)
-
end
-
-
def method_override(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- def call(env)
-
start_time = Utils.clock_time
-
_, headers, _ = response = @app.call (env)
-
-
request_time = Utils.clock_time - start_time
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #59 ):
-
-
-
-
-
-
-57
-58
-59
-60
-61
-62
-
-
-
-
- response = nil
-
events = @subscriber.collect_events do
-
response = @app.call (env)
-
end
-
-
headers = response[1]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #24 ):
-
-
-
-
-
-
-22
-23
-24
-25
-26
-27
-
-
-
-
- events = []
-
ActiveSupport::IsolatedExecutionState[KEY] = events
-
yield
-
events
-
ensure
-
ActiveSupport::IsolatedExecutionState.delete(KEY)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #58 ):
-
-
-
-
-
-
-56
-57
-58
-59
-60
-61
-
-
-
-
- def call(env)
-
response = nil
-
events = @subscriber.collect_events do
-
response = @app.call(env)
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #14 ):
-
-
-
-
-
-
-12
-13
-14
-15
-16
-17
-
-
-
-
- state = @executor.run!(reset: true)
-
begin
-
response = @app.call (env)
-
-
if env["action_dispatch.report_exception"]
-
error = env["action_dispatch.exception"]
-
-
-
-
-
-
-
-
-
- Extracted source (around line #37 ):
-
-
-
-
-
-
-35
-36
-37
-38
-39
-40
-
-
-
-
- end
-
else
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #25 ):
-
-
-
-
-
-
-23
-24
-25
-26
-27
-28
-
-
-
-
-
-
def call(env)
-
@file_handler.attempt(env) || @app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #131 ):
-
-
-
-
-
-
-129
-130
-131
-132
-133
-134
-
-
-
-
-
-
def call(env)
-
_, headers, body = response = @app.call (env)
-
-
if body.respond_to?(:to_path)
-
case type = variation(env)
-
-
-
-
-
-
-
-
-
- Extracted source (around line #334 ):
-
-
-
-
-
-
-332
-333
-334
-335
-336
-337
-
-
-
-
- status, headers, body = [200, { Rack::CONTENT_TYPE => 'text/html' }, [blank_page_html.dup]] # important to dup here!
-
else
-
status, headers, body = @app.call (env)
-
end
-
ensure
-
trace.disable if trace
-
-
-
-
-
-
-
-
-
- Extracted source (around line #536 ):
-
-
-
-
-
-
-534
-535
-536
-537
-538
-539
-
-
-
-
- def call(env)
-
req = build_request env
-
app.call req.env
-
end
-
-
# Defines additional Rack env configuration that is added on each call.
-
-
-
-
-
-
-
-
-
- Extracted source (around line #279 ):
-
-
-
-
-
-
-277
-278
-279
-280
-281
-282
-
-
-
-
- def call(env)
-
env[Const::PUMA_CONFIG] = @config
-
@app.call (env)
-
end
-
end
-
-
-
-
-
-
-
-
-
-
- Extracted source (around line #99 ):
-
-
-
-
-
-
-97
-98
-99
-100
-101
-102
-
-
-
-
- if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call (env)
-
end
-
else
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
-
-
-
-
-
-
-
-
-
- Extracted source (around line #390 ):
-
-
-
-
-
-
-388
-389
-390
-391
-392
-393
-
-
-
-
- t[:with_force_shutdown] = true
-
end
-
yield
-
ensure
-
t[:with_force_shutdown] = false
-
end
-
-
-
-
-
-
-
-
-
- Extracted source (around line #98 ):
-
-
-
-
-
-
-96
-97
-98
-99
-100
-101
-
-
-
-
- begin
-
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
-
status, headers, app_body = @thread_pool.with_force_shutdown do
-
@app.call(env)
-
end
-
else
-
-
-
-
-
-
-
-
-
- Extracted source (around line #472 ):
-
-
-
-
-
-
-470
-471
-472
-473
-474
-475
-
-
-
-
- while true
-
@requests_count += 1
-
case handle_request (client, requests + 1)
-
when false
-
break
-
when :async
-
-
-
-
-
-
-
-
-
- Extracted source (around line #254 ):
-
-
-
-
-
-
-252
-253
-254
-255
-256
-257
-
-
-
-
- @status = :run
-
-
@thread_pool = ThreadPool.new(thread_name, options) { |client| process_client client }
-
-
if @queue_requests
-
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
-
-
-
-
-
-
-
-
-
- Extracted source (around line #167 ):
-
-
-
-
-
-
-165
-166
-167
-168
-169
-170
-
-
-
-
-
-
begin
-
@out_of_band_pending = true if block.call (work)
-
rescue Exception => e
-
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
-
end
-
-
-
-
-
-
-
-
-
-Rails.root: /app
-
-
-
-
-
-
- Request
- Parameters :
{"motorista"=>"Bruno Corgozinho", "id"=>"3"}
-
-
-
-
-
_csrf_token: "5gp9GSGXtkcowZwum9G8lo4PfrqgU6k5QOTqGibFw2g"
-session_id: "5b30872b26fbe71a6e42fd31083d905e"
-user_return_to: "/"
-warden.user.user.key: [[1], "$2a$12$OItHogL1JMQsM3o7sAh5SO"]
-
-
-
-
-
GATEWAY_INTERFACE: "CGI/1.2"
-HTTP_ACCEPT: "text/html, application/xhtml+xml"
-HTTP_ACCEPT_ENCODING: "gzip, deflate"
-HTTP_ACCEPT_LANGUAGE: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"
-HTTP_VERSION: "HTTP/1.1"
-ORIGINAL_SCRIPT_NAME: ""
-REMOTE_ADDR: "192.168.144.1"
-SERVER_NAME: "100.75.222.23"
-SERVER_PROTOCOL: "HTTP/1.1"
-
-
-Response
-Headers :
None
-
-
-
-
-
-
diff --git a/Erros/Action Controller_ Exception caughtquando termina de criar o usuario motorista.html b/Erros/Action Controller_ Exception caughtquando termina de criar o usuario motorista.html
deleted file mode 100644
index 92d39ef..0000000
--- a/Erros/Action Controller_ Exception caughtquando termina de criar o usuario motorista.html
+++ /dev/null
@@ -1,276 +0,0 @@
-
-
-
-
-
-
-
- Reem LogĂstica
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Novo UsuĂĄrio
-
-
- Preencha os dados para criar um novo acesso
-
-
-
-
-
-
-
-
-
-
-
- Nome completo
-
-
-
-
- Perfil de acesso
- Administrador
-Gerente
-Operador
-Motorista
-
-
-
-
-
UsuĂĄrio ativo
-
UsuĂĄrios inativos nĂŁo conseguem fazer login
-
-
-
-
-
-
-
-
-
-
-
Credenciais de acesso
-
-
-
-
-
-
PIN de acesso
-
-
PIN (4 dĂgitos)
-
-
Deve ser Ășnico entre todos os motoristas
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Erros/Imagem colada (2).png b/Erros/Imagem colada (2).png
deleted file mode 100644
index f584995d64d2ca9f6fc46661d9fe0047289b1c6e91b3457722f098912b4a588c..0000000000000000000000000000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 135171
zcmce-Wn7fo_Xi3V(kjvd#{i_eOGHpfrMsCyx;s@sTBUPPM5SXSh8Cm;5b5q5x&{~q
z?w;fEoZsiZzW+NfjHzcoYp=c5H`atdQ+-TIL`Q^&he!JK$s=_-V+s&!}Ura1e{%f4H6o-HryS~BZ9Skmk|KeMJeJl(SQ
zoW9v_5y5q6Y)3iwo1jUiQfxUalJ?Tn9
zB+Yn@*e}6m3r2~16wQsLuhZpYL>y4A0^A4MkN$a_H7SQ+OwTPl^&l>_mFK*OLJ{xd
zRU4fLX%jMgjBVb$=yFpblKJcIqlA7Fhp~Y&Igt*UW3ULDGzhg7@w2V3EvEhe0Es+e8G{=ir1y^ptyxTOMo
zUR$x~2C-%~&q=#6D$?8p+BI#|X@CtZ&&_$^+rUhAz$ZY!0vl|3c(&7KC=ZQ0P
z*Dq;VWy&G>_=**x^^R*G484fuB;kS8UWOcNCFv2nIoB9%VL0sC8VASPX#GlpzgFmzX)>W+Nna#)-6{{$hw>
zZ`$7VybEwY>4-lo;G2Iw=^i4RviIe#pul1tOS5Iux*=59j6cN)g=C>fRtXY2*FaJv
zixJn?y0;0EG}9)ImW;YwHWfA~D&h3>N~B1oWeiTK?Bwr#s~r4qph;))Uh=oqNG4bv
zcJlVx@HJn5pF&E(0-q&9u8*hjrPv*AbHZjgJ)b~|jk|L1?eRAT7G)tR-y50hWv&TK
zJQUcyWaSW
zyTA)4j3a#Fn0B
z5_oEYzaTv+n+DluP}bPN!H~X{A=BNHg=~lKby7BOFx&mvvO6trMlCT#m$$ibTuT+(h$9#F2?v#y9$(#^S{?i>XK?M>-8cG_2){MAt}>oO}odjzecAu{v6y-qOMlDEW?3@?@~zNASDd7tXLX(Q*Kd0#NZYW
zRozntM+5t%c)RK|hnfFe#RXh+-B2t=>a>jZkQccwoYj#19(g@J#U;enBNPSCE<-vo
z&Bz!eSpE5gdhKYLeKN1vIgBu^<250DXvYE{Ca10n$tRqGe;9=9VEjfKYF5nIHGWyc
zBJtmFJ44~4>+`W!Y|5;n3EZ0GwGTB2`EyL(JaJxHV|%fX+W4zvRE{q#m`mqD=$?E}
zl~oT+H54B*9EyN8YwEK6xs4XXO-|e%%kei~PhKQhaWp+IzRPnlwh?*UzfROik|yP>
z@!z`x@x^3Qg7eXzICuqIE<+alTHBu@w+na$=oNZY?GTWbn}1S_t;YqbCpO7#?=ZcDCUwm$O1g5T=O-t6#PTr7|Bmv-
zixe!&;)V%UV#QI!)FY+#w(pES{ZpXcJTY6y*!flW5$RgMUpdOdaGP$1?v!cwwH035
z{5+dM{pmq1_n-TC*R@2aj7aVWhH+(T`AB(F%*Dob6uePV7&DcIk3^o6I>pPUrKUOyAR_Zlzo
z{G)Wj1uH0YujAam8*1X$T#->DmgW*uP{7%fF4a|kF)aD0nWq-p7x=Wn>BBP&Cf~4f
zVZ?Lhng+DTYHw&mWPc~}L|p#wjXWb_*C}`qTf{J#$YoglGA{gSQ7T`eiZ&!YFN?Bw8A>=g7`)aS-BXi^;2K{#Ib9-v
zU3cY7w9#VUJB^F2N=)Ng$`@Dl@DQeoT+BPf-`*LYFY;j;ZjPsQ9a*?azBXLg{r0Sm
zkjEqj&c?=RQXaF&F?_A={I3R;w3f-rXr!l155}Xg7^CmcH0(Pa$edQWQOg!%GBC{c
z_j%gYd0y$E!flMSTpYXKc!u6vKUSgf*`aM-xlWP2V3=34476o3|Gy
zydm8Nk8Etj-BAMzXEly@IXEK2Wlo!dUp>A`$ky=_-+yJGJ{oo8U31qMiF6x>dQ?PD
zFLVASc6@Vov5U{=$dpdx>+;x{IfTWPn$v7DwEG@RT;;(7ymDJ@=mOWp+-LQM!DyS#
zC^2Mu&9xxMGxTjLI2w(qoYok&63@ql23T_rW6
zrD1J*fp=u-s2%v%ip_ADo&|V0Y1$zoYL@wam(-IKTPmca0p&d%s}#5iYha2)a!*OS
z9e-cJQ@NfmpG)nR9S)f!kw;0H9}H7?EBUI13Z~n^T1v!U-eIbAPs5#s9L2<$fmMO^
zk@B!D78MpgHrlw3*Ml~*KRO=OA04vVXcsGnZ0{QPvZ1Ai9Va>~7;19Wb1ys%l|`27
zN}sM1Px)_HJyZ(A+n(lWu-L0$IKmwFeU+6mk7+4$?h+=uC5cS4fU(}czn!<$KZPYQ
zB=>?DUpLM#0Ksc)H+y9mY#@u~k_ka1a*R5f8QDyScpDRqZ}xh5%G{P&Cea@uQ6!2wAuG#%dF2;mfY)
z(++Lv`C~Sio=M5oqwFaN+>C$m=EO$yRBzh6ah7JsjoRbyP(ZzcR17Nn>y#dEA}j>k
z=H|SePseFOWd}YNlFblG@TS&@jo(&3B2m&4_HuJix4Se+nzNo}Uy91R;
zjmtLFR)Dg(I)6X3d%1_N*?Y-^e7C8p@=dgi>U`)y)wyHk#gOx}n9Cz8#A;{d9R(N|Zy!CJNi_7Q?Q
zTipgS=9jO;JkKPbc6`CRPeIj*ajYd8skxRZ?Ml_mdvzPl$|aRS!Ah|=De|CSfz*2J
z>v!hfdGn&Or{^3rZyT~OOK!}=>d9CZfB;eipp%J%1<$*=G})12)Q8@P_seUFnHf!n
z!#SC-!f{Uo`xOFoq}%08cbma1S#0(C7)AF|p+uD{a@%i9!>mC>#>8Y;SR()?w@haL
zRV!Qk6$TA)D%yl6Vf-oo4%{)~{g&3>jcoy9AxwQ*>9MmYCY=JMTFOz8E)d&ry>fKU
zaS4LBnE%E2)>i46pAGx6h6W)SkqcM8?(OqYx=vAcExeZ(T&txM35nh7$jL|+Kv)bp
zEf6)X-Ce}ZoKw(NH
zssZ`did(AM!-XEz(Gd?de)Tdh4~bw;bD{oT6+)8q71GJ!Sgtv>mKtqPvA3btfd8{l
zg8*dGn-=rgK#B%dY*R>=xV9|&B2f$|cn=pNC?&@}<>bIU&+$`*UMyTMxcQlGL$PR4
zF>it>Kg%ENIGbIK%=~Dul8nsdQn7$=mCv)W#Ac>TVKerv$cC1SDe4}W?D^u
zms^+cdh_Fxr2+}%UQhuiAWk^faP>NdnIG1~)$nY*)yikSi=}T5-
z;pgYB?iP0D8pzj(%W8a}-v2AP!Hbq*yu3+7r&2g5S75o!dXxy1+7^0?^9}9WS~P(}
z_6F65-(%(8=2y(G%nrL)WjUfYu+BzRbSMFm<|y;D;ND!i7l5mtt&sgIQ8?Vo#43<
z^g>`6aV9-cYtJeh9NglfpsXVaSumcfgMO~G-{W5%TVxjDQB4)h+oBr?*xA)8THK@N
zS*CRX+#JlM)UV*8FX?@lnqeP!j#)o0Dox<$=O>y7z!XWHN?Ki!QA-Ja{y;3N
zxR~eFEpi{fCPL#tG)0BobVtW0OEO0BCY|YXTT5o`v}PJe-^nb>G(pe6a7x2Z1v0x0?6samk7XpBotpitXy#(+p!mvDLm0EHopi;DOn=J4Jp&8MJhS(;8~I$WmV
z7*=rJc2a(xZi62s35zo9ay35w%yPxMT9@u{l-E_{{uy72k*@WN6Zu2ao+f5a{fd{9
zzUWyLs%thmJ6VOL+I{ocz!$HlDs)3o&@O)Q7@D4rGvSm7JHHD?;uRK)sRsqoW3NZ;
z=86m+`%DH*2g?=hd{7{zf1MN5hy!{zR9VXW7U{^Cq*}wykAG#WfXY{8v0rirktkyT
zj(5W^SIV0=+l?y8mh-m)At1t%J6omk_pd8Y>|6qk!)8&j=7wT3btfx1@~&
zHjDsNHCWjyG_fR)VN&}`NGbzr5|BYOyV%~|?u5NNTGy2WRoL-3odWs07NAq8p8K<{
zA@vV@;*4owfUZUe74@>jg-=OUs~6eP4wnI<^|R)&&JvIDrhbL>sN(6VUme+wrl#al
zKBsckBC=1ph^c5%8?Aksa{=DhOrY^u`F@vn!K_ykp`X8>^|MMc5d4o%vrpvxeUb)p
z{poMSy+5`%5RYL^&(iK*tKHtu2C&EZsQo#3X5o?fu8>$_hvnRqghKykd4a@2!{+aD
zk42MG6`z^nQ8sn;7i##9RoZLKJlJko)GyNf9jU<_3kk&}GP9o3mRsU3@q4VQ>P5;l
z_WDB5XVXtkPhG0V$(Vk@KpDXG&ZTudKR{U*|$g}z(A8ex$9Mx#3HTGLTh{3*OC%3i93tbgPJk#GEFAL0p#>)hnR
zz;;%-_nqc<89YxMPdGsQ5mT=t@Yy*3{O28g`n%->88{qkV)ZQW4+Vc$D+y`gEHOLN
z)caQrnS0C1sp0>xmU231Sfkv<#VJs<)AQ%f9-SGs3aP+dn)oa0*#8x8-_7-GpB&m)
zBl-V){dIznf3>MQWJF#6Y6Q1#{;Tf15xxKKBCk6?`%h%PLX+^MN0-X8$he&8w#W
zk1k-^?buQp2Q6Cm{-cV#xry*=E@NMO_`lB?CPoJ~Ne`0#t2F%ItmgmxcK=TLNr>C4o)$wv+WbaZ^^aj4%{U=XdH1>9OK0vxt_gDH}jXTs7epOpmd(PQhR#hcx
z?0=y3F*`f*MDyQWzAYdSx3Xdlp4F>%W1DY{&Q{M+l=s&k)+}8Q&CU9g4xhXtV`7#D
zbxgR{`m2^AjC6%x{JMAf-%Ja{@9XPRP8H1ksy3g>?qN3iD@Q5vvUGFm#1}(d9}i~a$oIO@GV0ZCs~@E)5#hD_Yr6HAomTd7
zuBc5ZpWW$m%uC~Ubzt!kIosP#_2;Y5&0373yg&Qjcdi~p&(+122sblUvg%5lPn>Oj
zF;GRABlobq%In>aACI()^*c@xt{pu+yCa``Ydgz@5%cl^irm)tSZJH=An3;9~wK--d-D@DvP_*W04WJ4d^F=N89hFO|6RwTgSX
zildmZO_v${SH5#pC!)CSeG-%lBh8kNOne>B-8~A2j+VO;^nX!ZTpMDTEPa(?JAW%*2HkBVzDG3QH;^ZE#1B9~E9CuN{1%@wN1
zgyCtQ$n9++$-M=Ndn}pzqHQ1=h~+IbpKI&Z>B(L{tIXZjp$1Eqr&IhrP#Qa8
zQNyB=M)`fz*x*&z)WdH5a&FtSci9SY@7{ecND-JwG~lFq`0$}Mh_F9~JWDx+|MDj5
zUw^$-OyKA%)qviqMmIv>Mu*4L2_+@Gt*TxbKYuYxldf$tY2oF@?EyC+)#?Y|GmA>LezWi5{Ub8|5o^u
z=U>%x*f%8HOox!P=~Awrzp6d{J2@Thh`8wv)jd{IjK4qoTu;b;P1D%#m{Qzh?MDG^
zznIVdeLU<5`AkgBrbjtuHEG%U+n?b4s{%o@RD-mV`+xwKbZa;^xgCs{+uGYAXQ^
z)^i0##fW^Pc@`C~vzDaelYE|z&Q8mjtlRM%CYkZ^H_vn{;$LWdF@!(M)jwF9aOvXJ
zJ}pfLSw&Q?azH0!-Cx1bpqAE&a9y4ln)L6j+h3;)?}k_mc2aVt%fw=swvCy!^E9uX
zV^4|kZm4B0$iM=SU->F;(9pPP4KJv`A>G~}?(OYWjdh;W?kb1gZAP9Hbab8jxf;p*
zV#^rE!D^vcnPWlp1jf@C#>*J=xjVJ5+~87MOe?R$dj6*`Irz`AAu71@&@g>Y?qN5r
zpr8;s&IjpD|DFrh4ZC>pV#;=_!G6Sgchlv)@W^PZ^Ll?ygX-6eQoen8?zY1A6Du|{
zOGD#?WMoS+Fh68VBz5tMc<}V+ks?0!>YHLTmHW*<1u{V
zrU;$A=Py*)grO#F|mm7rov+BHkk#(C;rb2rKg~u#a=lMXY)v19OAQqo)Fe*Uc$C>L
ztM+6_JRbMh*qiJPAzt;v4kq1_qH7MuVt`2h+`^&mR~Mq_<;$1WgVm(ao^;5`w_FDl
z$c%Q7hEG&fTI_@^;G-*Jg%WrgENUM9y?9~d4CVj`x<#(2MkW^O0xdbXO+`t`qC0O(
zeU;_UEIUCOA=~TJ^u&$5GT0~i+Jy+zrte6pzXUap@h9!VF*npyM!D@!i9e=-
zqkgE%pK;uCr9C@Vs^?vESjsWRt*i`g-S?3aEwlZXbiFjEeo3SKd7(k&Y$+>j4w)iw
zT587z9)flQ$>RG6DtElUeAg(aj*s#B=^VKNyamUoP_juR_
zf}`1AX%aY8Xw=+vytln`@WC~yNOEVO-w$)}h>FXtVW`Xb?9{s+b7(uf3`+n0>NwBz
zp-QUo;wltofiB8W&N!t9_w9B$1oXVc$a4(L0E0WVpa7fpw?-q=1PDw_Onej+cK6}E
zJJY?=RsaPTRp{xZfa<@EY=A*~izfX}zhmObd3rZIr@CEx8`|e%rPr_T8}pRg4u7!9
zA5wPqGx?1zhWkkw62;`orug0%)mX=em_Uq4$
z;?UsWZpd8;wGrnWn}LGx7IQOn(WEh2j7u%4P27DAuf%nkEVe?zb3M!8k*CdYu4_jL
zNn_hEbz>nrI~P|pQ{Yid7?Xe0?Mf1hwT0;_&{E%JX7-Rz2l|R7EzP>X+By?BT(@WN
zaoq*%^JUYHkMe%|tAQ(%HN^(HkaB$lx*j9uzIKzRVcStBWD#`8qv3hApGE!zbb@y6
zMH$C3I)M%6Oap~FvEW;iUJ;O273S*~(zV4fTD5NVPv+&EVzH|~ODS?5KAfoDAiX7c
zshQVnzMQaV`dBDmXYVYG$@^BNO}Wkf#xQJD0J^>R31--p#BJR4T4c%+6pw-R-19MxO
zSWHFkuguTOMN)BvPt}C2+tGufR%MJq%L8a33!0Zy#dxWIn1RUSX5Nu{cY1t$e9e4q
zY+g^U$A&lwjp!R5nRBKWFET&q1Smw;C%~aSC8lS!`Qm2>75?MGo3-{N=xN_xp-~fS
zYnq^8{DV3+20BG>?m%RuhM1X|#SdwHI^t6yw|Lp97`ZESIY^9j@@PM579a4alle#beU$AmGiW5H&;#O$tO$m^2b
z*hL4UclZG(I|NgH{ui7}H)Y)&UWJk5*j8Sx$NJn!6S3dbZMbbCHE--cN*SNh4dJ9B
zc8>XrQ&MtbaLAJXd4uf5nARUvA@i%NFE~x=krvY%x3uC6D(##x8DwN+5L9CKr7KsS
ze{q=f>&5Etp~)M2dt*y@(asl^yEB^4&-PLbzK&PeOW=W$L4Mr->lY}Wq#%`QV=SuN
zI?OA3`5wpM8M@dt$tX1|M86pM$|nFl-8}GA`Wpf*^m@qYB#zn%OCPVu(FyB6!jLgo
z|N6vseX_=a=8CV!*4!7Sz^XZC@1|xB(^CBxePs!@QAM>)wWp;iPN!9EWeW@UbTGAA
zf+34Q*unJ<06<6T3%4itK&4j2ULid?8+VK-=+bhYou9vW)1lKKvgKuq`E7xIOtM)|
z>5tRXQ^_IHTavH$_uY^X0jtjV94(ag(eW`*vQ2znE8$HmpGH(p`@+sH5|jo`OUF9R
z+uS_#-ak07YX50qJ^bPBVrN3Ld1;lsg>ahDca_CFEqqar_2lLHxc5&~6+6*>W8duC
zKpQJ_I;*|FakEDL(NBRg5v@GUmf`0K%L}KB_=<__A;rbTZJeKjhNrO+3GDdWzw#~i
zq$(<>Q8HcNeysb-m>nC?(&H5lX3H@Qc9YdDD5(NX1b>h%N$W(qtXT*VGGJPaf_J;fG}x*NvyS+qKx?6Xms$b0Ji-QwTnJIjSxsn&RIxVbZPYE*DC
zNO+j9l+4zOJ6tHPJ-W~q<9+7r%@%NWc+Cng*xB=iXpa84a2Skr0UWU5yB=8|>
zCUZW2E(@FvSn5gXt|&C9-9!Om0t$#2;3Wd~otDL~R!wr7q+?@GQO^414k`mD?x*|B
z=uhcVk!Ca#2bI2(?1dL@4F@1*^GEHLVB|6v9$-UBOZ5wlYnUZi=Wyr;cSys-!!4m!
z{W-QeKFh1WR!|n_0ktZxXA0hTbx+IS1)cky5aurT`bzbVRE>017v+~)?Xeis%OnP1
z34RuMyAFE=gWxQTJJx$dI}(V^4$1SNClBlhs$|B6(+YLFwZ{R}1hu?NgrcE(u7l$@
z)hrrPxt_R~7@yn@SPumSMKk<7Ak(
zLk&AV*pl`??)}09fc&FpZ~d1#Zp09hUAXw_Dk&+b87*9XI8-iVGqjM;Bi*o8`pe-`
z&ssKU^WH{|rMe|xo~neMjV&BB=t~3yuF`5H>YtVMbkb@8C6uk?+azkH4HC@{C`xFO
zj0KtcMGG}_YosAoP5R^~SHYfe#rS0?@BEgYmd-UW6O_ufe@4J)xnEeh(@#F_`wb;x
z`WNVQwZ|uyD!D%aZnSHimS9zUW1s2~hTxJHr)$h8H8nMAEk<|XBYKnBXUn)io5LM7
z_(dg6cz=bmL#a4wF}Lk}am^)TmS&5)>Jecqt!&2ZS%_P&)3+)B`=V%sQa}B81c;;l
z9}z*Ml->hbc7s{b36tT;=hgnK7-eCxVf3TO%`lUKkkHb~>UoW-eJuBjo_Svl0H0&6
z7CVy&h-9?2ja5>Gn(c)5RxH%ER|C&`4}4}|#IOCFYk*OwIlCt20IM?O#E`ua0K2)xJLaa3LsP!T=B{$uaSyjmRksYv#@%
z2XasMbAi;+I5ootRlHuIcAj?rQmKDIHpVI;FB8?&+`Kwj$piLkUpFZsq2*hyhHkwb
z8z{k_OwREEsv9Yc9<^iPRCO^ru;K-P!YrLW4_h_rm-gUaY>jlypCMFg0-2
z2wa5z<3})%pt8(uG%+M(Gm(5AaK?On($~vw_|prGO4(`b>8bT-o=|RX?yWukp=V|V
z`iPh4Dev)lp-Dd9`8j!F@2!l=$t$8;pyMX6>p<2PI)?3pJ5Yr$;E+?^RqFcFlQi41
zhDd6jGV@~>fX!)K$2S~a4GrfSBzMY7Qe0s6kBQgtw$6*|oQ#I!v
z#pBrBrF@=-nbYIF)4kcph6(RYw}g}5zkk@Fo+DWgJBb-JDI7{?FI!)?BV!VOae
z(Mm^rm1O?}g9w<^2l~9)bb!O4HGqu9_!Ts1QKn(~n
z&5vW2y!<5dwTr4^puctAxz*5(&=>I0YX>{%k1El=t~XVaKs2VCJQ8{9DTKq@?(6^I~n25n*)p`xt3$X3qkw06|nDCv(`km}NE^>d48t&sL6RDi`
zrl!yS_*E~T>RlaXyV-Nv<@_S~uUrkNDolEDO#xwg=EoSYZz74_qSoaNWDGUCHu{Ae
zH0%%1;wP2!t!*uQzC+45*)M;drNnOa2B$W0MvYqz;EXsjeXK17kXhTFm;IT{8zfRW
zrmal?l)jg*UWxgwToX=qp1aiarD;!-kHT|nQtuo7FW9-xC&o}6)yGG%g_Hj2TypMCJY4}`jg2gl6
z7kvXHE;vAW5M|gBUkqhq%*Hr4tmfNqKg5%hvobS>003(NRIk7umFMUK-n!9#y4k3G<@Bt(3?iuw)a605QP^!MspuUG6SJQ)
zx4+p=2jlX0(~tv^Ili_1@OA|`m^&YfzC
zI-XR|Y41o1LRM>VWAtMcuxCJ1OYO2{njnzbNq`4lw;uhLU6{nZ&87m;_TT_4IA;caE@lnr2&W))TNwyf%@DV!
zNPeoQY`(d!hs=;@n@6KTAg(h}KYkR;IC0+i$`VYdonbM@YhAtJQwEYXmw<>68cp+5
zS(yYiTnMD}-*V6QZxEw+pyC5tizP)b#Zqi)n%{Cer}B)#t)o9@N8x#KfWI-VL}o_=
zwsWihGu6RryNYUWw*OKKD=Z)}+$7-pFXqU|gTd>9>pXWXDO)v-#%rr>cq{U-U!1y%>+l
z8_bLI+P!x`FPH^14ZF#*lx!W*($+;AP#_p2+*$bet_Oui(>>4Ew)fZ^nN8LEJuC7k
zA`@6mhz}Vuftfq=##Reo`yM@VV-*!m?*F1D;CaaT8OT)C8ISdC=w@drt$%%^Jv%!~
zmv)a3gG2b;I~PzM|J<|73p#KI|2&E!TK_Y={@0@W|H1vjxixRv%XgYTnT#MT2pk)Z
zPps^9K0YE;h(;tVap|k)X|mY~)33gGaeC6#%V|=vuO@ErJ1^eNJvB>dSJ{NcN-6?s
z3=p;1eCYi$egVFe?sRGY>aS|QYs#aAY7Jcv3fqAyJ~>BPg=jjVQr*HIZ7rov-zCp<
z>nE}|JY~*a53bF-%g~G1%`~H@-3_!qn9GO8(mc(mJ^;M_9v}|uBNYiL0s&~cCcx7}
z+Nb=l7C*xWS|#Wwa5K4^drQcr352VTUo=mhc;XdOx}};k@PA3K^XbV%At2AxV+o$+
zd)S|1Peol1Icq#NO&)T@pojY9yra~|5#VL}ajFU8f#Ia5$dPcsPVIr6Q(U;Aq3=ax5h{4c8*eig^_Qj|Jh~f^XWi6AO`7R_^rD5H_L~2_Vz4*D0q*R
z`8n9V&?){Xz}k0u3>-~qS}7-~zx>LP!yl>hO?vsq0lt|?sRYb-L5E-Qz_q~P9@k~0
z8PF(n7@|Z$3*D^7FsiHf>^8afzJLGzLL-3S-d5|np|Q7GfYk4LxK4}r^5rZ1kxEaV
zy`>(+!r9|kL_$yy9xx*^fwEVf7)v7-VGtUdWl(t)(w&a~0D#368K-N$==Is?;nui}
zj18Of6^q#Zdb!7Rg4XSa8{>vuDl8O$zV_EadDyiJo`5N5AbIe5_SgZGR*jYJt3ue0OYYLpLrqRkZg{jC&b|1%XQ={qxne&y23X5
zGypiH@NQ}WY~{|?fQ^w@1-AgOB4l0x{nyXNESE)gT!$IRHK0!GZv7lhf3$5m31Bz
zEQwur1?P=7aO##er-`b9LaWwBgZ9Q)JU9WX8eghj@&~Q2b1rpfbOAYB6pDKMD>CZPF>MJMj)!YZT?DN&GboiY{+c?7(z@`d$zmSOv`5@
z7)ixFv;9HN0R=?(b|iAYs;#Fe3&AmQdhvp8oegtWBDZ@DIZ)qkNtVDsX#}3u%9zLO
z%i{u*n#4L^bkzoCA)Epxjn_rd`Ew{y^FU@_dV>mJ(pR59{1qSQ+XRwB45B5|
zg`^Ljy_Mq3*Dt4Ytivz@iRtoRLP80b_m=qm$22QTR+gX2cuP4fvM1obiH>&Xs&SgV
zfI)A#`U|IZzYYySw33e3+ou`mYL@E@;bOWDyw(`F+s+M^+8vhc)+nwg&84=_dL`zt@6EtTO<^{DGUddSEhYULg?^KI8W3;_eZPT}g;AQO9LI0UY
z5Frk+3d9FT$rAV#ngyESIDx0e(FB~JMxgHW6lF;8)V-2SpV!Qg_Q!c+_1N!>;&rD@
zdp|^5pL7{v7YjIT$Lkq5bP7yC!?|>s0LMviC~EcRv-*>{y$i9lH`G(FXaB9R0o8f7
zTm>~`?=`my#q(tz2I4jHeM{s-HLSvRm@%l(pyFAc=9}?DkQor59)8uR9{mDDkHHll3@UY5hzh?V_@>3Fo6%CL)mVqA7
zRoPAN*q~HyGl(}*`{FLo8Ai<9A{H@}M8-whm?CgkR5EYun=w|7;$8j1lp?5u>7}WC0Sas5jIM=XBx%3UoB1f2VG|zLap11NUYVoUho$z2$CZY3?2i??IbE
z;9QPd90Px3e@xzag{%20DY2_Tg*}Q27;Ii0u92~kI12d=2klAH@_v4PQi*LXZQdSGDTiVRheqQ+
zzUlSr5+PRJUwC-miwEF2>`5=+3i=dayQ#_6f8Z%`}A8G#h`0RiNjh)AT|Ne&2
z#W@Uy0B;+nCUaY8IOxCu6CQa`U8|UoCulSAI%IFz`L@)gMID&v{Sd~)EX5UF%G0nw
zHxmeqy!8phD`yI0=#Q^`egZoj#p6@ICj&Y_N5#!Q5T(DpjebOUfD)OR=BfYDz!>=6
zxwxKWrutkvAFJaA{3=`}w{G)-*4Nh?%eCzb_M%(@rt6KHgnmQK>&_tCV!wS8bYK$5
z`?(?FFyW-PVdAx!(j^W%IiUXuimQHwO-l$dwJt~%Vk#u77~5*uVca+{nel*Tad)Zx
zdH_s#u)GL((3QeTE<=4>eZ9TC7DhO9vi%_}93*8k`P5yuK1|RK1P*@Uv7miDAtj{)
z=i-81v^tUt=ipRQtMtr>ii~7lYJc}IBU=NAVR*WAJ_*AIi_Oh!I`xu~)Ld?9gcLK4
zXIPx?q!69vWk(H00zl7{e-#>yYobKyd;>P+KAMpmlQm(asNw!nS7>KEmy){q$NVOw
zUP!WUqB3TeKmBl*VX^3{+pfEx2!i$6pP8gB0B_{LsLvsT_|M|=2R1`_@(-Jw1CBNg
zz?jCw-5-b>JrUo)Z;491<1i#|4(JC6W=xB|p97QmN!`i8@j((yioJ|oC-=ew^SP&$
zi<5WmRBbVlmw{e%j_HLSUl9VcFHX2p4R#GEley#K@Rkb?9y|!L9WAwJ!|_#j)X?75
zE}+jnQBy;YVJWk;(kfz7*z01)7fDiUF9+h)*GnJiP6k>5gy9WFOh8y}0aoEtZ;^xV
zx4pv(Mk{t#J3&@Mz#I;3zM(xieb(noCpcFV@2!Z^-UnT;Isl)B({hz!4jstMPaB@g6
zrAnVK+8hjFh?oXFenLq*N>HNXE>>t#34Y1|d}5dZWdb+{JHV5#BbRA1-$4c`C9=zh
zc$1Z2{>%apQI1eEWH_SRt44Hx{ZfAcG!!7&I-pX89D`x7hVk_zoNSos7ePeHu{(si
zOkjlvTJ2pRbOG2pk(k6~5CiE>1B%xgoe2^GFLl_U1Ac;1c5C$H7&$%Pg`fluNmfOi
zBn!CLWTN}Z$n_wj8ty>;tN*aGv}pFOh!1e6hS%(Lud#|siw7e
zcV}nzjNm*Rm@7b>#9krU7d=G8-_+3>0J_7ev^_hhuit%{0+6j=4-9LQpMtc@1zn7g
zuuZSbtJHQJ2-}d})D9q?;HIL0uK%*d1~*qQkf)WlRgX^#y(Bkgz2TA9ZmM7+9M%V#DD9NiDV
zZ5hZnfdPM^1Wc`f$b3KZ#~9ixg}lB-M@Q6%)8rW?gb$MqsGD*%Sb%3FHywa|FpQaGa{5kD#N-2|%FOjCEb;`GyyEP)~<5$5||In0arp9(ri{P*Ks%31u^^u6>
z%u&y!m$ntl>&+2XT~D4=mG{X4M25e8P#9V4XcRfm%*;X;XZ7MmZq)gV|H=LaNNs#U
zn_EHJ28}t9e0I7GJ)DQ%zG7ehPhr}dwBIW_~Xr;2<-JR68%D|LhVTHFY-F4t_{+49QU%`
z&fQu6{TAH&=3r*MnNpOy)f_s73fX;GL)JgP3Rw%vlb7q;?>@`b`l{JNo
z^lVvxqWSaas*Qrm?Yg`B+5M(|=yV-4v0T
zI=mnidJqvI_rK3Vl@oIZZ3+qsIQXgl_sa$fEmIUU-Z}sG-mA}NqdNFA2G}V6T=Dfa
zwJHPfRLsklrx8)||Go7Mk(K()d22a}k}~Yy^}W7EscL^JTX>uAb|x6-8nDSVVfk?yoqNuO`U&0*
z*q;7gNrS!*Z&(LdVSybl<&~UVT#8nnF-DBKE|;42-I%xd`Sye7x!Fnus>sPd*bY4>xjLQiV8ky5gL+(#U&T;
zd(R5{$)@@`->5d@0C!H=@3;N7Hzf?~d|E7Vni#M3_O*JJVTwDOXI1{um4Ah&VZTYPwmL9WO*&J%@AfmWG9IX3Gs?o`Ho#fIF-E%*8Z{mn
zzARk;eU4PP6XOY2RJ`Ye|HZ%i!yh{(iR{(<9h@Dn=NB(kkSgR4ejJoCF7M3K%x(Gx
zB;lT+L@+7te4xk)(nip>?KuOD
zpY=O8Frred7{ooA`ug61c}%$Xyn+P=u-Id#xqD+*E|)?E6^jhnQYi-teaRJK7%kQ-
zae3P7QbH6Wge?ktN-tzS5k`@M-Q#o<
z=aTY0_})F40cIdQHb#2DCC=2t=(W0cy&&m#Xf;UX9>;&1w>e8s7Ox*02f57s;X-1=xBJjS-bZy4H%bCz961G_>e4{H&9)ECkFSjgzY&86&LXId9|XS)
z3_NG>@Tic)B3-}6D^i^bPg@Qd>D8T#p+Ii%zwkY7p$6bm03~7F{tPEGj3k+SG_odm
zC?_$qu&!)wegRmbmgn_#kYz9L!L0Mf;G?0!u(&vi)z#HUnW8PTpJq8$(O70!TvX_h&J-T=U1{q)5giT4
z@%g1Cvc&6G{kmb^93P)}DkEdE6ttmP;j$C7#Q*y+!@Bky^upJR)t^0lD1tm#*P9sS
zB_Je>(VQzsm_+j}P$M|WDJYJ&g1M!hKAoN@ovJcv|EGX{Kpv{nw~>=mpqCNj8}w?c
zew$lTW4qbumQivxaw6UO6{gNyB*a3A=WCS3X9#m*8MZ3P0G%dDMFR(17d
z^_+;RDiH!q7??Zf7Z<)nx_di%T*IS~kdwm&dY4z2&~_RPsLw`^^<+xsJE(AgfxXFwT3!9Fl8*af~&{bsP0FaZJv|2u6FUt-P0
zu<||T2$P4_c7Fe!-Ef{V*@#hs=l97M=M05omXwru93PuBog0-?IjXZBI63`Zo$!km
zKmAdg;28w!ju+q94F}t)NCeHW<;C|{V6$d+u5$(e=<$>I>mP69)zmm{4VTg!m?V0m
z$LD^AqW>^ck<($ALz@Iiim|f;eho6Duy7^EOiByjeFdg-*SLE7P`lPPn^vYyE&5h1
zGRRiYskI}vJ)S!jh{WBnL@K}!?+FXH2`E4Gcq;`Zhf+3*C;IKFM~TIVtc06TvDr|%
zdsjkPdF9RozfR=ZnG3lH@)YZx)AFw#YRTbzD+;KM4wzVT@rRR3eix@>QT_dd-U-x_
z!Fmh33q?4X#xU3Lm`}!uE9ZWNwi1OZ0$^=-+)InrtN0>X*o5!-?^5gOuRonXrLZKL
z4t&pTKRQ6VYiq1541j2NZb79vBrdFB_B~e5omC(%(iKvg2)ity#N<`)&_%Lo8MY+9
zpC_8{`=)^Q$QLKWI7i*%!cyV(_DQih+>t%FL?unF^5{4VE;-$?yD!ang`GFcOsf7}
zo`tU2p+>Y)g>Z|?Jyt??WcUa?ZgDZ@BKS->p8&4%q(d6uuW7T>>BMtiQDRa
z{(J*VF~Mt5ZPd@{M{6sl&(9z5-be8t4pj~^ySH`)zkW@}qJ31^INuT7m$4}v1a?P*
zz%;*(*XY&S)^Ywj9E=2*u88qDRSgXUlwJ6Bpo)we$U1j75{gf%@&+Af5$J%FOiiePv~ZkgqICaAd
zErsw2v&+lB==;)%O}<1_@oQB$)pz7D0Um;O7o1X1^Jf>7d@@c?hY{{WT5B4v$RnHm
z);r2)1-*tPVS#}-Z!CRm?40OCJ3O4m=Q>NRv>pmO%!AC|gL_WK0D11|9=)@1_=%i1
z#^=^`1{M|`FE3k_JUfnAwBzGrFb9P6hrNVO;Vw6|&ySJ^NB!BRvJngO^TZcomC8A8
zLxyTGH8=RTrh!6RrI#i81jRikZhl++B8@bTN!n
z&$#F!oi~ObC@a(H&1ku_TKm|hk8Q>aXTE5TEV1-yk7)KQGMCrWy9fMA&Ht_?_?vX8
zzVs_lOa;DTNCuP7w6DB~6ZBjSy1Kp7A`cW3E
zx25=1%v_zyCjYtS<=z}J6tV97wm)P(mBt`B`|+h}b#`^}cM?}pnH7`5ZtgGSJxQaG-M1o;Z_N(+A-c1(RpiY4
zdV3Sjnyo{&q1}4J&fL5A&PlfS@eK+sU{C%;F6s+1pHM?JM|pVwEb776koXHJ^LPLE
z!Ed8LE@EZht>)r@jFe~mVeF6lq`YR(oZ%83-#k!EHD4y
za>c}k&M5k9fJuIs)1Gl*fwya@5pyK_qm+VK04SQ~+W?~n@_BJ&Nk{j`kDsStBI@YG
zy7B6*&e;iSLzcoxppUMs^pmO!f?qg}b5C$pA?_u9-Z@Wd)(nV=!DQ7*&8!|93ubm8ZutIib~{
z7)-8&k^LoB_)i~sdxw_1dv|3pN6oT#ab@AEZb%%90#`~Y^dIofuV*KxX63p_Sy=7d
z-mROhHIW`jsjl7h%gSPwPV1fBicd=$rQK@#tXEIMWpPB&%5Pw7!Sh&lG<+f|R!^
z5Mvc;2ip21tqjNezlR?eV#+l`HUD1jr`68WjrGBT0{jQOo*6I7x
z2LCyWNnKZoh{3u~NmU+uZ>MNma%y)Qg=T-C2W-IFHUMPfXy6dx;sHY#l*zp>tnsJx
zAXGH&^W8@)!oh9o=!nOd6L-;Vx$_!f(sdIb|5;Z;^+>h-(q%H04Cb+r{=)HKTE}YX
zu!I4nTMq9J`Kh-SJ7PTqHa*?;Hy^b{Sj{-~kEFXV{JZJvl+vj{j_6WtF}yZ8pwM2OK~qyNYy
zEDRTe-N8W~S9fH!#;wZbh~gi$5--BYi;HIb(n!AX4L%vFGTvwEOc1lQT`wBrD+czM
z$nw(IZGzi%$x}pMpEKOR#JvZ|H+1b&+{*uq*pEj|=u)G5Avq2EMSjhQ6;$RQD5n9$p?!oGHyB$~un#_Q|1ZU>ZG@HitH
z&w+7T9JH2n5b$wzx_zb3Ltup(r3h2e@v!R6GxQBtOyePYl%h;@SiXm>@Y*14syT-(
zfj1?8yNCYTbk9`q9a=#~e;x1RXy~?ojy3zyJ&Qr@o0BeU6i|ihpoeVZwJN0)g+jOl
zjjU`0g3o;N^uhb{=M;i=QMVp#X>P4K?Jaed=Z%CPH&(tD5)g{nxS_sFh2vW8T^s+f
zO?_G?i}i$};I})F5H%J>^HYJpi;9rv&U0O%UqA@f&BqW015}2Wk1vd1g--jcl8Si>
z|GfS5JqEEX_6`oI&Qo57#)hga$&^IScQ~|m6$zoKf>zsO?Ic+-v%RKBKu9RY!<9{;
z+RYKc$M5Wn2K+?Zoq$jTH|%(G>YC&*N^=St8yNUxnJdlaYV(?^Fok22ioARG&IMj@
zwcH`k+i{}qzFNXgH%AMo?`@1COb*%R3P8z;rm4vk+aZ*-x_IrroB9bwvzWv7T|oSP
zuMhb3_lF4|xJOYoogFzFa}a;1_J|&Cixjk*=g{%k=LM4;i%Owv=1Z_3-(l0LdUa5#
zoh284{m!nq;`o3-43DL6qzVo~|3q7x7p;ttNaa@2gpwN8X+~=5*P%6A=R=OQIi(a|
zwXuO&qfF318`PgT(lzst;xL7`vXp-tS{o@fVHb5jY=UhWW=VAL1sxzO8U9^n(b4k$
zuCj2``{?pnQLa{?&W)SLO7X68$oz#qKfgFVz5GGlb4fEp1=TXyo3_&!q`iesG*_Jm+guL&cZ;Q)K(^8=73JU8QAwn4K3b3{0X&0A@e2q
zCl0N25(4tK8IeTb<~eKblzT;f^T+8F5vTb<9^SsAmkvkb?;;+FKvz-|6Cwlm7NW(k
zUv&vE-xpK^4aZ|T`e{6l+@Qx*=C_+D*)3qpd}~@nCQM3%3KoNvgx7
z7LzNb5nm$uwV7E<%W{6Z+Y47~%S%XH3y+JFZOYZiqxACHM}MBRRaK;J%@FhKTSb?;
zrthqge{cn#rfV2M3mz7KjvrkA{WZQf01(=(^fpS8_MLg8(_TH_+GiL!EBpHOwM-=2
ztVSstC1vSt;}R#QcSYvbd|og6U18<{!yWCwcO`DYK^V%sq6FKbWLYd#Y%QVPis=P}
zfbH><3ETRAA+-jb!(qVg*7i}Y+@uH6l1@IpX~&?s%qB75U|E^cnOh=}}3gYi)M
zvwQd6Q2XIU2YRl|(u&1!1%k-;OKhx6WjD~nnRT=8Ya@kC)B0AT#GP^3t4}c#JlAS7hX}#j69b3pY(pCcLJ5R)9bl?9@$dmD?|Z;uEEhnVb~6
zQ{}gp~
zl^mcqNbugF1+q%&*|P%Gtz1JsbzNPJ-o9Rjexm9rVEzBfb16?qJ4d~B)#?iRbo}%~
zh9z@TPh@3z0M|$}$mpy(J`jcm>xQtqa9v%6u8B!=@Wrunxp~OQ=;*E9n)sdaYR{gj
zyX<+S@0>TxEX0X#>Fo+T)+U;&f#R$jC=lzR*I&E`%$u=e`X1`ve{YyVRZ
z8mdK0TDV@rPQ+4ni-%<9zb)UVn{G?OvYVyYxuiCN`yc(@VYK_Wf1l$>0;b7D=I
zyUS;=)`QVV6&Mknib2szXXt+=plvqAC;kwa!z$(PkMdX-XQ@QfSPD6;KUL!A2Xk4I
zr>CCfxWH?$MnxBwLazgF=zWDRTOo!c+qbA$gI-jK9_*&o#<94E;jT}~+dLJH;WM|=
z49e_VIdT~#xQ0tA3zr0gKSsP<+d!7eNQqIKC#!V_Kz^X;0?>48M(5pCyfrVDfX_}F
z7?`-6=IqrP(=ES#+0nNhY>ouVWl*zJVO_q4(=<33(H}+tTI`PR;mjMVeh6PiEdnQYst
zZUeg4c}q}2H4=pyDZY3yQe;XESW2>`P5ua5LUS<1LTGSCZ-%uupk;p2X;3{9rWQMl
z`Xt(78`0KB3w%rx+!MgY_80DYt%3Nz3rHv9XdTgo9e6oL-^el$xRDG1WzANGG7hV|
zyly;v*j~#cj+?N%XV5#J$)Z&lC9cepw`|jZFSjZA&u@5l6R$Tx)O~Ye<(_J}882zz
zwPFb$iJS*1jBeis%tHO7a~`HKc`+z~ZZS+ct>sbDbLgNxnnf%uU7R`5{ulEwtH3NB
zu^?3_`wvL`_iv}Yuk9sx|(K4>9hqo>BnsH;J*6GNE+ZZF90%Ox%Cwkbl|{NqJKf6G<=a9?|`{Zdmk~VF9KJI1dc>AAEcsJ!z)>^BFhpHBe_16&GK>
z%g^o?7iR$owT+EU_H_P#{=++$-~A;APH3s=srpc}Y#adCP_j||@5^zM7S_-}v!bI<
zQOqj%GJF1?f1AZfD=H{>lJxv@d^lqp`#=3Vs)=vFf0p@@DG<>@PLY;h`yXD$#f}l(
zV&mg#L`yr@e_aR7i_5@h-mny9RgWi(SJvp^{I9E2xEFSZ;`L2$kZb=J%hKA=*%@>-
zAYyx9XiC|EAvp|C1yvwbfI!b#e$FI&|2{q;i~7UTO;IKxf`32i+szOb{EJs=(Ih~M
z=y5DF3x#oUq5=JGXlVG-f#LtIi{Xh@5BdkmJTT45BIjR(^1*(uY)q%1ppdPF_y~Kz
z9RjTpkKLW3kGM&icJ_8IU@2e{!p4UVY&yqzk{Y0n$OP}fbr&!vEDuz&19a8|fP7K^
zOR)X7fnOH7uftz+Ia6Qnon6OGl?a&;tO&*_NaH-XJy+q!=_six$}-PXZjeq?DguF8C{N-&g8J`Hwn
za$&c3U`Vl9>3srSBDdv9hKrl4Xx*zMh;;ds$nm9BNpl3GUf?Wg9BGU$opg-^N3?ja
zYO5KvMNQEo+t?LIba@2WYwy*@M>xhB<#lP?Gd4Zwv$kb=$
ztj1aegVAEV1;AypbxMO^v(n=rrd(bFxcf_508w<&RUXq;cdAb*Iu~!^PWI%#ehcbL
zZF!+qwe75&cT%5vafE@N2@f&F7mv5ZlgEfOD4dDYsP_s=Ue{;b(esL@LA$0MI+8K~
z6btgC)AT3bzDMn0&@OR;Ma*Mu@Cj5}kc39@JL8h9tkC5huR=)2FyF7#jRhi|yP~2U
z>!jClNTh*OCqeGY6k%=LfA#8mTwFYuAan;>!`G)uht}hibH=*8f1rI=I;3Ic4G$z6
zv_BnIF!n%-0t{gQg3ZHY+*ss{xA@GwM^?>f;M$PuQVu`M0c#ubNabv8WCcB~-ih@d-qISDNTHD+Z?S
z{enz{&E~jT0B6xw_Zy&iws&_QW-!mg90YI@Z|p52OUn*KSM84lnaC(1*W07B&!j+U
z*z%3j0ICrO*;swO54w*ps3wD}t3@+SO@Yh!1pikRY%(0guO^ePUcQE#4v-b-rcp9`
z0FdwJ@oVjx8<@+~g4$q$?lY{A(9qa606IYxgrTBu@a$wTb57=vQgo!s_OkubshP!8
zr8_@`u-^=1dVoe2O+ZkP?BW`jv6h-@gqI}orm2awR0n^f|$vPA{n
zfbeL$&piVfkQ#Jjz(Brx?>F}XVcg@Ab{l7*z@=wPb!-@oLw9<*@}ETmXnwM5K!hyu
zAv*i{R2w1k^!HrwGNvP)Ki6xJeqMh5{JCzg*vX3bEyBt7FensQwh?A$=b$ng=IG^s
zxxs4>c}gu2nC>2&0MIbRB)cx>3Sc}SU|
zR7LREJO7!QvXl_*l?q@Uck2S+#xtDzo6J?YEl${&cQM=I%C9pXprU*@?wi!q4ekiFmKfCulA&xazi5^d;1zaHMJlK
z3C4ZUy==v>Fh>UhKi$>e68fnN!NncDyW{Zo(P%Ucquar%&rjWH=;3UkMMOK;8f*S!
zP1&9s4lWg*^YIkPJXjW8NLA&|PPR|SLkP$f`H-cubs9aD@XOZDDIK;^)Zhcp2%dzQ
zNcu}ATieWb0A7!cn^c+(=9+_c7d}}p_X~akWN2h~^96zq^|lQ6P(zRl_FQ`gY$w~t
zk=(>QD8ackH9J4i>k}MOw9jE-zmRic4WM^0V9kT+qriHLGE~(snA{&q?J07L)Wj0~
z@&B+hhT%yK7F94r*OOE#S+JA|IR{Nm-t-6
zjzQDj;nq|$k&b6a_e}k>)z#wx>-*~qGX2>~0k^0n73#+_7}N+o-+&Y&s;Wy(P0eDY
z=u78})~BuD-2BgOaI-V*O;Ci2=givz@BZVx$MNFU2P2H((%)4MX+&8&q8Kitf?eJV
zQGiK;mXkASS*X;?X!W;zN7KJc;{&}vP1`v-su%$q^lfuR3VNCwTchE?bZ;ML^Z}vJ
zf7ul$P{OCQ6c74*bltu5KyTOfUJL?;?_aJ>udbR6@(VJyL@_W=hy$Yz92je7E@J@2
zSZOqiL0};3$293oRcJT7_Sfub`3B(|{p#q@Tk51h%`fbY&$W$QsoGY78lQ%OPA1(k
zUO^@-ENtA7aQS|cTr)tTs>OEirEW{)4efP&Vh`-Pg9-C^$h#vKpjM!FG3GLk{ZWDj
zf~iSEe_byxu_=$UJ5D7|#L^vMcFxZ9;^M*1lb(s(CY{@7rBlhM_~pU=N8pLVoY(=H?9ISO+Vf-GyfX+jx(m
zh2@)wsHj(`tNiytI5l9Vz(7z|Uar@@MuI$vwa}~$T4;-aV2ww?+SSK4y)r=_UG|qsUF`!c|HDH>G591$M*+&w
zxO8twhTxxO@mCL~bMzVpIan6S<#Z7tTay
z`Gr2n{`aph?oCeco)NO2#sl$J;kfmty4uw;z3|4y@R#l&if5y+aU5ArY6cWnwLZ)n
z3bh++FFk#mOzT72c2kD=?ERTC%ab!pmf!4I4OjYhiq%=&swv6H%ndYX>FHr^WNF&H
zyXF)AL~W|M5Uh!paY*^*eVnI?UGmgnzEZ1o!U1uN$bJ|VK$(wnlu?{|6K&q}uy^DF
zuQ&op`I}l=MpAE9OBfh&8G~02?E``Xkwe3&8UssdaF7o=k|1zClZ^rN(x5Ws(L9}0
z4_Yp~V6$)iW-vW>;&FQyx;#^2p`pmrt~MJa^q#x+ya-)VsxTXNc*tvUCMWyRn8xMr
zkD;ojmh_S-Q@L6wQ7+Lq8^h(O`vD5ThCN~h-kA&JOl^~WeT9SG@b)coai>qf2|`2C
zZ{7VGB{O-&(;jnNTS?bmak$Z(-*p8`IL#QeX-v+}iJzdid{P;Bs54aGejf
z1o*-BQ9O!I`B5zPS*ykc7vnl68sxP~vG);bWYB&b%0_@jrn}f35BkO@Im-4Nv#+IC
zQ+J`kyA5$fj7&|Fm3W}pt-9#cm|s{pOS4U4XtA%wAV!Pd%p15=xpeft!u_CtNp5yr
z+Z&5x%y3$PaSiEG`@O7!%dDT6uszY5Q_qeb)CyN?JNU)6db0RYo^?Q%%GBQF#8Xf`
zi}pnzxXA`{=0JEnzevEEboy%bC4$q^!V-P6@;pXc$02W>fz3hZFYqplLuApDwuqOu
zjtjM>Uf`Ma=sO`V{Aw;Axw){
z1j^u(f8lKa13}1nUA*?Bi4PACPa@DOCIxhx8$;ZmHvDiUKF5UA0Uitch*ZOyGvhos
zT!45l&jeM?wS=9w+p-g7L(F0-$Jc1d=>F}$8$zW+j7q^Xsn^h5Nu1_nTi8`JY`^!>@#g3M?HiNarXBMMHUt(KYj7Xb90Oc+47*YKC&E^aq=*qC*C&2c`
z4~dxu=IpV;31rygu^AOYx?MG^j(RU_H%@qisDz~#T1%a3UuG&XgiAB2~u&FaUJyu_Is43
z*wbBm3+>|k=kBL(-WS{AuxstY_Oi3RJ-wG#vxowDj=E|)nqM$I3t>~z3Ye_5U+Rqc
zou!6|Xt8U*ZL2be@)JXP!QE9gu$#5eVvbiniJ_FJVVU&z!t(?U^RNwm3s{d$w9z%71f
zez6OOiV^IR+tBte@4Jmph_(m0-eC{8^KXf!4?pHtQJ4Wig^SVp+Wf-O&c2Gy_NIn)
zp=)20Uj&qOHFoi?7=QJg#l{QwMw(28-L#sDjEojR!2Mb2BF;16W|li}`hxQ_y1$^X
z7(re1sJSxkxC<0O$EyKdtYWis)}LcT>wzchESz=Ig_t);m+yfP@~SFXd`5_+X=)jX
zUS8Iyg+q$w_+&5*?mhGw@^U*|((!W55Rzfz;ws7J2gV_
zvDb9y0)kUa!+AZgAf4$B)Hcm(yQ~uRhBuedK#tTr?*XC&)X;Nr*qTMR0b&M;q~{!<
zcnduMEemkQjXMUCftJwDqIdKC;<0)SLnvC*x);3vE@wHVe^>2iwf4!_AB~$K68sjh
z^*h^T;T#CKJ*iqq;RO1BF(~;<(SJ`$2|qzviclRt)%z&qov<-h0(k_)T9t@##+AUEiQ+59*3Jrbf+TnSJ6j@InC8nW0{5$B*K*dB&cXA1JuK{5IH4J|Y2@w%A<4Juhkj;@Gwf_G?%#a|N2r$uL
zuZ-r}H$dsPj3usZJQ99hZfAs&Gp72EcTzyWsHx=B$^#G&0bIZdwNW-B-v%0l;X>C?
zDHbq$&