112 lines
4.2 KiB
Ruby
112 lines
4.2 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/totais (todos os motoristas + drill-down)
|
|
# • motorista→ /motorista/totais (só ele, mesma conta, mesmo PDF)
|
|
#
|
|
# RECORTE (igual ao filtro da lista de consolidações): entram as consolidações
|
|
# ATIVAS cujo período está DENTRO da faixa escolhida (data_inicio >= inicio e
|
|
# data_fim <= fim). Não é sobreposição de propósito — uma consolidação que
|
|
# atravessa a borda do filtro entraria inteira e o "total do período" ficaria
|
|
# maior que o período.
|
|
#
|
|
# 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
|
|
def initialize(inicio:, fim:, motorista: nil, incluir_rascunhos: false)
|
|
@inicio = inicio.to_date
|
|
@fim = fim.to_date
|
|
@motorista = motorista.presence
|
|
@incluir_rascunhos = incluir_rascunhos
|
|
end
|
|
|
|
attr_reader :inicio, :fim, :motorista
|
|
|
|
# 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.no_periodo(@inicio, @fim)
|
|
# Só o que já foi FECHADO conta como total do período; rascunho ainda
|
|
# muda de valor. `merge` (e não where(consolidacoes: {status: ...}))
|
|
# porque status é enum: quem sabe traduzir :finalizada é o próprio model.
|
|
base = base.finalizada unless @incluir_rascunhos
|
|
|
|
escopo = ConsolidacaoMotorista.ativos
|
|
.joins(:consolidacao)
|
|
.merge(base)
|
|
.includes(:consolidacao)
|
|
escopo = escopo.where(motorista_nome: @motorista) if @motorista
|
|
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
|
|
|
|
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
|