teste #11
@@ -227,6 +227,22 @@ class DashboardController < ApplicationController
|
|||||||
# Custo por motorista (todos)
|
# Custo por motorista (todos)
|
||||||
@fin_por_motorista = cms.group(:motorista_nome).sum(:valor_total)
|
@fin_por_motorista = cms.group(:motorista_nome).sum(:valor_total)
|
||||||
.sort_by { |_, v| -v }
|
.sort_by { |_, v| -v }
|
||||||
|
|
||||||
|
# Ranking CONSOLIDADO (aba "Consolidado" do card Motoristas): o valor REAL
|
||||||
|
# fechado por motorista + quantas entregas entraram nesse fechamento.
|
||||||
|
#
|
||||||
|
# ⚠️ A quantidade é DISTINCT tracking_id, NÃO a contagem de linhas: a mesma
|
||||||
|
# entrega pode ter vários pilares (Normal + Bônus + Retirada = 3 linhas em
|
||||||
|
# consolidacao_entregas), e contar linhas inflaria o número de entregas.
|
||||||
|
#
|
||||||
|
# Partimos de @fin_por_motorista (que vem de `cms`, já .ativos) e só buscamos
|
||||||
|
# a quantidade: assim a aba lista exatamente os motoristas do fechamento —
|
||||||
|
# arquivado não aparece — e a soma bate com o KPI "Custo total" do topo.
|
||||||
|
qtd_por_motorista = ces.group(:motorista_nome).distinct.count(:tracking_id)
|
||||||
|
|
||||||
|
@ranking_consolidado = @fin_por_motorista.map do |nome, valor|
|
||||||
|
{ nome: nome, valor: valor, entregas: qtd_por_motorista[nome].to_i }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# `atendidas` já vem filtrado por checkout no período (concluídas + falhadas).
|
# `atendidas` já vem filtrado por checkout no período (concluídas + falhadas).
|
||||||
|
|||||||
@@ -7,15 +7,27 @@ module Motorista
|
|||||||
def index
|
def index
|
||||||
nome = current_user.nome
|
nome = current_user.nome
|
||||||
|
|
||||||
# Card 1 — Valor ESTIMADO (mês atual): entregas pagas × preco_entrega
|
# 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_inicio = Date.current.beginning_of_month
|
||||||
@estimado_fim = Date.current
|
@estimado_fim = Date.current
|
||||||
entregas_mes = Entrega.pagas.da_conta_gade
|
atendidas = Entrega.atendidas.da_conta_gade
|
||||||
.do_motorista(nome)
|
.do_motorista(nome)
|
||||||
.no_periodo(@estimado_inicio, @estimado_fim)
|
.no_periodo_checkout(@estimado_inicio, @estimado_fim)
|
||||||
.count
|
@entregas_concluidas = atendidas.concluidas.count
|
||||||
@valor_estimado = entregas_mes * Configuracao.preco_entrega
|
@entregas_falhadas = atendidas.falhadas.count
|
||||||
@entregas_mes = entregas_mes
|
@entregas_mes = @entregas_concluidas + @entregas_falhadas
|
||||||
|
@valor_estimado = @entregas_mes * Configuracao.preco_entrega
|
||||||
|
|
||||||
# Card 2 — Valor CONSOLIDADO: soma das consolidações finalizadas
|
# Card 2 — Valor CONSOLIDADO: soma das consolidações finalizadas
|
||||||
@minhas_consolidacoes = Consolidacao.ativas
|
@minhas_consolidacoes = Consolidacao.ativas
|
||||||
|
|||||||
42
app/views/dashboard/_ranking_motoristas.html.erb
Normal file
42
app/views/dashboard/_ranking_motoristas.html.erb
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<%# Lista do ranking de motoristas — markup COMPARTILHADO pelas abas "Estimado" e
|
||||||
|
"Consolidado" do dashboard. Os dois conjuntos têm a mesma forma
|
||||||
|
(:nome, :valor, :entregas); só muda a origem dos números, então duplicar o
|
||||||
|
markup faria as duas abas divergirem visualmente na primeira alteração.
|
||||||
|
|
||||||
|
Locais: itens (array de hashes), vazio (texto do estado sem dados). %>
|
||||||
|
<% if itens.any? %>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<%# Base da barra: o MAIOR nº de entregas da aba, não o primeiro item — a aba
|
||||||
|
consolidada é ordenada por VALOR, então o primeiro pode não ser o maior em
|
||||||
|
quantidade e a barra passaria de 100%. Sem entregas (só bônus/desconto,
|
||||||
|
por exemplo) a barra fica vazia em vez de dividir por zero. %>
|
||||||
|
<% max = itens.map { |x| x[:entregas].to_i }.max.to_f %>
|
||||||
|
<% itens.each_with_index do |m, i| %>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="text-xs font-bold w-5 text-center
|
||||||
|
<%= i == 0 ? 'text-yellow-400' : i == 1 ? 'text-gray-300' : i == 2 ? 'text-orange-600' : 'text-gray-400' %>">
|
||||||
|
<%= i + 1 %>
|
||||||
|
</span>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="flex items-center justify-between mb-1">
|
||||||
|
<span class="text-white text-sm font-medium truncate"><%= m[:nome] %></span>
|
||||||
|
<span class="text-[#f97316] text-sm font-semibold ml-2 flex-shrink-0">
|
||||||
|
<%= moeda(m[:valor]) %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<%# Barra de progresso %>
|
||||||
|
<div class="w-full bg-white/5 rounded-full h-1.5">
|
||||||
|
<div class="h-1.5 rounded-full bg-[#f97316]"
|
||||||
|
style="width: <%= max.zero? ? 0 : [(m[:entregas] / max * 100).round, 100].min %>%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-gray-400 text-xs"><%= m[:entregas] %> entregas</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="text-center py-8 text-gray-400">
|
||||||
|
<div class="mb-2"><%= icone :caminhao, tamanho: 'w-8 h-8', cor: 'text-gray-600', espaco: false %></div>
|
||||||
|
<p class="text-sm"><%= vazio %></p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@@ -262,42 +262,48 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%# Top motoristas %>
|
<%# Top motoristas — DUAS ABAS sobre o mesmo ranking:
|
||||||
|
• Estimado = entregas atendidas × preço da entrega (mesma base do card
|
||||||
|
laranja do topo).
|
||||||
|
• Consolidado = o que foi REALMENTE fechado nas consolidações do período,
|
||||||
|
já com bônus/desconto/retirada aplicados, e a quantidade
|
||||||
|
exata de entregas que entrou no fechamento.
|
||||||
|
Os dois números divergem DE PROPÓSITO: o estimado cobre tudo que foi
|
||||||
|
atendido no período; o consolidado, só o que já entrou em consolidação
|
||||||
|
finalizada. Por isso cada aba diz de onde vem o seu número. %>
|
||||||
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
|
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
|
||||||
<h2 class="text-lg font-semibold text-white mb-4 flex items-center gap-2"><%= icone :ranking, espaco: false %> Motoristas</h2>
|
<div class="flex flex-wrap items-center justify-between gap-2 mb-4">
|
||||||
|
<h2 class="text-lg font-semibold text-white flex items-center gap-2"><%= icone :ranking, espaco: false %> Motoristas</h2>
|
||||||
|
|
||||||
<% if @motoristas.any? %>
|
<%# Mesmas pílulas da paginação de pagamentos (as classes estão duplicadas
|
||||||
<div class="space-y-3">
|
no JS que troca a aba — mexeu aqui, mexa lá). %>
|
||||||
<% @motoristas.each_with_index do |m, i| %>
|
<div id="ranking-abas" class="flex gap-1.5">
|
||||||
<div class="flex items-center gap-3">
|
<button type="button" data-aba="estimado"
|
||||||
<span class="text-xs font-bold w-5 text-center
|
class="px-3 py-1.5 rounded-lg text-xs font-semibold transition-colors bg-[#f97316] text-white">
|
||||||
<%= i == 0 ? 'text-yellow-400' : i == 1 ? 'text-gray-300' : i == 2 ? 'text-orange-600' : 'text-gray-400' %>">
|
Estimado
|
||||||
<%= i + 1 %>
|
</button>
|
||||||
</span>
|
<button type="button" data-aba="consolidado"
|
||||||
<div class="flex-1 min-w-0">
|
class="px-3 py-1.5 rounded-lg text-xs font-semibold transition-colors bg-[#0a0a0a] text-gray-400 border border-white/5 hover:text-white">
|
||||||
<div class="flex items-center justify-between mb-1">
|
Consolidado
|
||||||
<span class="text-white text-sm font-medium truncate"><%= m[:nome] %></span>
|
</button>
|
||||||
<span class="text-[#f97316] text-sm font-semibold ml-2 flex-shrink-0">
|
|
||||||
<%= moeda(m[:valor]) %>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<%# Barra de progresso %>
|
|
||||||
<div class="w-full bg-white/5 rounded-full h-1.5">
|
|
||||||
<% max = @motoristas.first[:entregas].to_f %>
|
|
||||||
<div class="h-1.5 rounded-full bg-[#f97316]"
|
|
||||||
style="width: <%= [(m[:entregas] / max * 100).round, 100].min %>%"></div>
|
|
||||||
</div>
|
|
||||||
<span class="text-gray-400 text-xs"><%= m[:entregas] %> entregas</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
</div>
|
||||||
<div class="text-center py-8 text-gray-400">
|
|
||||||
<div class="mb-2"><%= icone :caminhao, tamanho: 'w-8 h-8', cor: 'text-gray-600', espaco: false %></div>
|
<div id="ranking-painel-estimado">
|
||||||
<p class="text-sm">Nenhuma entrega concluída neste período</p>
|
<p class="text-gray-400 text-xs mb-3">Entregas atendidas × preço da entrega</p>
|
||||||
</div>
|
<%= render 'ranking_motoristas',
|
||||||
<% end %>
|
itens: @motoristas,
|
||||||
|
vazio: 'Nenhuma entrega atendida neste período' %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%# Sem JS a aba fica escondida — aceitável: o mesmo dado consolidado
|
||||||
|
também aparece no gráfico "Custo por motorista" logo abaixo. %>
|
||||||
|
<div id="ranking-painel-consolidado" class="hidden">
|
||||||
|
<p class="text-gray-400 text-xs mb-3">Valor fechado nas consolidações do período</p>
|
||||||
|
<%= render 'ranking_motoristas',
|
||||||
|
itens: @ranking_consolidado,
|
||||||
|
vazio: 'Nenhuma consolidação finalizada neste período' %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -577,6 +583,38 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<%# Abas do ranking de motoristas (Estimado × Consolidado) %>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
// As classes repetem as do ERB porque a troca de aba reescreve o className
|
||||||
|
// inteiro — mesmo padrão da paginação de pagamentos acima.
|
||||||
|
const ATIVA = 'px-3 py-1.5 rounded-lg text-xs font-semibold transition-colors bg-[#f97316] text-white';
|
||||||
|
const INATIVA = 'px-3 py-1.5 rounded-lg text-xs font-semibold transition-colors bg-[#0a0a0a] text-gray-400 border border-white/5 hover:text-white';
|
||||||
|
|
||||||
|
function initAbasRanking() {
|
||||||
|
const abas = document.getElementById('ranking-abas');
|
||||||
|
if (!abas || abas.dataset.iniciado) return;
|
||||||
|
abas.dataset.iniciado = '1';
|
||||||
|
|
||||||
|
const botoes = Array.from(abas.querySelectorAll('button'));
|
||||||
|
|
||||||
|
botoes.forEach(function (btn) {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
botoes.forEach(function (b) {
|
||||||
|
const ativa = (b === btn);
|
||||||
|
const painel = document.getElementById('ranking-painel-' + b.dataset.aba);
|
||||||
|
b.className = ativa ? ATIVA : INATIVA;
|
||||||
|
if (painel) painel.classList.toggle('hidden', !ativa);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initAbasRanking();
|
||||||
|
document.addEventListener('turbo:load', initAbasRanking);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<%# Filtro de operação — submete o form ao marcar/desmarcar %>
|
<%# Filtro de operação — submete o form ao marcar/desmarcar %>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
|
|||||||
@@ -36,8 +36,15 @@
|
|||||||
<p class="text-black/80 font-bold uppercase tracking-wide flex items-center gap-2"><%= icone :dinheiro, cor: nil, espaco: false %> Valor estimado deste mês</p>
|
<p class="text-black/80 font-bold uppercase tracking-wide flex items-center gap-2"><%= icone :dinheiro, cor: nil, espaco: false %> Valor estimado deste mês</p>
|
||||||
<p class="text-black/70 text-sm mt-1"><%= icone :calendario, cor: nil %> <%= l @estimado_inicio, format: :short %> a <%= l @estimado_fim, format: :short %></p>
|
<p class="text-black/70 text-sm mt-1"><%= icone :calendario, cor: nil %> <%= l @estimado_inicio, format: :short %> a <%= l @estimado_fim, format: :short %></p>
|
||||||
<p class="text-black font-black text-4xl sm:text-5xl mt-2 leading-tight whitespace-nowrap"><%= moeda(@valor_estimado) %></p>
|
<p class="text-black font-black text-4xl sm:text-5xl mt-2 leading-tight whitespace-nowrap"><%= moeda(@valor_estimado) %></p>
|
||||||
|
<%# "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. %>
|
||||||
<p class="text-black/80 text-base mt-2">
|
<p class="text-black/80 text-base mt-2">
|
||||||
<%= @entregas_mes %> entregas feitas e confirmadas
|
<%= @entregas_mes %> entregas atendidas
|
||||||
|
</p>
|
||||||
|
<p class="text-black/70 text-sm mt-1">
|
||||||
|
<%= @entregas_concluidas %> entregues · <%= @entregas_falhadas %> sem sucesso (pagas também)
|
||||||
</p>
|
</p>
|
||||||
<p class="text-black/60 text-sm mt-3">
|
<p class="text-black/60 text-sm mt-3">
|
||||||
<%= icone :alerta, cor: nil %> Valor aproximado. O valor certo sai quando a empresa fechar o pagamento.
|
<%= icone :alerta, cor: nil %> Valor aproximado. O valor certo sai quando a empresa fechar o pagamento.
|
||||||
|
|||||||
@@ -120,3 +120,101 @@ RSpec.describe 'Dashboard — contabilização de falhas', type: :request do
|
|||||||
expect(corpo).to include('R$ 30,00') # total estimado do período
|
expect(corpo).to include('R$ 30,00') # total estimado do período
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Trava a ABA "CONSOLIDADO" do card de motoristas.
|
||||||
|
#
|
||||||
|
# O card tem duas abas sobre o mesmo ranking e elas mostram números diferentes de
|
||||||
|
# propósito: "Estimado" = atendidas × preço; "Consolidado" = o que realmente foi
|
||||||
|
# fechado (com bônus/desconto/retirada) + a quantidade de entregas do fechamento.
|
||||||
|
# Confundir as duas é justamente o que este spec impede.
|
||||||
|
RSpec.describe 'Dashboard — aba Consolidado do ranking de motoristas', type: :request do
|
||||||
|
include_context 'espelho de rastreio'
|
||||||
|
|
||||||
|
let(:inicio) { '2026-08-01' }
|
||||||
|
let(:fim) { '2026-08-14' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:configuracao, chave: 'preco_entrega', valor: '10.0')
|
||||||
|
sign_in create(:gerente)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Só o conteúdo da aba consolidada: a aba estimada renderiza o MESMO markup
|
||||||
|
# (moeda + "N entregas"), então asserção no body inteiro passaria por acidente.
|
||||||
|
def painel_consolidado
|
||||||
|
get dashboard_path, params: { inicio: inicio, fim: fim }
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
Nokogiri::HTML(response.body).at_css('#ranking-painel-consolidado').text
|
||||||
|
end
|
||||||
|
|
||||||
|
# cria as entregas ANTES do motorista: ConsolidacaoMotorista#recalcular_valor
|
||||||
|
# (before_save) soma as consolidacao_entregas do mesmo nome — sem elas o
|
||||||
|
# valor_total nasceria zerado.
|
||||||
|
def consolidar!(nome, linhas, consolidacao)
|
||||||
|
linhas.each { |l| create(:consolidacao_entrega, { consolidacao: consolidacao, motorista_nome: nome }.merge(l)) }
|
||||||
|
create(:consolidacao_motorista, consolidacao: consolidacao, motorista_nome: nome)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:consolidacao) do
|
||||||
|
create(:consolidacao, status: :finalizada,
|
||||||
|
data_inicio: Date.new(2026, 8, 1), data_fim: Date.new(2026, 8, 14))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'mostra o valor fechado e a quantidade exata de entregas de cada motorista' do
|
||||||
|
# JOSE: 2 entregas (TRK-A e TRK-B). TRK-A tem DOIS pilares (normal + bônus),
|
||||||
|
# então são 3 linhas para 2 entregas — é o caso que quebra se alguém contar
|
||||||
|
# linhas em vez de tracking_id distinto.
|
||||||
|
consolidar!('JOSE DA SILVA', [
|
||||||
|
{ tracking_id: 'TRK-A', tipo: :entrega_normal, valor_aplicado: 10.0 },
|
||||||
|
{ tracking_id: 'TRK-A', tipo: :bonus, valor_aplicado: 5.0 },
|
||||||
|
{ tracking_id: 'TRK-B', tipo: :entrega_normal, valor_aplicado: 10.0 }
|
||||||
|
], consolidacao)
|
||||||
|
|
||||||
|
consolidar!('MARIA SOUZA', [
|
||||||
|
{ tracking_id: 'TRK-C', tipo: :entrega_normal, valor_aplicado: 10.0 }
|
||||||
|
], consolidacao)
|
||||||
|
|
||||||
|
texto = painel_consolidado
|
||||||
|
|
||||||
|
expect(texto).to include('JOSE DA SILVA')
|
||||||
|
expect(texto).to include('R$ 25,00') # 10 + 5 + 10, valor REAL do fechamento
|
||||||
|
expect(texto).to include('2 entregas') # e NÃO 3: TRK-A conta uma vez só
|
||||||
|
|
||||||
|
expect(texto).to include('MARIA SOUZA')
|
||||||
|
expect(texto).to include('R$ 10,00')
|
||||||
|
expect(texto).to include('1 entregas')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'subtrai o desconto do valor fechado' do
|
||||||
|
consolidar!('JOSE DA SILVA', [
|
||||||
|
{ tracking_id: 'TRK-A', tipo: :entrega_normal, valor_aplicado: 10.0 },
|
||||||
|
{ tracking_id: 'TRK-A', tipo: :desconto, valor_aplicado: 4.0 }
|
||||||
|
], consolidacao)
|
||||||
|
|
||||||
|
expect(painel_consolidado).to include('R$ 6,00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'não lista motorista arquivado, que saiu do fechamento' do
|
||||||
|
consolidar!('JOSE DA SILVA', [
|
||||||
|
{ tracking_id: 'TRK-A', tipo: :entrega_normal, valor_aplicado: 10.0 }
|
||||||
|
], consolidacao)
|
||||||
|
cm = consolidar!('MARIA SOUZA', [
|
||||||
|
{ tracking_id: 'TRK-C', tipo: :entrega_normal, valor_aplicado: 10.0 }
|
||||||
|
], consolidacao)
|
||||||
|
cm.arquivar!(nil)
|
||||||
|
|
||||||
|
texto = painel_consolidado
|
||||||
|
|
||||||
|
expect(texto).to include('JOSE DA SILVA')
|
||||||
|
expect(texto).not_to include('MARIA SOUZA')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'ignora consolidação em rascunho — só entra o que foi finalizado' do
|
||||||
|
rascunho = create(:consolidacao, status: :rascunho,
|
||||||
|
data_inicio: Date.new(2026, 8, 1), data_fim: Date.new(2026, 8, 14))
|
||||||
|
consolidar!('JOSE DA SILVA', [
|
||||||
|
{ tracking_id: 'TRK-A', tipo: :entrega_normal, valor_aplicado: 10.0 }
|
||||||
|
], rascunho)
|
||||||
|
|
||||||
|
expect(painel_consolidado).to include('Nenhuma consolidação finalizada neste período')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
107
spec/requests/motorista_dashboard_spec.rb
Normal file
107
spec/requests/motorista_dashboard_spec.rb
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
# Trava a CONTABILIZAÇÃO DAS ENTREGAS SEM SUCESSO no painel do MOTORISTA.
|
||||||
|
#
|
||||||
|
# O painel do motorista ficou para trás quando o dashboard administrativo passou
|
||||||
|
# a usar `atendidas` (ver spec/requests/dashboard_spec.rb): continuava em `pagas`
|
||||||
|
# (só concluídas) e no eixo `planned_date`. O motorista via um valor MENOR do que
|
||||||
|
# ia receber — e a diferença só aparecia no fechamento, sem explicação.
|
||||||
|
#
|
||||||
|
# ⚠️ Sem sucesso TEM checkout: o motorista foi ao local e fechou a visita com
|
||||||
|
# motivo de insucesso. O deslocamento teve custo, a empresa paga, e o checkout é
|
||||||
|
# o eixo de data correto também para essas.
|
||||||
|
RSpec.describe 'Painel do motorista — contabilização das entregas sem sucesso', type: :request do
|
||||||
|
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.
|
||||||
|
let(:hoje) { Date.new(2026, 8, 20) }
|
||||||
|
let(:preco) { 10.0 }
|
||||||
|
let(:nome) { 'JOSE DA SILVA' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
travel_to(hoje)
|
||||||
|
create(:configuracao, chave: 'preco_entrega', valor: preco.to_s)
|
||||||
|
sign_in create(:motorista, nome: nome)
|
||||||
|
end
|
||||||
|
|
||||||
|
after { travel_back }
|
||||||
|
|
||||||
|
def abrir_painel
|
||||||
|
get motorista_dashboard_path
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
response.body
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'soma a entrega sem sucesso ao valor estimado' do
|
||||||
|
semear([
|
||||||
|
{ driver: nome, vehicle: 'GADE_001', status: 'completed',
|
||||||
|
planned_date: Time.zone.local(2026, 8, 5, 8, 0), checkout: Time.zone.local(2026, 8, 5, 10, 0) },
|
||||||
|
{ driver: nome, vehicle: 'GADE_001', status: 'failed',
|
||||||
|
planned_date: Time.zone.local(2026, 8, 6, 8, 0), checkout: Time.zone.local(2026, 8, 6, 11, 0) }
|
||||||
|
])
|
||||||
|
|
||||||
|
corpo = abrir_painel
|
||||||
|
|
||||||
|
# 2 atendidas × R$ 10,00 — com a base antiga (`pagas`) seria 1 e R$ 10,00.
|
||||||
|
expect(corpo).to include('2 entregas atendidas')
|
||||||
|
expect(corpo).to include('1 entregues · 1 sem sucesso')
|
||||||
|
expect(corpo).to include('R$ 20,00')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'conta a sem sucesso pela data do CHECKOUT, não pela planejada' do
|
||||||
|
# Planejada em 31/07 (fora do mês), fechada em 01/08 (dentro) → entra.
|
||||||
|
semear([
|
||||||
|
{ driver: nome, vehicle: 'GADE_002', status: 'failed',
|
||||||
|
planned_date: Time.zone.local(2026, 7, 31, 8, 0), checkout: Time.zone.local(2026, 8, 1, 9, 0) }
|
||||||
|
])
|
||||||
|
|
||||||
|
corpo = abrir_painel
|
||||||
|
|
||||||
|
expect(corpo).to include('1 entregas atendidas')
|
||||||
|
expect(corpo).to include('0 entregues · 1 sem sucesso')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'exclui a sem sucesso planejada no mês mas com checkout fora' do
|
||||||
|
# O espelho do caso acima: planejada em 10/08 (dentro), fechada em 01/09
|
||||||
|
# (fora) → não entra. Com o eixo antigo (planned_date) entrava.
|
||||||
|
semear([
|
||||||
|
{ driver: nome, vehicle: 'GADE_003', status: 'failed',
|
||||||
|
planned_date: Time.zone.local(2026, 8, 10, 8, 0), checkout: Time.zone.local(2026, 9, 1, 9, 0) }
|
||||||
|
])
|
||||||
|
|
||||||
|
corpo = abrir_painel
|
||||||
|
|
||||||
|
expect(corpo).to include('0 entregas atendidas')
|
||||||
|
expect(corpo).to include('0 entregues · 0 sem sucesso')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'não conta a entrega pendente, que ainda não tem checkout' do
|
||||||
|
semear([
|
||||||
|
{ driver: nome, vehicle: 'GADE_004', status: 'pending',
|
||||||
|
planned_date: Time.zone.local(2026, 8, 7, 8, 0), checkout: nil }
|
||||||
|
])
|
||||||
|
|
||||||
|
corpo = abrir_painel
|
||||||
|
|
||||||
|
expect(corpo).to include('0 entregas atendidas')
|
||||||
|
expect(corpo).to include('0 entregues · 0 sem sucesso')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'não mistura a entrega sem sucesso de outro motorista' do
|
||||||
|
semear([
|
||||||
|
{ driver: nome, vehicle: 'GADE_005', status: 'completed',
|
||||||
|
planned_date: Time.zone.local(2026, 8, 4, 8, 0), checkout: Time.zone.local(2026, 8, 4, 10, 0) },
|
||||||
|
{ driver: 'MARIA SOUZA', vehicle: 'GADE_006', status: 'failed',
|
||||||
|
planned_date: Time.zone.local(2026, 8, 4, 8, 0), checkout: Time.zone.local(2026, 8, 4, 15, 0) }
|
||||||
|
])
|
||||||
|
|
||||||
|
corpo = abrir_painel
|
||||||
|
|
||||||
|
expect(corpo).to include('1 entregas atendidas')
|
||||||
|
expect(corpo).to include('1 entregues · 0 sem sucesso')
|
||||||
|
expect(corpo).to include('R$ 10,00')
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user