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_prefixOu 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.90Detalhes que importam nesse arquivo:
- O país aceita ISO2 ou ISO3 —
BReBRAfuncionam 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
- Table rates — Adobe Experience League
- RateQuery.php (2.4-develop) — GitHub
- zip_codes.xml do Magento_Directory — GitHub