如果实行免费送货,为什么“送货总额”将销售报价项目的row_weight设置为0?


21

前言:这旨在作为Magento为社区(和我本人)的体系结构的书面观察,以及一个实际的问题。我们正在使用经过大量修改的购物车和结帐体验,但是此问题的根源在于Magento的核心逻辑。

背景

我们使用标准购物车价格规则功能创建了免费送货优惠券。优惠券没有任何条件,唯一的动作就是将Free Shipping设置为For matching items only。由于没有条件,这将设置free_shipping1所有销售报价的项目。

按照惯例,我们还启用了免费送货送货方式。该Freeshipping运营商模式将提供利率每当请求有免费送货,或小计匹配或超过阈值(但我们不使用阈值选项)。见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 */
}

而且Mage_Shipping_Model_Carrier_Freeshipping::_updateFreeMethodQuote是这样的:

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);
    }
}

因此,只要所有商品均free_shipping设置为真实值(由于优惠券,它们将变为真实值),我们应该免费送货。而我们做到了!

问题

但是,这样做有一个主要的副作用:依赖某项商品的任何运输方式row_weight(例如我们的FedEx承运商定制版本的情况)将无法计算正确的运费,因为每件商品row_weight均设置0为启用免费送货时的运费。

有趣的是,Magento的默认运输公司实际上都没有依靠row_weight,但是在弄清楚为什么/何时row_weight设置为时,我们将继续讨论0

弄清楚为什么row_weight设置为0

这部分实际上很容易挖掘。大量的运费计算发生在中Mage_Sales_Model_Quote_Address_Total_Shipping::collect,包括设置row_weight0

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);
        }
    }

为什么这不影响Magento的默认运营商

如果你做一个正则表达式搜索/row_?weight/i(例如getRowWeightsetRowWeightsetData('row_weight')在等)Mage_Shipping(简单的运营商)和Mage_Usa(联邦快递,UPS,以及其他一些运营商),没有弹出。为什么?因为默认承运人使用的是总地址权重,而不是单个项目的权重。

例如,让我们看一下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());
    }

请求从何处获得包裹重量?答案在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());

我们可以忽略$item->getRowWeight()这里的使用,因为requestShippingRates在没有提供特定项目作为参数的情况下调用了这里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();

这应该看起来很熟悉,因为如果免费送货生效,这是每个项目的row_weight设置位置0。注意$addressWeight每个项目的总和$rowWeight,但是row_weight设置为之前已经0完成

基本上,地址权重将始终是所有项目的总权重,而不管free_shipping每个项目的值如何。由于Magento的默认运营商仅依赖地址权重,因此row_weight不会出现问题。

那为什么我们需要 row_weight

我们需row_weight要这样做是因为我们已经定制了Magento的FedEx承运商,以计算来自不同来源的商品的单独费率,即使这些商品要运往同一目的地(因此属于同一地址)也是如此。例如,如果您住在新泽西州,那么从新泽西州发货的商品比从加利福尼亚州发货的商品便宜(快捷)-并且如果您的订单中同时包含新泽西州和加利福尼亚州的商品,则可以看到费用(并估算出交货日期)。

总而言之,看起来我们可以通过忽略row_weightweight * qty直接使用来轻松解决此问题。但是,它确实导致我们:

问题

如果实行免费送货,为什么Shipping总计将row_weight销售报价项目设置为0?这似乎没有在任何地方使用。

进一步的观察

我忽略了提及,row_weight实际上它可以是非零值,但weight * qty如果free_shipping是数字而不是,则仍然小于零true。我认为这样做的目的是为这样的情况提供解决方案:

我的购物车中有3件相同商品,每件重2磅。我申请了免费送货优惠券,但数量限制为2张,因此仅适用于其中2件。现在,当我查看运费时,我将查看2磅(2 + 0 + 0)而不是6磅(2 + 2 + 2)的运费。

这似乎很有意义,但是有两个主要问题:

  • 默认的Magento载体都没有这样工作(它们使用地址总重量,请参见上文)。

  • 即使某些承运人是这样工作的,这也意味着我可以选择任何运输方式(例如,隔夜运输),而只需支付一件商品的重量 -这意味着,商人将不得不支付其他2件商品的费用。由商人决定,我只支付了一件商品的重量,然后使用一种更具成本效益的方法运送了另外两件商品,从而有效地在Magento的显示内容与实际商品之间造成了不匹配运了出去。


嘿,没有参与者?:)
Agop 2015年

这么多赞成票,很少讨论!这就是Magento开发的生命。
阿戈普

Answers:


2

想通了我会刺这...;)

您在这里提出了一个非常有趣的问题,所以这就是我认为他们这样做的原因,但是,在这种特殊情况发生时,我仍在努力寻找。

