Correção do função de editar lançamento para quer consiga listar todas

This commit is contained in:
2026-07-21 16:23:25 -03:00
parent 2b0a3b52ef
commit faa5226e95
4 changed files with 390 additions and 19 deletions

198
bin/sondar_busca_nf Executable file
View File

@@ -0,0 +1,198 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# Descobre se a API do SimpliRoute aceita buscar visita por NF e/ou por período
# — a doc oficial só documenta `?planned_date=` de UM dia.
#
# ── RESULTADO DA RODADA DE 21/07/2026 (contra a API real) ───────────────────
# ✔ `&search=<NF>` FUNCIONA (não documentado): dia inteiro cai de ~3,8 MB/9 s
# para ~1 KB/0,6 s. É o que a tela usa hoje.
# ✘ `reference`, `reference_id`, `q`, `title`, `reference__*` — IGNORADOS em
# silêncio (200 + dia inteiro).
# ✘ Intervalo (`planned_date_from/to`, `since/until`, `__gte/__lte`,
# `date_from/to`) — TODOS ignorados: devolveram 2469, igual ao controle sem
# parâmetro nenhum.
# ✘ Sem `planned_date` a API devolve um conjunto padrão (~2469) que NÃO cobre
# o histórico — `?search=<NF>` sozinho escondeu a visita mais antiga da NF.
# ⇒ `planned_date` continua obrigatório; a busca por NF é dia a dia + search.
#
# Rode de novo se desconfiar que a API mudou.
#
# SÓ LEITURA: o script faz apenas GET/OPTIONS. Não altera nada.
#
# Rode NO SERVIDOR (é lá que vive o SIMPLIROUTE_TOKEN):
#
# SIMPLIROUTE_TOKEN=xxx bin/sondar_busca_nf --nf 82891 --data 2026-07-17
# SIMPLIROUTE_TOKEN=xxx bin/sondar_busca_nf --nf 82891 --data 2026-07-17 --sem-data
#
# --data deve ser um dia em que a NF EXISTE (é a referência de comparação).
# --sem-data testa a consulta sem `planned_date` nenhum; pode ser lenta ou vir
# gigante, por isso fica de fora por padrão.
#
# ⚠️ Por que comparar contagens: a API é Django REST. Parâmetro que o backend
# não registra é IGNORADO em silêncio — devolve 200 e a lista inteira. Então
# "voltou 200 com resultados" NÃO prova que filtrou. A prova é o filtro DIMINUIR
# o resultado do dia e sobrar só a NF pedida.
require 'net/http'
require 'json'
require 'uri'
require 'date'
BASE = ENV.fetch('SIMPLIROUTE_BASE_URL', 'https://api.simpliroute.com')
TOKEN = ENV['SIMPLIROUTE_TOKEN'].to_s
def sair(msg)
warn msg
exit 1
end
sair('Defina SIMPLIROUTE_TOKEN no ambiente.') if TOKEN.empty?
nf = nil
data = nil
sem_data = false
ARGV.each_with_index do |a, i|
nf = ARGV[i + 1] if a == '--nf'
data = ARGV[i + 1] if a == '--data'
sem_data = true if a == '--sem-data'
end
sair('Uso: bin/sondar_busca_nf --nf <numero> --data <YYYY-MM-DD> [--sem-data]') if nf.to_s.empty? || data.to_s.empty?
def requisicao(caminho, metodo: :get)
uri = URI.join(BASE, caminho)
req = metodo == :options ? Net::HTTP::Options.new(uri) : Net::HTTP::Get.new(uri)
req['Authorization'] = "Token #{TOKEN}"
req['Accept'] = 'application/json'
t0 = Time.now
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 120) { |h| h.request(req) }
corpo = begin
res.body.to_s.empty? ? nil : JSON.parse(res.body)
rescue JSON::ParserError
res.body.to_s[0, 200]
end
[res.code.to_i, corpo, res.body.to_s.bytesize, Time.now - t0]
rescue StandardError => e
[0, "ERRO: #{e.class}: #{e.message}", 0, Time.now - t0]
end
# A resposta pode ser lista crua OU paginada ({count, next, results}). No caso
# paginado o `count` já é o total — é a informação mais barata que existe aqui.
def itens(corpo)
return corpo if corpo.is_a?(Array)
return Array(corpo['results']) if corpo.is_a?(Hash) && corpo.key?('results')
[]
end
def total(corpo, lista)
corpo.is_a?(Hash) && corpo['count'] ? corpo['count'] : lista.size
end
def titulo(t)
puts "\n#{'─' * 70}\n#{t}\n#{'─' * 70}"
end
puts "Base: #{BASE} NF: #{nf} Data de referência: #{data}"
# ── 1. Linha de base: o dia inteiro, do jeito que a tela faz hoje ────────────
titulo("1. LINHA DE BASE · GET /v1/routes/visits/?planned_date=#{data}")
cod, corpo, bytes, seg = requisicao("/v1/routes/visits/?planned_date=#{data}")
sair("Falhou (HTTP #{cod}): #{corpo.inspect}") unless cod == 200
base_lista = itens(corpo)
base_total = total(corpo, base_lista)
base_ids = base_lista.map { |v| v['id'] }
da_nf = base_lista.select { |v| v['reference'].to_s == nf.to_s }
puts " HTTP 200 em #{seg.round(2)}s — #{bytes} bytes"
puts " visitas no dia .......... #{base_total}"
puts " paginado? ............... #{corpo.is_a?(Hash) ? "sim (count=#{corpo['count']}, next=#{corpo['next'].inspect})" : 'não (lista crua)'}"
puts " visitas com a NF #{nf} ... #{da_nf.size}"
da_nf.each { |v| puts " id=#{v['id']} tracking=#{v['tracking_id'].inspect} status=#{v['status']} data=#{v['planned_date']}" }
puts " ⚠️ tracking_id vem NULO na lista-por-data" if da_nf.any? && da_nf.all? { |v| v['tracking_id'].nil? }
sair("A NF #{nf} não aparece em #{data}. Passe uma data em que ela exista — sem isso não há como comparar.") if da_nf.empty?
# ── 2. Filtro por NF: o parâmetro DIMINUI o resultado do dia? ────────────────
# Se a contagem não mudar, o parâmetro foi ignorado (mesmo com HTTP 200).
titulo('2. FILTRO POR NF · <dia> + <param>=NF (diminuiu = filtro real)')
%w[reference reference_id search q title reference__exact reference__icontains].each do |param|
cod, corpo, _b, seg = requisicao("/v1/routes/visits/?planned_date=#{data}&#{param}=#{nf}")
lista = itens(corpo)
qtd = total(corpo, lista)
veredito =
if cod != 200 then "HTTP #{cod} — não aceito"
elsif qtd == base_total then 'IGNORADO (mesma contagem do dia inteiro)'
elsif qtd.zero? then 'aceito porém vazio — filtrou demais'
elsif lista.all? { |v| v['reference'].to_s == nf.to_s } then "★ FILTRA DE VERDADE — #{qtd} visita(s), todas da NF"
else "reduziu p/ #{qtd} mas veio NF de fora"
end
puts " #{param.ljust(22)} #{veredito} (#{seg.round(2)}s)"
end
# ── 3. Filtro por período ────────────────────────────────────────────────────
# Comparação: o intervalo <data>..<data+1> tem que devolver MAIS que só <data>.
dia2 = (Date.parse(data) + 1).to_s
titulo("3. FILTRO POR PERÍODO · #{data} a #{dia2} (> #{base_total} = intervalo respeitado)")
[
%w[planned_date_from planned_date_to],
%w[planned_date_after planned_date_before],
%w[planned_date__gte planned_date__lte],
%w[since until],
%w[start_date end_date],
%w[date_from date_to]
].each do |de, ate|
cod, corpo, _b, seg = requisicao("/v1/routes/visits/?#{de}=#{data}&#{ate}=#{dia2}")
lista = itens(corpo)
qtd = total(corpo, lista)
veredito =
if cod != 200 then "HTTP #{cod} — não aceito"
elsif qtd > base_total then "★ RESPEITA O INTERVALO — #{qtd} visitas (dia sozinho: #{base_total})"
elsif qtd == base_total then "mesma contagem do dia — inconclusivo/ignorado"
else "#{qtd} visitas — menos que o dia sozinho, estranho"
end
puts " #{"#{de}/#{ate}".ljust(38)} #{veredito} (#{seg.round(2)}s)"
end
# ── 4. OPTIONS: o DRF às vezes lista os filtros aceitos ──────────────────────
titulo('4. OPTIONS /v1/routes/visits/ (metadados do endpoint)')
cod, corpo, = requisicao('/v1/routes/visits/', metodo: :options)
if cod == 200 && corpo.is_a?(Hash)
puts " métodos ... #{corpo['renders'] ? corpo.slice('name', 'description', 'parses').inspect[0, 300] : corpo.keys.inspect}"
filtros = corpo['filters'] || corpo['filter_fields'] || corpo.dig('actions', 'GET')
puts " filtros ... #{filtros ? filtros.inspect[0, 500] : '(não declarados)'}"
else
puts " HTTP #{cod} — #{corpo.inspect[0, 200]}"
end
# ── 5. Sem planned_date nenhum — a pergunta direta ───────────────────────────
unless sem_data
puts "\n(Pulei o teste SEM planned_date. Rode com --sem-data para incluir — pode demorar ou vir gigante.)"
exit 0
end
titulo('5. SEM planned_date · a API devolve a NF inteira de uma vez?')
[
"/v1/routes/visits/?reference=#{nf}",
"/v1/routes/visits/?search=#{nf}",
'/v1/routes/visits/'
].each do |caminho|
cod, corpo, bytes, seg = requisicao(caminho)
lista = itens(corpo)
qtd = total(corpo, lista)
casam = lista.count { |v| v['reference'].to_s == nf.to_s }
puts " GET #{caminho}"
if cod == 200
puts " #{qtd} visita(s), #{casam} com a NF #{nf} — #{bytes} bytes em #{seg.round(2)}s"
datas = lista.select { |v| v['reference'].to_s == nf.to_s }.map { |v| v['planned_date'] }.uniq
puts " datas da NF: #{datas.inspect}" if datas.any?
puts ' ★ RESOLVE TUDO: a NF veio inteira, sem varrer dia a dia' if casam > 1 && casam == qtd
else
puts " HTTP #{cod} — #{corpo.inspect[0, 200]}"
end
end