diff --git a/Imagens para implantação /Imagem colada (4).png b/Imagens para implantação /Imagem colada (4).png new file mode 100644 index 0000000..b9365d6 Binary files /dev/null and b/Imagens para implantação /Imagem colada (4).png differ diff --git a/Imagens para implantação /Imagem colada (5).png b/Imagens para implantação /Imagem colada (5).png new file mode 100644 index 0000000..2a3e57b Binary files /dev/null and b/Imagens para implantação /Imagem colada (5).png differ diff --git a/app/controllers/operacoes_dashboard_controller.rb b/app/controllers/operacoes_dashboard_controller.rb index 2e45720..4322b5d 100644 --- a/app/controllers/operacoes_dashboard_controller.rb +++ b/app/controllers/operacoes_dashboard_controller.rb @@ -17,13 +17,16 @@ class OperacoesDashboardController < ApplicationController case @modo when 'global' - @metricas = montar(@tabelas_validas) + # Global: agrega TODAS as operações dentro da faixa de datas escolhida. + @metricas = montar(@tabelas_validas, inicio: @periodo_inicio, fim: @periodo_fim) when 'comparar' + # Comparar: cada operação inteira (sem filtro de data — só vale no Global). @op_a = Operacao.sanitizar([params[:op_a]]).first || @tabelas_validas[0] @op_b = Operacao.sanitizar([params[:op_b]]).first || @tabelas_validas[1] @metricas_a = montar([@op_a]) if @op_a @metricas_b = montar([@op_b]) if @op_b else + # Operação única: a operação inteira (a data vem dos próprios dados). @operacao = Operacao.sanitizar([params[:operacao]]).first || @tabelas_validas.first @metricas = montar([@operacao]) if @operacao end @@ -31,8 +34,8 @@ class OperacoesDashboardController < ApplicationController private - def montar(tabelas) - Analytics::OperacaoMetricas.new(tabelas: tabelas, inicio: @periodo_inicio, fim: @periodo_fim) + def montar(tabelas, inicio: nil, fim: nil) + Analytics::OperacaoMetricas.new(tabelas: tabelas, inicio: inicio, fim: fim) end # Faixa de datas do filtro (params inicio/fim). Default: início do mês → hoje. diff --git a/app/services/analytics/operacao_metricas.rb b/app/services/analytics/operacao_metricas.rb index af4f627..e723aa3 100644 --- a/app/services/analytics/operacao_metricas.rb +++ b/app/services/analytics/operacao_metricas.rb @@ -17,16 +17,29 @@ module Analytics # Centro padrão do mapa quando não há coordenadas (Grande São Paulo). SP_CENTRO = [-23.55, -46.63].freeze - def initialize(tabelas:, inicio:, fim:) + # inicio/fim são OPCIONAIS: quando nil, não há filtro de data e o conjunto é a + # própria operação inteira (visão de operação única). O filtro de período só é + # usado na visão Global. + def initialize(tabelas:, inicio: nil, fim: nil) @tabelas = Operacao.sanitizar(Array(tabelas)) - @inicio = inicio.to_date - @fim = fim.to_date + @inicio = inicio&.to_date + @fim = fim&.to_date end def operacoes_label @tabelas.map { |t| Operacao.label(t) }.join(', ') end + # Faixa real das entregas carregadas (pela data de checkout) — para exibir o + # período natural da operação no cabeçalho/painel. + def data_inicio + datas_checkout.min + end + + def data_fim + datas_checkout.max + end + # Linhas cruas (Array de Hash com chave string) — base de todas as agregações. def registros @registros ||= carregar @@ -128,6 +141,10 @@ module Analytics private + def datas_checkout + @datas_checkout ||= registros.filter_map { |r| data_de(r['checkout']) } + end + def contagem(rows, coluna) rows.group_by { |r| r[coluna].presence || '—' } .map { |nome, grupo| { nome: nome, total: grupo.size } } @@ -160,18 +177,19 @@ module Analytics conn = ActiveRecord::Base.connection unions = @tabelas.map do |tabela| - gade = conn.quote_table_name(tabela) - label = conn.quote(Operacao.label(tabela)) + gade = conn.quote_table_name(tabela) + label = conn.quote(Operacao.label(tabela)) + status = coluna_status_gade(conn, tabela) <<~SQL.strip SELECT #{label} AS operacao, r.driver, r.status, r.observation, r.contact_name, r.checkout, r.planned_date, r.latitude, r.longitude, r.checkout_latitude, r.checkout_longitude, - g.status AS status_gade + #{status} AS status_gade FROM ultimo r INNER JOIN #{gade} g ON r.reference_id::text = g.nota_fiscal - WHERE r.rn = 1 AND (#{periodo_sql(conn)}) + WHERE r.rn = 1#{filtro_periodo(conn)} SQL end @@ -188,16 +206,24 @@ module Analytics conn.select_all(sql).to_a end + # g.status existe só em ALGUMAS tabelas de operação (ex.: ubs_norte). Quando a + # coluna não existe, devolve NULL — o painel "Por Status" cai no rótulo "—". + # (A única coluna garantida em toda tabela gade é nota_fiscal.) + def coluna_status_gade(conn, tabela) + colunas = conn.columns(tabela).map(&:name) + colunas.include?('status') ? 'g.status' : 'CAST(NULL AS text)' + end + + # Filtro de período OPCIONAL — usado só na visão Global (inicio/fim presentes). # Concluídas/falhas pela DATA REAL (checkout); pendentes (sem checkout) pela # data planejada. Espelha Entrega.no_periodo_checkout + no_periodo. - def periodo_sql(conn) + def filtro_periodo(conn) + return '' unless @inicio && @fim ini = conn.quote(@inicio) fim_excl = conn.quote(@fim + 1) fim_dia = conn.quote(@fim.end_of_day) - <<~SQL.strip - (r.checkout >= #{ini} AND r.checkout < #{fim_excl}) - OR (r.checkout IS NULL AND r.planned_date >= #{ini} AND r.planned_date <= #{fim_dia}) - SQL + " AND ((r.checkout >= #{ini} AND r.checkout < #{fim_excl})" \ + " OR (r.checkout IS NULL AND r.planned_date >= #{ini} AND r.planned_date <= #{fim_dia}))" end # Replica Entrega.da_conta_gade como fragmento "AND (account_id ...)". diff --git a/app/views/operacoes_dashboard/_painel.html.erb b/app/views/operacoes_dashboard/_painel.html.erb index 3132c04..ec2e4b5 100644 --- a/app/views/operacoes_dashboard/_painel.html.erb +++ b/app/views/operacoes_dashboard/_painel.html.erb @@ -7,7 +7,9 @@ <% if titulo.present? %>
- <%= @periodo_inicio.strftime('%d/%m/%Y') %> – <%= @periodo_fim.strftime('%d/%m/%Y') %> + <% if @modo == 'global' %> + 🌐 Global · <%= @periodo_inicio.strftime('%d/%m/%Y') %> – <%= @periodo_fim.strftime('%d/%m/%Y') %> + <% elsif @modo == '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') %> + <% elsif @operacao %> + <%= Operacao.label(@operacao) %> + <% end %> · Atualizado às <%= Time.now.strftime('%H:%M') %>