Correção de horario e implantação da geração de Romaneio
This commit is contained in:
153
app/services/romaneios/plano_do_dia.rb
Normal file
153
app/services/romaneios/plano_do_dia.rb
Normal file
@@ -0,0 +1,153 @@
|
||||
# app/services/romaneios/plano_do_dia.rb
|
||||
#
|
||||
# Busca o plano de UMA data e devolve as paradas na forma canônica do
|
||||
# Romaneios::Normalizador — a mesma que Romaneios::PlanoPlanilha entrega.
|
||||
#
|
||||
# ⚠️ NÃO está verificado se a visita de /v1/routes/visits/ carrega veículo/ordem, e
|
||||
# isso não é testável na máquina de desenvolvimento (sem token, sem .env). Por isso
|
||||
# aqui existe uma ESCADA DE QUEDA explícita, e não um caminho feliz único:
|
||||
#
|
||||
# a) rotas da API trazem vehicle + lista ordenada de visitas -> origem :api
|
||||
# b) as visitas trazem alguma chave de veículo/ordem -> origem :api
|
||||
# c) nada disso (ou sem token) -> ESPELHO local
|
||||
# d) espelho vazio na data -> NotFound, com a
|
||||
# mensagem que manda o operador usar o .xlsx do plano
|
||||
#
|
||||
# A rung (c) não é consolo: o romaneio é impresso ANTES do dia começar, e ela cobre
|
||||
# "plano publicado e já sincronizado, mas a API fora do ar" — a falha mais provável.
|
||||
# Rode bin/sondar_plano_do_dia no servidor para fechar (a)/(b).
|
||||
module Romaneios
|
||||
class PlanoDoDia
|
||||
# Chaves candidatas nos hashes da API. Só as usamos se vierem PREENCHIDAS —
|
||||
# a API devolve chave existente com valor nil o tempo todo.
|
||||
CHAVES_VEICULO = %w[vehicle vehicle_name vehicle_id].freeze
|
||||
CHAVES_MOTORISTA = %w[driver driver_name].freeze
|
||||
CHAVES_ORDEM = %w[order sequence position route_order].freeze
|
||||
|
||||
attr_reader :origem
|
||||
|
||||
def initialize(data:, client: nil)
|
||||
@data = data.to_date
|
||||
@client = client
|
||||
@origem = nil
|
||||
end
|
||||
|
||||
def linhas
|
||||
@linhas ||= carregar
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def carregar
|
||||
linhas = da_api
|
||||
return linhas if linhas.any?
|
||||
|
||||
linhas = do_espelho
|
||||
return linhas if linhas.any?
|
||||
|
||||
raise SimpliRoute::NotFound,
|
||||
"O plano de #{@data.strftime('%d/%m/%Y')} não está disponível no SimpliRoute " \
|
||||
'nem no espelho de rastreio. Envie a planilha do plano (.xlsx).'
|
||||
end
|
||||
|
||||
# ── (a) e (b) ────────────────────────────────────────────────────────────
|
||||
def da_api
|
||||
return [] unless SimpliRoute.configurado?
|
||||
|
||||
cliente = @client || SimpliRoute::Client.new
|
||||
visitas = cliente.visitas_da_data(@data)
|
||||
return [] if visitas.empty?
|
||||
|
||||
linhas = por_rotas(cliente, visitas)
|
||||
linhas = por_visitas(visitas) if linhas.empty?
|
||||
@origem = 'api' if linhas.any?
|
||||
linhas
|
||||
rescue SimpliRoute::Error
|
||||
# Token ausente/expirado ou API fora: cai para o espelho, que é a mesma
|
||||
# informação já sincronizada.
|
||||
[]
|
||||
end
|
||||
|
||||
# (a) A rota traz o veículo e a lista de visitas na ordem do roteiro.
|
||||
def por_rotas(cliente, visitas)
|
||||
rotas = cliente.rotas_da_data(@data)
|
||||
return [] if rotas.empty?
|
||||
|
||||
por_id = visitas.index_by { |v| v['id'] }
|
||||
linhas = []
|
||||
|
||||
rotas.each do |rota|
|
||||
veiculo = primeiro_valor(rota, CHAVES_VEICULO)
|
||||
next if veiculo.empty?
|
||||
|
||||
ids = Array(rota['visits']).map { |v| v.is_a?(Hash) ? v['id'] : v }
|
||||
ids = visitas.select { |v| v['route'].to_s == rota['id'].to_s }.map { |v| v['id'] } if ids.empty?
|
||||
|
||||
ids.each_with_index do |id, i|
|
||||
visita = por_id[id]
|
||||
next if visita.nil?
|
||||
|
||||
linhas << monta(visita, veiculo, primeiro_valor(rota, CHAVES_MOTORISTA), i + 1)
|
||||
end
|
||||
end
|
||||
|
||||
linhas
|
||||
end
|
||||
|
||||
# (b) A visita traz o veículo direto; a ordem sai de `order`/`sequence` ou,
|
||||
# na falta, do horário estimado de chegada.
|
||||
def por_visitas(visitas)
|
||||
com_veiculo = visitas.reject { |v| primeiro_valor(v, CHAVES_VEICULO).empty? }
|
||||
return [] if com_veiculo.empty?
|
||||
|
||||
com_veiculo.group_by { |v| primeiro_valor(v, CHAVES_VEICULO) }.flat_map do |veiculo, lista|
|
||||
ordenadas(lista).each_with_index.map do |visita, i|
|
||||
monta(visita, veiculo, primeiro_valor(visita, CHAVES_MOTORISTA), i + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def ordenadas(lista)
|
||||
lista.sort_by.with_index do |v, i|
|
||||
chave = CHAVES_ORDEM.filter_map { |k| v[k] }.first
|
||||
[chave.nil? ? Float::INFINITY : chave.to_i, v['estimated_time_arrival'].to_s, i]
|
||||
end
|
||||
end
|
||||
|
||||
def monta(visita, veiculo, motorista, ordem)
|
||||
Normalizador.linha(
|
||||
veiculo: veiculo, ordem: ordem, motorista: motorista,
|
||||
titulo: visita['title'], endereco: visita['address'],
|
||||
anotacoes: visita['notes'], nota_fiscal: visita['reference'],
|
||||
tracking_id: visita['tracking_id']
|
||||
)
|
||||
end
|
||||
|
||||
def primeiro_valor(hash, chaves)
|
||||
chaves.filter_map { |k| hash[k] }.map(&:to_s).find { |v| !v.strip.empty? }.to_s.strip
|
||||
end
|
||||
|
||||
# ── (c) Espelho local ────────────────────────────────────────────────────
|
||||
# `db_reem_simplerout_2026` já tem driver, vehicle, title, address, notes e eta.
|
||||
# Só leitura, como sempre.
|
||||
def do_espelho
|
||||
registros = Entrega.da_conta_gade
|
||||
.no_periodo(@data, @data)
|
||||
.where.not(vehicle: [nil, ''])
|
||||
.order(:vehicle, :eta)
|
||||
.to_a
|
||||
return [] if registros.empty?
|
||||
|
||||
@origem = 'espelho'
|
||||
registros.group_by(&:vehicle).flat_map do |veiculo, lista|
|
||||
lista.each_with_index.map do |e, i|
|
||||
Normalizador.linha(
|
||||
veiculo: veiculo, ordem: i + 1, motorista: e.driver,
|
||||
titulo: e.title, endereco: e.address, anotacoes: e.notes,
|
||||
nota_fiscal: e.reference_id, tracking_id: e.tracking_id
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user