diff --git a/app/services/romaneios/plano_do_dia.rb b/app/services/romaneios/plano_do_dia.rb index 4189bf9..c5d13b2 100644 --- a/app/services/romaneios/plano_do_dia.rb +++ b/app/services/romaneios/plano_do_dia.rb @@ -20,7 +20,17 @@ 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 + # + # ⚠️ A ORDEM AQUI É O CONSERTO DE UM BUG REAL, não estilo. Na API o campo + # `vehicle` da rota é o **ID numérico** do veículo (630011), e o nome que a + # operação usa — o mesmo que a planilha do plano traz na coluna "Veículo" — + # é GADE_038. Com `vehicle` na frente, o ID ganhava sempre e o romaneio saía + # impresso "CONTROLE DE ENTREGA — 630011", com o motorista sem saber que + # carro pegar. Nome primeiro; ID só como último recurso, porque veículo + # vazio faz a linha ser descartada e o romaneio sair sem paradas. + CHAVES_VEICULO = %w[vehicle_name vehicle_label vehicle vehicle_id].freeze + # Dentro do objeto do veículo (quando a API aninha em vez de mandar o id). + CHAVES_NOME_VEIC = %w[name label display_name plate].freeze CHAVES_MOTORISTA = %w[driver driver_name].freeze CHAVES_ORDEM = %w[order sequence position route_order].freeze @@ -59,7 +69,7 @@ module Romaneios return [] if visitas.empty? linhas = por_rotas(cliente, visitas) - linhas = por_visitas(visitas) if linhas.empty? + linhas = por_visitas(visitas, cliente) if linhas.empty? @origem = 'api' if linhas.any? linhas rescue SimpliRoute::Error @@ -77,7 +87,7 @@ module Romaneios linhas = [] rotas.each do |rota| - veiculo = primeiro_valor(rota, CHAVES_VEICULO) + veiculo = nome_do_veiculo(rota, cliente) next if veiculo.empty? ids = Array(rota['visits']).map { |v| v.is_a?(Hash) ? v['id'] : v } @@ -96,11 +106,11 @@ module Romaneios # (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? } + def por_visitas(visitas, cliente) + com_veiculo = visitas.reject { |v| nome_do_veiculo(v, cliente).empty? } return [] if com_veiculo.empty? - com_veiculo.group_by { |v| primeiro_valor(v, CHAVES_VEICULO) }.flat_map do |veiculo, lista| + com_veiculo.group_by { |v| nome_do_veiculo(v, cliente) }.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 @@ -123,6 +133,39 @@ module Romaneios ) end + # O NOME do veículo como a operação o conhece (GADE_038), nunca o id. + # + # Três formas já vistas/possíveis no mesmo campo, por isso as três camadas: + # { "vehicle_name" => "GADE_038" } -> nome direto + # { "vehicle" => { "name" => "GADE_038" } } -> aninhado + # { "vehicle" => 630011 } -> só o id, precisa resolver + # + # Quando sobra só o id, procuramos o nome na lista de veículos da conta. Se + # essa busca não responder, devolvemos o id mesmo: um romaneio com o número + # do carro é ruim, um romaneio VAZIO (que é o que acontece devolvendo "") é + # pior — a linha sem veículo é descartada. + def nome_do_veiculo(hash, cliente) + aninhado = hash['vehicle'] + return primeiro_valor(aninhado, CHAVES_NOME_VEIC) if aninhado.is_a?(Hash) + + valor = primeiro_valor(hash, CHAVES_VEICULO) + return valor unless valor.match?(/\A\d+\z/) + + nomes_de_veiculo(cliente)[valor].presence || valor + end + + # id (string) => nome. Uma chamada por importação, memoizada: são ~70 veículos + # e a alternativa seria uma consulta por rota. + def nomes_de_veiculo(cliente) + @nomes_de_veiculo ||= cliente.veiculos.each_with_object({}) do |v, memo| + next unless v.is_a?(Hash) + + id = v['id'].to_s.strip + nome = primeiro_valor(v, CHAVES_NOME_VEIC) + memo[id] = nome unless id.empty? || nome.empty? + end + end + def primeiro_valor(hash, chaves) chaves.filter_map { |k| hash[k] }.map(&:to_s).find { |v| !v.strip.empty? }.to_s.strip end diff --git a/app/services/simpli_route/client.rb b/app/services/simpli_route/client.rb index e769f06..58494a1 100644 --- a/app/services/simpli_route/client.rb +++ b/app/services/simpli_route/client.rb @@ -80,6 +80,19 @@ module SimpliRoute [] 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`). + # + # Best-effort pelo mesmo motivo de `rotas_da_data`: sem token na máquina de + # desenvolvimento não dá para verificar o caminho, então endpoint ausente + # devolve [] e Romaneios::PlanoDoDia segue com o id em vez de quebrar a + # importação inteira. + def veiculos + Array(get('/v1/routes/vehicles/')) + rescue Error + [] + end + # Resolve o `id` numérico (usado na URL de escrita) a partir de uma Entrega # do espelho local, que só tem tracking_id (SR...) + reference_id (NF) + # planned_date. Casa pelo tracking_id; se não achar, tenta pela NF. diff --git a/docs/BATERIA-DE-TESTES.md b/docs/BATERIA-DE-TESTES.md index 24cfbd4..5a94281 100644 --- a/docs/BATERIA-DE-TESTES.md +++ b/docs/BATERIA-DE-TESTES.md @@ -145,9 +145,13 @@ O bloco "Operação do mês" tem que aparecer em todo romaneio, e o formulário precisa funcionar **sem JavaScript** (é `
` nativo + form comum): ```bash -agent-browser eval "const d=[...document.querySelectorAll('details')].find(x=>x.textContent.includes('operação')); d ? 'existe | aberto:'+d.open+' | opcoes:'+d.querySelectorAll('option').length : 'AUSENTE'" +agent-browser eval "(()=>{const d=[...document.querySelectorAll('details')].find(x=>x.querySelector('select[name=operacao_tabela]')); return d ? 'existe | aberto:'+d.open+' | opcoes:'+d.querySelectorAll('option').length : 'AUSENTE';})()" ``` +⚠️ Procure pelo `select[name=operacao_tabela]`, **não** pelo texto "Operação do +mês": esse rótulo fica FORA do `
` e é renderizado em maiúsculas pelo +CSS, então busca por texto dá falso negativo (já aconteceu num diagnóstico). + **PASSOU se**: o bloco existe; num romaneio **sem** operação ele vem `aberto:true` (a pendência fica à vista) e num **com** operação vem `aberto:false`; a lista tem mais de uma `option`.