Jump to content

[Fix] Quantity Discounts do not work properly (BUGS 6792 & 6827)


inveostore.com

Recommended Posts

The aim of this post is to explain the fix for Quantity Discounts to PS developers (in Bugtracker under 6792 & 6827) and show how to fix it.

Description: Quantity Discounts do not work properly -> currently they only work correctly for percentage discounts and with a Tax assigned to product which "correct" the final price so everything looks almost fine (sorry for description - it is too much time ago we debug this problem and I do not really remember more details).

Reason: The reason why it does not work is basically due to the design of code located in classes/PaymentModule.php:


$reduc = QuantityDiscount::getValue($price_wt, $qtyD->id_discount_type, $qtyD->value, new Currency(intval($order->id_currency)));



This code INCORRECTLY computes quantity discount amount for product_quantity_discount field in order_details table, because it computes the discount from price ($price_wt variable) to which was Quantity Discount already applied (and also Group Reduction applied what is wrong as well).

The problem with "Quantity Discount already applied" can easily be solved by getting the price of product by calling Product::getPriceStatic() method with 1 quantity of product (currently it uses variable with price computed for multiple products).
The problem with "Group Reduction already applied" has to be solved by adding new argument to Product::getPriceStatic() (there is no other way).

Here is a fix:

Open classes/PaymentModule.php

This fixes how product_quantity_discount field is computed (need also changes in Product::getPriceStatic() - described below):

Find:

$reduc = QuantityDiscount::getValue($price_wt, $qtyD->id_discount_type, $qtyD->value, new Currency(intval($order->id_currency)));



Replace with:

$price_wt_no_qtyD = Product::getPriceStatic(intval($product['id_product']), true, ($product['id_product_attribute'] ? intval($product['id_product_attribute']) : NULL), 6, NULL, false, true, 1, false, NULL, NULL, NULL, false);
$reduc = QuantityDiscount::getValue($price_wt_no_qtyD, $qtyD->id_discount_type, $qtyD->value, ($tax) ? true : false, $tax, $currency);



This fixes the price inserted to product_price field in order_details table which needs to be pure price without any reductions applied:

Find:

'.floatval(Product::getPriceStatic(intval($product['id_product']), false, ($product['id_product_attribute'] ? intval($product['id_product_attribute']) : NULL), (Product::getTaxCalculationMethod(intval($order->id_customer)) == PS_TAX_EXC ? 2 : 6), NULL, false, false, $product['cart_quantity'], false, intval($order->id_customer), intval($order->id_cart), intval($order->id_address_delivery))).',



Replace with:

'.floatval(Product::getPriceStatic(intval($product['id_product']), false, ($product['id_product_attribute'] ? intval($product['id_product_attribute']) : NULL), (Product::getTaxCalculationMethod(intval($order->id_customer)) == PS_TAX_EXC ? 2 : 6), NULL, false, false, 1, false, intval($order->id_customer), intval($order->id_cart), intval($order->id_address_delivery))).',



This fixes strange warning but not required I think:

Find:

'.(int)QuantityDiscount::getDiscountFromQuantity(intval($product['id_product']), intval($product['cart_quantity'])).',



Replace with:

'.((!QuantityDiscount::getDiscountFromQuantity(intval($product['id_product']), intval($product['cart_quantity']))) ? '0' : '1').',




Now we need to add new argument to Product::getPriceStatic() method in classes/Product.php file:
(to be able to get price without Group Reduction discount applied - this fix does not change the behavior of this method so any old code will work exactly same as before)

Find:

public static function getPriceStatic($id_product, $usetax = true, $id_product_attribute = NULL, $decimals = 6, $divisor = NULL, $only_reduc = false, $usereduc = true, $quantity = 1, $forceAssociatedTax = false, $id_customer = NULL, $id_cart = NULL, $id_address_delivery = NULL)



Replace with:

public static function getPriceStatic($id_product, $usetax = true, $id_product_attribute = NULL, $decimals = 6, $divisor = NULL, $only_reduc = false, $usereduc = true, $quantity = 1, $forceAssociatedTax = false, $id_customer = NULL, $id_cart = NULL, $id_address_delivery = NULL, $group_reduc = true)



