"Detener el procesamiento de reglas adicionales" no se aplica a todos los artículos

8

Parece haber un error con "Detener el procesamiento de reglas adicionales" en Magento CE1.9 / EE1.13 donde solo el primer artículo de su carrito recibe el descuento.

Esperaría: si tengo varias reglas de carrito de compras, cada una de las cuales tiene "Detener el procesamiento de reglas adicionales: Sí", solo se aplicará la primera de estas reglas, sin embargo, se aplicará en su totalidad a todos los elementos coincidentes para esa regla.

Lo que sucede: el descuento solo se aplica al primer artículo del carrito, después de lo cual se detiene el procesamiento de la regla.

Ver capturas de pantalla: El descuento que espero para todo el carrito es de $ 50, pero debido a "Detener el procesamiento de reglas adicionales" solo veo $ 25.

Panel de administración de Magento

Magento Frontend Checkout

Joseph McDermott
fuente

Respuestas:

7

Creo que esto podría deberse a que _calculator se almacena efectivamente como un singleton dentro de la clase Mage_SalesRule_Model_Quote_Discount, lo que significa que el segundo elemento que se procesará alcanzará $ this -> _ stopFurtherRules == true y fianza.

Mi proceso de pensamiento es almacenar la ID de la regla $ que está bien para ser procesada, permitiendo que otros elementos procesen solo esta regla.

Según CE1.9.0.1 y 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();

Esta es mi solución propuesta, ¡me interesaría saber las razones por las que es una idea terrible!

Joseph McDermott
fuente
2

Lo que funcionó para mí fue restablecer el indicador de detener otras reglas después de que cada elemento se procese para permitir que el siguiente elemento verifique las reglas en su contra.

agregue esta línea:

$this->_stopFurtherRules = false;

directamente después de este bucle en el process()método:

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

Eso estaba en línea 518, para mí.

En mi opinión, Magento lo tiene al revés. Repite los elementos, luego las reglas para cada elemento. Debería estar iterando las reglas, luego los artículos, para que una regla pueda aplicarse a todo el carrito y solo así evitar más descuentos.

Walf
fuente
El problema con ese enfoque es (y esto es una suposición solo al echar un vistazo al código, no lo he probado) pierde la funcionalidad de 'detener más reglas', es decir. todas sus reglas se procesarán, incluso si tiene una regla que solo debe aplicarse por sí sola. Actualización: estoy de acuerdo con el comentario directo, creo que debería procesar las reglas y luego los elementos.
Joseph McDermott
@JosephMcDermott Eso no es correcto. Todavía detiene el procesamiento adicional de las reglas para ese artículo. Para muchos descuentos, se detendrá en la misma regla para cada artículo. Para los artículos a los que no se aplica una regla previamente coincidente, solo se pueden descontar tanto como lo permitan otras reglas aplicables. ¿Y Magento no solo permite que se use un solo código de cupón a la vez?
Walf
Creo que ha entendido mal la intención de detener la bandera de reglas adicionales, es para el nivel de regla, no para el nivel de elemento. Si tiene dos reglas de promoción, ninguna de las cuales requiere un código de promoción, la primera para el 30% si gasta £ 300, la segunda para el 20% si gasta £ 200, marcaría la primera regla como de menor prioridad con detener otras reglas procesamiento: sí para que el cliente solo obtenga un 30% de descuento, en lugar del 30% seguido de% 20. O puede tener una venta global del 10% de descuento en todo (sin promoción), pero si el cliente ingresa un código de promoción, no desea que el cliente obtenga esto, así que use las reglas adicionales de detención.
Joseph McDermott
@JosephMcDermott No, no lo he hecho. Soy muy consciente del propósito de esa bandera, pero Magento claramente no la usa como uno espera. Mi solución permite que cada elemento siga las reglas, al menos hasta que toquen esa bandera. Estoy seguro de que hay una mejor manera de evitar el procesamiento adicional de las reglas por completo y descontar todo el carrito, pero le garantizo que es mucho más complejo que restablecer una variable.
Walf
Bien, al menos estamos de acuerdo en que Magento lo ha hecho mal :) El público ahora tiene dos soluciones para elegir, cualquiera de las cuales debería al menos señalar a los desarrolladores en la dirección correcta.
Joseph McDermott
2

Esto se solucionó en una versión posterior de Magento CE. En 1.9.2.1 puede encontrar la solución, pero puede haberse solucionado antes.

El código original se ve así:

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

Y el código fijo debería ser:

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

La diferencia es la $this->_stopFurtherRules = false;yif ($this->_stopFurtherRules) {...}

Nada más.

O, si tiene 1.9, simplemente puede reemplazar todo el archivo sin peligro.

Espero que esto ayude a alguien.

Wouter
fuente
1

Para todo lo que necesita solucionar ese problema, debe anular el método de proceso para que la clase Mage_SalesRule_Model_Validator sea como se muestra a continuación

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
fuente