Grande correção do filtro de veiculos na area de consolidação
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user