, Correçoes na busca da API para ver os ultimos planos

This commit is contained in:
2026-08-28 17:03:37 -03:00
parent bfa474262f
commit 3e51d0e03b
6 changed files with 603 additions and 36 deletions

View File

@@ -36,8 +36,23 @@ module Romaneios
attr_reader :origem
def initialize(data:, client: nil)
@data = data.to_date
# Duas formas de pedir o plano, e a primeira é a boa:
#
# PlanoDoDia.new(plano: {...}) -> o PLANO escolhido na lista (o hash que
# SimpliRoute::Client#planos devolve)
# PlanoDoDia.new(data: ...) -> o jeito antigo, por data
#
# POR QUE O PLANO VENCE: o operador pensa por operação ("lançou a UBS
# Sudeste"), não por data — e a data era mesmo impossível de acertar, porque
# o plano tem uma JANELA e o dia das rotas ora é o começo dela, ora o fim
# (ver SimpliRoute::Client#rota). Com o plano em mãos a data deixa de ser
# chute: pergunta-se à API.
#
# A forma por data continua porque o "Reimportar" de um romaneio antigo só
# tem a data, e porque ela é o caminho para o espelho local quando a API cai.
def initialize(data: nil, plano: nil, client: nil)
@plano = plano
@data = data&.to_date
@client = client
@origem = nil
end
@@ -46,25 +61,90 @@ module Romaneios
@linhas ||= carregar
end
# O dia REAL do plano — o que o Romaneio grava em `planned_date`. Uma
# chamada: todas as rotas de um plano dividem o mesmo dia.
def data_resolvida
@data ||= begin
rota = cliente.rota(Array(@plano && @plano['routes']).first)
rota && rota['planned_date'] ? Date.parse(rota['planned_date']) : nil
rescue Date::Error, SimpliRoute::Error
nil
end
end
private
def cliente
@cliente ||= @client || SimpliRoute::Client.new
end
def carregar
linhas = da_api
linhas = @plano ? da_api_por_plano : da_api
return linhas if linhas.any?
linhas = do_espelho
return linhas if linhas.any?
# O espelho é por DATA. Vindo de um plano, ele só entra depois que a data
# foi resolvida — sem ela não há o que consultar, e chamar `do_espelho`
# com @data nil traria o período inteiro.
linhas = do_espelho if @data
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).'
raise SimpliRoute::NotFound, mensagem_de_ausencia
end
# A mensagem diz o que a pessoa escolheu, não um dado interno: quem clicou
# num plano na lista não reconhece a data resolvida, e quem digitou a data
# não sabe de plano nenhum.
def mensagem_de_ausencia
if @plano && @data.nil?
"Não consegui descobrir o dia do plano \"#{@plano['name']}\" no SimpliRoute. " \
'Escolha outro plano ou envie a planilha do plano (.xlsx).'
elsif @plano
"O plano \"#{@plano['name']}\" (#{@data.strftime('%d/%m/%Y')}) não tem paradas no " \
'SimpliRoute nem no espelho de rastreio. Envie a planilha do plano (.xlsx).'
else
"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
end
# ── Caminho do PLANO ESCOLHIDO (o normal desde 28/08/2026) ───────────────
# Três chamadas, todas verificadas contra a API real:
#
# 1. a 1ª rota do plano -> o DIA real (Client#rota)
# 2. visitas do dia -> 2047 visitas, 3 MB, ~9 s (Client#visitas_da_data)
# 3. veículos da conta -> traduz 634185 em GADE_057 (dentro de por_visitas)
#
# O FILTRO PELAS ROTAS DO PLANO É O PONTO DA COISA, não uma otimização: dois
# planos podem cair no MESMO dia (uma operação normal e uma de avulsas, por
# exemplo), e sem ele o romaneio de um sairia com as paradas do outro. A
# visita traz `route`, e o plano traz a lista de uuids de rota — o cruzamento
# é exato. Medido: 2047 visitas do dia, 73 rotas, todas com `route`,
# `vehicle` e `order` preenchidos (2047/2047).
#
# Depois do filtro é o mesmo `por_visitas` de sempre: ele já agrupa por
# veículo, resolve o nome (GADE_057) e ordena por `order`.
def da_api_por_plano
return [] unless SimpliRoute.configurado?
data = data_resolvida
return [] if data.nil?
do_plano = Array(@plano['routes']).map(&:to_s).to_set
visitas = cliente.visitas_da_data(data).select { |v| do_plano.include?(v['route'].to_s) }
return [] if visitas.empty?
linhas = por_visitas(visitas, cliente)
@origem = 'api' if linhas.any?
linhas
rescue SimpliRoute::Error
[]
end
# ── (a) e (b) ────────────────────────────────────────────────────────────
def da_api
return [] unless SimpliRoute.configurado?
cliente = @client || SimpliRoute::Client.new
cliente = self.cliente
visitas = cliente.visitas_da_data(@data)
return [] if visitas.empty?

