Préface: Ceci est destiné à servir à la fois d'observation écrite de l'architecture de Magento pour la communauté (et moi-même), ainsi que d'une question réelle. Nous travaillons avec une expérience de panier et de paiement fortement modifiée, mais la racine de ce problème est dans la logique principale de Magento.
Contexte
Nous avons créé un coupon de livraison gratuite en utilisant la fonctionnalité standard de règles de prix du panier. Il n'y a aucune condition sur le coupon, et la seule action est celle qui Free Shipping
est définie sur For matching items only
. Puisqu'il n'y a pas de conditions, cela s'appliquera free_shipping
à 1
tous les articles du devis de vente.
Comme d'habitude, nous avons également activé la méthode d'expédition Livraison gratuite. Le Freeshipping
modèle de transporteur fournira des tarifs chaque fois que la demande a la livraison gratuite ou que le sous-total correspond ou dépasse le seuil (mais nous n'utilisons pas l'option de seuil). Voir 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 */
}
Et Mage_Shipping_Model_Carrier_Freeshipping::_updateFreeMethodQuote
ressemble à ceci:
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);
}
}
Donc, tant que tous les articles ont free_shipping
une valeur vraie (ce qu'ils seront, en raison du coupon), nous devrions obtenir la livraison gratuite. Et nous le faisons!
Le problème
Cependant, il y a un effet secondaire majeur: toutes les méthodes d'expédition qui reposent sur un article row_weight
(comme c'est le cas avec notre version personnalisée du transporteur FedEx) ne parviendront pas à calculer les tarifs d'expédition appropriés parce que chaque article row_weight
est défini 0
lorsque la livraison gratuite est active.
Chose intéressante, aucun des transporteurs maritimes par défaut de Magento ne compte vraiment row_weight
, mais nous y reviendrons après avoir compris pourquoi / quand row_weight
est réglé sur 0
.
Comprendre pourquoi row_weight
est réglé sur0
Cette partie était en fait assez facile à déterrer. Une grande partie des calculs d'expédition se produisent Mage_Sales_Model_Quote_Address_Total_Shipping::collect
, notamment la définition 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);
}
}
Pourquoi cela n'affecte pas les opérateurs par défaut de Magento
Si vous effectuez une recherche regex pour /row_?weight/i
(par exemple getRowWeight
, setRowWeight
, setData('row_weight')
, etc.) Mage_Shipping
(transporteurs simples) et Mage_Usa
(FedEx, UPS, et d'autres transporteurs), rien n'apparaît. Pourquoi? Étant donné que les transporteurs par défaut utilisent le poids total de l'adresse, pas les poids individuels des articles.
Par exemple, regardons 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());
}
Et d'où la demande obtient-elle le poids du colis? La réponse est 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());
Nous pouvons ignorer l'utilisation d' $item->getRowWeight()
ici car requestShippingRates
est appelé sans fournir un élément spécifique en tant que paramètre dans 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();
Cela devrait vous sembler familier, car c'est le même endroit où chaque article row_weight
est défini 0
si la livraison gratuite est en vigueur. Remarquez comment $addressWeight
résume chaque élément $rowWeight
, mais cela est fait avant row_weight
est défini sur0
.
Fondamentalement, le poids de l'adresse sera toujours le poids total de tous les articles, quelle que soit la free_shipping
valeur de chaque article. Étant donné que les transporteurs par défaut de Magento ne dépendent que du poids de l'adresse, le problème avec row_weight
n'apparaît pas.
Alors pourquoi avons-nous besoin row_weight
Nous en avons besoin row_weight
parce que nous avons personnalisé le transporteur FedEx de Magento pour calculer des tarifs distincts pour les articles qui viennent de différentes origines, même s'ils vont vers la même destination (et font donc partie de la même adresse). Par exemple, si vous vivez au NJ, il est moins cher (et plus rapide) d'expédier un article du NJ que de CA - et si vous avez des articles du NJ et de CA dans votre commande, vous pouvez voir le coût (et l'estimation date de livraison) de chaque expédition.
Dans l'ensemble, il semble que nous pouvons facilement contourner ce problème en ignorant row_weight
et en utilisant weight * qty
directement. Mais cela nous amène à:
La question
Pourquoi le Shipping
total définit-il les row_weight
articles du devis de vente 0
si la livraison gratuite est en vigueur? Cela ne semble être utilisé nulle part.
Observations complémentaires
J'ai négligé de mentionner que cela row_weight
pourrait en fait être non nul, mais toujours inférieur à weight * qty
, si free_shipping
c'est un nombre au lieu de true
. Je suppose que le but de ceci est de fournir une solution à un scénario comme celui-ci:
J'ai 3 articles du même produit dans mon panier, chaque article pesant 2 livres. J'applique un coupon de livraison gratuite, mais il est limité à une quantité de 2, il ne s'applique donc qu'à 2 des articles. Maintenant, quand je regarde les tarifs d'expédition, je vais regarder les tarifs d'expédition pour 2 lb (2 + 0 + 0) plutôt que 6 lb (2 + 2 + 2).
Cela semble logique, mais cela pose deux problèmes majeurs:
Aucun des transporteurs Magento par défaut ne fonctionne comme ça (ils utilisent le poids total de l'adresse, voir ci-dessus).
Même si certains des transporteurs fonctionnaient comme cela, cela signifierait que je pourrais choisir n'importe quelle méthode d'expédition (par exemple, expédition de nuit) et ne payer que le poids de 1 article - ce qui signifie que le commerçant devrait couvrir le coût des 2 autres articles. . Il appartiendrait au commerçant de comprendre en quelque sorte que je n'ai payé que le poids d'un article, puis d'expédier les 2 autres articles en utilisant une méthode plus rentable, créant effectivement un décalage entre ce que Magento affiche et la façon dont les articles étaient réellement expédiés.