Jump to content

change order quantity to a decimal value


wfreebird

Recommended Posts

Hi guys,

I am working on a shop for selling fabrics where customers need to be able to order a quantity in a decimal value.
So the customer needs to be able to order for example 1,5 meters of a fabric.
Can anyone tell me where i can find the code to do so?
Change the database is no problem and i am good with php and html/css but i am new with prestashop and smarty.

I hope anyone can help me.

Thank you in advance.

Greetings Werner

Link to comment
Share on other sites

Hi Whitelighter,

Thank you for your reply.
This weak i tried your solution but i came upon an other problem. The shop does not sell only fabrics but also other stuff like studs and zippers witch need to be sell-ed in single pieces.
Also all the online shops that sell fabrics have the functionality to order a decimal value.

I hope there is somebody that can help me because i really need this.

Greetings Werner

Link to comment
Share on other sites

One more idea
add field `measured` int default 0 to table ps_product,
set it to 1 for fabrics and 0 for other goods,
in tpl files check this field (it will be automatically the property of object $product):

{if $product.measured>0}
 {assign var='productPrice' value=$product.price*100}
{else}
 {assign var='productPrice' value=$product.price}
{/if}



then change the price output from $product.price to $productPrice, for instance:

{displayWtPrice p=$productPrice}

Link to comment
Share on other sites

Hi guys!

I found the solution. I realized it on my website. It is very very simple.

First of all change INT to Decimal in database ps_product - quantity AND change intval to floatval near quantity or qty in the following files:

1. /order.php
2. /cart.php
3.classes/cart.php
4.classes/product.php
5.classes/order.php
6.themes/prestashop/product.tpl

  • Like 1
Link to comment
Share on other sites

  • 1 month later...
  • 1 month later...

Helo coloboque,

I have solved the problem in 1.4. I will give you the solution tomorrow because it is a lang story and i don't have the time now.
I wanted to post the solution much ealier but i didn't have the time for it.
So tomorrow i post it here.

With kind regards
Werner

Link to comment
Share on other sites

Hi,

So here is finally my answer, sorry promised Saturday but there was something between.

It's a lot of work in 1.4.
First I changed the database. I searched in all the tables with the fields quantity and changed the type to decimal(17,2).
The tables are:
- ps
- ps
- ps_discount
- ps_orders (total_products and total_products_wt)
- ps_order_detail (all the fields with quantity in the name)
- ps_order_return_detail
- ps_order_slip_detail
- ps_pack
- ps_product
- ps_product_attribute
- ps_product_sale
- ps_specific_price
- ps_stock_mvt

After this you go to your prestashop folder
Now you have to search for all the files where the quantity value is set.
Open the files one by one and search for "intval($row['cart_quantity'])" for example. Every line with the word "intval" before the word "quantity" or "qty".
Change the word "intval" to "floatval".
Then repeat all but now you search for the word "int" an change this to "float".
You need to do this for the files:
- prestashop/classes/Attribute.php
- prestashop/classes/Cart.php
- prestashop/classes/Customization.php
- prestashop/classes/Discount.php
- prestashop/classes/Order.php
- prestashop/classes/OrderDetail.php
- prestashop/classes/OrderHistory.php
- prestashop/classes/OrderReturn.php
- prestashop/classes/Product.php
- prestashop/classes/ProductSale.php
- prestashop/classes/QuantityDiscount.php
- prestashop/classes/StockMvt.php
- prestashop/controllers/CartController.php
- prestashop/controllers/OrderController.php
- prestashop/order.php
- prestashop/cart.php
- prestashop/themes/prestashop/product.tpl (or if you have a other theme prestashop/themes/themename/product.tpl

Please check yourself if the above files are all the files you need to change. Just open the other files as well and search.
I did it with dreamweaver and used "find and replace" type by Find "int" and by Replace "float".

I hope this was useful.
It's a lot of work but in the end it works for the users of your shop.
Good luck

With kind regards,
Werner

  • Like 1
Link to comment
Share on other sites

Dear Stanik,

If you do it right it works with the new version of prestashop.
I did this with prestashop 1.4.0.17.
You can see the result at: www.lacouture.nl
It is a dutch site so all text is in dutch.

Unfurtunetly you can only use a dot and not a comma.

Hope this helps.

With kind regards,
Werner

Link to comment
Share on other sites

  • 1 month later...

First, my thanks belongs to wfreebird for his instructions about edit Prestashop database and source codes - it very helped me.
I solved a problem with a dot and comma. You can add few lines to source code for an automatic changing comma for a dot.

1. edit javascript in path: \themes\prestashop\js\cart-summary.js

a) In function "function updateQty(val)" make some changes (there are only in begin of function):

function updateQty(val)
{
 val = changeToDot(val);
 var id = $(this.el).attr('name');
 var exp = new RegExp("^([0-9]*[.]?)?[0-9]+$");


 $('input[name='+ id +']').val(val);


 ////...next code was not changed...////

 if (exp.test(val) == true)
 {
   var hidden = $('input[name='+ id +'_hidden]').val();
   var input = $('input[name='+ id +']').val();
   var QtyToUp = parseFloat(input) - parseFloat(hidden);

   if (parseFloat(QtyToUp) > 0)
     upQuantity(id.replace('quantity_', ''),QtyToUp);
   else if(parseFloat(QtyToUp) < 0)
     downQuantity(id.replace('quantity_', ''),QtyToUp);
 }
 else
   $('input[name='+ id +']').val($('input[name='+ id +'_hidden]').val());
}




B) Add this function before function "function updateQty(val)":

function changeToDot(myFloatNumber){
for (var i=0;i        var currentChar = myFloatNumber.charAt(i);

       if (currentChar == ","){
           myFloatNumber = myFloatNumber.replace(/,/, '.');
           break;
       }
   }
   return myFloatNumber;
}




2. edit php file in path: \controllers\CartController.php

find and comment a row with:

$qty = (float)(abs(Tools::getValue('qty', 1)));


and place instead this row:

$qty = (float)(abs(str_replace(",",".",Tools::getValue('qty', 1))));




It´s all. I hope this helps you.

Link to comment
Share on other sites

  • 9 months later...

I'm trying to do the same in the last version of prestashop and there is no way that works. I am configuring a fabric store, and the minimum quantity I need is 0,20 cm. I change all the .php archives and database but still not work.

 

Any advise?

Link to comment
Share on other sites

That`s the same I need, a store that sells fabrics and other products that must be sold in units. It seems that this solution doesn`t work with 1.45 and 1.47. We will try harder and if if we became to a solution I`ll let you know.

 

I believe that the only person that will solve this issue will be someone that knows very well Prestasop`s core in order to make the needed changes.

 

 

Any help????

 

 

Thanks!!!! :)

Link to comment
Share on other sites

  • 1 month later...

Hi guys,

 

Can we we ask a prestashop developer to make a module or to ad it in the next upgrade together?

I have coded a previous version to have the quantities in a decimal value but my problem now is that i can't upload to the new version and i want to update the outdated design of the site.

So if anyone know how to contact a developer i will be happy to contact him or her and ask.

 

With kind regards,

Werner Vrijvogel

Link to comment
Share on other sites

  • 8 months later...
  • 2 weeks later...

i've done all the canges (wfreebird gave) but...

 

When i Add product... let´s say with value 10.25 from product.php, with a minium_quantity of 10.25 (the same valu)

It seem likt that value is rounde (ceil...) to 10. So it´s not possible to add 10.25. But 11....

 

Maybe i forgot some changes. Plz Help....

Link to comment
Share on other sites

  • 1 month later...
So the following attachment has my notes how I fixed a decimal product quantity order. It says step by step what to do, It worked for me on Prestashop 1.5.4.1. decimal_input_notes.txt It might be done easier but these are my notes so why not share them Goodluck

 

Hi, I followed all the steps but there are some things I have not made correctly:

- Show units with decimals in the confirmation email that is sent to the client (order_conf)

- Subtract the stock in decimals

 

Did you get it to work?

 

PD: I'm working with 1.5.3.1

  • Like 1
Link to comment
Share on other sites

Hi, I followed all the steps but there are some things I have not made correctly:

- Show units with decimals in the confirmation email that is sent to the client (order_conf)

- Subtract the stock in decimals

 

Did you get it to work?

 

PD: I'm working with 1.5.3.1

 

Yeah, I've found out my notes arent complete yet. Some error's still in here. I am still working on it. Hope to post an update tomorrow. Sorry for not posting a complete solution.

  • Like 1
Link to comment
Share on other sites

Hello,

thanks for this solution , I have the same errors that diosimmy and cant put 0.55 or 0.etc on minium quantity, anyway im searching what we need to change for works correctly :D

 

Have an other fail:

 

/module/cashondelivery/validation

 

-> Cart cannot be loaded or an order has already been placed using this cart

 

:/ maybe i need change validations quantity files?

 

Thanks in advance

Edited by tabutnas (see edit history)
Link to comment
Share on other sites

Soo my own solution worked perfect for me locally. But no I've tried it on a host with a fresh prestashop install. And now my total price doesent caculate add a decimal quantity.

I've been searching for a fix for 2 days now.. Cant seem to get it working. I can fix the pdf + email bug but, not his one. So any help is welcome.

 

In this screenshot you see the wrong calculation.

post-566138-0-31976900-1369239295_thumb.png

 

I keep thinking it's a database that need's some adaption.... But cant find it!

Thank you in advance.

  • Like 1
Link to comment
Share on other sites

Yes, for me is the same.. but crash on /module/cashondelivery/validation have 500 internal server error at do this change. :(

Edit:

 

I managed to fix this bug, my problems now are;

  • Minimum quantity adapt to decimals
  • Some feilds (backend-front-end) don't show decimals

Edited by tabutnas (see edit history)
Link to comment
Share on other sites

well now works 100% fine to me, now i understand china philosophy XD

well this is my contribution;

i change some files... basically just search and change to float or modify functions 2 show correct quantities as decimal.

 

sql: equal than bolinga but add this changes

ps_product -> out_of_stock

ps_product_attribute -> minimal_quantity

just search and change tables what have "out_of_stock or minimal_quantity" and change to decimal 17,2 and "default minimum"

 

Some variables u need 2 search and change:

quantity

minimal_quantity

minimum_quantity

$qty

$nbTotalProducts

out_of_stock

 

 

that's all, thank bolinga :D

  • Like 2
Link to comment
Share on other sites

  • 2 weeks later...
  • 3 weeks later...

Bolinga, did you finally solve your problem?

 

Tabutnas, how exactly did you change the files? I don't understand exactly what "modify functions" is, or how do you make the search.

 

I don't have much idea of web programming, and I have tried to change my prestashop to decimals but it keeps displaying a wrong total price.

 

Do you have problems when adding new products to the catalog (error message: "Minimum quantity incorrect", I have to change it each time I edit a product, from 1.00 to 1).

 

Thank you!

Link to comment
Share on other sites

Bolinga, did you finally solve your problem?

 

Tabutnas, how exactly did you change the files? I don't understand exactly what "modify functions" is, or how do you make the search.

 

I don't have much idea of web programming, and I have tried to change my prestashop to decimals but it keeps displaying a wrong total price.

 

Do you have problems when adding new products to the catalog (error message: "Minimum quantity incorrect", I have to change it each time I edit a product, from 1.00 to 1).

 

Thank you!

My problem is fixed now :) had to change invoice and pdf controllers for admin and front.

 

If your total price is not calculated correctly, you missed a int at the controllers. Also make shure to change all the controllers for in the controllers/admin folder, since this is the place where you input the amount of products. Best thing might be open up all the controllers and search qty and change the int's to float and after that do the same for quant.

 

I'm sorry that I stopped making notes of all the changes that I made..

Goodluck though

Link to comment
Share on other sites

  • 1 month later...
  • 2 weeks later...

I've changed all these things...but in the cart summary the total isn't correct

 

nsfz.jpg

 

In the suggested changes I don't understand these:

 

-OwnName_cart -quantity

-quantity_per_user

-OwnName_cart_rule_product_group -quantity

-OwnName_customization -quantity

-OwnName_order_detail -quantity

-product_quantity_in_stock

-OwnName_order_return_detail -product_quantity

-OwnName_pack -quantity

-OwnName_product -minimal_quantity

-quantity

-OwnName_product_shop -minimal_quantity

-OwnName_stock_avaible -quantity

-OwnName_product_sale -quantity

-OwnName_cart_product -quantity

 

In the db there aren't table called "OwnName" (or with the ps site's name) :(

 

Thank you!!

Link to comment
Share on other sites

  • 1 month later...

I've changed all these things...but in the cart summary the total isn't correct

 

nsfz.jpg

 

In the suggested changes I don't understand these:

 

-OwnName_cart -quantity

-quantity_per_user

-OwnName_cart_rule_product_group -quantity

-OwnName_customization -quantity

-OwnName_order_detail -quantity

-product_quantity_in_stock

-OwnName_order_return_detail -product_quantity

-OwnName_pack -quantity

-OwnName_product -minimal_quantity

-quantity

-OwnName_product_shop -minimal_quantity

-OwnName_stock_avaible -quantity

-OwnName_product_sale -quantity

-OwnName_cart_product -quantity

 

In the db there aren't table called "OwnName" (or with the ps site's name) :(

 

Thank you!!

 

At the installation of Prestashop you can chose a unique name for these tables. People refer them as "OwnName" since it's different for every installation.

Link to comment
Share on other sites

  • 3 weeks later...

Yes, for me is the same.. but crash on /module/cashondelivery/validation have 500 internal server error at do this change. :(

Edit:

 

I managed to fix this bug, my problems now are;

  • Minimum quantity adapt to decimals
  • Some feilds (backend-front-end) don't show decimals

 

Hi Tabutnas, I am working on the same and have this problem now also. What did you do to correct this? Any more people that solved the complete decimal issue, having tips?

 

Also, the cart doesnt calculate correctly; it ignores the decimal figure, only uses the digit before the "." . I am using PS 1.5.4.1

Edited by AnitaR (see edit history)
Link to comment
Share on other sites

  • 2 weeks later...

you can use to find column in database table with name quantity in it by this SQL statement

SELECT DISTINCT TABLE_NAME, column_name, column_type
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME LIKE  '%quantity%'
AND TABLE_SCHEMA =  'write your DB name here'
  • Like 2
Link to comment
Share on other sites

  • 5 months later...

Hello,

 

I don't understand something

When you say "In the DATABASE change all these items to decimals(17,2)"; that means that we have to change "quantity" by "decimals (17,2)", or only "decimals", or only "(17,2)" ???

Thanks for your help

Link to comment
Share on other sites

  • 4 weeks later...

Hi,

It's possible make a product pack with a product decimal quantity? I already change the table ps_pack and the classes/Pack.php file but when I save the product Prestashop's put a round number in the all product quantity.

Anybody can help me?

 

PD: sorry for my bad english

Link to comment
Share on other sites

Hi,

 

It's possible make a product pack with a product decimal quantity? I already change the table ps_pack and the classes/Pack.php file but when I save the product Prestashop's put a round number in the all product quantity.

 

Anybody can help me?

 

PD: sorry for my bad english

 

Sorry, I forget change the addItem function in AdminProductsController.php. Fix it!

 

Link to comment
Share on other sites

  • 2 months later...
  • 3 weeks later...

Hi,

 

here's a solution that works for me (version 1.6.0.9):

 

1) Change the database quantity related fields type to DECIMAL (17,2).

 

ps_product: quantity, minimal_quantity

ps_order_return_detail: product_quantity

ps_pack: quantity

ps_product_attribute: quantity, minimal_quantity

ps_product_sale: quantity

ps_stock: physical_quantity, usable_quantity

ps_cart_rule: quantity, quantity_per_user

ps_customization: quantity, quantity_refunded, quantity_returned

ps_order_detail: product_quantity, product_quantity_in_stock, product_quantity_refunded, product_quantity_return

ps_specific_price_rule: from_quantity

ps_order_slip_detail: product_quantity

ps_supply_order_detail: quantity_recieved, quantity_expected

ps_stock_avaible: quantity 

ps_cart_product: quantity 

ps_stock_mvt: physical_quantity

ps_cart_rule_product_rule_group: quantity

 

2) Change declarations and checks related to variables like quantity, qty and similar from int to float.

 

2.1)

For examle: 

Override the OrderDetail.php class with following changes:

line 170-173

'product_quantity' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true), 'product_quantity_in_stock' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'product_quantity_return' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'product_quantity_refunded' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'product_quantity_reinjected' =>array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'),

line 180

'product_quantity_discount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'),

line 191 

'discount_quantity_applied' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'),

line 562

$this->product_quantity = (float)($product['cart_quantity'])

line 570-571

$productQuantity = (float)(Product::getQuantity($this->product_id, $this->product_attribute_id)); $this->product_quantity_in_stock = ($productQuantity - (float)($product['cart_quantity']) < 0) ? $productQuantity : (float)($product['cart_quantity']);

Remaining classes to check and override are:

Order.php

Customization.php

Cart.php

Product.php

 

2.2) Override front controllers:

 

CartController.php

ProductController.php

OrderOpcController.php

 

2.3) Override admin controllers:

 

AdminOrdersController.php

AdminCartsController.php

AdminProductsController.php

 

2.4) Change admin template file /admin/themes/default/template/controllers/orders/controllers/orders/_product_line.tpl

 

line 69

<span class="product_quantity_show{if (float)$product['product_quantity'] > 1} badge{/if}">{$product['product_quantity']}</span>

Basically this allows you to see decimal quantity in orders (ORDERS->Orders) from back office.

 

2.5) Change your theme templates:

 

order-detail.tpl

shopping-cart.tpl

product.tpl

 

2.6) Change your theme blockcart module:

 

blockcart-json.tpl

blockcart.tpl

blockcart.php

 

2.7) Change your theme js files:

 

order-opc.js

 

line 932

newTotalQty += parseFloat($(this).val());

line 944 

totalQty += parseFloat($(this).val());

product.js

 

line 250

var currentVal = parseFloat($('input[name='+fieldName+']').val());

line 264

var currentVal = parseFloat($('input[name='+fieldName+']').val());

cart-summary.js

 

line 346 - new RegExp checks floats instead of integers, this allows to manually change and automatically update product quantity during checkout on cart page.

var exp = new RegExp("^[0-9]+\.[0-9]+$");

line 352

var QtyToUp = parseFloat(input) - parseFloat(hidden); 

line 354

if (parseFloat(QtyToUp) > 0)

line 356

else if(parseFloat(QtyToUp) < 0)

line 442

(parseFloat(jsonData.summary.products[i].customization_quantity) > 0))

line 801

nbrProducts += parseFloat(product_list[i].quantity);

So, that’s it. Actually there is more work to do to change customer account views in order to represent decimal values. Maybe some other templates still to change. Anyway, this basically does the trick.

Edited by Mykhailo (see edit history)
  • Like 9
Link to comment
Share on other sites

  • 2 weeks later...

Great post Mykhailo.

Do you know if the changes to the database will be lost in an upgrade of PS? 

I have no idea, actually, I'm running the latest version right now  :rolleyes:

 

PS.

 

Just a quick update to the solution:
 
Do not forget to change your Product.php class in order to save decimal quantities in back end.
Link to comment
Share on other sites

  • 2 weeks later...
  • 4 months later...

hi,
i've tried several times the procedure , but every time something doesn't work....
someone can post the files  modified to work with prestashop 1.6.0.9 with decimals?, i need decimal to start my own shop.. i'm not a programmer, when i modify the files from int to float i made every time a mistake...
 

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...
  • 1 month later...
  • 2 months later...

Im sorry for re open the thread, but I have the same problem, need the qty to br from 0,1 to about 9,999 with 3 decimal precission, Im usin the version 1.6. It look the problems persists for many users, a guy made a module but sells it for 247 euros, im not a enterpyse, just starting a small store to my town, so i can afford that price.

anyone can tell me how to just admit decimal values in qty field?

Thx a lot guys!

Link to comment
Share on other sites

  • 3 months later...
  • 2 weeks later...
  • 1 month later...

Just realised PrestaShop doesn't like real numbers for product quantities. Like the the person who originally started this thread, I am setting up a store which will sell fabrics, among other things.

 

Thus, some products will be 1, 2, 3...n while others will be 1.5, 2.5, 3, n.n.

 

There have been solutions for earlier versions of the system (1.3/1.4) but having only just started working with it, I am unsure if there is any potential for them to work in 1.6.1.1 (guess probably not though).

 

While there is a module, many, including my client, are not in a position to be able to spend the 199 euro asking price.

 

Couldn't there be something built into the core of the system (in the next release even), for each product for instance, that either asks if the product is sold by the metre, or as a whole quantity, and the system honours the value typed in, both when writing it to the database, and when retrieving it for display?

 

If anyone has thoughts or assistance, anything, I, and quite possibly others, would be very appreciative.

Link to comment
Share on other sites

  • 1 month later...

I have tried this on 1.6.1.1. Either I missed something or there is more to change than in previous versions, as currently, it is not accepting submitted decimal values.

 

Surely there is a better way than having to "hack core" and render a system unable to be upgraded due to the probability the upgrade will overwrite all changes.

 

I wonder if anyone from PrestaShop is listening.

 

...

 

Guess not.

Link to comment
Share on other sites

  • 3 months later...
  • 3 months later...
  • 3 months later...

HI,

 

Hello, I'd like to set up the decimal quantities for my products on prestashop 1.5.2.0; all of that works fine actually, except for the back office part. There seems to be no way to update a quantity in an already passed order - any idea about what file could be managing quantities updates ?updatequantity.png Screenshot follows, thanks a lot !

Link to comment
Share on other sites

  • 3 months later...
  • 1 month later...

Hi,

 

here's a solution that works for me (version 1.6.0.9):

 

1) Change the database quantity related fields type to DECIMAL (17,2).

 

ps_product: quantity, minimal_quantity

ps_order_return_detail: product_quantity

ps_pack: quantity

ps_product_attribute: quantity, minimal_quantity

ps_product_sale: quantity

ps_stock: physical_quantity, usable_quantity

ps_cart_rule: quantity, quantity_per_user

ps_customization: quantity, quantity_refunded, quantity_returned

ps_order_detail: product_quantity, product_quantity_in_stock, product_quantity_refunded, product_quantity_return

ps_specific_price_rule: from_quantity

ps_order_slip_detail: product_quantity

ps_supply_order_detail: quantity_recieved, quantity_expected

ps_stock_avaible: quantity 

ps_cart_product: quantity 

ps_stock_mvt: physical_quantity

ps_cart_rule_product_rule_group: quantity

 

2) Change declarations and checks related to variables like quantity, qty and similar from int to float.

 

2.1)

For examle: 

Override the OrderDetail.php class with following changes:

line 170-173

'product_quantity' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true), 'product_quantity_in_stock' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'product_quantity_return' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'product_quantity_refunded' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'product_quantity_reinjected' =>array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'),

line 180

'product_quantity_discount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'),

line 191 

'discount_quantity_applied' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'),

line 562

$this->product_quantity = (float)($product['cart_quantity'])

line 570-571

$productQuantity = (float)(Product::getQuantity($this->product_id, $this->product_attribute_id)); $this->product_quantity_in_stock = ($productQuantity - (float)($product['cart_quantity']) < 0) ? $productQuantity : (float)($product['cart_quantity']);

Remaining classes to check and override are:

Order.php

Customization.php

Cart.php

Product.php

 

2.2) Override front controllers:

 

CartController.php

ProductController.php

OrderOpcController.php

 

2.3) Override admin controllers:

 

AdminOrdersController.php

AdminCartsController.php

AdminProductsController.php

 

2.4) Change admin template file /admin/themes/default/template/controllers/orders/controllers/orders/_product_line.tpl

 

line 69

<span class="product_quantity_show{if (float)$product['product_quantity'] > 1} badge{/if}">{$product['product_quantity']}</span>

Basically this allows you to see decimal quantity in orders (ORDERS->Orders) from back office.

 

2.5) Change your theme templates:

 

order-detail.tpl

shopping-cart.tpl

product.tpl

 

2.6) Change your theme blockcart module:

 

blockcart-json.tpl

blockcart.tpl

blockcart.php

 

2.7) Change your theme js files:

 

order-opc.js

 

line 932

newTotalQty += parseFloat($(this).val());

line 944 

totalQty += parseFloat($(this).val());

product.js

 

line 250

var currentVal = parseFloat($('input[name='+fieldName+']').val());

line 264

var currentVal = parseFloat($('input[name='+fieldName+']').val());

cart-summary.js

 

line 346 - new RegExp checks floats instead of integers, this allows to manually change and automatically update product quantity during checkout on cart page.

