Prefácio: Isso serve como uma observação escrita da arquitetura do Magento para a comunidade (e para mim), bem como uma pergunta real. Estamos trabalhando com uma experiência fortemente modificada de carrinho e caixa, mas a raiz desse problema está na lógica principal do Magento.
fundo
Criamos um cupom de frete grátis usando a funcionalidade de regras de preço do carrinho de compras padrão. Não há condições no cupom e a única ação Free Shipping
é definida como For matching items only
. Uma vez que não há condições, isto irá definir free_shipping
para 1
para todos os itens de cotação de vendas.
Como é habitual, também ativamos o método de envio Frete grátis. O Freeshipping
modelo da transportadora fornecerá taxas sempre que a solicitação tiver frete grátis ou o subtotal corresponder ou exceder o limite (mas não estamos usando a opção de limite). Veja Mage_Shipping_Model_Carrier_Freeshipping::collectRates
:
$this->_updateFreeMethodQuote($request);
if (($request->getFreeShipping()) // <-- This is the condition we're relying on
|| ($request->getBaseSubtotalInclTax() >=
$this->getConfigData('free_shipping_subtotal'))
) {
/* Snip: Add $0.00 method to the result */
}
E Mage_Shipping_Model_Carrier_Freeshipping::_updateFreeMethodQuote
fica assim:
protected function _updateFreeMethodQuote($request)
{
$freeShipping = false;
$items = $request->getAllItems();
$c = count($items);
for ($i = 0; $i < $c; $i++) {
if ($items[$i]->getProduct() instanceof Mage_Catalog_Model_Product) {
if ($items[$i]->getFreeShipping()) {
$freeShipping = true;
} else {
return;
}
}
}
if ($freeShipping) {
$request->setFreeShipping(true);
}
}
Portanto, desde que todos os itens tenham sido free_shipping
definidos como um valor verdadeiro (que será devido ao cupom), devemos receber frete grátis. E nós fazemos!
O problema
No entanto, há um efeito colateral importante: quaisquer métodos de envio que dependam de um item row_weight
(como é o caso da nossa versão personalizada da transportadora FedEx) não conseguirão calcular as taxas de envio adequadas, pois cada item row_weight
é definido 0
quando o frete grátis está ativo.
Curiosamente, nenhuma das transportadoras padrão do Magento realmente depende row_weight
, mas vamos chegar a isso depois de descobrirmos por que / quando row_weight
está definido 0
.
Descobrir por que row_weight
está definido como0
Esta parte foi realmente muito fácil de desenterrar. Uma grande parte dos cálculos de remessa ocorre Mage_Sales_Model_Quote_Address_Total_Shipping::collect
, incluindo a configuração row_weight
para 0
:
public function collect(Mage_Sales_Model_Quote_Address $address)
{
parent::collect($address);
foreach ($items as $item) {
/* Snip: Handling virtual items and parent items */
if ($item->getHasChildren() && $item->isShipSeparately()) {
/* Snip: Handling items with children */
}
else {
if (!$item->getProduct()->isVirtual()) {
$addressQty += $item->getQty();
}
$itemWeight = $item->getWeight();
$rowWeight = $itemWeight*$item->getQty();
$addressWeight+= $rowWeight;
if ($freeAddress || $item->getFreeShipping()===true) {
$rowWeight = 0;
} elseif (is_numeric($item->getFreeShipping())) {
$freeQty = $item->getFreeShipping();
if ($item->getQty()>$freeQty) {
$rowWeight = $itemWeight*($item->getQty()-$freeQty);
}
else {
$rowWeight = 0;
}
}
$freeMethodWeight+= $rowWeight;
$item->setRowWeight($rowWeight);
}
}
Por que isso não afeta as operadoras padrão do Magento
Se você fizer uma pesquisa regex para /row_?weight/i
(por exemplo getRowWeight
, setRowWeight
, setData('row_weight')
, etc.) em Mage_Shipping
(portadores simples) e Mage_Usa
(FedEx, UPS, e algumas outras operadoras), nada aparece. Por quê? Como as operadoras padrão usam o peso total do endereço, não os pesos dos itens individuais.
Por exemplo, vejamos Mage_Usa_Model_Shipping_Carrier_Fedex::setRequest
:
public function setRequest(Mage_Shipping_Model_Rate_Request $request)
{
$this->_request = $request;
$r = new Varien_Object();
/* Snip */
$weight = $this->getTotalNumOfBoxes($request->getPackageWeight());
$r->setWeight($weight);
if ($request->getFreeMethodWeight()!= $request->getPackageWeight()) {
$r->setFreeMethodWeight($request->getFreeMethodWeight());
}
E de onde a solicitação obtém o peso do pacote? A resposta está em Mage_Sales_Model_Quote_Address::requestShippingRates
:
public function requestShippingRates(Mage_Sales_Model_Quote_Item_Abstract $item = null)
{
/** @var $request Mage_Shipping_Model_Rate_Request */
$request = Mage::getModel('shipping/rate_request');
/* Snip */
$request->setPackageWeight($item ? $item->getRowWeight() : $this->getWeight());
Podemos ignorar o uso $item->getRowWeight()
daqui porque requestShippingRates
é chamado sem fornecer um item específico como parâmetro em Mage_Sales_Model_Quote_Address_Total_Shipping::collect
:
public function collect(Mage_Sales_Model_Quote_Address $address)
{
parent::collect($address);
foreach ($items as $item) {
/* Snip: Handling virtual items and parent items */
if ($item->getHasChildren() && $item->isShipSeparately()) {
/* Snip: Handling items with children */
}
else {
if (!$item->getProduct()->isVirtual()) {
$addressQty += $item->getQty();
}
$itemWeight = $item->getWeight();
$rowWeight = $itemWeight*$item->getQty();
$addressWeight+= $rowWeight;
if ($freeAddress || $item->getFreeShipping()===true) {
$rowWeight = 0;
} elseif (is_numeric($item->getFreeShipping())) {
$freeQty = $item->getFreeShipping();
if ($item->getQty()>$freeQty) {
$rowWeight = $itemWeight*($item->getQty()-$freeQty);
}
else {
$rowWeight = 0;
}
}
$freeMethodWeight+= $rowWeight;
$item->setRowWeight($rowWeight);
}
}
$address->setWeight($addressWeight);
$address->setFreeMethodWeight($freeMethodWeight);
$address->collectShippingRates();
Isso deve parecer familiar, pois é o mesmo local em que cada item row_weight
é definido 0
se o frete grátis estiver em vigor. Observe como $addressWeight
resume cada item do $rowWeight
, mas isso é feito antes row_weight
está definido para0
.
Basicamente, o peso do endereço sempre será o peso total de todos os itens, independentemente do free_shipping
valor de cada item. Como as operadoras padrão do Magento dependem apenas do peso do endereço, o problema row_weight
não aparece.
Então, por que precisamos row_weight
Precisamos row_weight
porque personalizamos a transportadora FedEx da Magento para calcular taxas separadas para itens provenientes de origens diferentes, mesmo que eles estejam indo para o mesmo destino (e, portanto, fazendo parte do mesmo endereço). Por exemplo, se você mora em NJ, é mais barato (e mais rápido) enviar um item de NJ do que de CA - e se você tiver itens de NJ e CA em seu pedido, poderá ver o custo (e as estimativas data de entrega) de cada remessa.
Em suma, parece que podemos solucionar esse problema facilmente ignorando row_weight
e usando weight * qty
diretamente. Mas isso nos leva a:
A questão
Por que o Shipping
total define os row_weight
itens da cotação de venda 0
se o frete grátis está em vigor? Isso não parece ser utilizado em lugar algum.
Observações adicionais
Eu esqueci de mencionar que, row_weight
na verdade, pode ser diferente de zero, mas ainda menor que weight * qty
, se free_shipping
for um número, em vez de true
. Presumo que o objetivo disso seja fornecer uma solução para um cenário como este:
Eu tenho 3 itens do mesmo produto no meu carrinho, cada item pesando 2 libras. Aplico um cupom de frete grátis, mas ele é limitado a uma quantidade de 2, portanto se aplica somente a 2 itens. Agora, quando eu olhar as taxas de envio, estarei analisando as taxas de envio por 2 libras (2 + 0 + 0) em vez de 6 libras (2 + 2 + 2).
Isso parece fazer sentido, mas há dois grandes problemas:
Nenhuma das operadoras Magento padrão funciona assim (elas usam o peso total do endereço, veja acima).
Mesmo que algumas das transportadoras funcionassem dessa maneira, isso significaria que eu poderia escolher qualquer método de envio (por exemplo, remessa noturna) e pagar apenas pelo peso de 1 item - ou seja, o comerciante teria que cobrir o custo dos outros 2 itens . Caberia ao comerciante descobrir, de alguma forma, que paguei apenas o peso de 1 item e depois enviei os outros 2 itens usando um método mais econômico, criando uma incompatibilidade entre o que o Magento exibe e como os itens eram realmente enviado para fora.