diff --git a/Imagens para implantação /Imagem colada (8).png b/Imagens para implantação /Imagem colada (8).png new file mode 100644 index 0000000..ef2166a Binary files /dev/null and b/Imagens para implantação /Imagem colada (8).png differ diff --git a/app/services/analytics/operacao_metricas.rb b/app/services/analytics/operacao_metricas.rb index 35a126b..744a6ed 100644 --- a/app/services/analytics/operacao_metricas.rb +++ b/app/services/analytics/operacao_metricas.rb @@ -162,6 +162,30 @@ module Analytics pontos_mapa.first || SP_CENTRO end + # Limite de marcadores no modo "Pontos" (Leaflet fica pesado com milhares). + MAX_PONTOS = 2000 + + # Pontos detalhados (modo "Pontos" do mapa) — um por entrega CONCLUÍDA com + # coordenada de CHECKOUT (onde a entrega foi registrada). Inclui dados para o + # popup (NF, destinatário, endereço, unidade, motorista, data). + def pontos_detalhados + @pontos_detalhados ||= linhas.filter_map do |r| + next unless r['status'] == 'completed' + lat = num(r['checkout_latitude']) || num(r['latitude']) + lng = num(r['checkout_longitude']) || num(r['longitude']) + next if lat.nil? || lng.nil? || (lat.zero? && lng.zero?) + { + lat: lat, lng: lng, + nf: r['reference_id'], + nome: r['nome_completo'].presence || r['contact_name'].presence || '—', + endereco: r['endereco_completo'].presence || r['address'].presence, + unidade: r['contact_name'], + motorista: r['driver'], + checkout: data_de(r['checkout'])&.strftime('%d/%m/%Y') + } + end.first(MAX_PONTOS) + end + private def datas_checkout @@ -200,16 +224,15 @@ module Analytics conn = ActiveRecord::Base.connection unions = @tabelas.map do |tabela| - gade = conn.quote_table_name(tabela) - label = conn.quote(Operacao.label(tabela)) - status = coluna_status_gade(conn, tabela) + gade = conn.quote_table_name(tabela) + label = conn.quote(Operacao.label(tabela)) <<~SQL.strip SELECT #{label} AS operacao, - r.driver, r.status, r.observation, r.contact_name, + r.reference_id, r.driver, r.status, r.observation, r.contact_name, r.address, r.checkout, r.planned_date, r.latitude, r.longitude, r.checkout_latitude, r.checkout_longitude, - #{status} AS status_gade + #{selects_gade(conn, tabela)} FROM ultimo r INNER JOIN #{gade} g ON r.reference_id::text = g.nota_fiscal WHERE r.rn = 1#{filtro_periodo(conn)} @@ -231,12 +254,19 @@ 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 extras da tabela gade que só existem em ALGUMAS operações (ex.: + # ubs_norte). Quando a coluna não existe, devolve NULL com o mesmo alias — assim + # o UNION entre operações continua consistente. (A única coluna garantida em + # toda tabela gade é nota_fiscal.) As chaves são uma whitelist fixa (não entram + # dados do usuário no SQL). + GADE_OPCIONAIS = { 'status' => 'status_gade', 'nome_completo' => 'nome_completo', + 'endereco_completo' => 'endereco_completo' }.freeze + + def selects_gade(conn, tabela) colunas = conn.columns(tabela).map(&:name) - colunas.include?('status') ? 'g.status' : 'CAST(NULL AS text)' + GADE_OPCIONAIS.map do |origem, apelido| + colunas.include?(origem) ? "g.#{origem} AS #{apelido}" : "CAST(NULL AS text) AS #{apelido}" + end.join(", ") end # Filtro de período OPCIONAL — usado só na visão Global (inicio/fim presentes). diff --git a/app/views/operacoes_dashboard/_mapa.html.erb b/app/views/operacoes_dashboard/_mapa.html.erb index 0e196ea..027bdea 100644 --- a/app/views/operacoes_dashboard/_mapa.html.erb +++ b/app/views/operacoes_dashboard/_mapa.html.erb @@ -1,15 +1,31 @@ <%# locals: metricas (Analytics::OperacaoMetricas) %> -<% pontos = metricas.pontos_mapa %> -
Mapa de Calor de Entregas
- <%= pontos.size %> pontos +<% heat = metricas.pontos_mapa %> +<% pontos = metricas.pontos_detalhados %> +Mapa de Entregas
+Sem coordenadas para exibir no período.
<% else %> - ++ 🔥 Calor mostra a densidade · 📍 Pontos mostra cada entrega (clique no marcador para detalhes; coordenada de check-out). +
<% end %>