Frete & Logística

Table rate por faixa de CEP no Magento 2: como funciona o prefixo

Não precisa de módulo pago nem de uma linha por CEP. O próprio core já compara o prefixo do CEP com a coluna da tabela — está escrito na RateQuery.

Por Roger Takemiya · Publicado em · 5 min de leitura

Table rate é o carrier de frete mais subestimado do Magento. Muita gente acha que ele só faz preço por estado, ou que exige uma linha de CSV para cada CEP do Brasil — o que daria um arquivo impossível de manter.

Não é isso. O código que monta a consulta tem um comportamento que resolve faixa de CEP sem nenhum módulo extra, e ele está lá desde sempre. Só não está escrito em lugar nenhum do manual.

O que o Magento faz com o CEP que o cliente digita

Quem monta a consulta é a classe Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\RateQuery. No fim dela tem esta função privada, que faz todo o serviço:

private function getDestPostcodePrefix()
{
    if (!preg_match("/^(.+)-(.+)$/", $this->request->getDestPostcode() ?? '', $zipParts)) {
        return $this->request->getDestPostcode();
    }

    return $zipParts[1];
}

Traduzindo: tem hífen, devolve o que vem antes dele. Não tem hífen, devolve o CEP como veio. Esse valor entra na consulta como :postcode_prefix, ao lado do :postcode com o CEP completo. E o WHERE aceita os dois:

dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = :postcode
dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = :postcode_prefix

Ou seja: para o CEP 01310-100, o Magento procura uma linha com 01310-100 e também uma com 01310. Uma linha só cobre os mil CEPs daquele prefixo.

A ordenação decide quem ganha quando as duas existem: dest_country_id DESC, dest_region_id DESC, dest_zip DESC, condition_value DESC, com LIMIT 1. Como 01310-100 é maior que 01310 na comparação de texto, a linha específica vence a genérica. Você pode ter a faixa como regra geral e uma exceção pontual para um CEP problemático.

Montar o CSV por faixa

O arquivo tem cinco colunas, nesta ordem: Country, Region/State, Zip/Postal Code, a coluna da condição e Shipping Price. O nome da quarta coluna muda conforme a condição escolhida no admin: Weight (and above), Order Subtotal (and above) ou # of Items (and above). Exporte o modelo pelo próprio admin em vez de digitar o cabeçalho de cabeça.

Um exemplo com peso, cobrindo faixas de São Paulo capital e um fallback nacional:

Country,Region/State,Zip/Postal Code,Weight (and above),Shipping Price
BRA,SP,01310,0,18.90
BRA,SP,01310,5,28.90
BRA,SP,013,0,22.90
BRA,*,*,0,49.90

Detalhes que importam nesse arquivo:

  • O país aceita ISO2 ou ISO3 — BR e BRA funcionam igual.
  • O estado é a sigla que existe no diretório do Magento: SP, RJ, MG. As unidades federativas brasileiras já vêm cadastradas.
  • * é curinga em país, estado e CEP. Coluna de CEP vazia é convertida em * automaticamente.
  • A coluna da condição é a partir de. Duas linhas com o mesmo destino e valores 0 e 5 formam a faixa de 0 a 5 kg e de 5 kg para cima.

Repare na terceira linha do exemplo: 013 também é um prefixo válido, mas ela nunca vai ser usada, porque o Magento compara o prefixo inteiro (01310), não pedaço a pedaço. Prefixo, aqui, quer dizer exatamente o que está antes do hífen.

Os dois detalhes que estragam tudo

1. O botão de importar some. Os campos Export e Import do table rate são declarados no system.xml só com showInWebsite="1". Em Default Config eles simplesmente não aparecem, e todo mundo já achou que o Magento estava quebrado por causa disso. Troque o escopo para o website no seletor do canto superior esquerdo e os campos surgem.

2. O cliente pode digitar sem hífen. Esse é o furo de verdade. O arquivo zip_codes.xml do módulo Directory declara dois padrões válidos para o Brasil:

<code id="pattern_1" active="true" example="12345678">^[0-9]{8}$</code>
<code id="pattern_2" active="true" example="12345-678">^[0-9]{5}\-[0-9]{3}$</code>

Quem digita 01310100 passa na validação normalmente. Só que aí não tem hífen, o prefixo vira 01310100 e nenhuma linha de faixa casa: o cliente cai no curinga e paga o frete nacional. Você recebe a reclamação uma semana depois, sem conseguir reproduzir, porque no seu teste você digitou com hífen.

O jeito de fechar esse buraco é garantir que o CEP chegue formatado no cálculo — máscara no campo do checkout é o caminho mais comum em loja brasileira, e vários temas e módulos de checkout nacionais já fazem isso. Antes de montar um CSV de 500 linhas por faixa, teste os dois formatos no seu checkout e veja se os dois devolvem o mesmo preço. Se não devolverem, resolva a máscara primeiro.

Terminada a importação, confira o que entrou de verdade — o admin lista as linhas no mesmo painel, e o banco não mente:

SELECT dest_zip, condition_value, price
FROM shipping_tablerate
WHERE dest_zip LIKE '013%';

Perguntas rápidas

Dá para fazer faixa de CEP com intervalo, tipo de 01000 a 05999?

Não com o table rate do core. Ele compara valor exato, então intervalo você resolve escrevendo uma linha para cada prefixo de 5 dígitos que quer cobrir. Um CSV gerado por planilha resolve; escrever à mão, não.

Preciso reimportar o CSV toda vez que mudo um preço?

Sim, a importação substitui as linhas daquele website inteiro. Por isso vale manter o CSV versionado no repositório do projeto: o arquivo é a fonte da verdade, e o que está no banco é só uma cópia do último import.

O table rate serve junto com um carrier de cotação em tempo real?

Serve, e é uma combinação boa. O carrier em tempo real cobre o caso geral e o table rate entra como opção fixa e barata para as regiões onde você já sabe o custo. O cliente vê as duas e escolhe.

Pra conferir na fonte

  1. Table rates — Adobe Experience League
  2. RateQuery.php (2.4-develop) — GitHub
  3. zip_codes.xml do Magento_Directory — GitHub

table rate cep csv

Precisa de um orçamento? Ficarei feliz em ajudar. Clique Aqui