«Остановить дальнейшую обработку правил» не распространяется на все позиции

8

Похоже, что в Magento CE1.9 / EE1.13 есть ошибка «Остановить дальнейшую обработку правил», когда скидка предоставляется только на первый товар в вашей корзине.

Я ожидаю: если у меня есть несколько правил корзины покупок, каждое из которых имеет «Остановить дальнейшую обработку правил: да», будет применяться только первое из этих правил, однако оно будет применяться полностью ко всем соответствующим элементам для этого правила.

Что происходит: скидка распространяется только на первый товар в корзине, после чего обработка правила прекращается.

Смотрите скриншоты: скидка, которую я ожидаю на всю корзину, составляет 50 долларов, но из-за «Остановить дальнейшую обработку правил» я вижу только 25 долларов.

Панель управления Magento

Magento Frontend Checkout

Джозеф МакДермотт
источник

Ответы:

7

Я думаю, что это может быть связано с тем, что _calculator эффективно хранится в виде синглтона в классе Mage_SalesRule_Model_Quote_Discount, что означает, что второй обрабатываемый элемент получит $ this -> _ stopFutureRules == true и выдает залог.

Мой мыслительный процесс заключается в сохранении идентификатора $ rule, который можно обрабатывать, что позволяет другим элементам обрабатывать только это правило.

Согласно CE1.9.0.1 и EE1.14.0.1

Mage_SalesRule_Model_Validator line 316

