Grande correção do filtro de veiculos na area de consolidação

This commit is contained in:
2026-07-03 09:58:59 -03:00
parent 83310e9e66
commit eca6df39eb
17 changed files with 681 additions and 41 deletions

View File

@@ -8,6 +8,11 @@ class Consolidacao < ApplicationRecord
has_many :consolidacao_motoristas, dependent: :destroy
has_many :consolidacao_entregas, dependent: :destroy
has_many :consolidacao_veiculos, dependent: :destroy
# Bucket dos lançamentos sem veículo (termo/apontamento manual legado). Mesmo
# sentinel do controller e de ConsolidacaoVeiculo.
SEM_VEICULO = ConsolidacaoVeiculo::SEM_VEICULO
# ── Enums ───────────────────────────────────────────────────
enum status: { rascunho: 0, finalizada: 1, arquivada: 2 }
@@ -111,21 +116,34 @@ class Consolidacao < ApplicationRecord
def finalizar!(user)
return false unless todas_entregas_classificadas?
update!(
status: :finalizada,
finalizado_por: user.id,
finalizado_em: Time.current
)
transaction do
# Finalizar a consolidação fecha (consolida) os veículos que ainda estavam
# abertos — mantém o estado por veículo coerente com o todo finalizado.
consolidacao_veiculos.ativos.abertos.update_all(
fechado_em: Time.current, fechado_por: user.id, updated_at: Time.current
)
update!(
status: :finalizada,
finalizado_por: user.id,
finalizado_em: Time.current
)
end
end
# Só está completa quando CADA motorista tem todas as suas entregas elegíveis
# classificadas. (Antes era uma soma agregada com >=, que mascarava motoristas
# pendentes quando outro tinha entregas a mais.)
# Só está completa quando CADA motorista ativo está completo (nada por
# classificar, descontados os veículos arquivados). Fechar veículo é um passo
# independente; ao finalizar, os veículos abertos são consolidados juntos.
def todas_entregas_classificadas?
consolidacao_motoristas.ativos.all? do |cm|
total = entregas_elegiveis_count(cm.motorista_nome)
total.zero? || classificadas_count(cm.motorista_nome) >= total
end
consolidacao_motoristas.ativos.all? { |cm| motorista_completo?(cm.motorista_nome) }
end
# Completude de UM motorista: não sobra entrega elegível por classificar. Os
# veículos ARQUIVADOS saem da conta (suas entregas deixam de ser exigidas),
# permitindo finalizar deixando um carro problemático de fora. Fechar um veículo
# é um passo independente (lock) — a finalização em si fecha os que restarem.
def motorista_completo?(motorista)
total = entregas_elegiveis_count(motorista) - eligible_veiculos_arquivados(motorista)
total.zero? || classificadas_count(motorista) >= total
end
def percentual_classificado
@@ -153,6 +171,28 @@ class Consolidacao < ApplicationRecord
)
end
# Elegíveis de UM veículo específico do motorista (respeita filtros da
# consolidação). O bucket "(sem veículo)" casa as entregas com vehicle NULL/vazio.
def entregas_elegiveis_count_veiculo(motorista, vehicle)
rel = Entrega.atendidas.da_conta_gade
.no_periodo_checkout(data_inicio, data_fim)
.do_motorista(motorista)
rel = rel.da_veiculo(vehicle_ids) if vehicle_ids.present?
rel = rel.da_operacoes(operacoes) if operacoes.present?
rel = vehicle == SEM_VEICULO ? rel.where(vehicle: [nil, '']) : rel.where(vehicle: vehicle)
rel.count
end
# Total de elegíveis dos veículos ARQUIVADOS do motorista — descontado do gate
# para que arquivar um carro problemático libere a finalização. Só toca a base
# externa quando existe algum veículo arquivado (mantém os testes isolados).
def eligible_veiculos_arquivados(motorista)
arquivados = consolidacao_veiculos.arquivados.where(motorista_nome: motorista)
return 0 if arquivados.empty?
arquivados.sum { |cv| entregas_elegiveis_count_veiculo(motorista, cv.vehicle) }
end
# Rótulos amigáveis das operações vinculadas (para exibição).
def operacoes_label
Array(operacoes).map { |t| Operacao.label(t) }
@@ -190,6 +230,7 @@ class Consolidacao < ApplicationRecord
motorista_nome: motorista,
valor_aplicado: ConsolidacaoEntrega.valor_para(tipo),
manual: true,
vehicle: entrega.try(:vehicle).presence, # snapshot do veículo da rastreio
created_by: user&.id
)
ce.save!
@@ -208,7 +249,7 @@ class Consolidacao < ApplicationRecord
# O motorista vem digitado (não de uma Entrega) e é incluído se ainda não
# estiver. tracking_id é sintético (não há Entrega), e a NF/observação ficam
# guardadas em nf_manual/obs_manual.
def adicionar_apontamento_manual!(motorista:, tipos:, user:, nf: nil, obs: nil, valor_extra: nil)
def adicionar_apontamento_manual!(motorista:, tipos:, user:, nf: nil, obs: nil, valor_extra: nil, vehicle: nil)
tipos_validos = Array(tipos).map(&:to_s).select { |t| ConsolidacaoEntrega.tipos.key?(t) }.uniq
raise ArgumentError, 'Selecione ao menos uma classificação.' if tipos_validos.empty?
@@ -232,6 +273,7 @@ class Consolidacao < ApplicationRecord
motorista_nome: nome,
valor_aplicado: valor,
manual: true,
vehicle: vehicle.presence, # veículo escolhido no lançamento (Q3)
nf_manual: nf.presence,
obs_manual: obs.presence,
created_by: user&.id
@@ -249,7 +291,7 @@ class Consolidacao < ApplicationRecord
# para atrelar (só motorista + quantidade, a um preço fixo configurável). Fica
# numa única linha (manual: true): quantidade guarda o N e valor_aplicado guarda
# o total do lote, então recalcular_motorista! e demais somas seguem inalteradas.
def adicionar_termos!(motorista:, quantidade:, user:)
def adicionar_termos!(motorista:, quantidade:, user:, vehicle: nil)
nome = motorista.to_s.strip
raise ArgumentError, 'Informe o motorista do termo.' if nome.blank?
@@ -266,6 +308,7 @@ class Consolidacao < ApplicationRecord
quantidade: qtd,
valor_aplicado: qtd * ConsolidacaoEntrega.valor_para('termo'), # total do lote
manual: true,
vehicle: vehicle.presence, # veículo escolhido no lançamento (Q3)
created_by: user&.id
)
@@ -286,6 +329,27 @@ class Consolidacao < ApplicationRecord
end
cm.update_column(:valor_total, total)
update_column(:valor_total, consolidacao_motoristas.ativos.sum(:valor_total))
sincronizar_veiculos!(nome)
end
# Mantém as linhas de consolidacao_veiculos do motorista em dia a partir dos
# pilares já classificados (100% DB — não toca a base externa). Cria/atualiza uma
# linha por veículo presente nas classificações e remove as que ficaram sem
# pilares e ainda não foram fechadas/arquivadas. Veículos elegíveis mas 100% sem
# classificar não geram linha aqui — o gate já os pega por classificadas < total.
def sincronizar_veiculos!(nome)
entregas = consolidacao_entregas.where(motorista_nome: nome)
presentes = entregas.pluck(:vehicle).map { |v| v.presence || SEM_VEICULO }.uniq
presentes.each do |veic|
cv = consolidacao_veiculos.find_or_initialize_by(motorista_nome: nome, vehicle: veic)
cv.save! # before_save :recalcular_valor atualiza o valor_total do veículo
end
consolidacao_veiculos.where(motorista_nome: nome)
.where(fechado_em: nil, arquivado_em: nil)
.where.not(vehicle: presentes)
.destroy_all
end
private