#!/usr/bin/env ruby # frozen_string_literal: true # # Sonda a API do SimpliRoute para descobrir se dá para ALTERAR as fotos de uma # visita. A documentação oficial não diz — este script responde na prática. # # Rode NO SERVIDOR (é lá que vive o SIMPLIROUTE_TOKEN): # # # 1) Leitura — não muda nada. Comece sempre por aqui. # SIMPLIROUTE_TOKEN=xxx bin/sondar_fotos_simpliroute --visita 871407488 # # # 2) Teste de escrita — adiciona uma foto de teste e RESTAURA em seguida. # SIMPLIROUTE_TOKEN=xxx bin/sondar_fotos_simpliroute --visita 871407488 --testar-escrita # # Para achar o id numérico de uma visita: é o mesmo que a tela "Editar # Lançamento" mostra em "Visita " depois de buscar pela NF. # # ⚠️ O teste de escrita mexe numa visita REAL. Use uma visita de teste ou uma # entrega antiga/sem importância. O valor original é impresso antes de qualquer # alteração e a restauração roda em `ensure` — se algo falhar, dá para refazer # na mão com o JSON impresso. require 'net/http' require 'json' require 'uri' BASE = ENV.fetch('SIMPLIROUTE_BASE_URL', 'https://api.simpliroute.com') TOKEN = ENV['SIMPLIROUTE_TOKEN'].to_s # URL pública qualquer, só para ver se a API aceita o campo. Não é baixada por nós. URL_TESTE = 'https://via.placeholder.com/64.png#sondagem-reem' def sair(msg) warn msg exit 1 end sair('Defina SIMPLIROUTE_TOKEN no ambiente.') if TOKEN.empty? visita_id = nil testar_escrita = false ARGV.each_with_index do |a, i| visita_id = ARGV[i + 1] if a == '--visita' testar_escrita = true if a == '--testar-escrita' end sair('Uso: bin/sondar_fotos_simpliroute --visita [--testar-escrita]') if visita_id.to_s.empty? def requisicao(metodo, caminho, corpo = nil) uri = URI.join(BASE, caminho) req = case metodo when :get then Net::HTTP::Get.new(uri) when :patch then Net::HTTP::Patch.new(uri) end req['Authorization'] = "Token #{TOKEN}" req['Content-Type'] = 'application/json' req['Accept'] = 'application/json' req.body = corpo.to_json if corpo res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: 10, read_timeout: 30) { |h| h.request(req) } body = begin res.body.to_s.empty? ? nil : JSON.parse(res.body) rescue JSON::ParserError res.body end [res.code.to_i, body] end def titulo(t) puts "\n#{'─' * 66}\n#{t}\n#{'─' * 66}" end # ── 1. Leitura: o que cada endpoint devolve de imagem ──────────────────────── titulo("1. GET /v1/routes/visits/#{visita_id}/") cod, visita = requisicao(:get, "/v1/routes/visits/#{visita_id}/") sair("Falhou (HTTP #{cod}): #{visita.inspect}") unless cod == 200 pictures_orig = Array(visita['pictures']) puts " status .............. #{visita['status'].inspect}" puts " reference (NF) ...... #{visita['reference'].inspect}" puts " pictures ............ #{pictures_orig.size} item(ns)" pictures_orig.each_with_index { |u, i| puts " [#{i}] #{u}" } puts " signature ........... #{visita['signature'].inspect}" titulo("2. GET /v1/plans/visits/#{visita_id}/detail/ (fonte extra de fotos)") cod_d, detalhe = requisicao(:get, "/v1/plans/visits/#{visita_id}/detail/") if cod_d == 200 && detalhe.is_a?(Hash) puts " OK. Chaves: #{detalhe.keys.sort.join(', ')}" puts " pictures ............ #{Array(detalhe['pictures']).size} item(ns)" Array(detalhe['pictures']).each_with_index { |u, i| puts " [#{i}] #{u}" } puts " signature ........... #{detalhe['signature'].inspect}" else puts " Indisponível (HTTP #{cod_d}) — a galeria segue só com a visita." end unless testar_escrita titulo('Leitura concluída — nada foi alterado') puts ' Para descobrir se dá para GRAVAR foto, rode de novo com --testar-escrita' puts ' (de preferência numa visita de teste).' exit 0 end # ── 3. Escrita: tenta acrescentar uma foto e restaura ──────────────────────── titulo('3. TESTE DE ESCRITA — PATCH pictures') puts " Valor ORIGINAL (guarde para restaurar na mão, se precisar):" puts " #{pictures_orig.to_json}" veredito = 'INDEFINIDO' begin novo = pictures_orig + [URL_TESTE] cod_p, resp = requisicao(:patch, "/v1/routes/visits/#{visita_id}/", { 'pictures' => novo }) puts "\n PATCH respondeu HTTP #{cod_p}" if cod_p != 200 veredito = 'NÃO É POSSÍVEL — a API recusou o PATCH' puts " corpo: #{resp.inspect[0, 400]}" else # 200 não basta: a API pode aceitar e ignorar o campo silenciosamente. _, confere = requisicao(:get, "/v1/routes/visits/#{visita_id}/") persistiu = Array(confere['pictures']).include?(URL_TESTE) veredito = persistiu ? 'É POSSÍVEL — a foto de teste persistiu' : 'NÃO — a API respondeu 200 mas IGNOROU o campo pictures' puts " pictures após o PATCH: #{Array(confere['pictures']).size} item(ns)" end ensure # Restaura sempre, mesmo se algo acima estourar. titulo('4. RESTAURANDO o valor original') cod_r, = requisicao(:patch, "/v1/routes/visits/#{visita_id}/", { 'pictures' => pictures_orig }) _, final = requisicao(:get, "/v1/routes/visits/#{visita_id}/") restaurado = Array(final['pictures']) ok = restaurado == pictures_orig && !restaurado.include?(URL_TESTE) puts " PATCH de restauração: HTTP #{cod_r}" puts(ok ? ' ✔ Restaurado — a visita voltou ao estado original.' : " ✘ ATENÇÃO: confira manualmente. Agora está: #{restaurado.to_json}") end titulo("VEREDITO: #{veredito}") puts <<~FIM Se deu "É POSSÍVEL", dá para implementar substituir/remover/adicionar foto — faltando só resolver ONDE hospedar a imagem nova (o app ainda não tem ActiveStorage com migrations/storage.yml). Se deu "NÃO", a alteração de foto terá de ser feita pela interface web do SimpliRoute, ou via o endpoint de checkout do app do motorista (POST /v1/mobile/visit/#{visita_id}/checkout/), que reescreve o checkout inteiro — bem mais invasivo. Vale confirmar com contact@simpliroute.com. FIM