Imṕlantação do dash board de acompanhamento diario das entregas

This commit is contained in:
2026-06-29 20:04:32 -03:00
parent 16032be620
commit 11531479f8
35 changed files with 848 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
require 'rails_helper'
RSpec.describe Analytics::OperacaoMetricas do
# Linhas simuladas (mesma forma do retorno de select_all) — evita depender das
# tabelas externas no teste. Stubamos #registros para focar nas agregações.
let(:rows) do
[
row(status: 'completed', driver: 'Carlos', sts: 'STS PERUS', gade: 'RECORRENTE', checkout: '2026-06-22 10:00:00', clat: '-23.5', clng: '-46.6'),
row(status: 'completed', driver: 'Carlos', sts: 'STS PERUS', gade: 'RECORRENTE', checkout: '2026-06-22 11:00:00', lat: '-23.6', lng: '-46.7'),
row(status: 'completed', driver: 'Pedro', sts: 'STS PIRITUBA', gade: 'RECORRENTE', checkout: '2026-06-23 09:00:00', clat: '-23.4', clng: '-46.5'),
row(status: 'failed', driver: 'Pedro', sts: 'STS PIRITUBA', gade: 'NOVO', obs: 'ÓBITO', checkout: '2026-06-23 12:00:00'),
row(status: 'failed', driver: 'Carlos', sts: 'STS PERUS', gade: 'NOVO', obs: 'ÓBITO', checkout: '2026-06-22 13:00:00'),
row(status: 'pending', driver: 'Marcos', sts: 'STS PERUS', gade: 'NOVO', planned: '2026-06-24')
]
end
subject(:metricas) do
described_class.new(tabelas: [], inicio: Date.new(2026, 6, 1), fim: Date.new(2026, 6, 30))
end
before do
# Isola das tabelas externas: não toca o catálogo nem db_reem_simplerout_2026.
allow(Operacao).to receive(:sanitizar).and_return([])
allow(metricas).to receive(:registros).and_return(rows)
end
it 'conta KPIs (total/sucesso/recusas/pendentes)' do
expect(metricas.total).to eq(6)
expect(metricas.sucesso).to eq(3)
expect(metricas.recusas).to eq(2)
expect(metricas.pendentes).to eq(1)
end
it 'calcula insucessos %' do
ins = metricas.insucessos_pct
expect(ins[:completed]).to eq(3)
expect(ins[:failed]).to eq(2)
expect(ins[:pct_completed]).to eq(50.0)
expect(ins[:pct_failed]).to eq(33.33)
end
it 'agrupa índices de falha por observação' do
expect(metricas.indices_falha).to eq([{ observation: 'ÓBITO', total: 2, pct: 100.0 }])
end
it 'agrupa por status_gade' do
expect(metricas.por_status_gade).to contain_exactly(
{ status: 'RECORRENTE', total: 3 },
{ status: 'NOVO', total: 3 }
)
end
it 'conta NFs por motorista (todas), desc' do
expect(metricas.por_motorista.first).to eq({ nome: 'Carlos', total: 3 })
expect(metricas.por_motorista.map { |h| h[:nome] }).to eq(%w[Carlos Pedro Marcos])
end
it 'conta entregas concluídas por STS (contact_name)' do
expect(metricas.por_sts).to eq([
{ nome: 'STS PERUS', total: 2 },
{ nome: 'STS PIRITUBA', total: 1 }
])
end
it 'agrupa por dia (completas x falhas)' do
pd = metricas.por_dia
expect(pd[:labels]).to eq(['22/06/2026', '23/06/2026'])
expect(pd[:completed]).to eq([2, 1])
expect(pd[:failed]).to eq([1, 1])
end
it 'monta pontos do mapa (prefere checkout, cai p/ checkin)' do
expect(metricas.pontos_mapa).to eq([[-23.5, -46.6], [-23.6, -46.7], [-23.4, -46.5]])
end
context 'sem registros' do
before { allow(metricas).to receive(:registros).and_return([]) }
it 'não divide por zero' do
expect(metricas.total).to eq(0)
expect(metricas.insucessos_pct[:pct_failed]).to eq(0.0)
expect(metricas.indices_falha).to eq([])
expect(metricas.pontos_mapa).to eq([])
expect(metricas.centro_mapa).to eq(described_class::SP_CENTRO)
end
end
def row(status:, driver:, sts:, gade:, obs: nil, checkout: nil, planned: nil, lat: nil, lng: nil, clat: nil, clng: nil)
{
'status' => status, 'driver' => driver, 'contact_name' => sts, 'status_gade' => gade,
'observation' => obs, 'checkout' => checkout, 'planned_date' => planned,
'latitude' => lat, 'longitude' => lng,
'checkout_latitude' => clat, 'checkout_longitude' => clng
}
end
end