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 Shippinges establecerlo For matching items only. Como no hay condiciones, se establecerá free_shippingen 1todos los artículos de presupuesto de ventas.
Como es habitual, también hemos habilitado el método de envío Envío gratuito. El Freeshippingmodelo 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::_updateFreeMethodQuoteve 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_shippingun 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_weightestá configurado 0cuando 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_weightestá configurado 0.
Averiguar por qué row_weightse 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_weightde 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 requestShippingRatesse 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_weightse configura cada artículo 0si el envío gratuito está en vigor. Observe cómo $addressWeightresume cada elemento $rowWeight, pero eso se hace antes row_weightse establece en0 .
Básicamente, el peso de la dirección siempre será el peso total de todos los artículos, independientemente del free_shippingvalor de cada artículo. Dado que los operadores predeterminados de Magento solo dependen del peso de la dirección, el problema con row_weightno aparece.
Entonces, ¿por qué necesitamos row_weight
Necesitamos row_weightporque 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_weighty usando weight * qtydirectamente. Pero sí nos lleva a:
La pregunta
¿Por qué el Shippingtotal establece el valor row_weightde los artículos de cotización de ventas 0si el envío gratuito está vigente? Esto no parece ser utilizado en ningún lado.
Observaciones adicionales
Olvidé mencionar que en row_weightrealidad podría ser distinto de cero, pero aún menor que weight * qty, si free_shippinges 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.