Prefacio: Esto está destinado a servir como una observación escrita de la arquitectura de Magento para la comunidad (y para mí), así como una pregunta real. Estamos trabajando con una experiencia de compra y pago muy modificada, pero la raíz de este problema está dentro de la lógica central de Magento.
Fondo
Hemos creado un cupón de envío gratuito utilizando la funcionalidad estándar de reglas de precios del carrito de compras. No hay condiciones en el cupón, y la única acción Free Shipping
es establecerlo For matching items only
. Como no hay condiciones, se establecerá free_shipping
en 1
todos los artículos de presupuesto de ventas.
Como es habitual, también hemos habilitado el método de envío Envío gratuito. El Freeshipping
modelo de transportista proporcionará tarifas siempre que la solicitud tenga envío gratuito o el subtotal coincida o supere el umbral (pero no estamos utilizando la opción de umbral). Ver 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 */
}
Y se Mage_Shipping_Model_Carrier_Freeshipping::_updateFreeMethodQuote
ve así:
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);
}
}
Por lo tanto, siempre y cuando todos los artículos tengan free_shipping
un valor verdadero (que lo harán, debido al cupón), deberíamos recibir el envío gratis. Y lo hacemos!
El problema
Sin embargo, hay un efecto secundario importante: cualquier método de envío que dependa de un artículo row_weight
(como es el caso de nuestra versión personalizada del transportista FedEx) no calculará las tarifas de envío adecuadas porque cada artículo row_weight
está configurado 0
cuando el envío gratuito está activo.
Curiosamente, ninguno de los transportistas de envío predeterminados de Magento realmente confía row_weight
, pero llegaremos a eso después de descubrir por qué / cuándo row_weight
está configurado 0
.
Averiguar por qué row_weight
se establece en0
Esta parte fue realmente bastante fácil de desenterrar. Una gran parte de los cálculos de envío ocurren Mage_Sales_Model_Quote_Address_Total_Shipping::collect
, incluida la configuración row_weight
de 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 qué esto no afecta a los operadores predeterminados de Magento
Si realiza una búsqueda de expresiones regulares /row_?weight/i
(p getRowWeight
. Ej . setRowWeight
, setData('row_weight')
Etc.) en Mage_Shipping
(operadores simples) y Mage_Usa
(FedEx, UPS y algunos otros operadores), no aparecerá nada. ¿Por qué? Debido a que los transportistas predeterminados usan el peso total de la dirección, no los pesos de los artículos individuales.
Por ejemplo, echemos un vistazo a 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());
}
¿Y de dónde obtiene la solicitud el peso del paquete? La respuesta está en 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 el uso de $item->getRowWeight()
aquí porque requestShippingRates
se llama sin proporcionar un elemento específico como parámetro en 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();
Esto debería parecer familiar, ya que este es el mismo lugar en el que row_weight
se configura cada artículo 0
si el envío gratuito está en vigor. Observe cómo $addressWeight
resume cada elemento $rowWeight
, pero eso se hace antes row_weight
se establece en0
.
Básicamente, el peso de la dirección siempre será el peso total de todos los artículos, independientemente del free_shipping
valor de cada artículo. Dado que los operadores predeterminados de Magento solo dependen del peso de la dirección, el problema con row_weight
no aparece.
Entonces, ¿por qué necesitamos row_weight
Necesitamos row_weight
porque hemos personalizado el operador FedEx de Magento para calcular tarifas separadas para artículos que provienen de diferentes orígenes, incluso si van al mismo destino (y por lo tanto son parte de la misma dirección). Por ejemplo, si vive en NJ, es más barato (y más rápido) enviar un artículo desde NJ que desde CA, y si tiene artículos de NJ y CA en su pedido, puede ver el costo (y estimado fecha de entrega) de cada envío.
En general, parece que podemos solucionar este problema fácilmente ignorando row_weight
y usando weight * qty
directamente. Pero sí nos lleva a:
La pregunta
¿Por qué el Shipping
total establece el valor row_weight
de los artículos de cotización de ventas 0
si el envío gratuito está vigente? Esto no parece ser utilizado en ningún lado.
Observaciones adicionales
Olvidé mencionar que en row_weight
realidad podría ser distinto de cero, pero aún menor que weight * qty
, si free_shipping
es un número en lugar de true
. Supongo que el propósito de esto es proporcionar una solución a un escenario como este:
Tengo 3 artículos del mismo producto en mi carrito, cada artículo pesa 2 libras. Solicito un cupón de envío gratuito, pero está limitado a una cantidad de 2, por lo que solo se aplica a 2 de los artículos. Ahora, cuando miro las tarifas de envío, miraré las tarifas de envío para 2 lb (2 + 0 + 0) en lugar de 6 lb (2 + 2 + 2).
Esto parece tener sentido, pero hay dos problemas principales:
Ninguno de los operadores de Magento predeterminados funciona así (usan el peso total de la dirección, ver arriba).
Incluso si algunos de los transportistas trabajaran así, eso significaría que podría elegir cualquier método de envío (por ejemplo, envío nocturno) y solo pagar el peso de 1 artículo , lo que significa que el comerciante tendría que cubrir el costo de los otros 2 artículos. . Depende del comerciante descubrir de alguna manera que solo pagué el peso de 1 artículo, y luego enviar los otros 2 artículos usando un método más rentable, creando efectivamente un desajuste entre lo que muestra Magento y cómo eran realmente los artículos enviado.