require 'rails_helper' require 'caxlsx' # A planilha do plano é o caminho B do romaneio — e é o que o cliente usa quando o # plano do dia ainda não está publicado. Os casos aqui são os que o export REAL # apresenta (medido em Imagens para implantação/SimpliRoute_Plan_ 2026-08-31.xlsx): # coluna "Motorista" vazia, sem "Aparelho", e a NF numa coluna com typo do próprio # SimpliRoute ("ID de referêncua"). RSpec.describe Romaneios::PlanoPlanilha do def planilha(cabecalho, linhas) pacote = Axlsx::Package.new pacote.workbook.add_worksheet(name: 'Plan') do |aba| aba.add_row(cabecalho) linhas.each { |l| aba.add_row(l) } end arquivo = Tempfile.new(['plano', '.xlsx']) arquivo.binmode arquivo.write(pacote.to_stream.read) arquivo.flush arquivo end it 'lê o formato real do export (sem Motorista, sem Aparelho, NF com o typo)' do arquivo = planilha( ['Motorista', 'Veículo', 'Índice de rota', 'Ordem de entrega', 'Título', 'Endereço', 'ID de referêncua', 'Anotações'], [[nil, 'GADE_038', 1, 1, 'NF 90507 - Cicera Beserra dos Anjos', 'Rua Amapola, 206, Jardim Vista Linda, São Paulo, SP, CEP: 05159-100', '90507', 'TEL: (11) 99262-0594']] ) linha = described_class.new(arquivo).linhas.first expect(linha[:veiculo]).to eq('GADE_038') expect(linha[:nota_fiscal]).to eq('90507') expect(linha[:nome]).to eq('CICERA BESERRA DOS ANJOS') expect(linha[:endereco]).to eq('RUA AMAPOLA, 206, JARDIM VISTA LINDA, SÃO PAULO, SP, CEP: 05159-100') expect(linha[:telefones]).to eq('(11) 99262-0594') expect(linha[:motorista]).to eq('') end it 'aceita a grafia corrigida da coluna de referência' do arquivo = planilha(['Veículo', 'Título', 'Endereço', 'ID de referência'], [['GADE_001', 'NF 1 - Fulano', 'Rua A, 1', '1']]) expect(described_class.new(arquivo).linhas.first[:nota_fiscal]).to eq('1') end it 'aceita título fora do padrão "NF n - NOME" sem perder a parada' do arquivo = planilha(['Veículo', 'Título', 'Endereço'], [['GADE_001', 'PONTO DE APOIO', 'Rua A, 1']]) linha = described_class.new(arquivo).linhas.first expect(linha[:nota_fiscal]).to eq('') expect(linha[:nome]).to eq('PONTO DE APOIO') end it 'tira o sufixo ", Brazil" do endereço e junta vários telefones' do arquivo = planilha(['Veículo', 'Título', 'Endereço', 'Anotações'], [['GADE_001', 'NF 1 - Fulano', 'Rua A, 1, Bairro, Brazil', 'TEL: (11) 95440-5780 / (11) 99824-8099']]) linha = described_class.new(arquivo).linhas.first expect(linha[:endereco]).to eq('RUA A, 1, BAIRRO') expect(linha[:telefones]).to eq('(11) 95440-5780 / (11) 99824-8099') end it 'renumera a ordem de 1..N dentro de cada veículo, na ordem de entrega do plano' do arquivo = planilha(['Veículo', 'Ordem de entrega', 'Título', 'Endereço'], [['GADE_001', 3, 'NF 3 - C', 'Rua C'], ['GADE_002', 9, 'NF 9 - I', 'Rua I'], ['GADE_001', 1, 'NF 1 - A', 'Rua A']]) linhas = described_class.new(arquivo).linhas primeiro = linhas.select { |l| l[:veiculo] == 'GADE_001' } expect(primeiro.map { |l| [l[:ordem], l[:nota_fiscal]] }).to eq([[1, '1'], [2, '3']]) expect(linhas.find { |l| l[:veiculo] == 'GADE_002' }[:ordem]).to eq(1) end it 'pula linha sem veículo (rodapé do próprio export) sem levantar erro' do arquivo = planilha(['Veículo', 'Título', 'Endereço'], [['GADE_001', 'NF 1 - A', 'Rua A'], [nil, nil, nil]]) expect(described_class.new(arquivo).linhas.size).to eq(1) end it 'recusa arquivo que não é .xlsx com uma mensagem acionável' do arquivo = Tempfile.new(['plano', '.xlsx']) arquivo.write('isto não é um zip') arquivo.flush expect { described_class.new(arquivo) } .to raise_error(described_class::Erro, /não é um \.xlsx/) end it 'recusa planilha sem a coluna Veículo — não dá para montar folha sem carro' do arquivo = planilha(['Título', 'Endereço'], [['NF 1 - A', 'Rua A']]) expect { described_class.new(arquivo).linhas } .to raise_error(described_class::Erro, /Veículo/) end end