"Interromper o processamento de regras adicionais" não se aplica a todos os itens

8

Parece haver um erro com "Interromper o processamento de regras adicionais" no Magento CE1.9 / EE1.13, onde apenas o primeiro item do seu carrinho recebe o desconto.

Eu esperaria: Se eu tiver várias regras de carrinho de compras, cada uma das quais com "Interromper o processamento de regras adicionais: Sim", somente a primeira dessas regras será aplicada, mas será aplicada integralmente a todos os itens correspondentes a essa regra.

O que está acontecendo: o desconto está sendo aplicado apenas ao primeiro item do carrinho, após o qual o processamento da regra é interrompido.

Veja as capturas de tela: O desconto que estou esperando para o carrinho inteiro é de US $ 50, mas devido a "Interromper o processamento de regras adicionais", estou vendo apenas US $ 25.

Painel de Administração Magento

Magento Frontend Checkout

Joseph McDermott
fonte

Respostas:

7

Eu acho que isso pode ocorrer porque o _calculator é efetivamente armazenado como um singleton na classe Mage_SalesRule_Model_Quote_Discount, o que significa que o segundo item a ser processado atingirá $ this -> _ stopFurtherRules == true e bail.

Meu processo de pensamento é armazenar o ID da regra $ que pode ser processada, permitindo que outros itens processem apenas essa regra.

Conforme CE1.9.0.1 e EE1.14.0.1

Linha 316 do Mage_SalesRule_Model_Validator

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

Linha 514 do Mage_SalesRule_Model_Validator

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

Esta é a minha solução proposta. Gostaria de ouvir as razões pelas quais essa é uma péssima idéia!

Joseph McDermott
fonte
2

O que funcionou para mim foi redefinir o sinalizador de parar mais regras depois que cada item é processado para permitir que o próximo item verifique as regras.

adicione esta linha:

$this->_stopFurtherRules = false;

diretamente após esse loop no process()método:

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

Isso estava online 518, para mim.

Na minha opinião, Magento tem de trás para frente. Ele itera os itens, depois as regras para cada item. Deve-se iterar as regras e, em seguida, os itens, para que uma regra possa ser aplicada a todo o carrinho e só assim impedir descontos adicionais.

Walf
fonte
O problema com essa abordagem é (e isso é uma suposição apenas de olhar para o código, eu não testei) você perde a funcionalidade de 'regras adicionais', ie. todas as suas regras serão processadas, mesmo se você tiver uma regra que deve ser aplicada apenas por conta própria. Atualização: concordo com o comentário invertido, acredito que ele deve processar regras e itens.
Joseph McDermott
@JosephMcDermott Isso não está correto. Ele ainda interrompe o processamento adicional de regras para esse item. Para muitos descontos, ele irá parar na mesma regra para cada item. Para todos os itens aos quais uma regra correspondida anteriormente não se aplica, eles podem ser descontados apenas quanto outras regras aplicáveis ​​permitirem. E o Magento não permite apenas que um único código de cupom seja usado ao mesmo tempo?
Walf
Eu acho que você não entendeu a intenção da bandeira de parar mais regras, é para nível de regra e não para item. Se você tiver duas regras promocionais, nenhuma das quais requer um código promocional, a primeira para 30% se você gastar R $ 300, a segunda para 20% se você gastar R $ 200, você marcaria a primeira regra como prioridade mais baixa com outras regras de interrupção processamento: sim, para que o cliente receba apenas 30% de desconto, em vez de 30%, seguido por% 20. Ou você pode ter uma venda global de 10% de desconto em tudo (sem promoção), mas se o cliente digitar um código promocional, você não deseja que o cliente receba isso, use regras de interrupção adicionais.
Joseph McDermott
@JosephMcDermott Não, eu não tenho. Estou ciente do objetivo dessa bandeira, mas o Magento claramente não a usa como se espera. Minha solução permite que cada item siga as regras, pelo menos até atingir a bandeira. Tenho certeza de que existe uma maneira melhor de impedir o processamento adicional de regras completamente e descontar todo o carrinho, mas garanto que é muito mais complexo do que redefinir uma variável.
Walf
OK, bem, pelo menos, concordamos que o Magento fez isso mal :) O público agora tem duas soluções para escolher, uma das quais deve pelo menos apontar os desenvolvedores na direção certa.
Joseph McDermott
2

Isso foi corrigido em uma versão posterior do Magento CE. No 1.9.2.1, você pode encontrar a solução, mas ela pode ter sido corrigida mais cedo.

O código original é assim:

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

E o código fixo deve ser:

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

A diferença é $this->_stopFurtherRules = false;aeif ($this->_stopFurtherRules) {...}

Nada mais.

Ou, se você estiver no 1.9, pode simplesmente substituir o arquivo inteiro sem perigo.

Espero que isso ajude alguém.

Wouter
fonte
1

Para tudo o que precisa corrigir esse problema, deve substituir o método de processo para que a classe Mage_SalesRule_Model_Validator seja como abaixo

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;
}
Ledian Hymetllari
fonte