- if ($this->_stopFurtherRules) {
+ if ($this->_stopFurtherRules !== false && $rule->getId() != $this->_stopFurtherRules) {

Mage_SalesRule_Model_Validator line 514

- $this->_stopFurtherRules = true;
+ $this->_stopFurtherRules = $rule->getId();

Это мое предлагаемое решение, мне было бы интересно услышать причины, по которым это ужасная идея!

Джозеф МакДермотт
источник
2

Что мне помогло, так это сброс флага « Остановить дальнейшие правила» после обработки каждого элемента, чтобы позволить следующему элементу сверять правила с ним.

добавьте эту строку:

$this->_stopFurtherRules = false;

непосредственно после этого цикла в process()методе:

foreach ($this->_getRules() as $rule) {
    ...
}

Это было на линии 518, для меня.

На мой взгляд, у Magento это задом наперед. Итерирует элементы, затем правила для каждого элемента. Следует повторить правила, затем элементы, чтобы правило могло применяться ко всей корзине и только тогда предотвращать дальнейшие скидки.

WALF
источник
Проблема с этим подходом состоит в том, что (и это предположение, просто взглянув на код, я не проверял), вы теряете функциональность «стоп-правила», т.е. все ваши правила будут обработаны, даже если у вас есть правило, которое должно применяться только самостоятельно. Обновление: я согласен с обратным комментарием, я считаю, что он должен обрабатывать правила, а затем элементы.
Джозеф МакДермотт
@JosephMcDermott Это не правильно. Это все еще останавливает дальнейшую обработку правил для этого элемента. Для многих скидок, он будет останавливаться на том же правиле для каждого товара. Для любых предметов, к которым ранее не подходящее правило не применяется, их можно сбрасывать со счетов только настолько, насколько позволяют другие применимые правила. И разве Magento не позволяет использовать только один код купона одновременно?
Уолф
Я думаю, вы неправильно поняли намерение флага «остановить дальнейшие правила», это для уровня правила, а не для уровня предмета. Если у вас есть два промо-правила, ни одно из которых не требует промо-кода: первое для 30%, если вы тратите 300 фунтов стерлингов, второе для 20%, если вы тратите 200 фунтов стерлингов, вы пометите первое правило как более низкое с приоритетом и остановите дальнейшие правила. обработка: да, чтобы клиент получал только 30% скидку вместо 30% с последующим% 20. Или у вас может быть глобальная продажа со скидкой 10% от всего (без промо), но если покупатель вводит промо-код, вы не хотите, чтобы покупатель получал его, поэтому используйте дополнительные правила стоп.
Джозеф МакДермотт
@JosephMcDermott Нет, не знаю. Я хорошо знаю цель этого флага, но Magento явно не использует его, как того ожидают. Мое решение позволяет каждому элементу проходить через правила, по крайней мере, пока они не достигнут этого флага. Я уверен, что есть лучший способ полностью предотвратить дальнейшую обработку правил и сделать скидку на всю корзину, но я гарантирую, что это намного сложнее, чем сброс одной переменной.
Уолф
Хорошо, по крайней мере, мы согласны, что Magento сделал это плохо :) У публики теперь есть два решения на выбор, любое из которых должно, по крайней мере, указывать разработчикам правильное направление.
Джозеф МакДермотт
2

Это было исправлено в более поздней версии Magento CE. В 1.9.2.1 вы можете найти решение, но оно могло быть исправлено раньше.

Исходный код выглядит так:

$appliedRuleIds = array();
foreach ($this->_getRules() as $rule) {
    if ($this->_stopFurtherRules) {
        break;
    }

И фиксированный код должен быть:

$appliedRuleIds = array();
$this->_stopFurtherRules = false;
foreach ($this->_getRules() as $rule) {
    // The if-clause is removed
    ...    

Разница есть $this->_stopFurtherRules = false;иif ($this->_stopFurtherRules) {...}

Ничего больше.

Или, если вы на 1.9, вы можете просто заменить весь файл без опасности.

Надеюсь, это кому-нибудь поможет.

Wouter
источник
1

Для всего, что нужно исправить, следует переопределить метод процесса для класса Mage_SalesRule_Model_Validator, как показано ниже

public function process(Mage_Sales_Model_Quote_Item_Abstract $item)
{
    $item->setDiscountAmount(0);
    $item->setBaseDiscountAmount(0);
    $item->setDiscountPercent(0);
    $quote      = $item->getQuote();
    $address    = $this->_getAddress($item);

    $itemPrice              = $this->_getItemPrice($item);
    $baseItemPrice          = $this->_getItemBasePrice($item);
    $itemOriginalPrice      = $this->_getItemOriginalPrice($item);
    $baseItemOriginalPrice  = $this->_getItemBaseOriginalPrice($item);

    if ($itemPrice < 0) {
        return $this;
    }

    $appliedRuleIds = array();
    $this->_stopFurtherRules = false;
    foreach ($this->_getRules() as $rule) {

        /* @var $rule Mage_SalesRule_Model_Rule */
        if (!$this->_canProcessRule($rule, $address)) {
            continue;
        }

        if (!$rule->getActions()->validate($item)) {
            continue;
        }

        $qty = $this->_getItemQty($item, $rule);
        $rulePercent = min(100, $rule->getDiscountAmount());

        $discountAmount = 0;
        $baseDiscountAmount = 0;
        //discount for original price
        $originalDiscountAmount = 0;
        $baseOriginalDiscountAmount = 0;

        switch ($rule->getSimpleAction()) {
            case Mage_SalesRule_Model_Rule::TO_PERCENT_ACTION:
                $rulePercent = max(0, 100-$rule->getDiscountAmount());
            //no break;
            case Mage_SalesRule_Model_Rule::BY_PERCENT_ACTION:
                $step = $rule->getDiscountStep();
                if ($step) {
                    $qty = floor($qty/$step)*$step;
                }
                $_rulePct = $rulePercent/100;
                $discountAmount    = ($qty * $itemPrice - $item->getDiscountAmount()) * $_rulePct;
                $baseDiscountAmount = ($qty * $baseItemPrice - $item->getBaseDiscountAmount()) * $_rulePct;
                //get discount for original price
                $originalDiscountAmount    = ($qty * $itemOriginalPrice - $item->getDiscountAmount()) * $_rulePct;
                $baseOriginalDiscountAmount =
                    ($qty * $baseItemOriginalPrice - $item->getDiscountAmount()) * $_rulePct;

                if (!$rule->getDiscountQty() || $rule->getDiscountQty()>$qty) {
                    $discountPercent = min(100, $item->getDiscountPercent()+$rulePercent);
                    $item->setDiscountPercent($discountPercent);
                }
                break;
            case Mage_SalesRule_Model_Rule::TO_FIXED_ACTION:
                $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount());
                $discountAmount    = $qty * ($itemPrice-$quoteAmount);
                $baseDiscountAmount = $qty * ($baseItemPrice-$rule->getDiscountAmount());
                //get discount for original price
                $originalDiscountAmount    = $qty * ($itemOriginalPrice-$quoteAmount);
                $baseOriginalDiscountAmount = $qty * ($baseItemOriginalPrice-$rule->getDiscountAmount());
                break;

            case Mage_SalesRule_Model_Rule::BY_FIXED_ACTION:
                $step = $rule->getDiscountStep();
                if ($step) {
                    $qty = floor($qty/$step)*$step;
                }
                $quoteAmount        = $quote->getStore()->convertPrice($rule->getDiscountAmount());
                $discountAmount     = $qty * $quoteAmount;
                $baseDiscountAmount = $qty * $rule->getDiscountAmount();
                break;

            case Mage_SalesRule_Model_Rule::CART_FIXED_ACTION:
                if (empty($this->_rulesItemTotals[$rule->getId()])) {
                    Mage::throwException(Mage::helper('salesrule')->__('Item totals are not set for rule.'));
                }

                /**
                 * prevent applying whole cart discount for every shipping order, but only for first order
                 */
                if ($quote->getIsMultiShipping()) {
                    $usedForAddressId = $this->getCartFixedRuleUsedForAddress($rule->getId());
                    if ($usedForAddressId && $usedForAddressId != $address->getId()) {
                        break;
                    } else {
                        $this->setCartFixedRuleUsedForAddress($rule->getId(), $address->getId());
                    }
                }
                $cartRules = $address->getCartFixedRules();
                if (!isset($cartRules[$rule->getId()])) {
                    $cartRules[$rule->getId()] = $rule->getDiscountAmount();
                }

                if ($cartRules[$rule->getId()] > 0) {
                    if ($this->_rulesItemTotals[$rule->getId()]['items_count'] <= 1) {
                        $quoteAmount = $quote->getStore()->convertPrice($cartRules[$rule->getId()]);
                        $baseDiscountAmount = min($baseItemPrice * $qty, $cartRules[$rule->getId()]);
                    } else {
                        $discountRate = $baseItemPrice * $qty /
                            $this->_rulesItemTotals[$rule->getId()]['base_items_price'];
                        $maximumItemDiscount = $rule->getDiscountAmount() * $discountRate;
                        $quoteAmount = $quote->getStore()->convertPrice($maximumItemDiscount);

                        $baseDiscountAmount = min($baseItemPrice * $qty, $maximumItemDiscount);
                        $this->_rulesItemTotals[$rule->getId()]['items_count']--;
                    }

                    $discountAmount = min($itemPrice * $qty, $quoteAmount);
                    $discountAmount = $quote->getStore()->roundPrice($discountAmount);
                    $baseDiscountAmount = $quote->getStore()->roundPrice($baseDiscountAmount);

                    //get discount for original price
                    $originalDiscountAmount = min($itemOriginalPrice * $qty, $quoteAmount);
                    $baseOriginalDiscountAmount = $quote->getStore()->roundPrice($baseItemOriginalPrice);

                    $cartRules[$rule->getId()] -= $baseDiscountAmount;
                }
                $address->setCartFixedRules($cartRules);

                break;

            case Mage_SalesRule_Model_Rule::BUY_X_GET_Y_ACTION:
                $x = $rule->getDiscountStep();
                $y = $rule->getDiscountAmount();
                if (!$x || $y > $x) {
                    break;
                }
                $buyAndDiscountQty = $x + $y;

                $fullRuleQtyPeriod = floor($qty / $buyAndDiscountQty);
                $freeQty  = $qty - $fullRuleQtyPeriod * $buyAndDiscountQty;

                $discountQty = $fullRuleQtyPeriod * $y;
                if ($freeQty > $x) {
                    $discountQty += $freeQty - $x;
                }

                $discountAmount    = $discountQty * $itemPrice;
                $baseDiscountAmount = $discountQty * $baseItemPrice;
                //get discount for original price
                $originalDiscountAmount    = $discountQty * $itemOriginalPrice;
                $baseOriginalDiscountAmount = $discountQty * $baseItemOriginalPrice;
                break;
        }

        $result = new Varien_Object(array(
            'discount_amount'      => $discountAmount,
            'base_discount_amount' => $baseDiscountAmount,
        ));
        Mage::dispatchEvent('salesrule_validator_process', array(
            'rule'    => $rule,
            'item'    => $item,
            'address' => $address,
            'quote'   => $quote,
            'qty'     => $qty,
            'result'  => $result,
        ));

        $discountAmount = $result->getDiscountAmount();
        $baseDiscountAmount = $result->getBaseDiscountAmount();

        $percentKey = $item->getDiscountPercent();
        /**
         * Process "delta" rounding
         */
        if ($percentKey) {
            $delta      = isset($this->_roundingDeltas[$percentKey]) ? $this->_roundingDeltas[$percentKey] : 0;
            $baseDelta  = isset($this->_baseRoundingDeltas[$percentKey])
                ? $this->_baseRoundingDeltas[$percentKey]
                : 0;
            $discountAmount += $delta;
            $baseDiscountAmount += $baseDelta;

            $this->_roundingDeltas[$percentKey]     = $discountAmount -
                $quote->getStore()->roundPrice($discountAmount);
            $this->_baseRoundingDeltas[$percentKey] = $baseDiscountAmount -
                $quote->getStore()->roundPrice($baseDiscountAmount);
            $discountAmount = $quote->getStore()->roundPrice($discountAmount);
            $baseDiscountAmount = $quote->getStore()->roundPrice($baseDiscountAmount);
        } else {
            $discountAmount     = $quote->getStore()->roundPrice($discountAmount);
            $baseDiscountAmount = $quote->getStore()->roundPrice($baseDiscountAmount);
        }

        /**
         * We can't use row total here because row total not include tax
         * Discount can be applied on price included tax
         */

        $itemDiscountAmount = $item->getDiscountAmount();
        $itemBaseDiscountAmount = $item->getBaseDiscountAmount();

        $discountAmount     = min($itemDiscountAmount + $discountAmount, $itemPrice * $qty);
        $baseDiscountAmount = min($itemBaseDiscountAmount + $baseDiscountAmount, $baseItemPrice * $qty);

        $item->setDiscountAmount($discountAmount);
        $item->setBaseDiscountAmount($baseDiscountAmount);

        $item->setOriginalDiscountAmount($originalDiscountAmount);
        $item->setBaseOriginalDiscountAmount($baseOriginalDiscountAmount);

        $appliedRuleIds[$rule->getRuleId()] = $rule->getRuleId();

        $this->_maintainAddressCouponCode($address, $rule);
        $this->_addDiscountDescription($address, $rule);

        if ($rule->getStopRulesProcessing()) {
            $this->_stopFurtherRules = true;
            break;
        }
    }

    $item->setAppliedRuleIds(join(',',$appliedRuleIds));
    $address->setAppliedRuleIds($this->mergeIds($address->getAppliedRuleIds(), $appliedRuleIds));
    $quote->setAppliedRuleIds($this->mergeIds($quote->getAppliedRuleIds(), $appliedRuleIds));

    return $this;
}
Ледиан Химетлари
источник