Find:

if ($usereduc)
   $price -= Tools::ps_round($price * Group::getReduction(((isset($id_customer) AND $id_customer) ? $id_customer : 0)) / 100, 2);



Replace with:

if ($usereduc && $group_reduc)
   $price -= Tools::ps_round($price * Group::getReduction(((isset($id_customer) AND $id_customer) ? $id_customer : 0)) / 100, 2);



Find:

$cacheId = $id_product.'-'.($usetax?'1':'0').'-'.$id_product_attribute.'-'.$decimals.'-'.$divisor.'-'.($only_reduc?'1':'0').'-'.($usereduc?'1':'0').'-'.$quantity.'-'.($id_customer ? $id_customer : '0');



Replace with:

$cacheId = $id_product.'-'.($usetax?'1':'0').'-'.$id_product_attribute.'-'.$decimals.'-'.$divisor.'-'.($only_reduc?'1':'0').'-'.($usereduc?'1':'0').'-'.($group_reduc?'1':'0').'-'.$quantity.'-'.($id_customer ? $id_customer : '0');



All is now FINALLY correctly inserted to database and only last two issues needs to be corrected:
Order::_deleteProduct() and Order::setProductPrices() method in classes/Order.php. This fix will be posted in next post because there is a limit of post size.

Link to comment
Share on other sites

Open classes/Order.php file and replace complete Order::setProductPrices() method with following code:

    public function setProductPrices(&$row)
   {
       if ($this->_taxCalculationMethod == PS_TAX_EXC)
           $row['product_price'] = Tools::ps_round($row['product_price'], 2);
       else
           $row['product_price_wt'] = Tools::ps_round($row['product_price'] * (1 + $row['tax_rate'] / 100), 2);
       if ($row['reduction_percent'])
       {
           if ($this->_taxCalculationMethod == PS_TAX_EXC)
               $row['product_price'] = $row['product_price'] - $row['product_price'] * ($row['reduction_percent'] * 0.01);
           else
               $row['product_price_wt'] = Tools::ps_round($row['product_price_wt'] - $row['product_price_wt'] * ($row['reduction_percent'] * 0.01), 2);
       }
       if ($row['reduction_amount'])
       {
           if ($this->_taxCalculationMethod == PS_TAX_EXC)
               $row['product_price'] = $row['product_price'] - $row['reduction_amount'];
           else
               $row['product_price_wt'] = Tools::ps_round($row['product_price_wt'] - $row['reduction_amount'] * (1 + ($row['tax_rate'] * 0.01)), 2);
       }

       // Added - makes Qty discounts work properly - BEGIN
       if ($row['product_quantity_discount'])
       {
           if ($this->_taxCalculationMethod == PS_TAX_EXC)
               $row['product_price'] = Tools::ps_round($row['product_price'] - $row['product_quantity_discount'] / (1 + ($row['tax_rate'] * 0.01)), 2);
           else
               $row['product_price_wt'] = $row['product_price_wt'] - $row['product_quantity_discount'];
       }
       // Added - makes Qty discounts work properly - END

       if ($row['group_reduction'])
       {
           if ($this->_taxCalculationMethod == PS_TAX_EXC)
               $row['product_price'] = $row['product_price'] - $row['product_price'] * ($row['group_reduction'] * 0.01);
           else
               $row['product_price_wt'] = Tools::ps_round($row['product_price_wt'] - $row['product_price_wt'] * ($row['group_reduction'] * 0.01), 2);
       }
       if (($row['reduction_percent'] OR $row['reduction_amount'] OR $row['group_reduction']) AND $this->_taxCalculationMethod == PS_TAX_EXC)
           $row['product_price'] = Tools::ps_round($row['product_price'], 2);
       // Added - makes Qty discounts work properly - BEGIN
       if($row['product_quantity_discount'] AND $this->_taxCalculationMethod == PS_TAX_INC)
           $row['product_price'] = Tools::ps_round($row['product_price'], 2);
       // Added - makes Qty discounts work properly - END
       if ($this->_taxCalculationMethod == PS_TAX_EXC)
           $row['product_price_wt'] = Tools::ps_round($row['product_price'] * (1 + ($row['tax_rate'] * 0.01)), 2) + Tools::ps_round($row['ecotax'] * (1 + $row['ecotax_tax_rate'] / 100), 2);
       else
       {
           $row['product_price_wt_but_ecotax'] = $row['product_price_wt'];
           $row['product_price_wt'] = Tools::ps_round($row['product_price_wt'] + $row['ecotax'] * (1 + $row['ecotax_tax_rate'] / 100), 2);
       }
       $row['total_wt'] = $row['product_quantity'] * $row['product_price_wt'];
       $row['total_price'] = $row['product_quantity'] * $row['product_price_wt'];
   }

