136 lines
5.4 KiB
Ruby
136 lines
5.4 KiB
Ruby
# app/services/analytics/totais_por_motorista.rb
|
||
#
|
||
# Quanto cada motorista somou no período — e QUAIS consolidações formam esse
|
||
# total. É a fonte única das duas telas de "total por motorista":
|
||
#
|
||
# • admin → /consolidacoes?visao=motoristas (modo de visualização da própria
|
||
# lista, com drill-down no motorista clicado)
|
||
# • motorista→ /motorista (o painel, já recortado nele — mesma conta, mesmo PDF)
|
||
#
|
||
# RECORTE — É O MESMO DO DASHBOARD FINANCEIRO, de propósito
|
||
# (DashboardController#carregar_dados_financeiro). Os dois números são lidos lado
|
||
# a lado pelo operador; quando divergem, a leitura é "o sistema está errado".
|
||
# Entram as consolidações ATIVAS que:
|
||
# • CRUZAM o período (data_inicio <= fim AND data_fim >= inicio) — um
|
||
# fechamento de 01/08 a 31/08 aparece ao filtrar 01/08–26/08; OU
|
||
# • tiveram algum PAGAMENTO dentro do período (pagar em agosto um fechamento
|
||
# de julho tem que aparecer em agosto).
|
||
#
|
||
# STATUS: por padrão só as FINALIZADAS — é o "Consolidado" do dashboard, o valor
|
||
# que já fechou. 'todos' inclui os rascunhos em andamento (a visão "Geral"), que
|
||
# ainda mudam de valor.
|
||
#
|
||
# Só motoristas ATIVOS (não arquivados) contam: é o mesmo conjunto que a
|
||
# consolidação paga (ConsolidacaoMotorista.ativos), então o total desta tela
|
||
# bate com a soma dos fechamentos.
|
||
module Analytics
|
||
class TotaisPorMotorista
|
||
# status: 'finalizada' (padrão — só o que já fechou), 'rascunho' ou
|
||
# 'todos'. É o MESMO select de status da lista de consolidações.
|
||
# motorista: recorte exato (drill-down / painel do motorista).
|
||
# busca: filtro parcial por nome, o campo de busca da lista.
|
||
def initialize(inicio:, fim:, motorista: nil, status: 'finalizada', busca: nil)
|
||
@inicio = inicio.to_date
|
||
@fim = fim.to_date
|
||
@motorista = motorista.presence
|
||
@status = status.presence || 'finalizada'
|
||
@busca = busca.presence
|
||
end
|
||
|
||
attr_reader :inicio, :fim, :motorista, :status
|
||
|
||
# ConsolidacaoMotorista do recorte, com a consolidação já carregada (as duas
|
||
# telas mostram nome/período/status dela em seguida).
|
||
def registros
|
||
@registros ||= begin
|
||
base = Consolidacao.ativas
|
||
# `public_send` sobre o enum: quem traduz :finalizada/:rascunho é o model.
|
||
base = base.public_send(@status) if %w[finalizada rascunho].include?(@status)
|
||
|
||
escopo = ConsolidacaoMotorista.ativos
|
||
.where(consolidacao_id: ids_do_periodo(base))
|
||
.includes(:consolidacao)
|
||
escopo = escopo.where(motorista_nome: @motorista) if @motorista
|
||
escopo = escopo.where('motorista_nome ILIKE ?', "%#{@busca}%") if @busca
|
||
escopo.to_a
|
||
end
|
||
end
|
||
|
||
# Uma linha por motorista, do maior total para o menor.
|
||
def linhas
|
||
@linhas ||= registros.group_by(&:motorista_nome).map do |nome, cms|
|
||
pagos = cms.select(&:pago?)
|
||
pendentes = cms.reject(&:pago?)
|
||
{
|
||
motorista: nome,
|
||
consolidacoes: cms.size,
|
||
lancamentos: lancamentos_por_motorista[nome].to_i,
|
||
valor_total: soma(cms),
|
||
valor_pago: soma(pagos),
|
||
valor_pendente: soma(pendentes),
|
||
pagas: pagos.size,
|
||
pendentes: pendentes.size
|
||
}
|
||
end.sort_by { |l| -l[:valor_total] }
|
||
end
|
||
|
||
# Consolidações que compõem o total de UM motorista (o drill-down), da mais
|
||
# recente para a mais antiga.
|
||
def consolidacoes_de(nome)
|
||
registros.select { |cm| cm.motorista_nome == nome }
|
||
.sort_by { |cm| [cm.consolidacao.data_fim, cm.consolidacao.data_inicio] }
|
||
.reverse
|
||
end
|
||
|
||
def motoristas
|
||
linhas.map { |l| l[:motorista] }
|
||
end
|
||
|
||
def total_geral = soma(registros)
|
||
def total_pago = soma(registros.select(&:pago?))
|
||
def total_pendente = soma(registros.reject(&:pago?))
|
||
def total_consolidacoes = registros.map(&:consolidacao_id).uniq.size
|
||
def vazio? = registros.empty?
|
||
|
||
def periodo_label
|
||
"#{@inicio.strftime('%d/%m/%Y')} a #{@fim.strftime('%d/%m/%Y')}"
|
||
end
|
||
|
||
private
|
||
|
||
# As consolidações do período: as que cruzam a faixa + as que receberam
|
||
# pagamento dentro dela. Mesmo par de conjuntos do dashboard financeiro.
|
||
def ids_do_periodo(base)
|
||
intervalo = @inicio.beginning_of_day..@fim.end_of_day
|
||
|
||
cruzam = base.cruzando_periodo(@inicio, @fim).ids
|
||
pagas = ConsolidacaoMotorista.ativos
|
||
.where(pago_em: intervalo)
|
||
.where(consolidacao_id: base.select(:id))
|
||
.distinct.pluck(:consolidacao_id)
|
||
(cruzam + pagas).uniq
|
||
end
|
||
|
||
def soma(cms)
|
||
cms.sum { |cm| cm.valor_total || 0 }
|
||
end
|
||
|
||
# Lançamentos classificados por motorista (soma da quantidade — um termo com
|
||
# quantidade 5 vale 5). Uma query só para todas as consolidações do recorte,
|
||
# em vez de uma por linha da tabela.
|
||
def lancamentos_por_motorista
|
||
@lancamentos_por_motorista ||= begin
|
||
ids = registros.map(&:consolidacao_id).uniq
|
||
if ids.empty?
|
||
{}
|
||
else
|
||
ConsolidacaoEntrega.where(consolidacao_id: ids,
|
||
motorista_nome: registros.map(&:motorista_nome).uniq)
|
||
.group(:motorista_nome)
|
||
.sum(:quantidade)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|