回顾USPS运营商方法,看起来对它们的API的国际请求为每种产品提供了逐项的权重。这是我发现的唯一可以做到这一点的载体。在下面找到完整的方法,然后在下面突出显示的部分。

protected function _formIntlShipmentRequest(Varien_Object $request)
    {
        $packageParams = $request->getPackageParams();
        $height = $packageParams->getHeight();
        $width = $packageParams->getWidth();
        $length = $packageParams->getLength();
        $girth = $packageParams->getGirth();
        $packageWeight = $request->getPackageWeight();
        if ($packageParams->getWeightUnits() != Zend_Measure_Weight::POUND) {
            $packageWeight = Mage::helper('usa')->convertMeasureWeight(
                $request->getPackageWeight(),
                $packageParams->getWeightUnits(),
                Zend_Measure_Weight::POUND
            );
        }
        if ($packageParams->getDimensionUnits() != Zend_Measure_Length::INCH) {
            $length = round(Mage::helper('usa')->convertMeasureDimension(
                $packageParams->getLength(),
                $packageParams->getDimensionUnits(),
                Zend_Measure_Length::INCH
            ));
            $width = round(Mage::helper('usa')->convertMeasureDimension(
                $packageParams->getWidth(),
                $packageParams->getDimensionUnits(),
                Zend_Measure_Length::INCH
            ));
            $height = round(Mage::helper('usa')->convertMeasureDimension(
                $packageParams->getHeight(),
                $packageParams->getDimensionUnits(),
                Zend_Measure_Length::INCH
            ));
        }
        if ($packageParams->getGirthDimensionUnits() != Zend_Measure_Length::INCH) {
            $girth = round(Mage::helper('usa')->convertMeasureDimension(
                $packageParams->getGirth(),
                $packageParams->getGirthDimensionUnits(),
                Zend_Measure_Length::INCH
            ));
        }

        $container = $request->getPackagingType();
        switch ($container) {
            case 'VARIABLE':
                $container = 'VARIABLE';
                break;
            case 'FLAT RATE ENVELOPE':
                $container = 'FLATRATEENV';
                break;
            case 'FLAT RATE BOX':
                $container = 'FLATRATEBOX';
                break;
            case 'RECTANGULAR':
                $container = 'RECTANGULAR';
                break;
            case 'NONRECTANGULAR':
                $container = 'NONRECTANGULAR';
                break;
            default:
                $container = 'VARIABLE';
        }
        $shippingMethod = $request->getShippingMethod();
        list($fromZip5, $fromZip4) = $this->_parseZip($request->getShipperAddressPostalCode());

        // the wrap node needs for remove xml declaration above
        $xmlWrap = new SimpleXMLElement('<?xml version = "1.0" encoding = "UTF-8"?><wrap/>');
        $method = '';
        $service = $this->getCode('service_to_code', $shippingMethod);
        if ($service == 'Priority') {
            $method = 'Priority';
            $rootNode = 'PriorityMailIntlRequest';
            $xml = $xmlWrap->addChild($rootNode);
        } else if ($service == 'First Class') {
            $method = 'FirstClass';
            $rootNode = 'FirstClassMailIntlRequest';
            $xml = $xmlWrap->addChild($rootNode);
        } else {
            $method = 'Express';
            $rootNode = 'ExpressMailIntlRequest';
            $xml = $xmlWrap->addChild($rootNode);
        }

        $xml->addAttribute('USERID', $this->getConfigData('userid'));
        $xml->addAttribute('PASSWORD', $this->getConfigData('password'));
        $xml->addChild('Option');
        $xml->addChild('Revision', self::DEFAULT_REVISION);
        $xml->addChild('ImageParameters');
        $xml->addChild('FromFirstName', $request->getShipperContactPersonFirstName());
        $xml->addChild('FromLastName', $request->getShipperContactPersonLastName());
        $xml->addChild('FromFirm', $request->getShipperContactCompanyName());
        $xml->addChild('FromAddress1', $request->getShipperAddressStreet2());
        $xml->addChild('FromAddress2', $request->getShipperAddressStreet1());
        $xml->addChild('FromCity', $request->getShipperAddressCity());
        $xml->addChild('FromState', $request->getShipperAddressStateOrProvinceCode());
        $xml->addChild('FromZip5', $fromZip5);
        $xml->addChild('FromZip4', $fromZip4);
        $xml->addChild('FromPhone', $request->getShipperContactPhoneNumber());
        if ($method != 'FirstClass') {
            if ($request->getReferenceData()) {
                $referenceData = $request->getReferenceData() . ' P' . $request->getPackageId();
            } else {
                $referenceData = $request->getOrderShipment()->getOrder()->getIncrementId()
                                 . ' P'
                                 . $request->getPackageId();
            }
            $xml->addChild('FromCustomsReference', 'Order #' . $referenceData);
        }
        $xml->addChild('ToFirstName', $request->getRecipientContactPersonFirstName());
        $xml->addChild('ToLastName', $request->getRecipientContactPersonLastName());
        $xml->addChild('ToFirm', $request->getRecipientContactCompanyName());
        $xml->addChild('ToAddress1', $request->getRecipientAddressStreet1());
        $xml->addChild('ToAddress2', $request->getRecipientAddressStreet2());
        $xml->addChild('ToCity', $request->getRecipientAddressCity());
        $xml->addChild('ToProvince', $request->getRecipientAddressStateOrProvinceCode());
        $xml->addChild('ToCountry', $this->_getCountryName($request->getRecipientAddressCountryCode()));
        $xml->addChild('ToPostalCode', $request->getRecipientAddressPostalCode());
        $xml->addChild('ToPOBoxFlag', 'N');
        $xml->addChild('ToPhone', $request->getRecipientContactPhoneNumber());
        $xml->addChild('ToFax');
        $xml->addChild('ToEmail');
        if ($method != 'FirstClass') {
            $xml->addChild('NonDeliveryOption', 'Return');
        }
        if ($method == 'FirstClass') {
            if (stripos($shippingMethod, 'Letter') !== false) {
                $xml->addChild('FirstClassMailType', 'LETTER');
            } else if (stripos($shippingMethod, 'Flat') !== false) {
                $xml->addChild('FirstClassMailType', 'FLAT');
            } else{
                $xml->addChild('FirstClassMailType', 'PARCEL');
            }
        }
        if ($method != 'FirstClass') {
            $xml->addChild('Container', $container);
        }
        $shippingContents = $xml->addChild('ShippingContents');
        $packageItems = $request->getPackageItems();
        // get countries of manufacture
        $countriesOfManufacture = array();
        $productIds = array();
        foreach ($packageItems as $itemShipment) {
                $item = new Varien_Object();
                $item->setData($itemShipment);

                $productIds[]= $item->getProductId();
        }
        $productCollection = Mage::getResourceModel('catalog/product_collection')
            ->addStoreFilter($request->getStoreId())
            ->addFieldToFilter('entity_id', array('in' => $productIds))
            ->addAttributeToSelect('country_of_manufacture');
        foreach ($productCollection as $product) {
            $countriesOfManufacture[$product->getId()] = $product->getCountryOfManufacture();
        }

        $packagePoundsWeight = $packageOuncesWeight = 0;
        // for ItemDetail
        foreach ($packageItems as $itemShipment) {
            $item = new Varien_Object();
            $item->setData($itemShipment);

            $itemWeight = $item->getWeight() * $item->getQty();
            if ($packageParams->getWeightUnits() != Zend_Measure_Weight::POUND) {
                $itemWeight = Mage::helper('usa')->convertMeasureWeight(
                    $itemWeight,
                    $packageParams->getWeightUnits(),
                    Zend_Measure_Weight::POUND
                );
            }
            if (!empty($countriesOfManufacture[$item->getProductId()])) {
                $countryOfManufacture = $this->_getCountryName(
                    $countriesOfManufacture[$item->getProductId()]
                );
            } else {
                $countryOfManufacture = '';
            }
            $itemDetail = $shippingContents->addChild('ItemDetail');
            $itemDetail->addChild('Description', $item->getName());
            $ceiledQty = ceil($item->getQty());
            if ($ceiledQty < 1) {
                $ceiledQty = 1;
            }
            $individualItemWeight = $itemWeight / $ceiledQty;
            $itemDetail->addChild('Quantity', $ceiledQty);
            $itemDetail->addChild('Value', $item->getCustomsValue() * $item->getQty());
            list($individualPoundsWeight, $individualOuncesWeight) = $this->_convertPoundOunces($individualItemWeight);
            $itemDetail->addChild('NetPounds', $individualPoundsWeight);
            $itemDetail->addChild('NetOunces', $individualOuncesWeight);
            $itemDetail->addChild('HSTariffNumber', 0);
            $itemDetail->addChild('CountryOfOrigin', $countryOfManufacture);

            list($itemPoundsWeight, $itemOuncesWeight) = $this->_convertPoundOunces($itemWeight);
            $packagePoundsWeight += $itemPoundsWeight;
            $packageOuncesWeight += $itemOuncesWeight;
        }
        $additionalPackagePoundsWeight = floor($packageOuncesWeight / self::OUNCES_POUND);
        $packagePoundsWeight += $additionalPackagePoundsWeight;
        $packageOuncesWeight -= $additionalPackagePoundsWeight * self::OUNCES_POUND;
        if ($packagePoundsWeight + $packageOuncesWeight / self::OUNCES_POUND < $packageWeight) {
            list($packagePoundsWeight, $packageOuncesWeight) = $this->_convertPoundOunces($packageWeight);
        }

        $xml->addChild('GrossPounds', $packagePoundsWeight);
        $xml->addChild('GrossOunces', $packageOuncesWeight);
        if ($packageParams->getContentType() == 'OTHER' && $packageParams->getContentTypeOther() != null) {
            $xml->addChild('ContentType', $packageParams->getContentType());
            $xml->addChild('ContentTypeOther ', $packageParams->getContentTypeOther());
        } else {
            $xml->addChild('ContentType', $packageParams->getContentType());
        }

        $xml->addChild('Agreement', 'y');
        $xml->addChild('ImageType', 'PDF');
        $xml->addChild('ImageLayout', 'ALLINONEFILE');
        if ($method == 'FirstClass') {
            $xml->addChild('Container', $container);
        }
        // set size
        if ($packageParams->getSize()) {
            $xml->addChild('Size', $packageParams->getSize());
        }
        // set dimensions
        $xml->addChild('Length', $length);
        $xml->addChild('Width', $width);
        $xml->addChild('Height', $height);
        if ($girth) {
            $xml->addChild('Girth', $girth);
        }

        $xml = $xmlWrap->{$rootNode}->asXML();
        return $xml;
    }