Link to comment
Share on other sites

Open classes/Order.php file and replace complete Order::_deleteProduct() method with following code:

    private function _deleteProduct($orderDetail, $quantity)
   {
       $row = Db::getInstance()->getRow('
                       SELECT *
                       FROM `'._DB_PREFIX_.'order_detail` od
                       WHERE od.`id_order_detail` = '.intval($orderDetail->id));
       $this->setProductPrices($row);

       /* Update cart */
       $cart = new Cart($this->id_cart);
       $cart->updateQty($quantity, $orderDetail->product_id, $orderDetail->product_attribute_id, false, 'down'); // customization are deleted in deleteCustomization
       $cart->update();

       /* Update order */
       $shippingDiff = $this->total_shipping - $cart->getOrderShippingCost();
       $price = $row['product_price_wt'] * $quantity;
       $this->total_products -= $row['product_price'] * $quantity;
       $this->total_products_wt -= $row['product_price_wt'] * $quantity;
       $this->total_shipping = $cart->getOrderShippingCost();
       $this->total_paid -= ($price + $shippingDiff);
       $this->total_paid_real -= ($price + $shippingDiff);

       /* Prevent from floating precision issues (total_products has only 2 decimals) */
       if ($this->total_products < 0)
           $this->total_products = 0;

       /* Prevent from floating precision issues */
       $this->total_paid = number_format($this->total_paid, 2, '.', '');
       $this->total_paid_real = number_format($this->total_paid_real, 2, '.', '');
       $this->total_products = number_format($this->total_products, 2, '.', '');
       $this->total_products_wt = number_format($this->total_products_wt, 2, '.', '');

       /* Update order detail */
       $orderDetail->product_quantity -= intval($quantity);

       if (!$orderDetail->product_quantity)
       {
           if (!$orderDetail->delete())
               return false;
           if (count($this->getProductsDetail()) == 0)
           {
               global $cookie;
               $history = new OrderHistory();
               $history->id_order = intval($this->id);
               $history->changeIdOrderState(_PS_OS_CANCELED_, intval($this->id));
               if (!$history->addWithemail())
                   return false;
           }
           return $this->update();
       }
       return $orderDetail->update() AND $this->update();
   }



Thats all. Now Quantity Discounts start to work properly. According to what we see in forums, integrating this fix to PS help a LOT of people. It was very common problem which reached many PS users.

If anything is not clear, feel free to ask us. I will reply ASAP.

BTW we made about 200 test orders during debugging this bug... :-)

Link to comment
Share on other sites

  • 4 weeks later...
  • 1 month later...

Because I have error on total calculation using discount per quantity without taxe, I could be happy with your corrected 1.3 PS version, Team will not update anymore 1.3 version.


I made all these modifications but this doesn't resolve miscalculation on total with percent quantity discount when taxe is not activated.

product A, price 2.50, discount 5% from 2

I added 2 on cart, price for each is 2.38, for 2 4.76 but total is 4.75 ! WRONG !

This issue cause a Paypal error on paiement (Paypal calculate right price 4.76)

Could it be possible to have a fix for it too?

Link to comment
Share on other sites

  • 4 weeks later...
  • 3 years later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...