PrestaShop: Quantity Discounts Fix

In PrestaShop 1.3 Quantity Discounts do not work properly – currently they only work correctly for percentage discounts and with a Tax assigned to the product which “correct” the final price so everything looks almost fine. However in fact it doesn’t in certain cases.

Note: We’ve previously posted this fix at prestashop.com: [Fix] Quantity Discounts do not work properly (BUGS 6792 & 6827)

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

$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 (also Group Reduction was 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 a new argument to Product::getPriceStatic() method.

Let’s fix it.

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 (not required):

.(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 will add a 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 which means any old code will work exactly the 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');

From now on all records are inserted to the database with correct values and the only last two issues needs to be corrected: Order::_deleteProduct() and Order::setProductPrices() methods in classes/Order.php file.

Open classes/Order.php file and replace the entire Order::setProductPrices() method with the 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);
	}

	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);

	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'];
}

Replace with:

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'];
}

Open classes/Order.php file and replace the entire Order::_deleteProduct() method with the 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();
}

That’s all. Now Quantity Discounts in PrestaShop 1.3 start to work properly 🙂

Leave a Reply

Cart  
(empty)

Cart Check out  »

Prices are tax inclusive.

The VAT rate for your country (US) * is 0,0 % because it is not a member of the European Union (EU).

* Please create an account if your country does not match.

Community feed
  • [Zen Cart] Can't execute Magic SEO URLs sitemaps
    I installed Magic SEO URLs Sitemaps Add-On for ZenCart MSU4.x/MSU5.x 2.0 on my server, when I try to generate sitemaps using: https://www.pechesudv155.owally.com/sitemaps.php I received :...
    by peter@pechesud.com
  • [phpBB3] Sharing users between phpBB / PrestaShop
    Hi i have 2 question 1- prestashop module support phpBB 3.3 ? 2- why we can not login in prestashop and phpbb with same user?
    by zohall
  • [phpBB3] AJAX Userinfo Extension
    Hi! I'm having trouble with this extension: https://www.phpbb.com/customise/db/exte ... _userinfo/ I tried asking support from the author but for no avail, since now. My request for support...
    by Lord Phobos
  • [phpBB3] Upgrade to phpBB 3.2.3
    Hello.. after Upgrade to phpBB 3.2.3 I am getting this message.. How can I solve this issue..? Thank you
    by ingbrzy
  • [phpBB3] URL path changed
    Hello.. I have changed my site path after moving to new hosting and now can not activate SEO module.. new path http://www.miuios.cz/domains/miuios.cz/ could you help me? thank you
    by ingbrzy2
Join our support forum » Pre-Sales Questions »
Featured Testimonials

Magic SEO URLs for PrestaShop is amazing product, it help me a lot with SEO, and the customer service is very nice and patient! I will built a new website, and I will buy this module again.

Joseph, the owner of Focus Vows

Magic SEO URLs for ZenCart were an excellent investment for our medium-sized business, and the support team revised the software to get our non-standard Zen Cart working. It does everything we need it to, and overall I have been thrilled with Magic SEO URLs. I mean every word of that. Thanks again.

Dimitri, the owner of Headphones - Headsonic Australia

More Testimonials »