Correção do função de editar lançamento para quer consiga listar todas
This commit is contained in:
198
bin/sondar_busca_nf
Executable file
198
bin/sondar_busca_nf
Executable 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
|
||||
Reference in New Issue
Block a user