var exp = new RegExp("^[0-9]+\.[0-9]+$");

line 352

var QtyToUp = parseFloat(input) - parseFloat(hidden); 

line 354

if (parseFloat(QtyToUp) > 0)

line 356

else if(parseFloat(QtyToUp) < 0)

line 442

(parseFloat(jsonData.summary.products[i].customization_quantity) > 0))

line 801

nbrProducts += parseFloat(product_list[i].quantity);

So, that’s it. Actually there is more work to do to change customer account views in order to represent decimal values. Maybe some other templates still to change. Anyway, this basically does the trick.

 

 

hello, i followed your suggestions and everything works fine except specific price rule ... I can't save it at ...does anyone know solution for this ? 

Link to comment
Share on other sites

  • 3 months later...
  • 4 months later...
  • 8 months later...

Hello guys.
Thank you for solving the problem with decimal. According to the instructions from Mykhailo. I set the decimal on Presta 1.6.1.17 and encountered some problems. Please help me figure it out.
1) Since I'm not a programmer, it's difficult for me to understand how to change the format decimal numbers the value in the input on Product page. For example, I have 1.900000000 in the input, and I need 1.9

2)I also could not get the elements - / + to add decimal numbers to the sum of products on the Cart page. Tell me, please, where can I find a place in the files where this can be fixed.

Thanks!

1.png

2.png

Edited by AlexPopov (see edit history)
Link to comment
Share on other sites

On 21.08.2014 at 12:44 AM, Mykhailo said:

Hi,

 

here's a solution that works for me (version 1.6.0.9):

 

1) Change the database quantity related fields type to DECIMAL (17,2).

 

ps_product: quantity, minimal_quantity

ps_order_return_detail: product_quantity

ps_pack: quantity

ps_product_attribute: quantity, minimal_quantity

ps_product_sale: quantity

ps_stock: physical_quantity, usable_quantity

ps_cart_rule: quantity, quantity_per_user

ps_customization: quantity, quantity_refunded, quantity_returned

ps_order_detail: product_quantity, product_quantity_in_stock, product_quantity_refunded, product_quantity_return

ps_specific_price_rule: from_quantity

ps_order_slip_detail: product_quantity

ps_supply_order_detail: quantity_recieved, quantity_expected

ps_stock_avaible: quantity 

ps_cart_product: quantity 

ps_stock_mvt: physical_quantity

ps_cart_rule_product_rule_group: quantity

 

2) Change declarations and checks related to variables like quantity, qty and similar from int to float.

 

2.1)

For examle: 

Override the OrderDetail.php class with following changes:

line 170-173


'product_quantity' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true), 'product_quantity_in_stock' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'product_quantity_return' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'product_quantity_refunded' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'), 'product_quantity_reinjected' =>array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'),

line 180


'product_quantity_discount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'),

line 191 


'discount_quantity_applied' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat'),

line 562


$this->product_quantity = (float)($product['cart_quantity'])

line 570-571


$productQuantity = (float)(Product::getQuantity($this->product_id, $this->product_attribute_id)); $this->product_quantity_in_stock = ($productQuantity - (float)($product['cart_quantity']) < 0) ? $productQuantity : (float)($product['cart_quantity']);

Remaining classes to check and override are:

Order.php

Customization.php

Cart.php

Product.php

 

2.2) Override front controllers:

 

CartController.php

ProductController.php

OrderOpcController.php

 

2.3) Override admin controllers:

 

AdminOrdersController.php

AdminCartsController.php

AdminProductsController.php

 

2.4) Change admin template file /admin/themes/default/template/controllers/orders/controllers/orders/_product_line.tpl

 

line 69


<span class="product_quantity_show{if (float)$product['product_quantity'] > 1} badge{/if}">{$product['product_quantity']}</span>

Basically this allows you to see decimal quantity in orders (ORDERS->Orders) from back office.

 

2.5) Change your theme templates:

 

order-detail.tpl

shopping-cart.tpl

product.tpl

 

2.6) Change your theme blockcart module:

 

blockcart-json.tpl

blockcart.tpl

blockcart.php

 

2.7) Change your theme js files:

 

order-opc.js

 

line 932


newTotalQty += parseFloat($(this).val());

line 944 


totalQty += parseFloat($(this).val());

product.js

 

line 250


var currentVal = parseFloat($('input[name='+fieldName+']').val());

line 264


var currentVal = parseFloat($('input[name='+fieldName+']').val());

cart-summary.js

 

line 346 - new RegExp checks floats instead of integers, this allows to manually change and automatically update product quantity during checkout on cart page.


var exp = new RegExp("^[0-9]+\.[0-9]+$");

line 352


var QtyToUp = parseFloat(input) - parseFloat(hidden); 

line 354


if (parseFloat(QtyToUp) > 0)

line 356


else if(parseFloat(QtyToUp) < 0)

line 442


(parseFloat(jsonData.summary.products[i].customization_quantity) > 0))

line 801


nbrProducts += parseFloat(product_list[i].quantity);

So, that’s it. Actually there is more work to do to change customer account views in order to represent decimal values. Maybe some other templates still to change. Anyway, this basically does the trick.

 

I thank Mykhailo for solving this problem. I repeated this trick in prestashop 1.6.1.19. For those who need to repeat this operation, I want to give two additions in the code that helped me a lot. These solutions will be useful if you need to add a product in 0.1 increments.

1) For Product page in the product.js you need to replace the script

// The button to increment the product value
$(document).on('click', '.product_quantity_up', function(e){
	e.preventDefault();
	fieldName = $(this).data('field-qty');
	var currentVal = parseInt($('input[name='+fieldName+']').val());
	if (!allowBuyWhenOutOfStock && quantityAvailable > 0)
		quantityAvailableT = quantityAvailable;
	else
		quantityAvailableT = 100000000;
	if (!isNaN(currentVal) && currentVal < quantityAvailableT)
		$('input[name='+fieldName+']').val(currentVal + 1).trigger('keyup');
	else
		$('input[name='+fieldName+']').val(quantityAvailableT);

	$('#quantity_wanted').change();
});
 // The button to decrement the product value
$(document).on('click', '.product_quantity_down', function(e){
	e.preventDefault();
	fieldName = $(this).data('field-qty');
	var currentVal = parseInt($('input[name='+fieldName+']').val());
	if (!isNaN(currentVal) && currentVal > 1)
		$('input[name='+fieldName+']').val(currentVal - 1).trigger('keyup');
	else
		$('input[name='+fieldName+']').val(1);

	$('#quantity_wanted').change();
});

to

var allowBuyWhenOutOfStock = true;
var quantityAvailable = 1000000;
var fieldName;

// The button to increment the product value
$(document).on('click', '.product_quantity_up', function (e) {
  e.preventDefault();
  fieldName = $(this).data('field-qty');
  var currentVal = parseFloat($('input[name=' + fieldName + ']').val());

  if (!allowBuyWhenOutOfStock && quantityAvailable > 0) {
    quantityAvailableT = quantityAvailable;
  } else {
    quantityAvailableT = 100000000;
  }

  if (!isNaN(currentVal) && currentVal < quantityAvailableT) {
    currentVal += 0.1;
  } else {
    currentVal = quantityAvailableT
  }
  
  $('input[name='+fieldName+']').val(currentVal.toFixed(2)).trigger('keyup');

  $('#quantity_wanted').change();

});

 // The button to decrement the product value
 $(document).on('click', '.product_quantity_down', function(e) {
  e.preventDefault();
  fieldName = $(this).data('field-qty');
  var currentVal = parseFloat($('input[name='+fieldName+']').val());
 
  if (!isNaN(currentVal) && currentVal > 1.0) {
    currentVal -= 0.1;
  } else {
    currentVal = 1;
  }

  $('input[name='+fieldName+']').val(currentVal.toFixed(2)).trigger('keyup');
  
  $('#quantity_wanted').change();
 });

 

2) For Cart page In the cart-summary.js you need to replace the script

qty = 1;

  to 

qty = 0.1;

I hope this will be useful to someone.

Link to comment
Share on other sites

  • 1 month later...
  • 2 weeks later...

Hello. I have a problem. I use presta 1.6. I moddified all files like in the instructions but I have a error., when I submit my order it's appearing a 500 page error. In the logs, it's about *ERROR*     2018/08/23 - 12:11:40: Property StockAvailable->quantity is not valid at line 917 in file classes/ObjectModel.php ". After tests I think that the error it's comming from cart.php, something it's wrong, but I dont' know what.

Can you help with a sugestion.

 

thank you.

this is my file cart.php

 

 

<?php
/*
* 2007-2017 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
*  @author PrestaShop SA <[email protected]>
*  @copyright  2007-2017 PrestaShop SA
*  @license    http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*  International Registered Trademark & Property of PrestaShop SA
*/

class CartCore extends ObjectModel
{
    public $id;

    public $id_shop_group;

    public $id_shop;

    /** @var int Customer delivery address ID */
    public $id_address_delivery;

    /** @var int Customer invoicing address ID */
    public $id_address_invoice;

    /** @var int Customer currency ID */
    public $id_currency;

    /** @var int Customer ID */
    public $id_customer;

    /** @var int Guest ID */
    public $id_guest;

    /** @var int Language ID */
    public $id_lang;

    /** @var bool True if the customer wants a recycled package */
    public $recyclable = 0;

    /** @var bool True if the customer wants a gift wrapping */
    public $gift = 0;

    /** @var string Gift message if specified */
    public $gift_message;

    /** @var bool Mobile Theme */
    public $mobile_theme;

    /** @var string Object creation date */
    public $date_add;

    /** @var string secure_key */
    public $secure_key;

    /** @var int Carrier ID */
    public $id_carrier = 0;

    /** @var string Object last modification date */
    public $date_upd;

    public $checkedTos = false;
    public $pictures;
    public $textFields;

    public $delivery_option;

    /** @var bool Allow to seperate order in multiple package in order to recieve as soon as possible the available products */
    public $allow_seperated_package = false;

    protected static $_nbProducts = array();
    protected static $_isVirtualCart = array();

    protected $_products = null;
    protected static $_totalWeight = array();
    protected $_taxCalculationMethod = PS_TAX_EXC;
    protected static $_carriers = null;
    protected static $_taxes_rate = null;
    protected static $_attributesLists = array();

    /** @var Customer|null */
    protected static $_customer = null;

    /**
     * @see ObjectModel::$definition
     */
    public static $definition = array(
        'table' => 'cart',
        'primary' => 'id_cart',
        'fields' => array(
            'id_shop_group' =>            array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
            'id_shop' =>                array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
            'id_address_delivery' =>    array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
            'id_address_invoice' =>    array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
            'id_carrier' =>            array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
            'id_currency' =>            array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
            'id_customer' =>            array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
            'id_guest' =>                array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
            'id_lang' =>                array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
            'recyclable' =>            array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'gift' =>                    array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'gift_message' =>            array('type' => self::TYPE_STRING, 'validate' => 'isMessage'),
            'mobile_theme' =>            array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'delivery_option' =>        array('type' => self::TYPE_STRING),
            'secure_key' =>            array('type' => self::TYPE_STRING, 'size' => 32),
            'allow_seperated_package' =>array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'date_add' =>                array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
            'date_upd' =>                array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
        ),
    );

    protected $webserviceParameters = array(
        'fields' => array(
            'id_address_delivery' => array('xlink_resource' => 'addresses'),
            'id_address_invoice' => array('xlink_resource' => 'addresses'),
            'id_currency' => array('xlink_resource' => 'currencies'),
            'id_customer' => array('xlink_resource' => 'customers'),
            'id_guest' => array('xlink_resource' => 'guests'),
            'id_lang' => array('xlink_resource' => 'languages'),
        ),
        'associations' => array(
            'cart_rows' => array('resource' => 'cart_row', 'virtual_entity' => true, 'fields' => array(
                'id_product' => array('required' => true, 'xlink_resource' => 'products'),
                'id_product_attribute' => array('required' => true, 'xlink_resource' => 'combinations'),
                'id_address_delivery' => array('required' => true, 'xlink_resource' => 'addresses'),
                'quantity' => array('required' => true),
                )
            ),
        ),
    );

    const ONLY_PRODUCTS = 1;
    const ONLY_DISCOUNTS = 2;
    const BOTH = 3;
    const BOTH_WITHOUT_SHIPPING = 4;
    const ONLY_SHIPPING = 5;
    const ONLY_WRAPPING = 6;
    const ONLY_PRODUCTS_WITHOUT_SHIPPING = 7;
    const ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING = 8;

    public function __construct($id = null, $id_lang = null)
    {
        parent::__construct($id);

        if (!is_null($id_lang)) {
            $this->id_lang = (int)(Language::getLanguage($id_lang) !== false) ? $id_lang : Configuration::get('PS_LANG_DEFAULT');
        }

        if ($this->id_customer) {
            if (isset(Context::getContext()->customer) && Context::getContext()->customer->id == $this->id_customer) {
                $customer = Context::getContext()->customer;
            } else {
                $customer = new Customer((int)$this->id_customer);
            }

            Cart::$_customer = $customer;

            if ((!$this->secure_key || $this->secure_key == '-1') && $customer->secure_key) {
                $this->secure_key = $customer->secure_key;
                $this->save();
            }
        }

        $this->setTaxCalculationMethod();
    }

    public function setTaxCalculationMethod()
    {
        $this->_taxCalculationMethod = Group::getPriceDisplayMethod(Group::getCurrent()->id);
    }

    public function add($autodate = true, $null_values = false)
    {
        if (!$this->id_lang) {
            $this->id_lang = Configuration::get('PS_LANG_DEFAULT');
        }
        if (!$this->id_shop) {
            $this->id_shop = Context::getContext()->shop->id;
        }

        $return = parent::add($autodate, $null_values);
        Hook::exec('actionCartSave');

        return $return;
    }

    public function update($null_values = false)
    {
        if (isset(self::$_nbProducts[$this->id])) {
            unset(self::$_nbProducts[$this->id]);
        }

        if (isset(self::$_totalWeight[$this->id])) {
            unset(self::$_totalWeight[$this->id]);
        }

        $this->_products = null;
        $return = parent::update($null_values);
        Hook::exec('actionCartSave');

        return $return;
    }

    /**
     * Update the address id of the cart
     *
     * @param int $id_address Current address id to change
     * @param int $id_address_new New address id
     */
    public function updateAddressId($id_address, $id_address_new)
    {
        $to_update = false;
        if (!isset($this->id_address_invoice) || $this->id_address_invoice == $id_address) {
            $to_update = true;
            $this->id_address_invoice = $id_address_new;
        }
        if (!isset($this->id_address_delivery) || $this->id_address_delivery == $id_address) {
            $to_update = true;
            $this->id_address_delivery = $id_address_new;
        }
        if ($to_update) {
            $this->update();
        }

        $sql = 'UPDATE `'._DB_PREFIX_.'cart_product`
        SET `id_address_delivery` = '.(int)$id_address_new.'
        WHERE  `id_cart` = '.(int)$this->id.'
            AND `id_address_delivery` = '.(int)$id_address;
        Db::getInstance()->execute($sql);

        $sql = 'UPDATE `'._DB_PREFIX_.'customization`
            SET `id_address_delivery` = '.(int)$id_address_new.'
            WHERE  `id_cart` = '.(int)$this->id.'
                AND `id_address_delivery` = '.(int)$id_address;
        Db::getInstance()->execute($sql);
    }

    public function delete()
    {
        if ($this->OrderExists()) { //NOT delete a cart which is associated with an order
            return false;
        }

        $uploaded_files = Db::getInstance()->executeS('
            SELECT cd.`value`
            FROM `'._DB_PREFIX_.'customized_data` cd
            INNER JOIN `'._DB_PREFIX_.'customization` c ON (cd.`id_customization`= c.`id_customization`)
            WHERE cd.`type`= 0 AND c.`id_cart`='.(int)$this->id
        );

        foreach ($uploaded_files as $must_unlink) {
            unlink(_PS_UPLOAD_DIR_.$must_unlink['value'].'_small');
            unlink(_PS_UPLOAD_DIR_.$must_unlink['value']);
        }

        Db::getInstance()->execute('
            DELETE cd
            FROM `' . _DB_PREFIX_ . 'customized_data` cd, `' . _DB_PREFIX_ . 'customization` c
            WHERE cd.`id_customization` = c.`id_customization`
            AND `id_cart` = ' . (int) $this->id
        );

        Db::getInstance()->execute('
            DELETE FROM `'._DB_PREFIX_.'customization`
            WHERE `id_cart` = '.(int)$this->id
        );

        if (!Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_cart_rule` WHERE `id_cart` = '.(int)$this->id)
         || !Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_product` WHERE `id_cart` = '.(int)$this->id)) {
            return false;
        }

        return parent::delete();
    }

    public static function getTaxesAverageUsed($id_cart)
    {
        $cart = new Cart((int)$id_cart);
        if (!Validate::isLoadedObject($cart)) {
            die(Tools::displayError());
        }

        if (!Configuration::get('PS_TAX')) {
            return 0;
        }

        $products = $cart->getProducts();
        $total_products_moy = 0;
        $ratio_tax = 0;

        if (!count($products)) {
            return 0;
        }

        foreach ($products as $product) {
            // products refer to the cart details

            if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice') {
                $address_id = (int)$cart->id_address_invoice;
            } else {
                $address_id = (int)$product['id_address_delivery'];
            } // Get delivery address of the product from the cart
            if (!Address::addressExists($address_id)) {
                $address_id = null;
            }

            $total_products_moy += $product['total_wt'];
            $ratio_tax += $product['total_wt'] * Tax::getProductTaxRate(
                (int)$product['id_product'],
                (int)$address_id
            );
        }

        if ($total_products_moy > 0) {
            return $ratio_tax / $total_products_moy;
        }

