Чому загальна вартість доставки встановлює вагу рядка ваги цінних пропозицій на 0, якщо діє безкоштовна доставка?


21

Передмова: Це покликане слугувати як письмовим спостереженням архітектури Magento для громади (і мене самого), так і актуальним питанням. Ми працюємо із сильно модифікованим кошиком та досвідом оформлення замовлень, але корінь цієї проблеми полягає в основній логіці Magento.

Фон

Ми створили купон на безкоштовну доставку, використовуючи стандартні функції цін на кошики для покупок. У купоні немає ніяких умов, і єдина дія, яка Free Shippingвстановлена For matching items only. Оскільки немає ніяких умов, це буде встановлено free_shippingв 1протягом всіх продажів цитати пунктів.

Як це прийнято, ми також увімкнули спосіб доставки Безкоштовна доставка. Модель 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_weightна 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);
        }
    }

Чому це не впливає на операторів за замовчуванням Magento

Якщо ви робите пошук регулярних виразів для /row_?weight/i(наприклад getRowWeight, setRowWeight, setData('row_weight')і т.д.) в Mage_Shipping(прості носії) і Mage_Usa(FedEx, 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 тому що ми настроїли перевізник FedEx Magento, щоб обчислювати окремі тарифи на товари, які надходять з різного походження, навіть якщо вони їдуть до одного місця призначення (і, таким чином, є частиною однієї адреси). Наприклад, якщо ви живете в штаті Нью-Джерсі, дешевше (і швидше) доставити товар з Нью-Джерсі, ніж з Каліфорнії, - і якщо у вас є предмети як з Нью-Джерсі, так і з Каліфорнії, ви зможете побачити вартість (і приблизну оцінку) дата доставки) кожної вантажу.

Загалом, схоже, що ми можемо легко обійти цю проблему, ігноруючи row_weightта використовуючи weight * qtyбезпосередньо. Але це нас призводить до:

Питання

Чому Shippingзагальний обсяг встановлених row_weightцін на продажі встановлюється, 0якщо діє безкоштовна доставка? Здається, це ніде не використовується.

Подальші спостереження

Я знехтував згадати, що row_weightнасправді може бути не нульовим, але все ж меншим, ніж weight * qty, якщо free_shippingце число замість true. Я припускаю, що мета цього - запропонувати рішення такого сценарію:

У мене в кошику є 3 предмети того самого товару, кожен предмет важить 2 фунти. Я застосовую купон на безкоштовну доставку, але він обмежений кількістю 2, тому він стосується лише двох предметів. Тепер, коли я дивлюсь на тарифи на доставку, я буду дивитись на тарифи на доставку за 2 фунти (2 + 0 + 0), а не за 6 фунтів (2 + 2 + 2).

Це, мабуть, має сенс, але в цьому є дві основні проблеми:

  • Жоден з операторів Magento за замовчуванням не працює так (вони використовують загальну вагу адреси, див. Вище).

  • Навіть якщо хтось із перевізників працював так, це означатиме, що я можу вибрати будь-який спосіб доставки (наприклад, доставка за ніч) і заплатити лише вагу 1 товару - це означає, що торговець повинен буде покрити вартість інших 2 товарів . Торговець мав би якось зрозуміти, що я заплатив лише вагу 1 предмета, а потім відвантажував інші 2 предмети, використовуючи більш економічний метод, фактично створюючи невідповідність між тим, що відображає Magento, і тим, як фактично були товари. відвантажений.


Хе, не приймають? :)
Agop

Стільки голосів, стільки дискусій! Таке життя розвитку Magento.
Agop

Відповіді:


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 Release 1.1.5 - Shipping.php

Імпорт дзеркала Magento Magento Release 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 :: збирати потрібно обчислити / оновити $ addressWeight та $ freeMethodWeight

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

і $ item-> setRowWeight shod не використовуються тут, оскільки пов'язані з елементом, а не з цитатою та адресою.

Моя обставина, що це помилка. Ймовірно, row_total ніколи не призначався для використання в методах доставки, тому модулі за замовчуванням не використовують його.

Я не зміг відстежити жоден журнал змін, який може пояснити, чому це було введено в версії 1.1.5.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.