特殊部分:

$packagePoundsWeight = $packageOuncesWeight = 0;
        // for ItemDetail
        foreach ($packageItems as $itemShipment) {
            $item = new Varien_Object();
            $item->setData($itemShipment);

            $itemWeight = $item->getWeight() * $item->getQty();
            if ($packageParams->getWeightUnits() != Zend_Measure_Weight::POUND) {
                $itemWeight = Mage::helper('usa')->convertMeasureWeight(
                    $itemWeight,
                    $packageParams->getWeightUnits(),
                    Zend_Measure_Weight::POUND
                );
            }
            if (!empty($countriesOfManufacture[$item->getProductId()])) {
                $countryOfManufacture = $this->_getCountryName(
                    $countriesOfManufacture[$item->getProductId()]
                );
            } else {
                $countryOfManufacture = '';
            }
            $itemDetail = $shippingContents->addChild('ItemDetail');
            $itemDetail->addChild('Description', $item->getName());
            $ceiledQty = ceil($item->getQty());
            if ($ceiledQty < 1) {
                $ceiledQty = 1;
            }
            $individualItemWeight = $itemWeight / $ceiledQty;
            $itemDetail->addChild('Quantity', $ceiledQty);
            $itemDetail->addChild('Value', $item->getCustomsValue() * $item->getQty());
            list($individualPoundsWeight, $individualOuncesWeight) = $this->_convertPoundOunces($individualItemWeight);
            $itemDetail->addChild('NetPounds', $individualPoundsWeight);
            $itemDetail->addChild('NetOunces', $individualOuncesWeight);
            $itemDetail->addChild('HSTariffNumber', 0);
            $itemDetail->addChild('CountryOfOrigin', $countryOfManufacture);

            list($itemPoundsWeight, $itemOuncesWeight) = $this->_convertPoundOunces($itemWeight);
            $packagePoundsWeight += $itemPoundsWeight;
            $packageOuncesWeight += $itemOuncesWeight;
        }

对我来说,Magento似乎是根据实际商品的重量来重新计算包裹的重量,而不是利用地址的重量。我不知道所传递的包装物品的重量是否未归零,因为考虑到费率响应将基于错误的重量数据,这将导致错误的物品重量。

虽然在黑暗中拍摄。


1

我对此也有兴趣,这行代码我发现了一些有趣的东西

$item->setRowWeight($rowWeight);

是在1.1.5版上引入的,在此之前功能是这样的。

Magento镜像导入Magento版本1.1.5-Shipping.php

Magento镜像导入Magento版本1.1.1-Shipping.php

            else {
            if (!$item->getProduct()->getTypeInstance()->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;
        }
    }

我的理解是Mage_Sales_Model_Quote_Address_Total_Shipping :: collect需要计算/更新$ addressWeight和$ freeMethodWeight

    $addressWeight      = $address->getWeight();
    $freeMethodWeight   = $address->getFreeMethodWeight();

在这里不使用$ item-> setRowWeight,因为它与项目有关,与报价和地址无关。

我敢打赌,这是一个错误。可能从不打算将row_total用于装运方法,这就是默认模块不使用它的原因。

我无法追踪任何可以解释为什么在1.1.5版中引入了变更日志。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.