        return 0;
    }

    /**
     * The arguments are optional and only serve as return values in case caller needs the details.
     */
    public function getAverageProductsTaxRate(&$cart_amount_te = null, &$cart_amount_ti = null)
    {
        $cart_amount_ti = $this->getOrderTotal(true, Cart::ONLY_PRODUCTS);
        $cart_amount_te = $this->getOrderTotal(false, Cart::ONLY_PRODUCTS);

        $cart_vat_amount = $cart_amount_ti - $cart_amount_te;

        if ($cart_vat_amount == 0 || $cart_amount_te == 0) {
            return 0;
        } else {
            return Tools::ps_round($cart_vat_amount / $cart_amount_te, 3);
        }
    }

    /**
     * @deprecated 1.5.0, use Cart->getCartRules()
     */
    public function getDiscounts($lite = false, $refresh = false)
    {
        Tools::displayAsDeprecated();
        return $this->getCartRules();
    }

    public function getCartRules($filter = CartRule::FILTER_ACTION_ALL)
    {
        // If the cart has not been saved, then there can't be any cart rule applied
        if (!CartRule::isFeatureActive() || !$this->id) {
            return array();
        }

        $cache_key = 'Cart::getCartRules_'.$this->id.'-'.$filter;
        if (!Cache::isStored($cache_key)) {
            $result = Db::getInstance()->executeS('
                SELECT cr.*, crl.`id_lang`, crl.`name`, cd.`id_cart`
                FROM `'._DB_PREFIX_.'cart_cart_rule` cd
                LEFT JOIN `'._DB_PREFIX_.'cart_rule` cr ON cd.`id_cart_rule` = cr.`id_cart_rule`
                LEFT JOIN `'._DB_PREFIX_.'cart_rule_lang` crl ON (
                    cd.`id_cart_rule` = crl.`id_cart_rule`
                    AND crl.id_lang = '.(int)$this->id_lang.'
                )
                WHERE `id_cart` = '.(int)$this->id.'
                '.($filter == CartRule::FILTER_ACTION_SHIPPING ? 'AND free_shipping = 1' : '').'
                '.($filter == CartRule::FILTER_ACTION_GIFT ? 'AND gift_product != 0' : '').'
                '.($filter == CartRule::FILTER_ACTION_REDUCTION ? 'AND (reduction_percent != 0 OR reduction_amount != 0)' : '')
                .' ORDER by cr.priority ASC'
            );
            Cache::store($cache_key, $result);
        } else {
            $result = Cache::retrieve($cache_key);
        }

        // Define virtual context to prevent case where the cart is not the in the global context
        $virtual_context = Context::getContext()->cloneContext();
        $virtual_context->cart = $this;

        foreach ($result as &$row) {
            $row['obj'] = new CartRule($row['id_cart_rule'], (int)$this->id_lang);
            $row['value_real'] = $row['obj']->getContextualValue(true, $virtual_context, $filter);
            $row['value_tax_exc'] = $row['obj']->getContextualValue(false, $virtual_context, $filter);
            // Retro compatibility < 1.5.0.2
            $row['id_discount'] = $row['id_cart_rule'];
            $row['description'] = $row['name'];
        }

        return $result;
    }

    /**
     * Return the cart rules Ids on the cart.
     * @param $filter
     * @return array
     * @throws PrestaShopDatabaseException
     */
    public function getOrderedCartRulesIds($filter = CartRule::FILTER_ACTION_ALL)
    {
        $cache_key = 'Cart::getOrderedCartRulesIds_'.$this->id.'-'.$filter.'-ids';
        if (!Cache::isStored($cache_key)) {
            $result = Db::getInstance()->executeS('
                SELECT cr.`id_cart_rule`
                FROM `'._DB_PREFIX_.'cart_cart_rule` cd
                LEFT JOIN `'._DB_PREFIX_.'cart_rule` cr ON cd.`id_cart_rule` = cr.`id_cart_rule`
                LEFT JOIN `'._DB_PREFIX_.'cart_rule_lang` crl ON (
                    cd.`id_cart_rule` = crl.`id_cart_rule`
                    AND crl.id_lang = '.(int)$this->id_lang.'
                )
                WHERE `id_cart` = '.(int)$this->id.'
                '.($filter == CartRule::FILTER_ACTION_SHIPPING ? 'AND free_shipping = 1' : '').'
                '.($filter == CartRule::FILTER_ACTION_GIFT ? 'AND gift_product != 0' : '').'
                '.($filter == CartRule::FILTER_ACTION_REDUCTION ? 'AND (reduction_percent != 0 OR reduction_amount != 0)' : '')
                .' ORDER BY cr.priority ASC'
            );
            Cache::store($cache_key, $result);
        } else {
            $result = Cache::retrieve($cache_key);
        }

        return $result;
    }

    public function getDiscountsCustomer($id_cart_rule)
    {
        if (!CartRule::isFeatureActive()) {
            return 0;
        }
        $cache_id = 'Cart::getDiscountsCustomer_'.(int)$this->id.'-'.(int)$id_cart_rule;
        if (!Cache::isStored($cache_id)) {
            $result = (int)Db::getInstance()->getValue('
                SELECT COUNT(*)
                FROM `'._DB_PREFIX_.'cart_cart_rule`
                WHERE `id_cart_rule` = '.(int)$id_cart_rule.' AND `id_cart` = '.(int)$this->id);
            Cache::store($cache_id, $result);
            return $result;
        }
        return Cache::retrieve($cache_id);
    }

    public function getLastProduct()
    {
        $sql = '
            SELECT `id_product`, `id_product_attribute`, id_shop
            FROM `'._DB_PREFIX_.'cart_product`
            WHERE `id_cart` = '.(int)$this->id.'
            ORDER BY `date_add` DESC';

        $result = Db::getInstance()->getRow($sql);
        if ($result && isset($result['id_product']) && $result['id_product']) {
            foreach ($this->getProducts() as $product) {
                if ($result['id_product'] == $product['id_product']
                && (
                    !$result['id_product_attribute']
                    || $result['id_product_attribute'] == $product['id_product_attribute']
                )) {
                    return $product;
                }
            }
        }

        return false;
    }

    /**
     * Return cart products
     *
     * @result array Products
     */
    public function getProducts($refresh = false, $id_product = false, $id_country = null)
    {
        if (!$this->id) {
            return array();
        }
        // Product cache must be strictly compared to NULL, or else an empty cart will add dozens of queries
        if ($this->_products !== null && !$refresh) {
            // Return product row with specified ID if it exists
            if (is_int($id_product)) {
                foreach ($this->_products as $product) {
                    if ($product['id_product'] == $id_product) {
                        return array($product);
                    }
                }
                return array();
            }
            return $this->_products;
        }

        // Build query
        $sql = new DbQuery();

        // Build SELECT
        $sql->select('cp.`id_product_attribute`, cp.`id_product`, cp.`quantity` AS cart_quantity, cp.id_shop, pl.`name`, p.`is_virtual`,
                        pl.`description_short`, pl.`available_now`, pl.`available_later`, product_shop.`id_category_default`, p.`id_supplier`,
                        p.`id_manufacturer`, product_shop.`on_sale`, product_shop.`ecotax`, product_shop.`additional_shipping_cost`,
                        product_shop.`available_for_order`, product_shop.`price`, product_shop.`active`, product_shop.`unity`, product_shop.`unit_price_ratio`,
                        stock.`quantity` AS quantity_available, p.`width`, p.`height`, p.`depth`, stock.`out_of_stock`, p.`weight`,
                        p.`date_add`, p.`date_upd`, IFNULL(stock.quantity, 0) as quantity, pl.`link_rewrite`, cl.`link_rewrite` AS category,
                        CONCAT(LPAD(cp.`id_product`, 10, 0), LPAD(IFNULL(cp.`id_product_attribute`, 0), 10, 0), IFNULL(cp.`id_address_delivery`, 0)) AS unique_id, cp.id_address_delivery,
                        product_shop.advanced_stock_management, ps.product_supplier_reference supplier_reference');

        // Build FROM
        $sql->from('cart_product', 'cp');

        // Build JOIN
        $sql->leftJoin('product', 'p', 'p.`id_product` = cp.`id_product`');
        $sql->innerJoin('product_shop', 'product_shop', '(product_shop.`id_shop` = cp.`id_shop` AND product_shop.`id_product` = p.`id_product`)');
        $sql->leftJoin('product_lang', 'pl', '
            p.`id_product` = pl.`id_product`
            AND pl.`id_lang` = '.(int)$this->id_lang.Shop::addSqlRestrictionOnLang('pl', 'cp.id_shop')
        );

        $sql->leftJoin('category_lang', 'cl', '
            product_shop.`id_category_default` = cl.`id_category`
            AND cl.`id_lang` = '.(int)$this->id_lang.Shop::addSqlRestrictionOnLang('cl', 'cp.id_shop')
        );

        $sql->leftJoin('product_supplier', 'ps', 'ps.`id_product` = cp.`id_product` AND ps.`id_product_attribute` = cp.`id_product_attribute` AND ps.`id_supplier` = p.`id_supplier`');

        // @todo test if everything is ok, then refactorise call of this method
        $sql->join(Product::sqlStock('cp', 'cp'));

        // Build WHERE clauses
        $sql->where('cp.`id_cart` = '.(int)$this->id);
        if ($id_product) {
            $sql->where('cp.`id_product` = '.(int)$id_product);
        }
        $sql->where('p.`id_product` IS NOT NULL');

        // Build ORDER BY
        $sql->orderBy('cp.`date_add`, cp.`id_product`, cp.`id_product_attribute` ASC');

        if (Customization::isFeatureActive()) {
            $sql->select('cu.`id_customization`, cu.`quantity` AS customization_quantity');
            $sql->leftJoin('customization', 'cu',
                'p.`id_product` = cu.`id_product` AND cp.`id_product_attribute` = cu.`id_product_attribute` AND cu.`id_cart` = '.(int)$this->id);
            $sql->groupBy('cp.`id_product_attribute`, cp.`id_product`, cp.`id_shop`');
        } else {
            $sql->select('NULL AS customization_quantity, NULL AS id_customization');
        }

        if (Combination::isFeatureActive()) {
            $sql->select('
                product_attribute_shop.`price` AS price_attribute, product_attribute_shop.`ecotax` AS ecotax_attr,
                IF (IFNULL(pa.`reference`, \'\') = \'\', p.`reference`, pa.`reference`) AS reference,
                (p.`weight`+ pa.`weight`) weight_attribute,
                IF (IFNULL(pa.`ean13`, \'\') = \'\', p.`ean13`, pa.`ean13`) AS ean13,
                IF (IFNULL(pa.`upc`, \'\') = \'\', p.`upc`, pa.`upc`) AS upc,
                IFNULL(product_attribute_shop.`minimal_quantity`, product_shop.`minimal_quantity`) as minimal_quantity,
                IF(product_attribute_shop.wholesale_price > 0,  product_attribute_shop.wholesale_price, product_shop.`wholesale_price`) wholesale_price
            ');

            $sql->leftJoin('product_attribute', 'pa', 'pa.`id_product_attribute` = cp.`id_product_attribute`');
            $sql->leftJoin('product_attribute_shop', 'product_attribute_shop', '(product_attribute_shop.`id_shop` = cp.`id_shop` AND product_attribute_shop.`id_product_attribute` = pa.`id_product_attribute`)');
        } else {
            $sql->select(
                'p.`reference` AS reference, p.`ean13`,
                p.`upc` AS upc, product_shop.`minimal_quantity` AS minimal_quantity, product_shop.`wholesale_price` wholesale_price'
            );
        }

        $sql->select('image_shop.`id_image` id_image, il.`legend`');
        $sql->leftJoin('image_shop', 'image_shop', 'image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop='.(int)$this->id_shop);
        $sql->leftJoin('image_lang', 'il', 'il.`id_image` = image_shop.`id_image` AND il.`id_lang` = '.(int)$this->id_lang);

        $result = Db::getInstance()->executeS($sql);

        // Reset the cache before the following return, or else an empty cart will add dozens of queries
        $products_ids = array();
        $pa_ids = array();
        if ($result) {
            foreach ($result as $key => $row) {
                $products_ids[] = $row['id_product'];
                $pa_ids[] = $row['id_product_attribute'];
                $specific_price = SpecificPrice::getSpecificPrice($row['id_product'], $this->id_shop, $this->id_currency, $id_country, $this->id_shop_group, $row['cart_quantity'], $row['id_product_attribute'], $this->id_customer, $this->id);
                if ($specific_price) {
                    $reduction_type_row = array('reduction_type' => $specific_price['reduction_type']);
                } else {
                    $reduction_type_row = array('reduction_type' => 0);
                }

                $result[$key] = array_merge($row, $reduction_type_row);
            }
        }
        // Thus you can avoid one query per product, because there will be only one query for all the products of the cart
        Product::cacheProductsFeatures($products_ids);
        Cart::cacheSomeAttributesLists($pa_ids, $this->id_lang);

        $this->_products = array();
        if (empty($result)) {
            return array();
        }

        $ecotax_rate = (float)Tax::getProductEcotaxRate($this->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
        $apply_eco_tax = Product::$_taxCalculationMethod == PS_TAX_INC && (int)Configuration::get('PS_TAX');
        $cart_shop_context = Context::getContext()->cloneContext();

        foreach ($result as &$row) {
            if (isset($row['ecotax_attr']) && $row['ecotax_attr'] > 0) {
                $row['ecotax'] = (float)$row['ecotax_attr'];
            }

            $row['stock_quantity'] = (float)$row['quantity'];
            // for compatibility with 1.2 themes
            $row['quantity'] = (float)$row['cart_quantity'];

            if (isset($row['id_product_attribute']) && (int)$row['id_product_attribute'] && isset($row['weight_attribute'])) {
                $row['weight'] = (float)$row['weight_attribute'];
            }

            if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice') {
                $address_id = (int)$this->id_address_invoice;
            } else {
                $address_id = (int)$row['id_address_delivery'];
            }
            if (!Address::addressExists($address_id)) {
                $address_id = null;
            }

            if ($cart_shop_context->shop->id != $row['id_shop']) {
                $cart_shop_context->shop = new Shop((int)$row['id_shop']);
            }

            $address = Address::initialize($address_id, true);
            $id_tax_rules_group = Product::getIdTaxRulesGroupByIdProduct((int)$row['id_product'], $cart_shop_context);
            $tax_calculator = TaxManagerFactory::getManager($address, $id_tax_rules_group)->getTaxCalculator();

            $row['price_without_reduction'] = Product::getPriceStatic(
                (int)$row['id_product'],
                true,
                isset($row['id_product_attribute']) ? (int)$row['id_product_attribute'] : null,
                6,
                null,
                false,
                false,
                $row['cart_quantity'],
                false,
                (int)$this->id_customer ? (int)$this->id_customer : null,
                (int)$this->id,
                $address_id,
                $specific_price_output,
                true,
                true,
                $cart_shop_context
            );

            $row['price_with_reduction'] = Product::getPriceStatic(
                (int)$row['id_product'],
                true,
                isset($row['id_product_attribute']) ? (int)$row['id_product_attribute'] : null,
                6,
                null,
                false,
                true,
                $row['cart_quantity'],
                false,
                (int)$this->id_customer ? (int)$this->id_customer : null,
                (int)$this->id,
                $address_id,
                $specific_price_output,
                true,
                true,
                $cart_shop_context
            );

            $row['price'] = $row['price_with_reduction_without_tax'] = Product::getPriceStatic(
                (int)$row['id_product'],
                false,
                isset($row['id_product_attribute']) ? (int)$row['id_product_attribute'] : null,
                6,
                null,
                false,
                true,
                $row['cart_quantity'],
                false,
                (int)$this->id_customer ? (int)$this->id_customer : null,
                (int)$this->id,
                $address_id,
                $specific_price_output,
                true,
                true,
                $cart_shop_context
            );

            switch (Configuration::get('PS_ROUND_TYPE')) {
                case Order::ROUND_TOTAL:
                    $row['total'] = $row['price_with_reduction_without_tax'] * (float)$row['cart_quantity'];
                    $row['total_wt'] = $row['price_with_reduction'] * (float)$row['cart_quantity'];
                    break;
                case Order::ROUND_LINE:
                    $row['total'] = Tools::ps_round($row['price_with_reduction_without_tax'] * (float)$row['cart_quantity'], _PS_PRICE_COMPUTE_PRECISION_);
                    $row['total_wt'] = Tools::ps_round($row['price_with_reduction'] * (float)$row['cart_quantity'], _PS_PRICE_COMPUTE_PRECISION_);
                    break;

                case Order::ROUND_ITEM:
                default:
                    $row['total'] = Tools::ps_round($row['price_with_reduction_without_tax'], _PS_PRICE_COMPUTE_PRECISION_) * (float)$row['cart_quantity'];
                    $row['total_wt'] = Tools::ps_round($row['price_with_reduction'], _PS_PRICE_COMPUTE_PRECISION_) * (float)$row['cart_quantity'];
                    break;
            }

            $row['price_wt'] = $row['price_with_reduction'];
            $row['description_short'] = Tools::nl2br($row['description_short']);

            // check if a image associated with the attribute exists
            if ($row['id_product_attribute']) {
                $row2 = Image::getBestImageAttribute($row['id_shop'], $this->id_lang, $row['id_product'], $row['id_product_attribute']);
                if ($row2) {
                    $row = array_merge($row, $row2);
                }
            }

            $row['reduction_applies'] = ($specific_price_output && (float)$specific_price_output['reduction']);
            $row['quantity_discount_applies'] = ($specific_price_output && $row['cart_quantity'] >= (float)$specific_price_output['from_quantity']);
            $row['id_image'] = Product::defineProductImage($row, $this->id_lang);
            $row['allow_oosp'] = Product::isAvailableWhenOutOfStock($row['out_of_stock']);
            $row['features'] = Product::getFeaturesStatic((int)$row['id_product']);

            if (array_key_exists($row['id_product_attribute'].'-'.$this->id_lang, self::$_attributesLists)) {
                $row = array_merge($row, self::$_attributesLists[$row['id_product_attribute'].'-'.$this->id_lang]);
            }

            $row = Product::getTaxesInformations($row, $cart_shop_context);

            $this->_products[] = $row;
        }

        return $this->_products;
    }

    public static function cacheSomeAttributesLists($ipa_list, $id_lang)
    {
        if (!Combination::isFeatureActive()) {
            return;
        }

        $pa_implode = array();

        foreach ($ipa_list as $id_product_attribute) {
            if ((int)$id_product_attribute && !array_key_exists($id_product_attribute.'-'.$id_lang, self::$_attributesLists)) {
                $pa_implode[] = (int)$id_product_attribute;
                self::$_attributesLists[(int)$id_product_attribute.'-'.$id_lang] = array('attributes' => '', 'attributes_small' => '');
            }
        }

        if (!count($pa_implode)) {
            return;
        }

        $result = Db::getInstance()->executeS('
            SELECT pac.`id_product_attribute`, agl.`public_name` AS public_group_name, al.`name` AS attribute_name
            FROM `'._DB_PREFIX_.'product_attribute_combination` pac
            LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
            LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
            LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (
                a.`id_attribute` = al.`id_attribute`
                AND al.`id_lang` = '.(int)$id_lang.'
            )
            LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (
                ag.`id_attribute_group` = agl.`id_attribute_group`
                AND agl.`id_lang` = '.(int)$id_lang.'
            )
            WHERE pac.`id_product_attribute` IN ('.implode(',', $pa_implode).')
            ORDER BY ag.`position` ASC, a.`position` ASC'
        );

        foreach ($result as $row) {
            self::$_attributesLists[$row['id_product_attribute'].'-'.$id_lang]['attributes'] .= $row['public_group_name'].' : '.$row['attribute_name'].', ';
            self::$_attributesLists[$row['id_product_attribute'].'-'.$id_lang]['attributes_small'] .= $row['attribute_name'].', ';
        }

        foreach ($pa_implode as $id_product_attribute) {
            self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes'] = rtrim(
                self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes'],
                ', '
            );

            self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes_small'] = rtrim(
                self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes_small'],
                ', '
            );
        }
    }

    /**
     * Return cart products quantity
     *
     * @result integer Products quantity
     */
    public function nbProducts()
    {
        if (!$this->id) {
            return 0;
        }

        return Cart::getNbProducts($this->id);
    }

    public static function getNbProducts($id)
    {
        // Must be strictly compared to NULL, or else an empty cart will bypass the cache and add dozens of queries
        if (isset(self::$_nbProducts[$id]) && self::$_nbProducts[$id] !== null) {
            return self::$_nbProducts[$id];
        }

        self::$_nbProducts[$id] = (int)Db::getInstance()->getValue('
            SELECT SUM(`quantity`)
            FROM `'._DB_PREFIX_.'cart_product`
            WHERE `id_cart` = '.(int)$id
        );

        return self::$_nbProducts[$id];
    }

    /**
     * @deprecated 1.5.0, use Cart->addCartRule()
     */
    public function addDiscount($id_cart_rule)
    {
        Tools::displayAsDeprecated();
        return $this->addCartRule($id_cart_rule);
    }

    public function addCartRule($id_cart_rule)
    {
        // You can't add a cart rule that does not exist
        $cartRule = new CartRule($id_cart_rule, Context::getContext()->language->id);

        if (!Validate::isLoadedObject($cartRule)) {
            return false;
        }

        if (Db::getInstance()->getValue('SELECT id_cart_rule FROM '._DB_PREFIX_.'cart_cart_rule WHERE id_cart_rule = '.(int)$id_cart_rule.' AND id_cart = '.(int)$this->id)) {
            return false;
        }

        // Add the cart rule to the cart
        if (!Db::getInstance()->insert('cart_cart_rule', array(
            'id_cart_rule' => (int)$id_cart_rule,
            'id_cart' => (int)$this->id
        ))) {
            return false;
        }

        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_ALL);
        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_SHIPPING);
        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_REDUCTION);
        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_GIFT);

        Cache::clean('Cart::getOrderedCartRulesIds_'.$this->id.'-'.CartRule::FILTER_ACTION_ALL. '-ids');
        Cache::clean('Cart::getOrderedCartRulesIds_'.$this->id.'-'.CartRule::FILTER_ACTION_SHIPPING. '-ids');
        Cache::clean('Cart::getOrderedCartRulesIds_'.$this->id.'-'.CartRule::FILTER_ACTION_REDUCTION. '-ids');
        Cache::clean('Cart::getOrderedCartRulesIds_'.$this->id.'-'.CartRule::FILTER_ACTION_GIFT. '-ids');

        if ((int)$cartRule->gift_product) {
            $this->updateQty(1, $cartRule->gift_product, $cartRule->gift_product_attribute, false, 'up', 0, null, false);
        }

        return true;
    }

    public function containsProduct($id_product, $id_product_attribute = 0, $id_customization = 0, $id_address_delivery = 0)
    {
        $sql = 'SELECT cp.`quantity` FROM `'._DB_PREFIX_.'cart_product` cp';

        if ($id_customization) {
            $sql .= '
                LEFT JOIN `'._DB_PREFIX_.'customization` c ON (
                    c.`id_product` = cp.`id_product`
                    AND c.`id_product_attribute` = cp.`id_product_attribute`
                )';
        }

        $sql .= '
            WHERE cp.`id_product` = '.(int)$id_product.'
            AND cp.`id_product_attribute` = '.(int)$id_product_attribute.'
            AND cp.`id_cart` = '.(int)$this->id;
        if (Configuration::get('PS_ALLOW_MULTISHIPPING') && $this->isMultiAddressDelivery()) {
            $sql .= ' AND cp.`id_address_delivery` = '.(int)$id_address_delivery;
        }

        if ($id_customization) {
            $sql .= ' AND c.`id_customization` = '.(int)$id_customization;
        }

        return Db::getInstance()->getRow($sql);
    }

    /**
     * Update product quantity
     *
     * @param int $quantity Quantity to add (or substract)
     * @param int $id_product Product ID
     * @param int $id_product_attribute Attribute ID if needed
     * @param string $operator Indicate if quantity must be increased or decreased
     */
    public function updateQty($quantity, $id_product, $id_product_attribute = null, $id_customization = false,
        $operator = 'up', $id_address_delivery = 0, Shop $shop = null, $auto_add_cart_rule = true)
    {
        if (!$shop) {
            $shop = Context::getContext()->shop;
        }

        if (Context::getContext()->customer->id) {
            if ($id_address_delivery == 0 && (int)$this->id_address_delivery) { // The $id_address_delivery is null, use the cart delivery address
                $id_address_delivery = $this->id_address_delivery;
            } elseif ($id_address_delivery == 0) { // The $id_address_delivery is null, get the default customer address
                $id_address_delivery = (int)Address::getFirstCustomerAddressId((int)Context::getContext()->customer->id);
            } elseif (!Customer::customerHasAddress(Context::getContext()->customer->id, $id_address_delivery)) { // The $id_address_delivery must be linked with customer
                $id_address_delivery = 0;
            }
        }

        $quantity = (float)$quantity;
        $id_product = (int)$id_product;
        $id_product_attribute = (int)$id_product_attribute;
        $product = new Product($id_product, false, Configuration::get('PS_LANG_DEFAULT'), $shop->id);

        if ($id_product_attribute) {
            $combination = new Combination((int)$id_product_attribute);
            if ($combination->id_product != $id_product) {
                return false;
            }
        }

        /* If we have a product combination, the minimal quantity is set with the one of this combination */
        if (!empty($id_product_attribute)) {
            $minimal_quantity = (int)Attribute::getAttributeMinimalQty($id_product_attribute);
        } else {
            $minimal_quantity = (int)$product->minimal_quantity;
        }

        if (!Validate::isLoadedObject($product)) {
            die(Tools::displayError());
        }

        if (isset(self::$_nbProducts[$this->id])) {
            unset(self::$_nbProducts[$this->id]);
        }

        if (isset(self::$_totalWeight[$this->id])) {
            unset(self::$_totalWeight[$this->id]);
        }

        Hook::exec('actionBeforeCartUpdateQty', array(
            'cart' => $this,
            'product' => $product,
            'id_product_attribute' => $id_product_attribute,
            'id_customization' => $id_customization,
            'quantity' => $quantity,
            'operator' => $operator,
            'id_address_delivery' => $id_address_delivery,
            'shop' => $shop,
            'auto_add_cart_rule' => $auto_add_cart_rule,
        ));

        if ((float)$quantity <= 0) {
            return $this->deleteProduct($id_product, $id_product_attribute, (int)$id_customization, 0, $auto_add_cart_rule);
        } elseif (!$product->available_for_order || (Configuration::get('PS_CATALOG_MODE') && !defined('_PS_ADMIN_DIR_'))) {
            return false;
        } else {
            /* Check if the product is already in the cart */
            $result = $this->containsProduct($id_product, $id_product_attribute, (int)$id_customization, (int)$id_address_delivery);

            /* Update quantity if product already exist */
            if ($result) {
                if ($operator == 'up') {
                    $sql = 'SELECT stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity
                            FROM '._DB_PREFIX_.'product p
                            '.Product::sqlStock('p', $id_product_attribute, true, $shop).'
                            WHERE p.id_product = '.$id_product;

                    $result2 = Db::getInstance()->getRow($sql);
                    $product_qty = (float)$result2['quantity'];
                    // Quantity for product pack
                    if (Pack::isPack($id_product)) {
                        $product_qty = Pack::getQuantity($id_product, $id_product_attribute);
                    }
                    $new_qty = (float)$result['quantity'] + (float)$quantity;
                    $qty = '+ '.(float)$quantity;

                    if (!Product::isAvailableWhenOutOfStock((int)$result2['out_of_stock'])) {
                        if ($new_qty > $product_qty) {
                            return false;
                        }
                    }
                } elseif ($operator == 'down') {
                    $qty = '- '.(float)$quantity;
                    $new_qty = (float)$result['quantity'] - (float)$quantity;
                    if ($new_qty < $minimal_quantity && $minimal_quantity > 1) {
                        return -1;
                    }
                } else {
                    return false;
                }

                /* Delete product from cart */
                if ($new_qty <= 0) {
                    return $this->deleteProduct((int)$id_product, (int)$id_product_attribute, (int)$id_customization, 0, $auto_add_cart_rule);
                } elseif ($new_qty < $minimal_quantity) {
                    return -1;
                } else {
                    Db::getInstance()->execute('
                        UPDATE `'._DB_PREFIX_.'cart_product`
                        SET `quantity` = `quantity` '.$qty.', `date_add` = NOW()
                        WHERE `id_product` = '.(int)$id_product.
                        (!empty($id_product_attribute) ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
                        AND `id_cart` = '.(int)$this->id.(Configuration::get('PS_ALLOW_MULTISHIPPING') && $this->isMultiAddressDelivery() ? ' AND `id_address_delivery` = '.(int)$id_address_delivery : '').'
                        LIMIT 1'
                    );
                }
            }
            /* Add product to the cart */
            elseif ($operator == 'up') {
                $sql = 'SELECT stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity
                        FROM '._DB_PREFIX_.'product p
                        '.Product::sqlStock('p', $id_product_attribute, true, $shop).'
                        WHERE p.id_product = '.$id_product;

                $result2 = Db::getInstance()->getRow($sql);

                // Quantity for product pack
                if (Pack::isPack($id_product)) {
                    $result2['quantity'] = Pack::getQuantity($id_product, $id_product_attribute);
                }

                if (!Product::isAvailableWhenOutOfStock((int)$result2['out_of_stock'])) {
                    if ((float)$quantity > $result2['quantity']) {
                        return false;
                    }
                }

                if ((float)$quantity < $minimal_quantity) {
                    return -1;
                }

                $result_add = Db::getInstance()->insert('cart_product', array(
                    'id_product' =>            (int)$id_product,
                    'id_product_attribute' =>    (int)$id_product_attribute,
                    'id_cart' =>                (int)$this->id,
                    'id_address_delivery' =>    (int)$id_address_delivery,
                    'id_shop' =>                $shop->id,
                    'quantity' =>                (float)$quantity,
                    'date_add' =>                date('Y-m-d H:i:s')
                ));

                if (!$result_add) {
                    return false;
                }
            }
        }

        // refresh cache of self::_products
        $this->_products = $this->getProducts(true);
        $this->update();
        $context = Context::getContext()->cloneContext();
        $context->cart = $this;
        Cache::clean('getContextualValue_*');
        if ($auto_add_cart_rule) {
            CartRule::autoAddToCart($context);
        }

        if ($product->customizable) {
            return $this->_updateCustomizationQuantity((float)$quantity, (int)$id_customization, (int)$id_product, (int)$id_product_attribute, (int)$id_address_delivery, $operator);
        } else {
            return true;
        }
    }

    /*
    ** Customization management
    */
    protected function _updateCustomizationQuantity($quantity, $id_customization, $id_product, $id_product_attribute, $id_address_delivery, $operator = 'up')
    {
        // Link customization to product combination when it is first added to cart
        if (empty($id_customization)) {
            $customization = $this->getProductCustomization($id_product, null, true);
            foreach ($customization as $field) {
                if ($field['quantity'] == 0) {
                    Db::getInstance()->execute('
                    UPDATE `'._DB_PREFIX_.'customization`
                    SET `quantity` = '.(float)$quantity.',
                        `id_product_attribute` = '.(int)$id_product_attribute.',
                        `id_address_delivery` = '.(int)$id_address_delivery.',
                        `in_cart` = 1
                    WHERE `id_customization` = '.(int)$field['id_customization']);
                }
            }
        }

        /* Deletion */
        if (!empty($id_customization) && (float)$quantity < 1) {
            return $this->_deleteCustomization((int)$id_customization, (int)$id_product, (int)$id_product_attribute);
        }

        /* Quantity update */
        if (!empty($id_customization)) {
            $result = Db::getInstance()->getRow('SELECT `quantity` FROM `'._DB_PREFIX_.'customization` WHERE `id_customization` = '.(int)$id_customization);
            if ($result && Db::getInstance()->NumRows()) {
                if ($operator == 'down' && (float)$result['quantity'] - (float)$quantity < 1) {
                    return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'customization` WHERE `id_customization` = '.(int)$id_customization);
                }

                return Db::getInstance()->execute('
                    UPDATE `'._DB_PREFIX_.'customization`
                    SET
                        `quantity` = `quantity` '.($operator == 'up' ? '+ ' : '- ').(float)$quantity.',
                        `id_address_delivery` = '.(int)$id_address_delivery.',
                        `in_cart` = 1
                    WHERE `id_customization` = '.(int)$id_customization);
            } else {
                Db::getInstance()->execute('
                    UPDATE `'._DB_PREFIX_.'customization`
                    SET `id_address_delivery` = '.(int)$id_address_delivery.',
                    `in_cart` = 1
                    WHERE `id_customization` = '.(int)$id_customization);
            }
        }
        // refresh cache of self::_products
        $this->_products = $this->getProducts(true);
        $this->update();
        return true;
    }

    /**
     * Add customization item to database
     *
     * @param int $id_product
     * @param int $id_product_attribute
     * @param int $index
     * @param int $type
     * @param string $field
     * @param int $quantity
     * @return bool success
     */
    public function _addCustomization($id_product, $id_product_attribute, $index, $type, $field, $quantity)
    {
        $exising_customization = Db::getInstance()->executeS('
            SELECT cu.`id_customization`, cd.`index`, cd.`value`, cd.`type` FROM `'._DB_PREFIX_.'customization` cu
            LEFT JOIN `'._DB_PREFIX_.'customized_data` cd
            ON cu.`id_customization` = cd.`id_customization`
            WHERE cu.id_cart = '.(int)$this->id.'
            AND cu.id_product = '.(int)$id_product.'
            AND in_cart = 0'
        );

        if ($exising_customization) {
            // If the customization field is alreay filled, delete it
            foreach ($exising_customization as $customization) {
                if ($customization['type'] == $type && $customization['index'] == $index) {
                    Db::getInstance()->execute('
                        DELETE FROM `'._DB_PREFIX_.'customized_data`
                        WHERE id_customization = '.(int)$customization['id_customization'].'
                        AND type = '.(int)$customization['type'].'
                        AND `index` = '.(int)$customization['index']);
                    if ($type == Product::CUSTOMIZE_FILE) {
                        @unlink(_PS_UPLOAD_DIR_.$customization['value']);
                        @unlink(_PS_UPLOAD_DIR_.$customization['value'].'_small');
                    }
                    break;
                }
            }
            $id_customization = $exising_customization[0]['id_customization'];
        } else {
            Db::getInstance()->execute(
                'INSERT INTO `'._DB_PREFIX_.'customization` (`id_cart`, `id_product`, `id_product_attribute`, `quantity`)
                VALUES ('.(int)$this->id.', '.(int)$id_product.', '.(int)$id_product_attribute.', '.(float)$quantity.')'
            );
            $id_customization = Db::getInstance()->Insert_ID();
        }

        $query = 'INSERT INTO `'._DB_PREFIX_.'customized_data` (`id_customization`, `type`, `index`, `value`)
            VALUES ('.(int)$id_customization.', '.(int)$type.', '.(int)$index.', \''.pSQL($field).'\')';

        if (!Db::getInstance()->execute($query)) {
            return false;
        }
        return true;
    }

    /**
     * Check if order has already been placed
     *
     * @return bool result
     */
    public function orderExists()
    {
        $cache_id = 'Cart::orderExists_'.(int)$this->id;
        if (!Cache::isStored($cache_id)) {
            $result = (bool)Db::getInstance()->getValue('SELECT count(*) FROM `'._DB_PREFIX_.'orders` WHERE `id_cart` = '.(int)$this->id);
            Cache::store($cache_id, $result);
            return $result;
        }
        return Cache::retrieve($cache_id);
    }

    /**
     * @deprecated 1.5.0, use Cart->removeCartRule()
     */
    public function deleteDiscount($id_cart_rule)
    {
        Tools::displayAsDeprecated();
        return $this->removeCartRule($id_cart_rule);
    }

    public function removeCartRule($id_cart_rule)
    {
        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_ALL);
        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_SHIPPING);
        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_REDUCTION);
        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_GIFT);

        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_ALL. '-ids');
        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_SHIPPING. '-ids');
        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_REDUCTION. '-ids');
        Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_GIFT. '-ids');

        $result = Db::getInstance()->delete('cart_cart_rule', '`id_cart_rule` = '.(int)$id_cart_rule.' AND `id_cart` = '.(int)$this->id, 1);

        $cart_rule = new CartRule($id_cart_rule, Configuration::get('PS_LANG_DEFAULT'));
        if ((int)$cart_rule->gift_product) {
            $this->updateQty(1, $cart_rule->gift_product, $cart_rule->gift_product_attribute, null, 'down', 0, null, false);
        }

        return $result;
    }

    /**
     * Delete a product from the cart
     *
     * @param int $id_product Product ID
     * @param int $id_product_attribute Attribute ID if needed
     * @param int $id_customization Customization id
     * @return bool result
     */
    public function deleteProduct($id_product, $id_product_attribute = null, $id_customization = null, $id_address_delivery = 0, $auto_add_cart_rule = true)
    {
        if (isset(self::$_nbProducts[$this->id])) {
            unset(self::$_nbProducts[$this->id]);
        }

        if (isset(self::$_totalWeight[$this->id])) {
            unset(self::$_totalWeight[$this->id]);
        }

        if ((int)$id_customization) {
            $product_total_quantity = (float)Db::getInstance()->getValue(
                'SELECT `quantity`
                FROM `'._DB_PREFIX_.'cart_product`
                WHERE `id_product` = '.(int)$id_product.'
                AND `id_cart` = '.(int)$this->id.'
                AND `id_product_attribute` = '.(int)$id_product_attribute);

            $customization_quantity = (float)Db::getInstance()->getValue('
            SELECT `quantity`
            FROM `'._DB_PREFIX_.'customization`
            WHERE `id_cart` = '.(int)$this->id.'
            AND `id_product` = '.(int)$id_product.'
            AND `id_product_attribute` = '.(int)$id_product_attribute.'
            '.((int)$id_address_delivery ? 'AND `id_address_delivery` = '.(int)$id_address_delivery : ''));

            if (!$this->_deleteCustomization((int)$id_customization, (int)$id_product, (int)$id_product_attribute, (int)$id_address_delivery)) {
                return false;
            }

            // refresh cache of self::_products
            $this->_products = $this->getProducts(true);
            return ($customization_quantity == $product_total_quantity && $this->deleteProduct((int)$id_product, (int)$id_product_attribute, null, (int)$id_address_delivery));
        }

        /* Get customization quantity */
        $result = Db::getInstance()->getRow('
            SELECT SUM(`quantity`) AS \'quantity\'
            FROM `'._DB_PREFIX_.'customization`
            WHERE `id_cart` = '.(int)$this->id.'
            AND `id_product` = '.(int)$id_product.'
            AND `id_product_attribute` = '.(int)$id_product_attribute);

        if ($result === false) {
            return false;
        }

        /* If the product still possesses customization it does not have to be deleted */
        if (Db::getInstance()->NumRows() && (float)$result['quantity']) {
            return Db::getInstance()->execute('
                UPDATE `'._DB_PREFIX_.'cart_product`
                SET `quantity` = '.(float)$result['quantity'].'
                WHERE `id_cart` = '.(int)$this->id.'
                AND `id_product` = '.(int)$id_product.
                ($id_product_attribute != null ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '')
            );
        }

        /* Product deletion */
        $result = Db::getInstance()->execute('
        DELETE FROM `'._DB_PREFIX_.'cart_product`
        WHERE `id_product` = '.(int)$id_product.'
        '.(!is_null($id_product_attribute) ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
        AND `id_cart` = '.(int)$this->id.'
        '.((int)$id_address_delivery ? 'AND `id_address_delivery` = '.(int)$id_address_delivery : ''));

        if ($result) {
            $return = $this->update();
            // refresh cache of self::_products
            $this->_products = $this->getProducts(true);
            CartRule::autoRemoveFromCart();
            if ($auto_add_cart_rule) {
                CartRule::autoAddToCart();
            }

            return $return;
        }

        return false;
    }

    /**
     * Delete a customization from the cart. If customization is a Picture,
     * then the image is also deleted
     *
     * @param int $id_customization
     * @return bool result
     */
    protected function _deleteCustomization($id_customization, $id_product, $id_product_attribute, $id_address_delivery = 0)
    {
        $result = true;
        $customization = Db::getInstance()->getRow('SELECT *
            FROM `'._DB_PREFIX_.'customization`
            WHERE `id_customization` = '.(int)$id_customization);

        if ($customization) {
            $cust_data = Db::getInstance()->getRow('SELECT *
                FROM `'._DB_PREFIX_.'customized_data`
                WHERE `id_customization` = '.(int)$id_customization);

            // Delete customization picture if necessary
            if (isset($cust_data['type']) && $cust_data['type'] == 0) {
                $result &= (@unlink(_PS_UPLOAD_DIR_.$cust_data['value']) && @unlink(_PS_UPLOAD_DIR_.$cust_data['value'].'_small'));
            }

            $result &= Db::getInstance()->execute(
                'DELETE FROM `'._DB_PREFIX_.'customized_data`
                WHERE `id_customization` = '.(int)$id_customization
            );

            if ($result) {
                $result &= Db::getInstance()->execute(
                    'UPDATE `'._DB_PREFIX_.'cart_product`
                    SET `quantity` = `quantity` - '.(float)$customization['quantity'].'
                    WHERE `id_cart` = '.(int)$this->id.'
                    AND `id_product` = '.(int)$id_product.
                    ((int)$id_product_attribute ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
                    AND `id_address_delivery` = '.(int)$id_address_delivery
                );
            }

            if (!$result) {
                return false;
            }

            return Db::getInstance()->execute(
                'DELETE FROM `'._DB_PREFIX_.'customization`
                WHERE `id_customization` = '.(int)$id_customization
            );
        }

        return true;
    }

    public static function getTotalCart($id_cart, $use_tax_display = false, $type = Cart::BOTH)
    {
        $cart = new Cart($id_cart);
        if (!Validate::isLoadedObject($cart)) {
            die(Tools::displayError());
        }

        $with_taxes = $use_tax_display ? $cart->_taxCalculationMethod != PS_TAX_EXC : true;
        return Tools::displayPrice($cart->getOrderTotal($with_taxes, $type), Currency::getCurrencyInstance((int)$cart->id_currency), false);
    }


    public static function getOrderTotalUsingTaxCalculationMethod($id_cart)
    {
        return Cart::getTotalCart($id_cart, true);
    }

    /**
    * This function returns the total cart amount
    *
    * Possible values for $type:
    * Cart::ONLY_PRODUCTS
    * Cart::ONLY_DISCOUNTS
    * Cart::BOTH
    * Cart::BOTH_WITHOUT_SHIPPING
    * Cart::ONLY_SHIPPING
    * Cart::ONLY_WRAPPING
    * Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING
    * Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING
    *
    * @param bool $withTaxes With or without taxes
    * @param int $type Total type
    * @param bool $use_cache Allow using cache of the method CartRule::getContextualValue
    * @return float Order total
    */
    public function getOrderTotal($with_taxes = true, $type = Cart::BOTH, $products = null, $id_carrier = null, $use_cache = true)
    {
        // Dependencies
        $address_factory    = Adapter_ServiceLocator::get('Adapter_AddressFactory');
        $price_calculator    = Adapter_ServiceLocator::get('Adapter_ProductPriceCalculator');
        $configuration        = Adapter_ServiceLocator::get('Core_Business_ConfigurationInterface');

        $ps_tax_address_type = $configuration->get('PS_TAX_ADDRESS_TYPE');
        $ps_use_ecotax = $configuration->get('PS_USE_ECOTAX');
        $ps_round_type = $configuration->get('PS_ROUND_TYPE');
        $ps_ecotax_tax_rules_group_id = $configuration->get('PS_ECOTAX_TAX_RULES_GROUP_ID');
        $compute_precision = $configuration->get('_PS_PRICE_COMPUTE_PRECISION_');

        if (!$this->id) {
            return 0;
        }

        $type = (int)$type;
        $array_type = array(
            Cart::ONLY_PRODUCTS,
            Cart::ONLY_DISCOUNTS,
            Cart::BOTH,
            Cart::BOTH_WITHOUT_SHIPPING,
            Cart::ONLY_SHIPPING,
            Cart::ONLY_WRAPPING,
            Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING,
            Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING,
        );

        // Define virtual context to prevent case where the cart is not the in the global context
        $virtual_context = Context::getContext()->cloneContext();
        $virtual_context->cart = $this;

        if (!in_array($type, $array_type)) {
            die(Tools::displayError());
        }

        $with_shipping = in_array($type, array(Cart::BOTH, Cart::ONLY_SHIPPING));

        // if cart rules are not used
        if ($type == Cart::ONLY_DISCOUNTS && !CartRule::isFeatureActive()) {
            return 0;
        }

        // no shipping cost if is a cart with only virtuals products
        $virtual = $this->isVirtualCart();
        if ($virtual && $type == Cart::ONLY_SHIPPING) {
            return 0;
        }

        if ($virtual && $type == Cart::BOTH) {
            $type = Cart::BOTH_WITHOUT_SHIPPING;
        }

        if ($with_shipping || $type == Cart::ONLY_DISCOUNTS) {
            if (is_null($products) && is_null($id_carrier)) {
                $shipping_fees = $this->getTotalShippingCost(null, (bool)$with_taxes);
            } else {
                $shipping_fees = $this->getPackageShippingCost((int)$id_carrier, (bool)$with_taxes, null, $products);
            }
        } else {
            $shipping_fees = 0;
        }

        if ($type == Cart::ONLY_SHIPPING) {
            return $shipping_fees;
        }

        if ($type == Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING) {
            $type = Cart::ONLY_PRODUCTS;
        }

        $param_product = true;
        if (is_null($products)) {
            $param_product = false;
            $products = $this->getProducts();
        }

        if ($type == Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING) {
            foreach ($products as $key => $product) {
                if ($product['is_virtual']) {
                    unset($products[$key]);
                }
            }
            $type = Cart::ONLY_PRODUCTS;
        }

        $order_total = 0;
        if (Tax::excludeTaxeOption()) {
            $with_taxes = false;
        }

        $products_total = array();
        $ecotax_total = 0;

        foreach ($products as $product) {
            // products refer to the cart details

            if ($virtual_context->shop->id != $product['id_shop']) {
                $virtual_context->shop = new Shop((int)$product['id_shop']);
            }

            if ($ps_tax_address_type == 'id_address_invoice') {
                $id_address = (int)$this->id_address_invoice;
            } else {
                $id_address = (int)$product['id_address_delivery'];
            } // Get delivery address of the product from the cart
            if (!$address_factory->addressExists($id_address)) {
                $id_address = null;
            }

            // The $null variable below is not used,
            // but it is necessary to pass it to getProductPrice because
            // it expects a reference.
            $null = null;
            $price = $price_calculator->getProductPrice(
                (int)$product['id_product'],
                $with_taxes,
                (int)$product['id_product_attribute'],
                6,
                null,
                false,
                true,
                $product['cart_quantity'],
                false,
                (int)$this->id_customer ? (int)$this->id_customer : null,
                (int)$this->id,
                $id_address,
                $null,
                $ps_use_ecotax,
                true,
                $virtual_context
            );

            $address = $address_factory->findOrCreate($id_address, true);

            if ($with_taxes) {
                $id_tax_rules_group = Product::getIdTaxRulesGroupByIdProduct((int)$product['id_product'], $virtual_context);
                $tax_calculator = TaxManagerFactory::getManager($address, $id_tax_rules_group)->getTaxCalculator();
            } else {
                $id_tax_rules_group = 0;
            }

            if (in_array($ps_round_type, array(Order::ROUND_ITEM, Order::ROUND_LINE))) {
                if (!isset($products_total[$id_tax_rules_group])) {
                    $products_total[$id_tax_rules_group] = 0;
                }
            } elseif (!isset($products_total[$id_tax_rules_group.'_'.$id_address])) {
                $products_total[$id_tax_rules_group.'_'.$id_address] = 0;
            }

            switch ($ps_round_type) {
                case Order::ROUND_TOTAL:
                    $products_total[$id_tax_rules_group.'_'.$id_address] += $price * (float)$product['cart_quantity'];
                    break;

                case Order::ROUND_LINE:
                    $product_price = $price * $product['cart_quantity'];
                    $products_total[$id_tax_rules_group] += Tools::ps_round($product_price, $compute_precision);
                    break;

                case Order::ROUND_ITEM:
                default:
                    $product_price = /*$with_taxes ? $tax_calculator->addTaxes($price) : */$price;
                    $products_total[$id_tax_rules_group] += Tools::ps_round($product_price, $compute_precision) * (float)$product['cart_quantity'];
                    break;
            }
        }

        foreach ($products_total as $key => $price) {
            $order_total += $price;
        }

        $order_total_products = $order_total;

        if ($type == Cart::ONLY_DISCOUNTS) {
            $order_total = 0;
        }

        // Wrapping Fees
        $wrapping_fees = 0;

        // With PS_ATCP_SHIPWRAP on the gift wrapping cost computation calls getOrderTotal with $type === Cart::ONLY_PRODUCTS, so the flag below prevents an infinite recursion.
        $include_gift_wrapping = (!$configuration->get('PS_ATCP_SHIPWRAP') || $type !== Cart::ONLY_PRODUCTS);

        if ($this->gift && $include_gift_wrapping) {
            $wrapping_fees = Tools::convertPrice(Tools::ps_round($this->getGiftWrappingPrice($with_taxes), $compute_precision), Currency::getCurrencyInstance((int)$this->id_currency));
        }
        if ($type == Cart::ONLY_WRAPPING) {
            return $wrapping_fees;
        }

        $order_total_discount = 0;
        $order_shipping_discount = 0;
        if (!in_array($type, array(Cart::ONLY_SHIPPING, Cart::ONLY_PRODUCTS)) && CartRule::isFeatureActive()) {
            // First, retrieve the cart rules associated to this "getOrderTotal"
            if ($with_shipping || $type == Cart::ONLY_DISCOUNTS) {
                $cart_rules = $this->getCartRules(CartRule::FILTER_ACTION_ALL);
            } else {
                $cart_rules = $this->getCartRules(CartRule::FILTER_ACTION_REDUCTION);
                // Cart Rules array are merged manually in order to avoid doubles
                foreach ($this->getCartRules(CartRule::FILTER_ACTION_GIFT) as $tmp_cart_rule) {
                    $flag = false;
                    foreach ($cart_rules as $cart_rule) {
                        if ($tmp_cart_rule['id_cart_rule'] == $cart_rule['id_cart_rule']) {
                            $flag = true;
                        }
                    }
                    if (!$flag) {
                        $cart_rules[] = $tmp_cart_rule;
                    }
                }
            }

            $id_address_delivery = 0;
            if (isset($products[0])) {
                $id_address_delivery = (is_null($products) ? $this->id_address_delivery : $products[0]['id_address_delivery']);
            }
            $package = array('id_carrier' => $id_carrier, 'id_address' => $id_address_delivery, 'products' => $products);

            // Then, calculate the contextual value for each one
            $flag = false;
            foreach ($cart_rules as $cart_rule) {
                // If the cart rule offers free shipping, add the shipping cost
                if (($with_shipping || $type == Cart::ONLY_DISCOUNTS) && $cart_rule['obj']->free_shipping && !$flag) {
                    $order_shipping_discount = (float)Tools::ps_round($cart_rule['obj']->getContextualValue($with_taxes, $virtual_context, CartRule::FILTER_ACTION_SHIPPING, ($param_product ? $package : null), $use_cache), $compute_precision);
                    $flag = true;
                }

                // If the cart rule is a free gift, then add the free gift value only if the gift is in this package
                if ((int)$cart_rule['obj']->gift_product) {
                    $in_order = false;
                    if (is_null($products)) {
                        $in_order = true;
                    } else {
                        foreach ($products as $product) {
                            if ($cart_rule['obj']->gift_product == $product['id_product'] && $cart_rule['obj']->gift_product_attribute == $product['id_product_attribute']) {
                                $in_order = true;
                            }
                        }
                    }

                    if ($in_order) {
                        $order_total_discount += $cart_rule['obj']->getContextualValue($with_taxes, $virtual_context, CartRule::FILTER_ACTION_GIFT, $package, $use_cache);
                    }
                }

                // If the cart rule offers a reduction, the amount is prorated (with the products in the package)
                if ($cart_rule['obj']->reduction_percent > 0 || $cart_rule['obj']->reduction_amount > 0) {
                    $order_total_discount += Tools::ps_round($cart_rule['obj']->getContextualValue($with_taxes, $virtual_context, CartRule::FILTER_ACTION_REDUCTION, $package, $use_cache), $compute_precision);
                }
            }
            $order_total_discount = min(Tools::ps_round($order_total_discount, 2), (float)$order_total_products) + (float)$order_shipping_discount;
            $order_total -= $order_total_discount;
        }

        if ($type == Cart::BOTH) {
            $order_total += $shipping_fees + $wrapping_fees;
        }

        if ($order_total < 0 && $type != Cart::ONLY_DISCOUNTS) {
            return 0;
        }

        if ($type == Cart::ONLY_DISCOUNTS) {
            return $order_total_discount;
        }

        return Tools::ps_round((float)$order_total, $compute_precision);
    }

    /**
    * Get the gift wrapping price
    * @param bool $with_taxes With or without taxes
    * @return float wrapping price
    */
    public function getGiftWrappingPrice($with_taxes = true, $id_address = null)
    {
        static $address = array();

        $wrapping_fees = (float)Configuration::get('PS_GIFT_WRAPPING_PRICE');

        if ($wrapping_fees <= 0) {
            return $wrapping_fees;
        }

        if ($with_taxes) {
            if (Configuration::get('PS_ATCP_SHIPWRAP')) {
                // With PS_ATCP_SHIPWRAP, wrapping fee is by default tax included
                // so nothing to do here.
            } else {
                if (!isset($address[$this->id])) {
                    if ($id_address === null) {
                        $id_address = (int)$this->{Configuration::get('PS_TAX_ADDRESS_TYPE')};
                    }
                    try {
                        $address[$this->id] = Address::initialize($id_address);
                    } catch (Exception $e) {
                        $address[$this->id] = new Address();
                        $address[$this->id]->id_country = Configuration::get('PS_COUNTRY_DEFAULT');
                    }
                }

                $tax_manager = TaxManagerFactory::getManager($address[$this->id], (int)Configuration::get('PS_GIFT_WRAPPING_TAX_RULES_GROUP'));
                $tax_calculator = $tax_manager->getTaxCalculator();
                $wrapping_fees = $tax_calculator->addTaxes($wrapping_fees);
            }
        } elseif (Configuration::get('PS_ATCP_SHIPWRAP')) {
            // With PS_ATCP_SHIPWRAP, wrapping fee is by default tax included, so we convert it
            // when asked for the pre tax price.
            $wrapping_fees = Tools::ps_round(
                $wrapping_fees / (1 + $this->getAverageProductsTaxRate()),
                _PS_PRICE_COMPUTE_PRECISION_
            );
        }

        return $wrapping_fees;
    }

    /**
     * Get the number of packages
     *
     * @return int number of packages
     */
    public function getNbOfPackages()
    {
        static $nb_packages = array();

        if (!isset($nb_packages[$this->id])) {
            $nb_packages[$this->id] = 0;
            foreach ($this->getPackageList() as $by_address) {
                $nb_packages[$this->id] += count($by_address);
            }
        }

        return $nb_packages[$this->id];
    }

    /**
     * Get products grouped by package and by addresses to be sent individualy (one package = one shipping cost).
     *
     * @return array array(
     *                   0 => array( // First address
     *                       0 => array(  // First package
     *                           'product_list' => array(...),
     *                           'carrier_list' => array(...),
     *                           'id_warehouse' => array(...),
     *                       ),
     *                   ),
     *               );
     * @todo Add avaibility check
     */
    public function getPackageList($flush = false)
    {
        static $cache = array();
        $cache_key = (int)$this->id.'_'.(int)$this->id_address_delivery;
        if (isset($cache[$cache_key]) && $cache[$cache_key] !== false && !$flush) {
            return $cache[$cache_key];
        }

        $product_list = $this->getProducts($flush);
        // Step 1 : Get product informations (warehouse_list and carrier_list), count warehouse
        // Determine the best warehouse to determine the packages
        // For that we count the number of time we can use a warehouse for a specific delivery address
        $warehouse_count_by_address = array();

        $stock_management_active = Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT');

        foreach ($product_list as &$product) {
            if ((int)$product['id_address_delivery'] == 0) {
                $product['id_address_delivery'] = (int)$this->id_address_delivery;
            }

            if (!isset($warehouse_count_by_address[$product['id_address_delivery']])) {
                $warehouse_count_by_address[$product['id_address_delivery']] = array();
            }

            $product['warehouse_list'] = array();

            if ($stock_management_active &&
                (int)$product['advanced_stock_management'] == 1) {
                $warehouse_list = Warehouse::getProductWarehouseList($product['id_product'], $product['id_product_attribute'], $this->id_shop);
                if (count($warehouse_list) == 0) {
                    $warehouse_list = Warehouse::getProductWarehouseList($product['id_product'], $product['id_product_attribute']);
                }
                // Does the product is in stock ?
                // If yes, get only warehouse where the product is in stock

                $warehouse_in_stock = array();
                $manager = StockManagerFactory::getManager();

                foreach ($warehouse_list as $key => $warehouse) {
                    $product_real_quantities = $manager->getProductRealQuantities(
                        $product['id_product'],
                        $product['id_product_attribute'],
                        array($warehouse['id_warehouse']),
                        true
                    );

                    if ($product_real_quantities > 0 || Pack::isPack((int)$product['id_product'])) {
                        $warehouse_in_stock[] = $warehouse;
                    }
                }

                if (!empty($warehouse_in_stock)) {
                    $warehouse_list = $warehouse_in_stock;
                    $product['in_stock'] = true;
                } else {
                    $product['in_stock'] = false;
                }
            } else {
                //simulate default warehouse
                $warehouse_list = array(0 => array('id_warehouse' => 0));
                $product['in_stock'] = StockAvailable::getQuantityAvailableByProduct($product['id_product'], $product['id_product_attribute']) > 0;
            }

            foreach ($warehouse_list as $warehouse) {
                $product['warehouse_list'][$warehouse['id_warehouse']] = $warehouse['id_warehouse'];
                if (!isset($warehouse_count_by_address[$product['id_address_delivery']][$warehouse['id_warehouse']])) {
                    $warehouse_count_by_address[$product['id_address_delivery']][$warehouse['id_warehouse']] = 0;
                }

                $warehouse_count_by_address[$product['id_address_delivery']][$warehouse['id_warehouse']]++;
            }
        }
        unset($product);

        arsort($warehouse_count_by_address);

        // Step 2 : Group product by warehouse
        $grouped_by_warehouse = array();

        foreach ($product_list as &$product) {
            if (!isset($grouped_by_warehouse[$product['id_address_delivery']])) {
                $grouped_by_warehouse[$product['id_address_delivery']] = array(
                    'in_stock' => array(),
                    'out_of_stock' => array(),
                );
            }

            $product['carrier_list'] = array();
            $id_warehouse = 0;
            foreach ($warehouse_count_by_address[$product['id_address_delivery']] as $id_war => $val) {
                if (array_key_exists((int)$id_war, $product['warehouse_list'])) {
                    $product['carrier_list'] = Tools::array_replace($product['carrier_list'], Carrier::getAvailableCarrierList(new Product($product['id_product']), $id_war, $product['id_address_delivery'], null, $this));
                    if (!$id_warehouse) {
                        $id_warehouse = (int)$id_war;
                    }
                }
            }

            if (!isset($grouped_by_warehouse[$product['id_address_delivery']]['in_stock'][$id_warehouse])) {
                $grouped_by_warehouse[$product['id_address_delivery']]['in_stock'][$id_warehouse] = array();
                $grouped_by_warehouse[$product['id_address_delivery']]['out_of_stock'][$id_warehouse] = array();
            }

            if (!$this->allow_seperated_package) {
                $key = 'in_stock';
            } else {
                $key = $product['in_stock'] ? 'in_stock' : 'out_of_stock';
                $product_quantity_in_stock = StockAvailable::getQuantityAvailableByProduct($product['id_product'], $product['id_product_attribute']);
                if ($product['in_stock'] && $product['cart_quantity'] > $product_quantity_in_stock) {
                    $out_stock_part = $product['cart_quantity'] - $product_quantity_in_stock;
                    $product_bis = $product;
                    $product_bis['cart_quantity'] = $out_stock_part;
                    $product_bis['in_stock'] = 0;
                    $product['cart_quantity'] -= $out_stock_part;
                    $grouped_by_warehouse[$product['id_address_delivery']]['out_of_stock'][$id_warehouse][] = $product_bis;
                }
            }

            if (empty($product['carrier_list'])) {
                $product['carrier_list'] = array(0 => 0);
            }

            $grouped_by_warehouse[$product['id_address_delivery']][$key][$id_warehouse][] = $product;
        }
        unset($product);

        // Step 3 : grouped product from grouped_by_warehouse by available carriers
        $grouped_by_carriers = array();
        foreach ($grouped_by_warehouse as $id_address_delivery => $products_in_stock_list) {
            if (!isset($grouped_by_carriers[$id_address_delivery])) {
                $grouped_by_carriers[$id_address_delivery] = array(
                    'in_stock' => array(),
                    'out_of_stock' => array(),
                );
            }
            foreach ($products_in_stock_list as $key => $warehouse_list) {
                if (!isset($grouped_by_carriers[$id_address_delivery][$key])) {
                    $grouped_by_carriers[$id_address_delivery][$key] = array();
                }
                foreach ($warehouse_list as $id_warehouse => $product_list) {
                    if (!isset($grouped_by_carriers[$id_address_delivery][$key][$id_warehouse])) {
                        $grouped_by_carriers[$id_address_delivery][$key][$id_warehouse] = array();
                    }
                    foreach ($product_list as $product) {
                        $package_carriers_key = implode(',', $product['carrier_list']);

                        if (!isset($grouped_by_carriers[$id_address_delivery][$key][$id_warehouse][$package_carriers_key])) {
                            $grouped_by_carriers[$id_address_delivery][$key][$id_warehouse][$package_carriers_key] = array(
                                'product_list' => array(),
                                'carrier_list' => $product['carrier_list'],
                                'warehouse_list' => $product['warehouse_list']
                            );
                        }

                        $grouped_by_carriers[$id_address_delivery][$key][$id_warehouse][$package_carriers_key]['product_list'][] = $product;
                    }
                }
            }
        }

        $package_list = array();
        // Step 4 : merge product from grouped_by_carriers into $package to minimize the number of package
        foreach ($grouped_by_carriers as $id_address_delivery => $products_in_stock_list) {
            if (!isset($package_list[$id_address_delivery])) {
                $package_list[$id_address_delivery] = array(
                    'in_stock' => array(),
                    'out_of_stock' => array(),
                );
            }

            foreach ($products_in_stock_list as $key => $warehouse_list) {
                if (!isset($package_list[$id_address_delivery][$key])) {
                    $package_list[$id_address_delivery][$key] = array();
                }
                // Count occurance of each carriers to minimize the number of packages
                $carrier_count = array();
                foreach ($warehouse_list as $id_warehouse => $products_grouped_by_carriers) {
                    foreach ($products_grouped_by_carriers as $data) {
                        foreach ($data['carrier_list'] as $id_carrier) {
                            if (!isset($carrier_count[$id_carrier])) {
                                $carrier_count[$id_carrier] = 0;
                            }
                            $carrier_count[$id_carrier]++;
                        }
                    }
                }
                arsort($carrier_count);
                foreach ($warehouse_list as $id_warehouse => $products_grouped_by_carriers) {
                    if (!isset($package_list[$id_address_delivery][$key][$id_warehouse])) {
                        $package_list[$id_address_delivery][$key][$id_warehouse] = array();
                    }
                    foreach ($products_grouped_by_carriers as $data) {
                        foreach ($carrier_count as $id_carrier => $rate) {
                            if (array_key_exists($id_carrier, $data['carrier_list'])) {
                                if (!isset($package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier])) {
                                    $package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier] = array(
                                        'carrier_list' => $data['carrier_list'],
                                        'warehouse_list' => $data['warehouse_list'],
                                        'product_list' => array(),
                                    );
                                }
                                $package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['carrier_list'] =
                                    array_intersect($package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['carrier_list'], $data['carrier_list']);
                                $package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['product_list'] =
                                    array_merge($package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['product_list'], $data['product_list']);

                                break;
                            }
                        }
                    }
                }
            }
        }

        // Step 5 : Reduce depth of $package_list
        $final_package_list = array();
        foreach ($package_list as $id_address_delivery => $products_in_stock_list) {
            if (!isset($final_package_list[$id_address_delivery])) {
                $final_package_list[$id_address_delivery] = array();
            }

            foreach ($products_in_stock_list as $key => $warehouse_list) {
                foreach ($warehouse_list as $id_warehouse => $products_grouped_by_carriers) {
                    foreach ($products_grouped_by_carriers as $data) {
                        $final_package_list[$id_address_delivery][] = array(
                            'product_list' => $data['product_list'],
                            'carrier_list' => $data['carrier_list'],
                            'warehouse_list' => $data['warehouse_list'],
                            'id_warehouse' => $id_warehouse,
                        );
                    }
                }
            }
        }
        $cache[$cache_key] = $final_package_list;
        return $final_package_list;
    }

    public function getPackageIdWarehouse($package, $id_carrier = null)
    {
        if ($id_carrier === null) {
            if (isset($package['id_carrier'])) {
                $id_carrier = (int)$package['id_carrier'];
            }
        }

        if ($id_carrier == null) {
            return $package['id_warehouse'];
        }

        foreach ($package['warehouse_list'] as $id_warehouse) {
            $warehouse = new Warehouse((int)$id_warehouse);
            $available_warehouse_carriers = $warehouse->getCarriers();
            if (in_array($id_carrier, $available_warehouse_carriers)) {
                return (int)$id_warehouse;
            }
        }
        return 0;
    }

    /**
     * Get all deliveries options available for the current cart
     * @param Country $default_country
     * @param bool $flush Force flushing cache
     *
     * @return array array(
     *                   0 => array( // First address
     *                       '12,' => array(  // First delivery option available for this address
     *                           carrier_list => array(
     *                               12 => array( // First carrier for this option
     *                                   'instance' => Carrier Object,
     *                                   'logo' => <url to the carriers logo>,
     *                                   'price_with_tax' => 12.4,
     *                                   'price_without_tax' => 12.4,
     *                                   'package_list' => array(
     *                                       1,
     *                                       3,
     *                                   ),
     *                               ),
     *                           ),
     *                           is_best_grade => true, // Does this option have the biggest grade (quick shipping) for this shipping address
     *                           is_best_price => true, // Does this option have the lower price for this shipping address
     *                           unique_carrier => true, // Does this option use a unique carrier
     *                           total_price_with_tax => 12.5,
     *                           total_price_without_tax => 12.5,
     *                           position => 5, // Average of the carrier position
     *                       ),
     *                   ),
     *               );
     *               If there are no carriers available for an address, return an empty  array
     */
    public function getDeliveryOptionList(Country $default_country = null, $flush = false)
    {
        static $cache = array();
        if (isset($cache[$this->id]) && !$flush) {
            return $cache[$this->id];
        }

        $delivery_option_list = array();
        $carriers_price = array();
        $carrier_collection = array();
        $package_list = $this->getPackageList($flush);

        // Foreach addresses
        foreach ($package_list as $id_address => $packages) {
            // Initialize vars
            $delivery_option_list[$id_address] = array();
            $carriers_price[$id_address] = array();
            $common_carriers = null;
            $best_price_carriers = array();
            $best_grade_carriers = array();
            $carriers_instance = array();

            // Get country
            if ($id_address) {
                $address = new Address($id_address);
                $country = new Country($address->id_country);
            } else {
                $country = $default_country;
            }

            // Foreach packages, get the carriers with best price, best position and best grade
            foreach ($packages as $id_package => $package) {
                // No carriers available
                if (count($packages) == 1 && count($package['carrier_list']) == 1 && current($package['carrier_list']) == 0) {
                    $cache[$this->id] = array();
                    return $cache[$this->id];
                }

                $carriers_price[$id_address][$id_package] = array();

                // Get all common carriers for each packages to the same address
                if (is_null($common_carriers)) {
                    $common_carriers = $package['carrier_list'];
                } else {
                    $common_carriers = array_intersect($common_carriers, $package['carrier_list']);
                }

                $best_price = null;
                $best_price_carrier = null;
                $best_grade = null;
                $best_grade_carrier = null;

                // Foreach carriers of the package, calculate his price, check if it the best price, position and grade
                foreach ($package['carrier_list'] as $id_carrier) {
                    if (!isset($carriers_instance[$id_carrier])) {
                        $carriers_instance[$id_carrier] = new Carrier($id_carrier);
                    }

                    $price_with_tax = $this->getPackageShippingCost((int)$id_carrier, true, $country, $package['product_list']);
                    $price_without_tax = $this->getPackageShippingCost((int)$id_carrier, false, $country, $package['product_list']);
                    if (is_null($best_price) || $price_with_tax < $best_price) {
                        $best_price = $price_with_tax;
                        $best_price_carrier = $id_carrier;
                    }
                    $carriers_price[$id_address][$id_package][$id_carrier] = array(
                        'without_tax' => $price_without_tax,
                        'with_tax' => $price_with_tax);

                    $grade = $carriers_instance[$id_carrier]->grade;
                    if (is_null($best_grade) || $grade > $best_grade) {
                        $best_grade = $grade;
                        $best_grade_carrier = $id_carrier;
                    }
                }

                $best_price_carriers[$id_package] = $best_price_carrier;
                $best_grade_carriers[$id_package] = $best_grade_carrier;
            }

            // Reset $best_price_carrier, it's now an array
            $best_price_carrier = array();
            $key = '';

            // Get the delivery option with the lower price
            foreach ($best_price_carriers as $id_package => $id_carrier) {
                $key .= $id_carrier.',';
                if (!isset($best_price_carrier[$id_carrier])) {
                    $best_price_carrier[$id_carrier] = array(
                        'price_with_tax' => 0,
                        'price_without_tax' => 0,
                        'package_list' => array(),
                        'product_list' => array(),
                    );
                }
                $best_price_carrier[$id_carrier]['price_with_tax'] += $carriers_price[$id_address][$id_package][$id_carrier]['with_tax'];
                $best_price_carrier[$id_carrier]['price_without_tax'] += $carriers_price[$id_address][$id_package][$id_carrier]['without_tax'];
                $best_price_carrier[$id_carrier]['package_list'][] = $id_package;
                $best_price_carrier[$id_carrier]['product_list'] = array_merge($best_price_carrier[$id_carrier]['product_list'], $packages[$id_package]['product_list']);
                $best_price_carrier[$id_carrier]['instance'] = $carriers_instance[$id_carrier];
                $real_best_price = !isset($real_best_price) || $real_best_price > $carriers_price[$id_address][$id_package][$id_carrier]['with_tax'] ?
                    $carriers_price[$id_address][$id_package][$id_carrier]['with_tax'] : $real_best_price;
                $real_best_price_wt = !isset($real_best_price_wt) || $real_best_price_wt > $carriers_price[$id_address][$id_package][$id_carrier]['without_tax'] ?
                    $carriers_price[$id_address][$id_package][$id_carrier]['without_tax'] : $real_best_price_wt;
            }

            // Add the delivery option with best price as best price
            $delivery_option_list[$id_address][$key] = array(
                'carrier_list' => $best_price_carrier,
                'is_best_price' => true,
                'is_best_grade' => false,
                'unique_carrier' => (count($best_price_carrier) <= 1)
            );

            // Reset $best_grade_carrier, it's now an array
            $best_grade_carrier = array();
            $key = '';

            // Get the delivery option with the best grade
            foreach ($best_grade_carriers as $id_package => $id_carrier) {
                $key .= $id_carrier.',';
                if (!isset($best_grade_carrier[$id_carrier])) {
                    $best_grade_carrier[$id_carrier] = array(
                        'price_with_tax' => 0,
                        'price_without_tax' => 0,
                        'package_list' => array(),
                        'product_list' => array(),
                    );
                }
                $best_grade_carrier[$id_carrier]['price_with_tax'] += $carriers_price[$id_address][$id_package][$id_carrier]['with_tax'];
                $best_grade_carrier[$id_carrier]['price_without_tax'] += $carriers_price[$id_address][$id_package][$id_carrier]['without_tax'];
                $best_grade_carrier[$id_carrier]['package_list'][] = $id_package;
                $best_grade_carrier[$id_carrier]['product_list'] = array_merge($best_grade_carrier[$id_carrier]['product_list'], $packages[$id_package]['product_list']);
                $best_grade_carrier[$id_carrier]['instance'] = $carriers_instance[$id_carrier];
            }

            // Add the delivery option with best grade as best grade
            if (!isset($delivery_option_list[$id_address][$key])) {
                $delivery_option_list[$id_address][$key] = array(
                    'carrier_list' => $best_grade_carrier,
                    'is_best_price' => false,
                    'unique_carrier' => (count($best_grade_carrier) <= 1)
                );
            }
            $delivery_option_list[$id_address][$key]['is_best_grade'] = true;

            // Get all delivery options with a unique carrier
            foreach ($common_carriers as $id_carrier) {
                $key = '';
                $package_list = array();
                $product_list = array();
                $price_with_tax = 0;
                $price_without_tax = 0;

                foreach ($packages as $id_package => $package) {
                    $key .= $id_carrier.',';
                    $price_with_tax += $carriers_price[$id_address][$id_package][$id_carrier]['with_tax'];
                    $price_without_tax += $carriers_price[$id_address][$id_package][$id_carrier]['without_tax'];
                    $package_list[] = $id_package;
                    $product_list = array_merge($product_list, $package['product_list']);
                }

                if (!isset($delivery_option_list[$id_address][$key])) {
                    $delivery_option_list[$id_address][$key] = array(
                        'is_best_price' => false,
                        'is_best_grade' => false,
                        'unique_carrier' => true,
                        'carrier_list' => array(
                            $id_carrier => array(
                                'price_with_tax' => $price_with_tax,
                                'price_without_tax' => $price_without_tax,
                                'instance' => $carriers_instance[$id_carrier],
                                'package_list' => $package_list,
                                'product_list' => $product_list,
                            )
                        )
                    );
                } else {
                    $delivery_option_list[$id_address][$key]['unique_carrier'] = (count($delivery_option_list[$id_address][$key]['carrier_list']) <= 1);
                }
            }
        }

        $cart_rules = CartRule::getCustomerCartRules(Context::getContext()->cookie->id_lang, Context::getContext()->cookie->id_customer, true, true, false, $this, true);

        $result = false;
        if ($this->id) {
            $result = Db::getInstance()->executeS('SELECT * FROM '._DB_PREFIX_.'cart_cart_rule WHERE id_cart = '.(int)$this->id);
        }

        $cart_rules_in_cart = array();

        if (is_array($result)) {
            foreach ($result as $row) {
                $cart_rules_in_cart[] = $row['id_cart_rule'];
            }
        }

        $total_products_wt = $this->getOrderTotal(true, Cart::ONLY_PRODUCTS);
        $total_products = $this->getOrderTotal(false, Cart::ONLY_PRODUCTS);

        $free_carriers_rules = array();

        $context = Context::getContext();
        foreach ($cart_rules as $cart_rule) {
            $total_price = $cart_rule['minimum_amount_tax'] ? $total_products_wt : $total_products;
            $total_price += $cart_rule['minimum_amount_tax'] && $cart_rule['minimum_amount_shipping'] ? $real_best_price : 0;
            $total_price += !$cart_rule['minimum_amount_tax'] && $cart_rule['minimum_amount_shipping'] ? $real_best_price_wt : 0;
            $condition = ($cart_rule['free_shipping'] && $cart_rule['carrier_restriction'] && $cart_rule['minimum_amount'] <= $total_price)?1:0;
            if(isset($cart_rule['code']) && !empty($cart_rule['code'])){
                $condition = ($cart_rule['free_shipping'] && $cart_rule['carrier_restriction'] && in_array($cart_rule['id_cart_rule'], $cart_rules_in_cart)
                    && $cart_rule['minimum_amount'] <= $total_price)?1:0;
            }
            if ($condition) {
                $cr = new CartRule((int)$cart_rule['id_cart_rule']);
                if (Validate::isLoadedObject($cr) &&
                    $cr->checkValidity($context, in_array((int)$cart_rule['id_cart_rule'], $cart_rules_in_cart), false, false)) {
                    $carriers = $cr->getAssociatedRestrictions('carrier', true, false);
                    if (is_array($carriers) && count($carriers) && isset($carriers['selected'])) {
                        foreach ($carriers['selected'] as $carrier) {
                            if (isset($carrier['id_carrier']) && $carrier['id_carrier']) {
                                $free_carriers_rules[] = (int)$carrier['id_carrier'];
                            }
                        }
                    }
                }
            }
        }

        // For each delivery options :
        //    - Set the carrier list
        //    - Calculate the price
        //    - Calculate the average position
        foreach ($delivery_option_list as $id_address => $delivery_option) {
            foreach ($delivery_option as $key => $value) {
                $total_price_with_tax = 0;
                $total_price_without_tax = 0;
                $position = 0;
                foreach ($value['carrier_list'] as $id_carrier => $data) {
                    $total_price_with_tax += $data['price_with_tax'];
                    $total_price_without_tax += $data['price_without_tax'];
                    $total_price_without_tax_with_rules = (in_array($id_carrier, $free_carriers_rules)) ? 0 : $total_price_without_tax;

                    if (!isset($carrier_collection[$id_carrier])) {
                        $carrier_collection[$id_carrier] = new Carrier($id_carrier);
                    }
                    $delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]['instance'] = $carrier_collection[$id_carrier];

                    if (file_exists(_PS_SHIP_IMG_DIR_.$id_carrier.'.jpg')) {
                        $delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]['logo'] = _THEME_SHIP_DIR_.$id_carrier.'.jpg';
                    } else {
                        $delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]['logo'] = false;
                    }

                    $position += $carrier_collection[$id_carrier]->position;
                }
                $delivery_option_list[$id_address][$key]['total_price_with_tax'] = $total_price_with_tax;
                $delivery_option_list[$id_address][$key]['total_price_without_tax'] = $total_price_without_tax;
                $delivery_option_list[$id_address][$key]['is_free'] = !$total_price_without_tax_with_rules ? true : false;
                $delivery_option_list[$id_address][$key]['position'] = $position / count($value['carrier_list']);
            }
        }

        // Sort delivery option list
        foreach ($delivery_option_list as &$array) {
            uasort($array, array('Cart', 'sortDeliveryOptionList'));
        }

        $cache[$this->id] = $delivery_option_list;
        return $cache[$this->id];
    }

    /**
     *
     * Sort list of option delivery by parameters define in the BO
     * @param $option1
     * @param $option2
     * @return int -1 if $option 1 must be placed before and 1 if the $option1 must be placed after the $option2
     */
    public static function sortDeliveryOptionList($option1, $option2)
    {
        static $order_by_price = null;
        static $order_way = null;
        if (is_null($order_by_price)) {
            $order_by_price = !Configuration::get('PS_CARRIER_DEFAULT_SORT');
        }
        if (is_null($order_way)) {
            $order_way = Configuration::get('PS_CARRIER_DEFAULT_ORDER');
        }

        if ($order_by_price) {
            if ($order_way) {
                return ($option1['total_price_with_tax'] < $option2['total_price_with_tax']) * 2 - 1;
            } // return -1 or 1
            else {
                return ($option1['total_price_with_tax'] >= $option2['total_price_with_tax']) * 2 - 1;
            }
        } // return -1 or 1
        elseif ($order_way) {
            return ($option1['position'] < $option2['position']) * 2 - 1;
        } // return -1 or 1
            else {
                return ($option1['position'] >= $option2['position']) * 2 - 1;
            } // return -1 or 1
    }

    public function carrierIsSelected($id_carrier, $id_address)
    {
        $delivery_option = $this->getDeliveryOption();
        $delivery_option_list = $this->getDeliveryOptionList();

        if (!isset($delivery_option[$id_address])) {
            return false;
        }

        if (!isset($delivery_option_list[$id_address][$delivery_option[$id_address]])) {
            return false;
        }

        if (!in_array($id_carrier, array_keys($delivery_option_list[$id_address][$delivery_option[$id_address]]['carrier_list']))) {
            return false;
        }

        return true;
    }

    /**
     * Get all deliveries options available for the current cart formated like Carriers::getCarriersForOrder
     * This method was wrote for retrocompatibility with 1.4 theme
     * New theme need to use Cart::getDeliveryOptionList() to generate carriers option in the checkout process
     *
     * @since 1.5.0
     *
     * @param Country $default_country
     * @param bool $flush Force flushing cache
     *
     */
    public function simulateCarriersOutput(Country $default_country = null, $flush = false)
    {
        $delivery_option_list = $this->getDeliveryOptionList($default_country, $flush);

        // This method cannot work if there is multiple address delivery
        if (count($delivery_option_list) > 1 || empty($delivery_option_list)) {
            return array();
        }

        $carriers = array();
        foreach (reset($delivery_option_list) as $key => $option) {
            $price = $option['total_price_with_tax'];
            $price_tax_exc = $option['total_price_without_tax'];
            $name = $img = $delay = '';

            if ($option['unique_carrier']) {
                $carrier = reset($option['carrier_list']);
                if (isset($carrier['instance'])) {
                    $name = $carrier['instance']->name;
                    $delay = $carrier['instance']->delay;
                    $delay = isset($delay[Context::getContext()->language->id]) ?
                        $delay[Context::getContext()->language->id] : $delay[(int)Configuration::get('PS_LANG_DEFAULT')];
                }
                if (isset($carrier['logo'])) {
                    $img = $carrier['logo'];
                }
            } else {
                $nameList = array();
                foreach ($option['carrier_list'] as $carrier) {
                    $nameList[] = $carrier['instance']->name;
                }
                $name = join(' -', $nameList);
                $img = ''; // No images if multiple carriers
                $delay = '';
            }
            $carriers[] = array(
                'name' => $name,
                'img' => $img,
                'delay' => $delay,
                'price' => $price,
                'price_tax_exc' => $price_tax_exc,
                'id_carrier' => Cart::intifier($key), // Need to translate to an integer for retrocompatibility reason, in 1.4 template we used intval
                'is_module' => false,
            );
        }
        return $carriers;
    }

    public function simulateCarrierSelectedOutput($use_cache = true)
    {
        $delivery_option = $this->getDeliveryOption(null, false, $use_cache);

        if (count($delivery_option) > 1 || empty($delivery_option)) {
            return 0;
        }

        return Cart::intifier(reset($delivery_option));
    }

    /**
     * Translate a string option_delivery identifier ('24,3,') in a int (3240002000)
     *
     * The  option_delivery identifier is a list of integers separated by a ','.
     * This method replace the delimiter by a sequence of '0'.
     * The size of this sequence is fixed by the first digit of the return
     *
     * @return int
     */
    public static function intifier($string, $delimiter = ',')
    {
        $elm = explode($delimiter, $string);
        $max = max($elm);
        return strlen($max).implode(str_repeat('0', strlen($max) + 1), $elm);
    }

    /**
     * Translate a int option_delivery identifier (3240002000) in a string ('24,3,')
     */
    public static function desintifier($int, $delimiter = ',')
    {
        $delimiter_len = $int[0];
        $int = strrev(substr($int, 1));
        $elm = explode(str_repeat('0', $delimiter_len + 1), $int);
        return strrev(implode($delimiter, $elm));
    }

    /**
     * Does the cart use multiple address
     * @return bool
     */
    public function isMultiAddressDelivery()
    {
        static $cache = array();

        if (!isset($cache[$this->id])) {
            $sql = new DbQuery();
            $sql->select('count(distinct id_address_delivery)');
            $sql->from('cart_product', 'cp');
            $sql->where('id_cart = '.(int)$this->id);

            $cache[$this->id] = Db::getInstance()->getValue($sql) > 1;
        }
        return $cache[$this->id];
    }

    /**
     * Get all delivery addresses object for the current cart
     */
    public function getAddressCollection()
    {
        $collection = array();
        $cache_id = 'Cart::getAddressCollection'.(int)$this->id;
        if (!Cache::isStored($cache_id)) {
            $result = Db::getInstance()->executeS(
                'SELECT DISTINCT `id_address_delivery`
                FROM `'._DB_PREFIX_.'cart_product`
                WHERE id_cart = '.(int)$this->id
            );
            Cache::store($cache_id, $result);
        } else {
            $result = Cache::retrieve($cache_id);
        }

        $result[] = array('id_address_delivery' => (int)$this->id_address_delivery);

        foreach ($result as $row) {
            if ((int)$row['id_address_delivery'] != 0) {
                $collection[(int)$row['id_address_delivery']] = new Address((int)$row['id_address_delivery']);
            }
        }
        return $collection;
    }

    /**
    * Set the delivery option and id_carrier, if there is only one carrier
    */
    public function setDeliveryOption($delivery_option = null)
    {
        if (empty($delivery_option) || count($delivery_option) == 0) {
            $this->delivery_option = '';
            $this->id_carrier = 0;
            return;
        }
        Cache::clean('getContextualValue_*');
        $delivery_option_list = $this->getDeliveryOptionList(null, true);

        foreach ($delivery_option_list as $id_address => $options) {
            if (!isset($delivery_option[$id_address])) {
                foreach ($options as $key => $option) {
                    if ($option['is_best_price']) {
                        $delivery_option[$id_address] = $key;
                        break;
                    }
                }
            }
        }

        if (count($delivery_option) == 1) {
            $this->id_carrier = $this->getIdCarrierFromDeliveryOption($delivery_option);
        }

        $this->delivery_option = serialize($delivery_option);
    }

    protected function getIdCarrierFromDeliveryOption($delivery_option)
    {
        $delivery_option_list = $this->getDeliveryOptionList();
        foreach ($delivery_option as $key => $value) {
            if (isset($delivery_option_list[$key]) && isset($delivery_option_list[$key][$value])) {
                if (count($delivery_option_list[$key][$value]['carrier_list']) == 1) {
                    return current(array_keys($delivery_option_list[$key][$value]['carrier_list']));
                }
            }
        }

        return 0;
    }

    /**
     * Get the delivery option selected, or if no delivery option was selected,
     * the cheapest option for each address
     *
     * @param Country|null $default_country
     * @param bool         $dontAutoSelectOptions
     * @param bool         $use_cache
     *
     * @return array|bool|mixed Delivery option
     */
    public function getDeliveryOption($default_country = null, $dontAutoSelectOptions = false, $use_cache = true)
    {
        static $cache = array();
        $cache_id = (int)(is_object($default_country) ? $default_country->id : 0).'-'.(int)$dontAutoSelectOptions;
        if (isset($cache[$cache_id]) && $use_cache) {
            return $cache[$cache_id];
        }

        $delivery_option_list = $this->getDeliveryOptionList($default_country);

        // The delivery option was selected
        if (isset($this->delivery_option) && $this->delivery_option != '') {
            $delivery_option = Tools::unSerialize($this->delivery_option);
            $validated = false;
            foreach ($delivery_option as $key) {
                foreach ($delivery_option_list as $id_address => $option) {
                    if (isset($delivery_option_list[$id_address][$key])) {
                        $validated = true;

                        if (!isset($delivery_option[$id_address])) {
                            $delivery_option = array();
                            $delivery_option[$id_address] = $key;
                        }

                        break 2;
                    }
                }
            }

            if ($validated) {
                $cache[$cache_id] = $delivery_option;
                return $delivery_option;
            }
        }

        if ($dontAutoSelectOptions) {
            return false;
        }

        // No delivery option selected or delivery option selected is not valid, get the better for all options
        $delivery_option = array();
        foreach ($delivery_option_list as $id_address => $options) {
            foreach ($options as $key => $option) {
                if (Configuration::get('PS_CARRIER_DEFAULT') == -1 && $option['is_best_price']) {
                    $delivery_option[$id_address] = $key;
                    break;
                } elseif (Configuration::get('PS_CARRIER_DEFAULT') == -2 && $option['is_best_grade']) {
                    $delivery_option[$id_address] = $key;
                    break;
                } elseif ($option['unique_carrier'] && in_array(Configuration::get('PS_CARRIER_DEFAULT'), array_keys($option['carrier_list']))) {
                    $delivery_option[$id_address] = $key;
                    break;
                }
            }

            reset($options);
            if (!isset($delivery_option[$id_address])) {
                $delivery_option[$id_address] = key($options);
            }
        }

        $cache[$cache_id] = $delivery_option;

        return $delivery_option;
    }

    /**
    * Return shipping total for the cart
    *
    * @param array|null $delivery_option Array of the delivery option for each address
    * @param bool $use_tax
    * @param Country|null $default_country
    * @return float Shipping total
    */
    public function getTotalShippingCost($delivery_option = null, $use_tax = true, Country $default_country = null)
    {
        if (isset(Context::getContext()->cookie->id_country)) {
            $default_country = new Country(Context::getContext()->cookie->id_country);
        }
        if (is_null($delivery_option)) {
            $delivery_option = $this->getDeliveryOption($default_country, false, false);
        }

        $total_shipping = 0;
        $delivery_option_list = $this->getDeliveryOptionList($default_country);
        foreach ($delivery_option as $id_address => $key) {
            if (!isset($delivery_option_list[$id_address]) || !isset($delivery_option_list[$id_address][$key])) {
                continue;
            }
            if ($use_tax) {
                $total_shipping += $delivery_option_list[$id_address][$key]['total_price_with_tax'];
            } else {
                $total_shipping += $delivery_option_list[$id_address][$key]['total_price_without_tax'];
            }
        }

        return $total_shipping;
    }

    /**
     * Return shipping total of a specific carriers for the cart
     *
     * @param int $id_carrier
     * @param array $delivery_option Array of the delivery option for each address
     * @param bool $useTax
     * @param Country|null $default_country
     * @param array|null $delivery_option
     * @return float Shipping total
     */
    public function getCarrierCost($id_carrier, $useTax = true, Country $default_country = null, $delivery_option = null)
    {
        if (is_null($delivery_option)) {
            $delivery_option = $this->getDeliveryOption($default_country);
        }

        $total_shipping = 0;
        $delivery_option_list = $this->getDeliveryOptionList();


        foreach ($delivery_option as $id_address => $key) {
            if (!isset($delivery_option_list[$id_address]) || !isset($delivery_option_list[$id_address][$key])) {
                continue;
            }
            if (isset($delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier])) {
                if ($useTax) {
                    $total_shipping += $delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]['price_with_tax'];
                } else {
                    $total_shipping += $delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]['price_without_tax'];
                }
            }
        }

        return $total_shipping;
    }

    /**
     * @deprecated 1.5.0, use Cart->getPackageShippingCost()
     */
    public function getOrderShippingCost($id_carrier = null, $use_tax = true, Country $default_country = null, $product_list = null)
    {
        Tools::displayAsDeprecated();
        return $this->getPackageShippingCost((int)$id_carrier, $use_tax, $default_country, $product_list);
    }

    /**
     * Return package shipping cost
     *
     * @param int          $id_carrier      Carrier ID (default : current carrier)
     * @param bool         $use_tax
     * @param Country|null $default_country
     * @param array|null   $product_list    List of product concerned by the shipping.
     *                                      If null, all the product of the cart are used to calculate the shipping cost
     * @param int|null $id_zone
     *
     * @return float Shipping total
     */
    public function getPackageShippingCost($id_carrier = null, $use_tax = true, Country $default_country = null, $product_list = null, $id_zone = null)
    {
        if ($this->isVirtualCart()) {
            return 0;
        }

        if (!$default_country) {
            $default_country = Context::getContext()->country;
        }

        if (!is_null($product_list)) {
            foreach ($product_list as $key => $value) {
                if ($value['is_virtual'] == 1) {
                    unset($product_list[$key]);
                }
            }
        }

        if (is_null($product_list)) {
            $products = $this->getProducts();
        } else {
            $products = $product_list;
        }

        if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice') {
            $address_id = (int)$this->id_address_invoice;
        } elseif (count($product_list)) {
            $prod = current($product_list);
            $address_id = (int)$prod['id_address_delivery'];
        } else {
            $address_id = null;
        }
        if (!Address::addressExists($address_id)) {
            $address_id = null;
        }

        if (is_null($id_carrier) && !empty($this->id_carrier)) {
            $id_carrier = (int)$this->id_carrier;
        }

        $cache_id = 'getPackageShippingCost_'.(int)$this->id.'_'.(int)$address_id.'_'.(int)$id_carrier.'_'.(int)$use_tax.'_'.(int)$default_country->id.'_'.(int)$id_zone;
        if ($products) {
            foreach ($products as $product) {
                $cache_id .= '_'.(int)$product['id_product'].'_'.(int)$product['id_product_attribute'];
            }
        }

        if (Cache::isStored($cache_id)) {
            return Cache::retrieve($cache_id);
        }

        // Order total in default currency without fees
        $order_total = $this->getOrderTotal(true, Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING, $product_list);

        // Start with shipping cost at 0
        $shipping_cost = 0;
        // If no product added, return 0
        if (!count($products)) {
            Cache::store($cache_id, $shipping_cost);
            return $shipping_cost;
        }

        if (!isset($id_zone)) {
            // Get id zone
            if (!$this->isMultiAddressDelivery()
                && isset($this->id_address_delivery) // Be carefull, id_address_delivery is not usefull one 1.5
                && $this->id_address_delivery
                && Customer::customerHasAddress($this->id_customer, $this->id_address_delivery
            )) {
                $id_zone = Address::getZoneById((int)$this->id_address_delivery);
            } else {
                if (!Validate::isLoadedObject($default_country)) {
                    $default_country = new Country(Configuration::get('PS_COUNTRY_DEFAULT'), Configuration::get('PS_LANG_DEFAULT'));
                }

                $id_zone = (int)$default_country->id_zone;
            }
        }

        if ($id_carrier && !$this->isCarrierInRange((int)$id_carrier, (int)$id_zone)) {
            $id_carrier = '';
        }

        if (empty($id_carrier) && $this->isCarrierInRange((int)Configuration::get('PS_CARRIER_DEFAULT'), (int)$id_zone)) {
            $id_carrier = (int)Configuration::get('PS_CARRIER_DEFAULT');
        }

        $total_package_without_shipping_tax_inc = $this->getOrderTotal(true, Cart::BOTH_WITHOUT_SHIPPING, $product_list);
        if (empty($id_carrier)) {
            if ((int)$this->id_customer) {
                $customer = new Customer((int)$this->id_customer);
                $result = Carrier::getCarriers((int)Configuration::get('PS_LANG_DEFAULT'), true, false, (int)$id_zone, $customer->getGroups());
                unset($customer);
            } else {
                $result = Carrier::getCarriers((int)Configuration::get('PS_LANG_DEFAULT'), true, false, (int)$id_zone);
            }

            foreach ($result as $k => $row) {
                if ($row['id_carrier'] == Configuration::get('PS_CARRIER_DEFAULT')) {
                    continue;
                }

                if (!isset(self::$_carriers[$row['id_carrier']])) {
                    self::$_carriers[$row['id_carrier']] = new Carrier((int)$row['id_carrier']);
                }

                /** @var Carrier $carrier */
                $carrier = self::$_carriers[$row['id_carrier']];

                $shipping_method = $carrier->getShippingMethod();
                // Get only carriers that are compliant with shipping method
                if (($shipping_method == Carrier::SHIPPING_METHOD_WEIGHT && $carrier->getMaxDeliveryPriceByWeight((int)$id_zone) === false)
                || ($shipping_method == Carrier::SHIPPING_METHOD_PRICE && $carrier->getMaxDeliveryPriceByPrice((int)$id_zone) === false)) {
                    unset($result[$k]);
                    continue;
                }

                // If out-of-range behavior carrier is set on "Desactivate carrier"
                if ($row['range_behavior']) {
                    $check_delivery_price_by_weight = Carrier::checkDeliveryPriceByWeight($row['id_carrier'], $this->getTotalWeight(), (int)$id_zone);

                    $total_order = $total_package_without_shipping_tax_inc;
                    $check_delivery_price_by_price = Carrier::checkDeliveryPriceByPrice($row['id_carrier'], $total_order, (int)$id_zone, (int)$this->id_currency);

                    // Get only carriers that have a range compatible with cart
                    if (($shipping_method == Carrier::SHIPPING_METHOD_WEIGHT && !$check_delivery_price_by_weight)
                    || ($shipping_method == Carrier::SHIPPING_METHOD_PRICE && !$check_delivery_price_by_price)) {
                        unset($result[$k]);
                        continue;
                    }
                }

                if ($shipping_method == Carrier::SHIPPING_METHOD_WEIGHT) {
                    $shipping = $carrier->getDeliveryPriceByWeight($this->getTotalWeight($product_list), (int)$id_zone);
                } else {
                    $shipping = $carrier->getDeliveryPriceByPrice($order_total, (int)$id_zone, (int)$this->id_currency);
                }

                if (!isset($min_shipping_price)) {
                    $min_shipping_price = $shipping;
                }

                if ($shipping <= $min_shipping_price) {
                    $id_carrier = (int)$row['id_carrier'];
                    $min_shipping_price = $shipping;
                }
            }
        }

        if (empty($id_carrier)) {
            $id_carrier = Configuration::get('PS_CARRIER_DEFAULT');
        }

        if (!isset(self::$_carriers[$id_carrier])) {
            self::$_carriers[$id_carrier] = new Carrier((int)$id_carrier, Configuration::get('PS_LANG_DEFAULT'));
        }

        $carrier = self::$_carriers[$id_carrier];

        // No valid Carrier or $id_carrier <= 0 ?
        if (!Validate::isLoadedObject($carrier)) {
            Cache::store($cache_id, 0);
            return 0;
        }
        $shipping_method = $carrier->getShippingMethod();

        if (!$carrier->active) {
            Cache::store($cache_id, $shipping_cost);
            return $shipping_cost;
        }

        // Free fees if free carrier
        if ($carrier->is_free == 1) {
            Cache::store($cache_id, 0);
            return 0;
        }

        // Select carrier tax
        if ($use_tax && !Tax::excludeTaxeOption()) {
            $address = Address::initialize((int)$address_id);

            if (Configuration::get('PS_ATCP_SHIPWRAP')) {
                // With PS_ATCP_SHIPWRAP, pre-tax price is deduced
                // from post tax price, so no $carrier_tax here
                // even though it sounds weird.
                $carrier_tax = 0;
            } else {
                $carrier_tax = $carrier->getTaxesRate($address);
            }
        }

        $configuration = Configuration::getMultiple(array(
            'PS_SHIPPING_FREE_PRICE',
            'PS_SHIPPING_HANDLING',
            'PS_SHIPPING_METHOD',
            'PS_SHIPPING_FREE_WEIGHT'
        ));

        // Free fees
        $free_fees_price = 0;
        if (isset($configuration['PS_SHIPPING_FREE_PRICE'])) {
            $free_fees_price = Tools::convertPrice((float)$configuration['PS_SHIPPING_FREE_PRICE'], Currency::getCurrencyInstance((int)$this->id_currency));
        }
        $orderTotalwithDiscounts = $this->getOrderTotal(true, Cart::BOTH_WITHOUT_SHIPPING, null, null, false);
        if ($orderTotalwithDiscounts >= (float)($free_fees_price) && (float)($free_fees_price) > 0) {
            Cache::store($cache_id, $shipping_cost);
            return $shipping_cost;
        }

        if (isset($configuration['PS_SHIPPING_FREE_WEIGHT'])
            && $this->getTotalWeight() >= (float)$configuration['PS_SHIPPING_FREE_WEIGHT']
            && (float)$configuration['PS_SHIPPING_FREE_WEIGHT'] > 0) {
            Cache::store($cache_id, $shipping_cost);
            return $shipping_cost;
        }

        // Get shipping cost using correct method
        if ($carrier->range_behavior) {
            if (!isset($id_zone)) {
                // Get id zone
                if (isset($this->id_address_delivery)
                    && $this->id_address_delivery
                    && Customer::customerHasAddress($this->id_customer, $this->id_address_delivery)) {
                    $id_zone = Address::getZoneById((int)$this->id_address_delivery);
                } else {
                    $id_zone = (int)$default_country->id_zone;
                }
            }

            if (($shipping_method == Carrier::SHIPPING_METHOD_WEIGHT && !Carrier::checkDeliveryPriceByWeight($carrier->id, $this->getTotalWeight(), (int)$id_zone))
            || ($shipping_method == Carrier::SHIPPING_METHOD_PRICE && !Carrier::checkDeliveryPriceByPrice($carrier->id, $total_package_without_shipping_tax_inc, $id_zone, (int)$this->id_currency)
            )) {
                $shipping_cost += 0;
            } else {
                if ($shipping_method == Carrier::SHIPPING_METHOD_WEIGHT) {
                    $shipping_cost += $carrier->getDeliveryPriceByWeight($this->getTotalWeight($product_list), $id_zone);
                } else { // by price
                    $shipping_cost += $carrier->getDeliveryPriceByPrice($order_total, $id_zone, (int)$this->id_currency);
                }
            }
        } else {
            if ($shipping_method == Carrier::SHIPPING_METHOD_WEIGHT) {
                $shipping_cost += $carrier->getDeliveryPriceByWeight($this->getTotalWeight($product_list), $id_zone);
            } else {
                $shipping_cost += $carrier->getDeliveryPriceByPrice($order_total, $id_zone, (int)$this->id_currency);
            }
        }
        // Adding handling charges
        if (isset($configuration['PS_SHIPPING_HANDLING']) && $carrier->shipping_handling) {
            $shipping_cost += (float)$configuration['PS_SHIPPING_HANDLING'];
        }

        // Additional Shipping Cost per product
        foreach ($products as $product) {
            if (!$product['is_virtual']) {
                $shipping_cost += $product['additional_shipping_cost'] * $product['cart_quantity'];
            }
        }

        $shipping_cost = Tools::convertPrice($shipping_cost, Currency::getCurrencyInstance((int)$this->id_currency));

        //get external shipping cost from module
        if ($carrier->shipping_external) {
            $module_name = $carrier->external_module_name;

            /** @var CarrierModule $module */
            $module = Module::getInstanceByName($module_name);

            if (Validate::isLoadedObject($module)) {
                if (array_key_exists('id_carrier', $module)) {
                    $module->id_carrier = $carrier->id;
                }
                if ($carrier->need_range) {
                    if (method_exists($module, 'getPackageShippingCost')) {
                        $shipping_cost = $module->getPackageShippingCost($this, $shipping_cost, $products);
                    } else {
                        $shipping_cost = $module->getOrderShippingCost($this, $shipping_cost);
                    }
                } else {
                    $shipping_cost = $module->getOrderShippingCostExternal($this);
                }

                // Check if carrier is available
                if ($shipping_cost === false) {
                    Cache::store($cache_id, false);
                    return false;
                }
            } else {
                Cache::store($cache_id, false);
                return false;
            }
        }

        if (Configuration::get('PS_ATCP_SHIPWRAP')) {
            if (!$use_tax) {
                // With PS_ATCP_SHIPWRAP, we deduce the pre-tax price from the post-tax
                    // price. This is on purpose and required in Germany.
                    $shipping_cost /= (1 + $this->getAverageProductsTaxRate());
            }
        } else {
            // Apply tax
            if ($use_tax && isset($carrier_tax)) {
                $shipping_cost *= 1 + ($carrier_tax / 100);
            }
        }

        $shipping_cost = (float)Tools::ps_round((float)$shipping_cost, (Currency::getCurrencyInstance((int)$this->id_currency)->decimals * _PS_PRICE_DISPLAY_PRECISION_));
        Cache::store($cache_id, $shipping_cost);

        return $shipping_cost;
    }

    /**
    * Return cart weight
    * @return float Cart weight
    */
    public function getTotalWeight($products = null)
    {
        if (!is_null($products)) {
            $total_weight = 0;
            foreach ($products as $product) {
                if (!isset($product['weight_attribute']) || is_null($product['weight_attribute'])) {
                    $total_weight += $product['weight'] * $product['cart_quantity'];
                } else {
                    $total_weight += $product['weight_attribute'] * $product['cart_quantity'];
                }
            }
            return $total_weight;
        }

        if (!isset(self::$_totalWeight[$this->id])) {
            if (Combination::isFeatureActive()) {
                $weight_product_with_attribute = Db::getInstance()->getValue('
                SELECT SUM((p.`weight` + pa.`weight`) * cp.`quantity`) as nb
                FROM `'._DB_PREFIX_.'cart_product` cp
                LEFT JOIN `'._DB_PREFIX_.'product` p ON (cp.`id_product` = p.`id_product`)
                LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa ON (cp.`id_product_attribute` = pa.`id_product_attribute`)
                WHERE (cp.`id_product_attribute` IS NOT NULL AND cp.`id_product_attribute` != 0)
                AND cp.`id_cart` = '.(int)$this->id);
            } else {
                $weight_product_with_attribute = 0;
            }

            $weight_product_without_attribute = Db::getInstance()->getValue('
            SELECT SUM(p.`weight` * cp.`quantity`) as nb
            FROM `'._DB_PREFIX_.'cart_product` cp
            LEFT JOIN `'._DB_PREFIX_.'product` p ON (cp.`id_product` = p.`id_product`)
            WHERE (cp.`id_product_attribute` IS NULL OR cp.`id_product_attribute` = 0)
            AND cp.`id_cart` = '.(int)$this->id);

            self::$_totalWeight[$this->id] = round((float)$weight_product_with_attribute + (float)$weight_product_without_attribute, 6);
        }

        return self::$_totalWeight[$this->id];
    }

    /**
     * @deprecated 1.5.0
     * @param CartRule $obj
     * @return bool|string
     */
    public function checkDiscountValidity($obj, $discounts, $order_total, $products, $check_cart_discount = false)
    {
        Tools::displayAsDeprecated();
        $context = Context::getContext()->cloneContext();
        $context->cart = $this;

        return $obj->checkValidity($context);
    }

    /**
    * Return useful informations for cart
    *
    * @return array Cart details
    */
    public function getSummaryDetails($id_lang = null, $refresh = false)
    {
        $context = Context::getContext();
        if (!$id_lang) {
            $id_lang = $context->language->id;
        }

        $delivery = new Address((int)$this->id_address_delivery);
        $invoice = new Address((int)$this->id_address_invoice);

        // New layout system with personalization fields
        $formatted_addresses = array(
            'delivery' => AddressFormat::getFormattedLayoutData($delivery),
            'invoice' => AddressFormat::getFormattedLayoutData($invoice)
        );

        $base_total_tax_inc = $this->getOrderTotal(true);
        $base_total_tax_exc = $this->getOrderTotal(false);

        $total_tax = $base_total_tax_inc - $base_total_tax_exc;

        if ($total_tax < 0) {
            $total_tax = 0;
        }

        $currency = new Currency($this->id_currency);

        $products = $this->getProducts($refresh);

        foreach ($products as $key => &$product) {
            $product['price_without_quantity_discount'] = Product::getPriceStatic(
                $product['id_product'],
                !Product::getTaxCalculationMethod(),
                $product['id_product_attribute'],
                6,
                null,
                false,
                false
            );

            if ($product['reduction_type'] == 'amount') {
                $reduction = (!Product::getTaxCalculationMethod() ? (float)$product['price_wt'] : (float)$product['price']) - (float)$product['price_without_quantity_discount'];
                $product['reduction_formatted'] = Tools::displayPrice($reduction);
            }
        }

        $gift_products = array();
        $cart_rules = $this->getCartRules();
        $total_shipping = $this->getTotalShippingCost();
        $total_shipping_tax_exc = $this->getTotalShippingCost(null, false);
        $total_products_wt = $this->getOrderTotal(true, Cart::ONLY_PRODUCTS);
        $total_products = $this->getOrderTotal(false, Cart::ONLY_PRODUCTS);
        $total_discounts = $this->getOrderTotal(true, Cart::ONLY_DISCOUNTS);
        $total_discounts_tax_exc = $this->getOrderTotal(false, Cart::ONLY_DISCOUNTS);

        // The cart content is altered for display
        foreach ($cart_rules as &$cart_rule) {
            // If the cart rule is automatic (wihtout any code) and include free shipping, it should not be displayed as a cart rule but only set the shipping cost to 0
            if ($cart_rule['free_shipping'] && (empty($cart_rule['code']) || preg_match('/^'.CartRule::BO_ORDER_CODE_PREFIX.'[0-9]+/', $cart_rule['code']))) {

                $cart_rule['value_real'] -= $total_shipping;
                $cart_rule['value_tax_exc'] -= $total_shipping_tax_exc;
                $cart_rule['value_real'] = Tools::ps_round($cart_rule['value_real'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
                $cart_rule['value_tax_exc'] = Tools::ps_round($cart_rule['value_tax_exc'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
                if ($total_discounts > $cart_rule['value_real']) {
                    $total_discounts -= $total_shipping;
                }
                if ($total_discounts_tax_exc > $cart_rule['value_tax_exc']) {
                    $total_discounts_tax_exc -= $total_shipping_tax_exc;
                }

                // Update total shipping
                $total_shipping = 0;
                $total_shipping_tax_exc = 0;
            }

            if ($cart_rule['gift_product']) {
                foreach ($products as $key => &$product) {
                    if (empty($product['gift']) && $product['id_product'] == $cart_rule['gift_product'] && $product['id_product_attribute'] == $cart_rule['gift_product_attribute']) {
                        // Update total products
                        $total_products_wt = Tools::ps_round($total_products_wt - $product['price_wt'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
                        $total_products = Tools::ps_round($total_products - $product['price'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);

                        // Update total discounts
                        $total_discounts = Tools::ps_round($total_discounts - $product['price_wt'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
                        $total_discounts_tax_exc = Tools::ps_round($total_discounts_tax_exc - $product['price'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);

                        // Update cart rule value
                        $cart_rule['value_real'] = Tools::ps_round($cart_rule['value_real'] - $product['price_wt'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
                        $cart_rule['value_tax_exc'] = Tools::ps_round($cart_rule['value_tax_exc'] - $product['price'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);

                        // Update product quantity
                        $product['total_wt'] = Tools::ps_round($product['total_wt'] - $product['price_wt'], (int)$currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
                        $product['total'] = Tools::ps_round($product['total'] - $product['price'], (int)$currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
                        $product['cart_quantity']--;

                        if (!$product['cart_quantity']) {
                            unset($products[$key]);
                        }

                        // Add a new product line
                        $gift_product = $product;
                        $gift_product['cart_quantity'] = 1;
                        $gift_product['price'] = 0;
                        $gift_product['price_wt'] = 0;
                        $gift_product['total_wt'] = 0;
                        $gift_product['total'] = 0;
                        $gift_product['gift'] = true;
                        $gift_products[] = $gift_product;

                        break; // One gift product per cart rule
                    }
                }
            }
        }

        foreach ($cart_rules as $key => &$cart_rule) {
            if (((float)$cart_rule['value_real'] == 0 && (int)$cart_rule['free_shipping'] == 0)) {
                unset($cart_rules[$key]);
            }
        }

        $summary = array(
            'delivery' => $delivery,
            'delivery_state' => State::getNameById($delivery->id_state),
            'invoice' => $invoice,
            'invoice_state' => State::getNameById($invoice->id_state),
            'formattedAddresses' => $formatted_addresses,
            'products' => array_values($products),
            'gift_products' => $gift_products,
            'discounts' => array_values($cart_rules),
            'is_virtual_cart' => (int)$this->isVirtualCart(),
            'total_discounts' => $total_discounts,
            'total_discounts_tax_exc' => $total_discounts_tax_exc,
            'total_wrapping' => $this->getOrderTotal(true, Cart::ONLY_WRAPPING),
            'total_wrapping_tax_exc' => $this->getOrderTotal(false, Cart::ONLY_WRAPPING),
            'total_shipping' => $total_shipping,
            'total_shipping_tax_exc' => $total_shipping_tax_exc,
            'total_products_wt' => $total_products_wt,
            'total_products' => $total_products,
            'total_price' => $base_total_tax_inc,
            'total_tax' => $total_tax,
            'total_price_without_tax' => $base_total_tax_exc,
            'is_multi_address_delivery' => $this->isMultiAddressDelivery() || ((int)Tools::getValue('multi-shipping') == 1),
            'free_ship' =>!$total_shipping && !count($this->getDeliveryAddressesWithoutCarriers(true, $errors)),
            'carrier' => new Carrier($this->id_carrier, $id_lang),
        );

        $hook = Hook::exec('actionCartSummary', $summary, null, true);
        if (is_array($hook)) {
            $summary = array_merge($summary, array_shift($hook));
        }

        return $summary;
    }

    public function checkQuantities($return_product = false)
    {
        if (Configuration::get('PS_CATALOG_MODE') && !defined('_PS_ADMIN_DIR_')) {
            return false;
        }

        foreach ($this->getProducts() as $product) {
            if (!$this->allow_seperated_package && !$product['allow_oosp'] && StockAvailable::dependsOnStock($product['id_product']) &&
                $product['advanced_stock_management'] && (bool)Context::getContext()->customer->isLogged() && ($delivery = $this->getDeliveryOption()) && !empty($delivery)) {
                $product['stock_quantity'] = StockManager::getStockByCarrier((int)$product['id_product'], (int)$product['id_product_attribute'], $delivery);
            }
            if (!$product['active'] || !$product['available_for_order']
                || (!$product['allow_oosp'] && $product['stock_quantity'] < $product['cart_quantity'])) {
                return $return_product ? $product : false;
            }
        }

        return true;
    }

    public function checkProductsAccess()
    {
        if (Configuration::get('PS_CATALOG_MODE')) {
            return true;
        }

        foreach ($this->getProducts() as $product) {
            if (!Product::checkAccessStatic($product['id_product'], $this->id_customer)) {
                return $product['id_product'];
            }
        }

        return false;
    }


    public static function lastNoneOrderedCart($id_customer)
    {
        $sql = 'SELECT c.`id_cart`
                FROM '._DB_PREFIX_.'cart c
                WHERE NOT EXISTS (SELECT 1 FROM '._DB_PREFIX_.'orders o WHERE o.`id_cart` = c.`id_cart`
                                    AND o.`id_customer` = '.(int)$id_customer.')
                AND c.`id_customer` = '.(int)$id_customer.'
                    '.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'c').'
                ORDER BY c.`date_upd` DESC';

        if (!$id_cart = Db::getInstance()->getValue($sql)) {
            return false;
        }

        return (int)$id_cart;
    }

    /**
    * Check if cart contains only virtual products
    *
    * @return bool true if is a virtual cart or false
    */
    public function isVirtualCart($strict = false)
    {
        if (!ProductDownload::isFeatureActive()) {
            return false;
        }

        if (!isset(self::$_isVirtualCart[$this->id])) {
            $products = $this->getProducts();
            if (!count($products)) {
                return false;
            }

            $is_virtual = 1;
            foreach ($products as $product) {
                if (empty($product['is_virtual'])) {
                    $is_virtual = 0;
                }
            }
            self::$_isVirtualCart[$this->id] = (int)$is_virtual;
        }

        return self::$_isVirtualCart[$this->id];
    }

    /**
     * Build cart object from provided id_order
     *
     * @param int $id_order
     * @return Cart|bool
     */
    public static function getCartByOrderId($id_order)
    {
        if ($id_cart = Cart::getCartIdByOrderId($id_order)) {
            return new Cart((int)$id_cart);
        }

        return false;
    }

    public static function getCartIdByOrderId($id_order)
    {
        $result = Db::getInstance()->getRow('SELECT `id_cart` FROM '._DB_PREFIX_.'orders WHERE `id_order` = '.(int)$id_order);
        if (!$result || empty($result) || !array_key_exists('id_cart', $result)) {
            return false;
        }
        return $result['id_cart'];
    }

    /**
     * Add customer's text
     *
     * @params int $id_product
     * @params int $index
     * @params int $type
     * @params string $textValue
     *
     * @return bool Always true
     */
    public function addTextFieldToProduct($id_product, $index, $type, $text_value)
    {
        return $this->_addCustomization($id_product, 0, $index, $type, $text_value, 0);
    }

    /**
     * Add customer's pictures
     *
     * @return bool Always true
     */
    public function addPictureToProduct($id_product, $index, $type, $file)
    {
        return $this->_addCustomization($id_product, 0, $index, $type, $file, 0);
    }

    /**
     * @deprecated 1.5.5.0
     * @param int $id_product
     * @param $index
     * @return bool
     */
    public function deletePictureToProduct($id_product, $index)
    {
        Tools::displayAsDeprecated();
        return $this->deleteCustomizationToProduct($id_product, 0);
    }

    /**
     * Remove a customer's customization
     *
     * @param int $id_product
     * @param int $index
     * @return bool
     */
    public function deleteCustomizationToProduct($id_product, $index)
    {
        $result = true;

        $cust_data = Db::getInstance()->getRow('
            SELECT cu.`id_customization`, cd.`index`, cd.`value`, cd.`type` FROM `'._DB_PREFIX_.'customization` cu
            LEFT JOIN `'._DB_PREFIX_.'customized_data` cd
            ON cu.`id_customization` = cd.`id_customization`
            WHERE cu.`id_cart` = '.(int)$this->id.'
            AND cu.`id_product` = '.(int)$id_product.'
            AND `index` = '.(int)$index.'
            AND `in_cart` = 0'
        );

        // Delete customization picture if necessary
        if ($cust_data['type'] == 0) {
            $result &= (@unlink(_PS_UPLOAD_DIR_.$cust_data['value']) && @unlink(_PS_UPLOAD_DIR_.$cust_data['value'].'_small'));
        }

        $result &= Db::getInstance()->execute('DELETE
            FROM `'._DB_PREFIX_.'customized_data`
            WHERE `id_customization` = '.(int)$cust_data['id_customization'].'
            AND `index` = '.(int)$index
        );
        return $result;
    }

    /**
     * Return custom pictures in this cart for a specified product
     *
     * @param int $id_product
     * @param int $type only return customization of this type
     * @param bool $not_in_cart only return customizations that are not in cart already
     * @return array result rows
     */
    public function getProductCustomization($id_product, $type = null, $not_in_cart = false)
    {
        if (!Customization::isFeatureActive()) {
            return array();
        }

        $result = Db::getInstance()->executeS('
            SELECT cu.id_customization, cd.index, cd.value, cd.type, cu.in_cart, cu.quantity
            FROM `'._DB_PREFIX_.'customization` cu
            LEFT JOIN `'._DB_PREFIX_.'customized_data` cd ON (cu.`id_customization` = cd.`id_customization`)
            WHERE cu.id_cart = '.(int)$this->id.'
            AND cu.id_product = '.(int)$id_product.
            ($type === Product::CUSTOMIZE_FILE ? ' AND type = '.(int)Product::CUSTOMIZE_FILE : '').
            ($type === Product::CUSTOMIZE_TEXTFIELD ? ' AND type = '.(int)Product::CUSTOMIZE_TEXTFIELD : '').
            ($not_in_cart ? ' AND in_cart = 0' : '')
        );
        return $result;
    }

    public static function getCustomerCarts($id_customer, $with_order = true)
    {
        return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
        SELECT *
        FROM '._DB_PREFIX_.'cart c
        WHERE c.`id_customer` = '.(int)$id_customer.'
        '.(!$with_order ? 'AND NOT EXISTS (SELECT 1 FROM '._DB_PREFIX_.'orders o WHERE o.`id_cart` = c.`id_cart`)' : '').'
        ORDER BY c.`date_add` DESC');
    }

    public static function replaceZeroByShopName($echo, $tr)
    {
        return ($echo == '0' ? Carrier::getCarrierNameFromShopName() : $echo);
    }

    public function duplicate()
    {
        if (!Validate::isLoadedObject($this)) {
            return false;
        }

        $cart = new Cart($this->id);
        $cart->id = null;
        $cart->id_shop = $this->id_shop;
        $cart->id_shop_group = $this->id_shop_group;

        if (!Customer::customerHasAddress((int)$cart->id_customer, (int)$cart->id_address_delivery)) {
            $cart->id_address_delivery = (int)Address::getFirstCustomerAddressId((int)$cart->id_customer);
        }

        if (!Customer::customerHasAddress((int)$cart->id_customer, (int)$cart->id_address_invoice)) {
            $cart->id_address_invoice = (int)Address::getFirstCustomerAddressId((int)$cart->id_customer);
        }

        if ($cart->id_customer) {
            $cart->secure_key = Cart::$_customer->secure_key;
        }

        $cart->add();

        if (!Validate::isLoadedObject($cart)) {
            return false;
        }

        $success = true;
        $products = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT * FROM `'._DB_PREFIX_.'cart_product` WHERE `id_cart` = '.(int)$this->id);

        $product_gift = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT cr.`gift_product`, cr.`gift_product_attribute` FROM `'._DB_PREFIX_.'cart_rule` cr LEFT JOIN `'._DB_PREFIX_.'order_cart_rule` ocr ON (ocr.`id_order` = '.(int)$this->id.') WHERE ocr.`id_cart_rule` = cr.`id_cart_rule`');

        $id_address_delivery = Configuration::get('PS_ALLOW_MULTISHIPPING') ? $cart->id_address_delivery : 0;

        foreach ($products as $product) {
            if ($id_address_delivery) {
                if (Customer::customerHasAddress((int)$cart->id_customer, $product['id_address_delivery'])) {
                    $id_address_delivery = $product['id_address_delivery'];
                }
            }

            foreach ($product_gift as $gift) {
                if (isset($gift['gift_product']) && isset($gift['gift_product_attribute']) && (int)$gift['gift_product'] == (int)$product['id_product'] && (int)$gift['gift_product_attribute'] == (int)$product['id_product_attribute']) {
                    $product['quantity'] = (float)$product['quantity'] - 1;
                }
            }

            $success &= $cart->updateQty(
                (float)$product['quantity'],
                (int)$product['id_product'],
                (int)$product['id_product_attribute'],
                null,
                'up',
                (int)$id_address_delivery,
                new Shop((int)$cart->id_shop),
                false
            );
        }

        // Customized products
        $customs = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
            SELECT *
            FROM '._DB_PREFIX_.'customization c
            LEFT JOIN '._DB_PREFIX_.'customized_data cd ON cd.id_customization = c.id_customization
            WHERE c.id_cart = '.(int)$this->id
        );

        // Get datas from customization table
        $customs_by_id = array();
        foreach ($customs as $custom) {
            if (!isset($customs_by_id[$custom['id_customization']])) {
                $customs_by_id[$custom['id_customization']] = array(
                    'id_product_attribute' => $custom['id_product_attribute'],
                    'id_product' => $custom['id_product'],
                    'quantity' => $custom['quantity']
                );
            }
        }

        // Insert new customizations
        $custom_ids = array();
        foreach ($customs_by_id as $customization_id => $val) {
            Db::getInstance()->execute('
                INSERT INTO `'._DB_PREFIX_.'customization` (id_cart, id_product_attribute, id_product, `id_address_delivery`, quantity, `quantity_refunded`, `quantity_returned`, `in_cart`)
                VALUES('.(int)$cart->id.', '.(int)$val['id_product_attribute'].', '.(int)$val['id_product'].', '.(int)$id_address_delivery.', '.(int)$val['quantity'].', 0, 0, 1)'
            );
            $custom_ids[$customization_id] = Db::getInstance(_PS_USE_SQL_SLAVE_)->Insert_ID();
        }

        // Insert customized_data
        if (count($customs)) {
            $first = true;
            $sql_custom_data = 'INSERT INTO '._DB_PREFIX_.'customized_data (`id_customization`, `type`, `index`, `value`) VALUES ';
            foreach ($customs as $custom) {
                if (!$first) {
                    $sql_custom_data .= ',';
                } else {
                    $first = false;
                }

                $customized_value = $custom['value'];

                if ((int)$custom['type'] == 0) {
                    $customized_value = md5(uniqid(rand(), true));
                    Tools::copy(_PS_UPLOAD_DIR_.$custom['value'], _PS_UPLOAD_DIR_.$customized_value);
                    Tools::copy(_PS_UPLOAD_DIR_.$custom['value'].'_small', _PS_UPLOAD_DIR_.$customized_value.'_small');
                }

                $sql_custom_data .= '('.(int)$custom_ids[$custom['id_customization']].', '.(int)$custom['type'].', '.
                    (int)$custom['index'].', \''.pSQL($customized_value).'\')';
            }
            Db::getInstance()->execute($sql_custom_data);
        }

        return array('cart' => $cart, 'success' => $success);
    }

    public function getWsCartRows()
    {
        return Db::getInstance()->executeS('
            SELECT id_product, id_product_attribute, quantity, id_address_delivery
            FROM `'._DB_PREFIX_.'cart_product`
            WHERE id_cart = '.(int)$this->id.' AND id_shop = '.(int)Context::getContext()->shop->id
        );
    }

    public function setWsCartRows($values)
    {
        if ($this->deleteAssociations()) {
            $query = 'INSERT INTO `'._DB_PREFIX_.'cart_product`(`id_cart`, `id_product`, `id_product_attribute`, `id_address_delivery`, `quantity`, `date_add`, `id_shop`) VALUES ';

            foreach ($values as $value) {
                $query .= '('.(int)$this->id.', '.(int)$value['id_product'].', '.
                    (isset($value['id_product_attribute']) ? (int)$value['id_product_attribute'] : 'NULL').', '.
                    (isset($value['id_address_delivery']) ? (int)$value['id_address_delivery'] : 0).', '.
                    (float)$value['quantity'].', NOW(), '.(int)Context::getContext()->shop->id.'),';
            }

            Db::getInstance()->execute(rtrim($query, ','));
        }

        return true;
    }

    public function setProductAddressDelivery($id_product, $id_product_attribute, $old_id_address_delivery, $new_id_address_delivery)
    {
        // Check address is linked with the customer
        if (!Customer::customerHasAddress(Context::getContext()->customer->id, $new_id_address_delivery)) {
            return false;
        }

        if ($new_id_address_delivery == $old_id_address_delivery) {
            return false;
        }

        // Checking if the product with the old address delivery exists
        $sql = new DbQuery();
        $sql->select('count(*)');
        $sql->from('cart_product', 'cp');
        $sql->where('id_product = '.(int)$id_product);
        $sql->where('id_product_attribute = '.(int)$id_product_attribute);
        $sql->where('id_address_delivery = '.(int)$old_id_address_delivery);
        $sql->where('id_cart = '.(int)$this->id);
        $result = Db::getInstance()->getValue($sql);

        if ($result == 0) {
            return false;
        }

        // Checking if there is no others similar products with this new address delivery
        $sql = new DbQuery();
        $sql->select('sum(quantity) as qty');
        $sql->from('cart_product', 'cp');
        $sql->where('id_product = '.(int)$id_product);
        $sql->where('id_product_attribute = '.(int)$id_product_attribute);
        $sql->where('id_address_delivery = '.(int)$new_id_address_delivery);
        $sql->where('id_cart = '.(int)$this->id);
        $result = Db::getInstance()->getValue($sql);

        // Removing similar products with this new address delivery
        $sql = 'DELETE FROM '._DB_PREFIX_.'cart_product
            WHERE id_product = '.(int)$id_product.'
            AND id_product_attribute = '.(int)$id_product_attribute.'
            AND id_address_delivery = '.(int)$new_id_address_delivery.'
            AND id_cart = '.(int)$this->id.'
            LIMIT 1';
        Db::getInstance()->execute($sql);

        // Changing the address
        $sql = 'UPDATE '._DB_PREFIX_.'cart_product
            SET `id_address_delivery` = '.(int)$new_id_address_delivery.',
            `quantity` = `quantity` + '.(int)$result.'
            WHERE id_product = '.(int)$id_product.'
            AND id_product_attribute = '.(int)$id_product_attribute.'
            AND id_address_delivery = '.(int)$old_id_address_delivery.'
            AND id_cart = '.(int)$this->id.'
            LIMIT 1';
        Db::getInstance()->execute($sql);

        // Changing the address of the customizations
        $sql = 'UPDATE '._DB_PREFIX_.'customization
            SET `id_address_delivery` = '.(int)$new_id_address_delivery.'
            WHERE id_product = '.(int)$id_product.'
            AND id_product_attribute = '.(int)$id_product_attribute.'
            AND id_address_delivery = '.(int)$old_id_address_delivery.'
            AND id_cart = '.(int)$this->id;
        Db::getInstance()->execute($sql);

        return true;
    }

    public function duplicateProduct($id_product, $id_product_attribute, $id_address_delivery,
        $new_id_address_delivery, $quantity = 1, $keep_quantity = false)
    {
        // Check address is linked with the customer
        if (!Customer::customerHasAddress(Context::getContext()->customer->id, $new_id_address_delivery)) {
            return false;
        }

        // Checking the product do not exist with the new address
        $sql = new DbQuery();
        $sql->select('count(*)');
        $sql->from('cart_product', 'c');
        $sql->where('id_product = '.(int)$id_product);
        $sql->where('id_product_attribute = '.(int)$id_product_attribute);
        $sql->where('id_address_delivery = '.(int)$new_id_address_delivery);
        $sql->where('id_cart = '.(int)$this->id);
        $result = Db::getInstance()->getValue($sql);

        if ($result > 0) {
            return false;
        }

        // Duplicating cart_product line
        $sql = 'INSERT INTO '._DB_PREFIX_.'cart_product
            (`id_cart`, `id_product`, `id_shop`, `id_product_attribute`, `quantity`, `date_add`, `id_address_delivery`)
            values(
                '.(int)$this->id.',
                '.(int)$id_product.',
                '.(int)$this->id_shop.',
                '.(int)$id_product_attribute.',
                '.(float)$quantity.',
                NOW(),
                '.(int)$new_id_address_delivery.')';

        Db::getInstance()->execute($sql);

        if (!$keep_quantity) {
            $sql = new DbQuery();
            $sql->select('quantity');
            $sql->from('cart_product', 'c');
            $sql->where('id_product = '.(int)$id_product);
            $sql->where('id_product_attribute = '.(int)$id_product_attribute);
            $sql->where('id_address_delivery = '.(int)$id_address_delivery);
            $sql->where('id_cart = '.(int)$this->id);
            $duplicatedQuantity = Db::getInstance()->getValue($sql);

            if ($duplicatedQuantity > $quantity) {
                $sql = 'UPDATE '._DB_PREFIX_.'cart_product
                    SET `quantity` = `quantity` - '.(float)$quantity.'
                    WHERE id_cart = '.(int)$this->id.'
                    AND id_product = '.(int)$id_product.'
                    AND id_shop = '.(int)$this->id_shop.'
                    AND id_product_attribute = '.(int)$id_product_attribute.'
                    AND id_address_delivery = '.(int)$id_address_delivery;
                Db::getInstance()->execute($sql);
            }
        }

        // Checking if there is customizations
        $sql = new DbQuery();
        $sql->select('*');
        $sql->from('customization', 'c');
        $sql->where('id_product = '.(int)$id_product);
        $sql->where('id_product_attribute = '.(int)$id_product_attribute);
        $sql->where('id_address_delivery = '.(int)$id_address_delivery);
        $sql->where('id_cart = '.(int)$this->id);
        $results = Db::getInstance()->executeS($sql);

        foreach ($results as $customization) {
            // Duplicate customization
            $sql = 'INSERT INTO '._DB_PREFIX_.'customization
                (`id_product_attribute`, `id_address_delivery`, `id_cart`, `id_product`, `quantity`, `in_cart`)
                VALUES (
                    '.(int)$customization['id_product_attribute'].',
                    '.(int)$new_id_address_delivery.',
                    '.(int)$customization['id_cart'].',
                    '.(int)$customization['id_product'].',
                    '.(float)$quantity.',
                    '.(int)$customization['in_cart'].')';

            Db::getInstance()->execute($sql);

            // Save last insert ID before doing another query
            $last_id = (int)Db::getInstance()->Insert_ID();

            // Get data from duplicated customizations
            $sql = new DbQuery();
            $sql->select('`type`, `index`, `value`');
            $sql->from('customized_data');
            $sql->where('id_customization = '.$customization['id_customization']);
            $last_row = Db::getInstance()->getRow($sql);

            // Insert new copied data with new customization ID into customized_data table
            $last_row['id_customization'] = $last_id;
            Db::getInstance()->insert('customized_data', $last_row);
        }

        $customization_count = count($results);
        if ($customization_count > 0) {
            $sql = 'UPDATE '._DB_PREFIX_.'cart_product
                SET `quantity` = `quantity` + '.(int)$customization_count * $quantity.'
                WHERE id_cart = '.(int)$this->id.'
                AND id_product = '.(int)$id_product.'
                AND id_shop = '.(int)$this->id_shop.'
                AND id_product_attribute = '.(int)$id_product_attribute.'
                AND id_address_delivery = '.(int)$new_id_address_delivery;
            Db::getInstance()->execute($sql);
        }

        return true;
    }

    /**
     * Update products cart address delivery with the address delivery of the cart
     */
    public function setNoMultishipping()
    {
        $emptyCache = false;
        if (Configuration::get('PS_ALLOW_MULTISHIPPING')) {
            // Upgrading quantities
            $sql = 'SELECT sum(`quantity`) as quantity, id_product, id_product_attribute, count(*) as count
                    FROM `'._DB_PREFIX_.'cart_product`
                    WHERE `id_cart` = '.(int)$this->id.'
                        AND `id_shop` = '.(int)$this->id_shop.'
                    GROUP BY id_product, id_product_attribute
                    HAVING count > 1';

            foreach (Db::getInstance()->executeS($sql) as $product) {
                $sql = 'UPDATE `'._DB_PREFIX_.'cart_product`
                    SET `quantity` = '.$product['quantity'].'
                    WHERE  `id_cart` = '.(int)$this->id.'
                        AND `id_shop` = '.(int)$this->id_shop.'
                        AND id_product = '.$product['id_product'].'
                        AND id_product_attribute = '.$product['id_product_attribute'];
                if (Db::getInstance()->execute($sql)) {
                    $emptyCache = true;
                }
            }

            // Merging multiple lines
            $sql = 'DELETE cp1
                FROM `'._DB_PREFIX_.'cart_product` cp1
                    INNER JOIN `'._DB_PREFIX_.'cart_product` cp2
                    ON (
                        (cp1.id_cart = cp2.id_cart)
                        AND (cp1.id_product = cp2.id_product)
                        AND (cp1.id_product_attribute = cp2.id_product_attribute)
                        AND (cp1.id_address_delivery <> cp2.id_address_delivery)
                        AND (cp1.date_add > cp2.date_add)
                    )';
            Db::getInstance()->execute($sql);
        }

        // Update delivery address for each product line
        $sql = 'UPDATE `'._DB_PREFIX_.'cart_product`
        SET `id_address_delivery` = (
            SELECT `id_address_delivery` FROM `'._DB_PREFIX_.'cart`
            WHERE `id_cart` = '.(int)$this->id.' AND `id_shop` = '.(int)$this->id_shop.'
        )
        WHERE `id_cart` = '.(int)$this->id.'
        '.(Configuration::get('PS_ALLOW_MULTISHIPPING') ? ' AND `id_shop` = '.(int)$this->id_shop : '');

        $cache_id = 'Cart::setNoMultishipping'.(int)$this->id.'-'.(int)$this->id_shop.((isset($this->id_address_delivery) && $this->id_address_delivery) ? '-'.(int)$this->id_address_delivery : '');
        if (!Cache::isStored($cache_id)) {
            if ($result = (bool)Db::getInstance()->execute($sql)) {
                $emptyCache = true;
            }
            Cache::store($cache_id, $result);
        }

        if (Customization::isFeatureActive()) {
            Db::getInstance()->execute('
            UPDATE `'._DB_PREFIX_.'customization`
            SET `id_address_delivery` = (
                SELECT `id_address_delivery` FROM `'._DB_PREFIX_.'cart`
                WHERE `id_cart` = '.(int)$this->id.'
            )
            WHERE `id_cart` = '.(int)$this->id);
        }

        if ($emptyCache) {
            $this->_products = null;
        }
    }

    /**
     * Set an address to all products on the cart without address delivery
     */
    public function autosetProductAddress()
    {
        $id_address_delivery = 0;
        // Get the main address of the customer
        if ((int)$this->id_address_delivery > 0) {
            $id_address_delivery = (int)$this->id_address_delivery;
        } else {
            $id_address_delivery = (int)Address::getFirstCustomerAddressId(Context::getContext()->customer->id);
        }

        if (!$id_address_delivery) {
            return;
        }

        // Update
        $sql = 'UPDATE `'._DB_PREFIX_.'cart_product`
            SET `id_address_delivery` = '.(int)$id_address_delivery.'
            WHERE `id_cart` = '.(int)$this->id.'
                AND (`id_address_delivery` = 0 OR `id_address_delivery` IS NULL)
                AND `id_shop` = '.(int)$this->id_shop;
        Db::getInstance()->execute($sql);

        $sql = 'UPDATE `'._DB_PREFIX_.'customization`
            SET `id_address_delivery` = '.(int)$id_address_delivery.'
            WHERE `id_cart` = '.(int)$this->id.'
                AND (`id_address_delivery` = 0 OR `id_address_delivery` IS NULL)';

        Db::getInstance()->execute($sql);
    }

    public function deleteAssociations()
    {
        return (Db::getInstance()->execute('
                DELETE FROM `'._DB_PREFIX_.'cart_product`
                WHERE `id_cart` = '.(int)$this->id) !== false);
    }

    /**
     * isGuestCartByCartId
     *
     * @param int $id_cart
     * @return bool true if cart has been made by a guest customer
     */
    public static function isGuestCartByCartId($id_cart)
    {
        if (!(int)$id_cart) {
            return false;
        }
        return (bool)Db::getInstance()->getValue('
            SELECT `is_guest`
            FROM `'._DB_PREFIX_.'customer` cu
            LEFT JOIN `'._DB_PREFIX_.'cart` ca ON (ca.`id_customer` = cu.`id_customer`)
            WHERE ca.`id_cart` = '.(int)$id_cart);
    }

    /**
     * isCarrierInRange
     *
     * Check if the specified carrier is in range
     *
     * @id_carrier int
     * @id_zone int
     */
    public function isCarrierInRange($id_carrier, $id_zone)
    {
        $carrier = new Carrier((int)$id_carrier, Configuration::get('PS_LANG_DEFAULT'));
        $shipping_method = $carrier->getShippingMethod();
        if (!$carrier->range_behavior) {
            return true;
        }

        if ($shipping_method == Carrier::SHIPPING_METHOD_FREE) {
            return true;
        }

        $check_delivery_price_by_weight = Carrier::checkDeliveryPriceByWeight(
            (int)$id_carrier,
            $this->getTotalWeight(),
            $id_zone
        );
        if ($shipping_method == Carrier::SHIPPING_METHOD_WEIGHT && $check_delivery_price_by_weight) {
            return true;
        }

        $check_delivery_price_by_price = Carrier::checkDeliveryPriceByPrice(
            (int)$id_carrier,
            $this->getOrderTotal(
                true,
                Cart::BOTH_WITHOUT_SHIPPING
            ),
            $id_zone,
            (int)$this->id_currency
        );
        if ($shipping_method == Carrier::SHIPPING_METHOD_PRICE && $check_delivery_price_by_price) {
            return true;
        }

        return false;
    }

    /**
     * @param bool $ignore_virtual Ignore virtual product
     * @param bool $exclusive If true, the validation is exclusive : it must be present product in stock and out of stock
     * @since 1.5.0
     *
     * @return bool false is some products from the cart are out of stock
     */
    public function isAllProductsInStock($ignore_virtual = false, $exclusive = false)
    {
        $product_out_of_stock = 0;
        $product_in_stock = 0;
        foreach ($this->getProducts() as $product) {
            if (!$exclusive) {
                if (((float)$product['quantity_available'] - (float)$product['cart_quantity']) < 0
                    && (!$ignore_virtual || !$product['is_virtual'])) {
                    return false;
                }
            } else {
                if ((float)$product['quantity_available'] <= 0
                    && (!$ignore_virtual || !$product['is_virtual'])) {
                    $product_out_of_stock++;
                }
                if ((float)$product['quantity_available'] > 0
                    && (!$ignore_virtual || !$product['is_virtual'])) {
                    $product_in_stock++;
                }

                if ($product_in_stock > 0 && $product_out_of_stock > 0) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     *
     * Execute hook displayCarrierList (extraCarrier) and merge theme to the $array
     * @param array $array
     */
    public static function addExtraCarriers(&$array)
    {
        $first = true;
        $hook_extracarrier_addr = array();
        foreach (Context::getContext()->cart->getAddressCollection() as $address) {
            $hook = Hook::exec('displayCarrierList', array('address' => $address));
            $hook_extracarrier_addr[$address->id] = $hook;

            if ($first) {
                $array = array_merge(
                    $array,
                    array('HOOK_EXTRACARRIER' => $hook)
                );
                $first = false;
            }
            $array = array_merge(
                $array,
                array('HOOK_EXTRACARRIER_ADDR' => $hook_extracarrier_addr)
            );
        }
    }

    /**
     * Get all the ids of the delivery addresses without carriers
     *
     * @param bool $return_collection Return a collection
     * @param array &$error contain an error message if an error occurs
     * @return array Array of address id or of address object
     */
    public function getDeliveryAddressesWithoutCarriers($return_collection = false, &$error = array())
    {
        $addresses_without_carriers = array();
        foreach ($this->getProducts() as $product) {
            if (!in_array($product['id_address_delivery'], $addresses_without_carriers)
                && !count(Carrier::getAvailableCarrierList(new Product($product['id_product']), null, $product['id_address_delivery'], null, null, $error))) {
                $addresses_without_carriers[] = $product['id_address_delivery'];
            }
        }
        if (!$return_collection) {
            return $addresses_without_carriers;
        } else {
            $addresses_instance_without_carriers = array();
            foreach ($addresses_without_carriers as $id_address) {
                $addresses_instance_without_carriers[] = new Address($id_address);
            }
            return $addresses_instance_without_carriers;
        }
    }
}

 

Link to comment
Share on other sites

  • 2 months later...

Hello, 

 

I must admit that I did those modifications last year on Prestashop 1.6 and it worked. Now I am cooking on Prestashop 1.7 I try to make those changes, however I am blocked. Can't move on, because products are not added to cart. After turning Debug ON, No errors are shown. I checked all files 3 - 4 time again. And I really cant find the problem. I have a feeling it is in JS file, or I might be wrong... 

When clicking on add to cart, the popup window shows "Your product was added", however its not adding to cart anything.

this is the link of my current working version https://goo.gl/MgLjUo

 

Link to comment
Share on other sites

  • 3 weeks later...

hi

i try it for 1.6.1.18

i just want to add 0.5

1 , 1.5 , 2 , 2.5 , 3 , 3.5 , ....

1) Change the database quantity related fields type to DECIMAL (17,2). how?

you mean phpmyadmin,database?

i changed it to 17.2,but it repaleced whit 17

 

ps_product.png

Link to comment
Share on other sites

On 12/4/2018 at 3:57 AM, androkot33 said:

Strange. Even you trying to change 'minimal_quantity' column nor `available_date`.  My table don't have  "Adjust Privileges" column as on your screenshot. Try to un-check it.

that was on local with wamp server

it work on host and i will test it again

Link to comment
Share on other sites

On 12/4/2018 at 3:57 AM, androkot33 said:

 

thx man i did it but need some other changes

001.thumb.png.899ab543d31c673c9c2b9c8ceb2a6ece.png

002.thumb.png.2faff9af94a6308f213db8c89600dcef.png

003.thumb.png.0ef5575295866f4d877030798f5b90a9.png

1) as u see in shop cart prices are correct but + - add + - 1

2) and sums of 3.5+1.5+3.5 it's not correct and show 8 instead of 8.5

for + - 0.5 i changed cart-summary.js

function upQuantity(id, qty)
{
	if (typeof(qty) == 'undefined' || !qty)
		qty = 0.5;
	var customizationId = 0;
	var productId = 0;
	var productAttributeId = 0;
	var id_address_delivery = 0;
	var ids = 0;
	ids = id.split('_');
	productId = parseFloat(ids[0]);
	if (typeof(ids[1]) !== 'undefined')
		productAttributeId = parseFloat(ids[1]);
	if (typeof(ids[2]) !== 'undefined' && ids[2] !== 'nocustom')
		customizationId = parseFloat(ids[2]);
	if (typeof(ids[3]) !== 'undefined')
		id_address_delivery = parseFloat(ids[3]);

and

function downQuantity(id, qty)
{
	var val = $('input[name=quantity_' + id + ']').val();
	var newVal = val;
	if(typeof(qty) == 'undefined' || !qty)
	{
		qty = 0.5;
		newVal = val - 1;
	}
	else if (qty < 0)
		qty = -qty;

	var customizationId = 0;
	var productId = 0;
	var productAttributeId = 0;
	var id_address_delivery = 0;
	var ids = 0;

	ids = id.split('_');
	productId = parseFloat(ids[0]);
	if (typeof(ids[1]) !== 'undefined')
		productAttributeId = parseFloat(ids[1]);
	if (typeof(ids[2]) !== 'undefined' && ids[2] !== 'nocustom')
		customizationId = parseFloat(ids[2]);
	if (typeof(ids[3]) !== 'undefined')
		id_address_delivery = parseFloat(ids[3]);

0.5 didn't work but 2 work fine (qty = 2;)

{ Strange, even i changed it back to qty = 1; still it is + - 2)

 

3) + - in product page

004.thumb.png.431e078a7496c2e3c30c563801fc2f57.pngfor key up

 

and down i changed product.tpl and added step="0.5"

					<div class="product_attributes clearfix">
						<!-- quantity wanted -->
						{if !$PS_CATALOG_MODE}
						<p id="quantity_wanted_p"{if (!$allow_oosp && $product->quantity <= 0) || !$product->available_for_order || $PS_CATALOG_MODE} style="display: none;"{/if}>
							<label for="quantity_wanted">{l s='Quantity'}</label>
							<input type="number" min="1" step="0.5" name="qty" id="quantity_wanted" class="text" value="{if isset($quantityBackup)}{$quantityBackup|floatval}{else}{if $product->minimal_quantity > 1}{$product->minimal_quantity}{else}1{/if}{/if}" />
							<a href="#" data-field-qty="qty" class="btn btn-default button-minus product_quantity_down">
								<span><i class="icon-minus"></i></span>
							</a>
							<a href="#" data-field-qty="qty" class="btn btn-default button-plus product_quantity_up">
								<span><i class="icon-plus"></i></span>
							</a>
							<span class="clearfix"></span>
						</p>
						{/if}
						<!-- minimal quantity wanted -->
						<p id="minimal_quantity_wanted_p"{if $product->minimal_quantity <= 1 || !$product->available_for_order || $PS_CATALOG_MODE} style="display: none;"{/if}>
							{l s='The minimum purchase order quantity for the product is'} <b id="minimal_quantity_label">{$product->minimal_quantity}</b>
						</p>

 

for + - keys in product.js

// The button to increment the product value
$(document).on('click', '.product_quantity_up', function(e){
	e.preventDefault();
	fieldName = $(this).data('field-qty');
	var currentVal = parseFloat($('input[name='+fieldName+']').val());
	if (!allowBuyWhenOutOfStock && quantityAvailable > 0)
		quantityAvailableT = quantityAvailable;
	else
		quantityAvailableT = 100000000;
	if (!isNaN(currentVal) && currentVal < quantityAvailableT)
		$('input[name='+fieldName+']').val(currentVal + 0.5).trigger('keyup');
	else
		$('input[name='+fieldName+']').val(quantityAvailableT);

	$('#quantity_wanted').change();
});
 // The button to decrement the product value
$(document).on('click', '.product_quantity_down', function(e){
	e.preventDefault();
	fieldName = $(this).data('field-qty');
	var currentVal = parseFloat($('input[name='+fieldName+']').val());
	 if (!isNaN(currentVal) && currentVal > 1 && currentVal > 0.5)
		$('input[name='+fieldName+']').val(currentVal - 0.5).trigger('keyup');
	else
		$('input[name='+fieldName+']').val(0.5);

	$('#quantity_wanted').change();
});

if (typeof minimalQuantity !== 'undefined' && minimalQuantity)
{
	checkMinimalQuantity();
	$(document).on('keyup', 'input[name=qty]', function(e){
		checkMinimalQuantity(minimalQuantity);
	});
}

 

I appreciate any help

Link to comment
Share on other sites

+ - problem in product page are done

product.tpl

					<div class="product_attributes clearfix">
						<!-- quantity wanted -->
						{if !$PS_CATALOG_MODE}

						{assign cat16 [['id_category' => 16]]}
						
						{if Product::idIsOnCategoryId($smarty.get.id_product, $cat16)}
						<p id="quantity_wanted_p"{if (!$allow_oosp && $product->quantity <= 0) || !$product->available_for_order || $PS_CATALOG_MODE} style="display: none;"{/if}>
						<label for="quantity_wanted">{l s='Quantity'}</label>
						<input type="text" min="1" name="qty" id="quantity_wanted" class="text" disabled="disabled" value="{if isset($quantityBackup)}{$quantityBackup|intval}{else}{if $product->minimal_quantity > 1}{$product->minimal_quantity}{else}1{/if}{/if}" />
                        <a href="#" data-field-qty="qty" class="btn btn-default button-minus product_quantity_down_1">
                        	<span><i class="icon-minus"></i></span>
                        </a>
                        <a href="#" data-field-qty="qty" class="btn btn-default button-minus product_quantity_up_1">
                         	<span><i class="icon-plus"></i></span>
							</a>
						</p>
						
						{else}
						<p id="quantity_wanted_p"{if (!$allow_oosp && $product->quantity <= 0) || !$product->available_for_order || $PS_CATALOG_MODE} style="display: none;"{/if}>
							<label for="quantity_wanted">{l s='Quantity'}</label>
							<input type="number" min="1"  name="qty" id="quantity_wanted" disabled="disabled" class="text" value="{if isset($quantityBackup)}{$quantityBackup|intval}{else}{if $product->minimal_quantity > 1}{$product->minimal_quantity}{else}1{/if}{/if}" />
							<a href="#" data-field-qty="qty" class="btn btn-default button-minus product_quantity_down_2">
								<span><i class="icon-minus"></i></span>
							</a>
							<a href="#" data-field-qty="qty" class="btn btn-default button-plus product_quantity_up_2">
								<span><i class="icon-plus"></i></span>
							</a>
							<span class="clearfix"></span>
						</p>	

						{/if}
						{/if}

 

product.js

// The button to increment the product value
$(document).on('click', '.product_quantity_up_1', function(e){
        e.preventDefault();
        fieldName = $(this).data('field-qty');
        var currentVal = parseInt($('input[name='+fieldName+']').val());
        if (!allowBuyWhenOutOfStock && quantityAvailable > 0)
                quantityAvailableT = quantityAvailable;
        else
                quantityAvailableT = 100000000;
        if (!isNaN(currentVal) && currentVal < quantityAvailableT)
                $('input[name='+fieldName+']').val(currentVal + 1).trigger('keyup');
        else
                $('input[name='+fieldName+']').val(quantityAvailableT);

        $('#quantity_wanted').change();
});
 // The button to decrement the product value
$(document).on('click', '.product_quantity_down_1', function(e){
        e.preventDefault();
        fieldName = $(this).data('field-qty');
        var currentVal = parseInt($('input[name='+fieldName+']').val());
        if (!isNaN(currentVal) && currentVal > 1 && currentVal > 1)
                $('input[name='+fieldName+']').val(currentVal - 1).trigger('keyup');
        else
                $('input[name='+fieldName+']').val(1);

        $('#quantity_wanted').change();    
});

// The button to increment the product value
$(document).on('click', '.product_quantity_up_2', function(e){
        e.preventDefault();
        fieldName = $(this).data('field-qty');
        var currentVal = parseFloat($('input[name='+fieldName+']').val());
        if (!allowBuyWhenOutOfStock && quantityAvailable > 0)
                quantityAvailableT = quantityAvailable;
        else
                quantityAvailableT = 100000000;
        if (!isNaN(currentVal) && currentVal < quantityAvailableT)
                $('input[name='+fieldName+']').val(currentVal + 0.5).trigger('keyup');
        else
                $('input[name='+fieldName+']').val(quantityAvailableT);

        $('#quantity_wanted').change();
});
 // The button to decrement the product value
$(document).on('click', '.product_quantity_down_2', function(e){
        e.preventDefault();
        fieldName = $(this).data('field-qty');
        var currentVal = parseFloat($('input[name='+fieldName+']').val());
        if (!isNaN(currentVal) && currentVal > 1 && currentVal > 0.5)
                $('input[name='+fieldName+']').val(currentVal - 0.5).trigger('keyup');
        else
                $('input[name='+fieldName+']').val(0.5);

        $('#quantity_wanted').change();    
});

 

Edited by lvlehdi (see edit history)
Link to comment
Share on other sites

13 hours ago, androkot33 said:

 

hey man

The ordering part works, customers can order a decimal quantity of products and the total price works fine!

But in my backoffice doesen't give a decimal quantity

any suggestion?

Link to comment
Share on other sites

  • 7 months later...
  • 7 months later...
  • 6 months 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...