View File

@@ -64,22 +64,64 @@ module SimpliRoute
Array(get(caminho))
end
# Rotas PLANEJADAS da data (Array de hashes). É aqui que costuma morar o par
# veículo/motorista, que a visita talvez não carregue — e o romaneio precisa do
# veículo para saber quantas folhas imprimir.
# UMA rota pelo uuid. Serve para descobrir o DIA REAL de um plano: o registro
# do plano só traz a JANELA (`start_date`/`end_date`), e o dia das rotas nem
# sempre é o começo dela — medido em 28/08/2026: EMAD SETEMBRO 2026 (31/08 a
# 08/09) tem rotas em 31/08 (= início), mas UBS OESTE AGOSTO 2026 (18/08 a
# 20/08) e UBS NORTE AGOSTO 2026 (17/08 a 20/08) têm rotas em 20/08 (= FIM).
# Era isto que tornava a data impossível de adivinhar de fora; agora se
# pergunta à API. Todas as rotas de um plano dividem o mesmo dia (amostra de
# 8 em 3 planos), então basta a primeira.
def rota(id)
get("/v1/routes/routes/#{id}/")
rescue Error
nil
end
# Rotas PLANEJADAS da data (Array de hashes). É aqui que mora o par
# veículo/motorista, e o romaneio precisa do veículo para saber quantas folhas
# imprimir.
#
# ⚠️ NÃO VERIFICADO contra a API real: não há token na máquina de
# desenvolvimento. Por isso é best-effort (endpoint inexistente devolve [], não
# explode) e por isso Romaneios::PlanoDoDia tem uma escada de queda até o
# espelho local. Rode `bin/sondar_plano_do_dia` no servidor antes de confiar
# nesta chamada — e anote o resultado no cabeçalho daquele script, como foi
# feito em bin/sondar_busca_nf.
# VERIFICADO em 28/08/2026 contra a API real: `?planned_date=2026-08-31`
# devolve 73 rotas (83 KB, 0,59 s), cada uma com `vehicle` (id numérico),
# `plan` (uuid) e `total_visits`. ⚠️ O parâmetro é OBRIGATÓRIO: sem ele a rota
# responde 200 com **lista vazia**, não com tudo — então "veio vazio" aqui
# pode ser data errada, nunca "a conta não tem rotas". A rota NÃO traz array
# de visitas (`visits` não existe); quem liga visita→rota é o campo `route`
# da própria visita.
def rotas_da_data(data)
Array(get("/v1/routes/routes/?planned_date=#{data.to_date.iso8601}"))
rescue Error
[]
end
# Os N planos mais recentes da conta — nome, período e as rotas de cada um.
#
# ⚠️ A CONCLUSÃO ANTIGA ("a API não expõe listagem de planos, só dá para
# buscar por data") ESTAVA ERRADA. `/v1/routes/plans/` existe, não é
# documentado, e devolve TUDO com o nome que a operação usa — "EMAD SETEMBRO
# 2026", "UBS OESTE AGOSTO 2026". Verificado em 28/08/2026 contra a API real:
# 138 planos, 185 KB, 0,86 s. Ver bin/sondar_planos.
#
# POR QUE ORDENAR E CORTAR AQUI, e não na URL: a API IGNORA todo filtro nesta
# rota. `ordering`, `limit`, `page`, `page_size`, `search`, `name`,
# `start_date__gte` — os dez candidatos testados devolveram os mesmos 138
# itens do controle sem parâmetro. Então não existe "pedir 5": pede-se tudo
# (uma chamada, menos de 1 s) e corta-se em Ruby.
#
# `created` é o critério: `start_date` de um plano é o PRIMEIRO dia da janela
# (o EMAD SETEMBRO 2026 vai de 31/08 a 08/09), e é justamente essa janela que
# tornava impossível adivinhar "a data do plano". Quem publicou por último é
# quem o operador quer — ele abre a ferramenta logo depois de lançar.
def planos(limite: 5)
Array(get('/v1/routes/plans/'))
.sort_by { |p| p['created'].to_s }
.reverse
.first(limite)
rescue Error
[]
end
# Veículos da conta, para traduzir o id numérico que vem nas rotas
# (`vehicle: 630011`) no nome que a operação usa (`GADE_038`).
#