Criação da visão geral da consolidação por motorista

This commit is contained in:
2026-08-26 11:15:52 -03:00
parent 8d24a57579
commit cd99a22553
12 changed files with 841 additions and 3 deletions

View File

@@ -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

View File

@@ -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