wfreebird Posted February 26, 2011 Share Posted February 26, 2011 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 More sharing options...
otzy Posted February 26, 2011 Share Posted February 26, 2011 what if to use centimetres? Customers will order 150cm of fabric. Link to comment Share on other sites More sharing options...
wfreebird Posted February 26, 2011 Author Share Posted February 26, 2011 Dear Otzy,Unfortunately is changing it in to cm not an option because the prices in the shop have to be in metres.But thank you for your suggestion.Regards Werner Link to comment Share on other sites More sharing options...
Burhan BVK Posted February 26, 2011 Share Posted February 26, 2011 Why don't you enter the prices in cm? You can just modify the tpl files to display the price in metres. You will have to modify product-list.tpl and product.tpl and anywhere else you display products. Link to comment Share on other sites More sharing options...
wfreebird Posted March 5, 2011 Author Share Posted March 5, 2011 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 More sharing options...
otzy Posted March 5, 2011 Share Posted March 5, 2011 One more ideaadd 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 More sharing options...
Fangor Posted March 5, 2011 Share Posted March 5, 2011 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.php2. /cart.php3.classes/cart.php4.classes/product.php5.classes/order.php6.themes/prestashop/product.tpl 1 Link to comment Share on other sites More sharing options...
wfreebird Posted March 5, 2011 Author Share Posted March 5, 2011 Hi Fangor,Thank you very much.I will try this one and let you know if it works.With kind regards,Werner Link to comment Share on other sites More sharing options...
siomosp Posted April 6, 2011 Share Posted April 6, 2011 Hi Fangor,it seems that your solution is about 1.3 only.Have you any suggestions about 1.4Thank you,regards,Panagiotis Link to comment Share on other sites More sharing options...
coloboque Posted May 27, 2011 Share Posted May 27, 2011 I have same problem...but I use 1.4.0. Anybody who solved the problem please share thу solution :-) Link to comment Share on other sites More sharing options...
wfreebird Posted May 27, 2011 Author Share Posted May 27, 2011 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 More sharing options...
coloboque Posted May 27, 2011 Share Posted May 27, 2011 Oh thank you!! i'm so happy) Link to comment Share on other sites More sharing options...
siomosp Posted May 27, 2011 Share Posted May 27, 2011 Very nice,thank you wfreebird! Link to comment Share on other sites More sharing options...
wfreebird Posted May 30, 2011 Author Share Posted May 30, 2011 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_mvtAfter this you go to your prestashop folderNow 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.tplPlease 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 luckWith kind regards,Werner 1 Link to comment Share on other sites More sharing options...
Stanik Posted May 31, 2011 Share Posted May 31, 2011 Hi. This is what I was looking for. Does it really works with the newest version of Prestashop?Does it resolves the difference between a dot and a comma? Link to comment Share on other sites More sharing options...
wfreebird Posted May 31, 2011 Author Share Posted May 31, 2011 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.nlIt 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 More sharing options...
Jiri Kral Posted July 16, 2011 Share Posted July 16, 2011 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.jsa) 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()); } 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.phpfind 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 More sharing options...
sousmart Posted May 5, 2012 Share Posted May 5, 2012 Hi everybody, Can anyone inform if this solution runs with recent versions like 1.45 or 1.47??? Regards, Carlos Link to comment Share on other sites More sharing options...
noayak Posted May 9, 2012 Share Posted May 9, 2012 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 More sharing options...
sousmart Posted May 9, 2012 Share Posted May 9, 2012 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 More sharing options...
jalle007 Posted May 10, 2012 Share Posted May 10, 2012 Guys this is a basic and a must for one webshop CMS to have quantity in decimals hope someone from presta will work on it 1 Link to comment Share on other sites More sharing options...
wfreebird Posted June 16, 2012 Author Share Posted June 16, 2012 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 More sharing options...
psandmore Posted June 20, 2012 Share Posted June 20, 2012 See the http://psandmore.com This module allows you to do exactly what you want. Have price in meters and order 12,3m. And it has much much more. Link to comment Share on other sites More sharing options...
Celil Posted March 12, 2013 Share Posted March 12, 2013 How it works for Ps 1.5.2 ? Link to comment Share on other sites More sharing options...
psandmore Posted March 12, 2013 Share Posted March 12, 2013 It works for many versions including 1.5.2 and 1.5.3. See our site for all versions. Link to comment Share on other sites More sharing options...
Celil Posted March 12, 2013 Share Posted March 12, 2013 I dont want to use module. I wanna fix with changing codes. 1 Link to comment Share on other sites More sharing options...
ricardoferreira.net Posted March 20, 2013 Share Posted March 20, 2013 @Ceil you do not need a module for this, the solution @wfreebird gave, worked quite well for me in v1.5.3, thank you all. Link to comment Share on other sites More sharing options...
AndreasAnemyr Posted March 28, 2013 Share Posted March 28, 2013 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 More sharing options...
Inform-All Posted May 14, 2013 Share Posted May 14, 2013 I've tried Wfreebird's solution, dident work out for me on 1.5.4.1. So currently working on a fix for newer versions. Link to comment Share on other sites More sharing options...
Inform-All Posted May 15, 2013 Share Posted May 15, 2013 (edited) 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 Edited May 15, 2013 by Bolinga (see edit history) Link to comment Share on other sites More sharing options...
diosimmy Posted May 19, 2013 Share Posted May 19, 2013 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 1 Link to comment Share on other sites More sharing options...
Inform-All Posted May 19, 2013 Share Posted May 19, 2013 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. 1 Link to comment Share on other sites More sharing options...
tabutnas Posted May 20, 2013 Share Posted May 20, 2013 (edited) 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 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 May 20, 2013 by tabutnas (see edit history) Link to comment Share on other sites More sharing options...
Inform-All Posted May 22, 2013 Share Posted May 22, 2013 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. I keep thinking it's a database that need's some adaption.... But cant find it! Thank you in advance. 1 Link to comment Share on other sites More sharing options...
tabutnas Posted May 24, 2013 Share Posted May 24, 2013 (edited) 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 May 24, 2013 by tabutnas (see edit history) Link to comment Share on other sites More sharing options...
tabutnas Posted May 25, 2013 Share Posted May 25, 2013 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 2 Link to comment Share on other sites More sharing options...
Inform-All Posted June 6, 2013 Share Posted June 6, 2013 (edited) The ordering part works, customers can order a decimal quantity of products and the total price works fine! But in my backoffice & the invoice doesen't give a decimal quantity, although the price is correct. Help is needed.. Edited June 6, 2013 by Bolinga (see edit history) Link to comment Share on other sites More sharing options...
julcons Posted June 26, 2013 Share Posted June 26, 2013 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 More sharing options...
Inform-All Posted July 2, 2013 Share Posted July 2, 2013 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 More sharing options...
Inform-All Posted August 9, 2013 Share Posted August 9, 2013 So I recently found out that if customers want to order <1 total quantity of products the checkout cart remains empty. I started a new topic for this, please have a look at: http://www.prestashop.com/forums/topic/266743-minimal-total-product-quantity-in-cart/ Link to comment Share on other sites More sharing options...
eporedieis Posted August 23, 2013 Share Posted August 23, 2013 I've changed all these things...but in the cart summary the total isn't correct 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 More sharing options...
Inform-All Posted October 9, 2013 Share Posted October 9, 2013 I've changed all these things...but in the cart summary the total isn't correct 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 More sharing options...
AnitaR Posted October 30, 2013 Share Posted October 30, 2013 (edited) 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 October 30, 2013 by AnitaR (see edit history) Link to comment Share on other sites More sharing options...
biglama Posted November 10, 2013 Share Posted November 10, 2013 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' 2 Link to comment Share on other sites More sharing options...
fayelpossi Posted May 3, 2014 Share Posted May 3, 2014 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 More sharing options...
vekia Posted May 3, 2014 Share Posted May 3, 2014 it's necessary to change quantity field datatype from INT (integer) to float(17,2) 1 Link to comment Share on other sites More sharing options...
fayelpossi Posted May 4, 2014 Share Posted May 4, 2014 So 17,2 was juste un example... Thanks Link to comment Share on other sites More sharing options...
diosimmy Posted May 29, 2014 Share Posted May 29, 2014 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 More sharing options...
diosimmy Posted May 29, 2014 Share Posted May 29, 2014 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 More sharing options...
wfreebird Posted August 3, 2014 Author Share Posted August 3, 2014 For all the people that are still waiting for this function please vote thru the following link if you didn't already done so. http://feedback.prestashop.com/forums/124931-general?filter=hot With kind regards Link to comment Share on other sites More sharing options...
Mykhailo Posted August 20, 2014 Share Posted August 20, 2014 (edited) 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 September 4, 2014 by Mykhailo (see edit history) 9 Link to comment Share on other sites More sharing options...
Kurt Moskjaer Anders Posted September 4, 2014 Share Posted September 4, 2014 Great post Mykhailo. Do you know if the changes to the database will be lost in an upgrade of PS? Link to comment Share on other sites More sharing options...
Mykhailo Posted September 4, 2014 Share Posted September 4, 2014 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 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 More sharing options...
Kurt Moskjaer Anders Posted September 15, 2014 Share Posted September 15, 2014 It works like a charm, thank you Link to comment Share on other sites More sharing options...
veronica perce Posted September 15, 2014 Share Posted September 15, 2014 Great post Mykhailo.even i was facing same problem bt now resolved. Link to comment Share on other sites More sharing options...
unscarred Posted February 6, 2015 Share Posted February 6, 2015 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... 1 Link to comment Share on other sites More sharing options...
AGS-Golzar Posted February 15, 2015 Share Posted February 15, 2015 Hi, I have same problem with 1.6.0.9 I change db and files that some users recommended. but when I add product to cart, pop up cart dialog,show int value of quantity not decimal. how could I fix it? Link to comment Share on other sites More sharing options...
psandmore Posted February 16, 2015 Share Posted February 16, 2015 Instead of creating your own solution, please review this module that allows decimal quantities. All modifications in db, for product page, cart, order, invoice, mail alerts, PayPal payments already done and tested. http://psandmore.com Link to comment Share on other sites More sharing options...
lab83 Posted March 18, 2015 Share Posted March 18, 2015 250€ Unfair Price! 1 Link to comment Share on other sites More sharing options...
neocronos Posted May 19, 2015 Share Posted May 19, 2015 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 More sharing options...
tonagra Posted May 21, 2015 Share Posted May 21, 2015 Can you send me in PM or here "guy made a module" contacts? Tnx Link to comment Share on other sites More sharing options...
gabrielchiron Posted September 3, 2015 Share Posted September 3, 2015 Sorry to reopen the post, but i have this issue in ps 1.6.1 i need to sell producto with decimals. Any idea? TY! Link to comment Share on other sites More sharing options...
gabrielchiron Posted September 14, 2015 Share Posted September 14, 2015 I try to UP this! ^^ Thx! Link to comment Share on other sites More sharing options...
coelmay Posted October 22, 2015 Share Posted October 22, 2015 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 More sharing options...
coelmay Posted November 30, 2015 Share Posted November 30, 2015 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 More sharing options...
Arnaud GUY Posted March 8, 2016 Share Posted March 8, 2016 Hi, I have the same problem in my presta1.5.6 It's impossible to update the décimal quantity ? The problem has solved ? Thanks Link to comment Share on other sites More sharing options...
Sandeep Tiwari Posted June 12, 2016 Share Posted June 12, 2016 (edited) For 1.6 version, it works but ajax cart not showing any products when quantity is less than 1 for eg 0.5 meters... anyone who resolves this issue Edited June 12, 2016 by Sandeep Tiwari (see edit history) Link to comment Share on other sites More sharing options...
Arnaud GUY Posted September 13, 2016 Share Posted September 13, 2016 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 ? Screenshot follows, thanks a lot ! Link to comment Share on other sites More sharing options...
Arnaud GUY Posted September 17, 2016 Share Posted September 17, 2016 up please Link to comment Share on other sites More sharing options...
SityXXX Posted January 11, 2017 Share Posted January 11, 2017 For 1.6 version, it works but ajax cart not showing any products when quantity is less than 1 for eg 0.5 meters... anyone who resolves this issue I resolved, but its not issue, its feature . PM me if it still actual? 1 Link to comment Share on other sites More sharing options...
jboku8 Posted March 4, 2017 Share Posted March 4, 2017 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 More sharing options...
maske2 Posted June 12, 2017 Share Posted June 12, 2017 Does anyone have a solution for version 1.7? I changed php files, database but have problem with js. can't find what i should change. Link to comment Share on other sites More sharing options...
ENS Enterprises Posted October 13, 2017 Share Posted October 13, 2017 Can you please send the screenshot of the issue. Link to comment Share on other sites More sharing options...
AlexPopov Posted July 9, 2018 Share Posted July 9, 2018 (edited) 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! Edited July 9, 2018 by AlexPopov (see edit history) Link to comment Share on other sites More sharing options...
AlexPopov Posted July 12, 2018 Share Posted July 12, 2018 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 More sharing options...
emil Posted August 13, 2018 Share Posted August 13, 2018 I'm looking solution for this - Prestashop 1.7 Link to comment Share on other sites More sharing options...
androkot33 Posted August 13, 2018 Share Posted August 13, 2018 I am willing to help. Message me if you interested. Link to comment Share on other sites More sharing options...
elyyza Posted August 23, 2018 Share Posted August 23, 2018 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 More sharing options...
mosmartin Posted November 8, 2018 Share Posted November 8, 2018 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 More sharing options...
lvlehdi Posted November 27, 2018 Share Posted November 27, 2018 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 Link to comment Share on other sites More sharing options...
androkot33 Posted November 30, 2018 Share Posted November 30, 2018 You need to change structure of table for this field. 1 Link to comment Share on other sites More sharing options...
lvlehdi Posted December 3, 2018 Share Posted December 3, 2018 On 12/1/2018 at 1:49 AM, androkot33 said: You need to change structure of table for this field. thx for reply Query error,can u help me!? Link to comment Share on other sites More sharing options...
androkot33 Posted December 4, 2018 Share Posted December 4, 2018 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. Link to comment Share on other sites More sharing options...
lvlehdi Posted December 6, 2018 Share Posted December 6, 2018 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 More sharing options...
lvlehdi Posted December 7, 2018 Share Posted December 7, 2018 On 12/4/2018 at 3:57 AM, androkot33 said: thx man i did it but need some other changes 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 for 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 More sharing options...
lvlehdi Posted December 8, 2018 Share Posted December 8, 2018 (edited) + - 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 December 8, 2018 by lvlehdi (see edit history) Link to comment Share on other sites More sharing options...
psandmore Posted December 8, 2018 Share Posted December 8, 2018 Stop to reinvent the wheel. Simply go and take this excellent module: https://demo-store.psandmore.com/ http://store.psandmore.com/ Link to comment Share on other sites More sharing options...
lvlehdi Posted December 9, 2018 Share Posted December 9, 2018 On 12/8/2018 at 5:37 PM, psandmore said: Stop to reinvent the wheel. Simply go and take this excellent module: https://demo-store.psandmore.com/ http://store.psandmore.com/ 5$ final offer Link to comment Share on other sites More sharing options...
androkot33 Posted December 10, 2018 Share Posted December 10, 2018 On 12/8/2018 at 7:07 AM, psandmore said: Stop to reinvent the wheel. Simply go and take this excellent module: https://demo-store.psandmore.com/ http://store.psandmore.com/ There is non for 1.7 version. Link to comment Share on other sites More sharing options...
lvlehdi Posted December 10, 2018 Share Posted December 10, 2018 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 More sharing options...
ENS Enterprises Posted December 11, 2018 Share Posted December 11, 2018 you can go for a module. Link to comment Share on other sites More sharing options...
Inform-All Posted December 11, 2018 Share Posted December 11, 2018 1 hour ago, ENS Enterprises said: you can go for a module. In his defence... He is from Iran, he cant use Paypal or dollars that easily to buy anything from the addons store. Link to comment Share on other sites More sharing options...
ilvutchenkoigor Posted December 12, 2018 Share Posted December 12, 2018 Is there a solution for the version Prestashop 1.7? Link to comment Share on other sites More sharing options...
lvlehdi Posted December 13, 2018 Share Posted December 13, 2018 16 hours ago, ilvutchenkoigor said: Is there a solution for the version Prestashop 1.7? i'm working on it,i'll let you know Link to comment Share on other sites More sharing options...
ilvutchenkoigor Posted December 13, 2018 Share Posted December 13, 2018 3 hours ago, lvlehdi said: i'm working on it,i'll let you know Ok. I'll wait. Thanks Link to comment Share on other sites More sharing options...
lvlehdi Posted December 14, 2018 Share Posted December 14, 2018 On 12/13/2018 at 6:07 PM, ilvutchenkoigor said: Ok. I'll wait. Thanks didn't found a way for 1.7 but i did it for 1.6.1.18 with The least changes Reed Me.txt Link to comment Share on other sites More sharing options...
ilvutchenkoigor Posted December 15, 2018 Share Posted December 15, 2018 18 hours ago, lvlehdi said: didn't found a way for 1.7 but i did it for 1.6.1.18 with The least changes Reed Me.txt Unfortunately, this option is not suitable for version 1.7. In version 1.7, all js are minified to one file. Link to comment Share on other sites More sharing options...
beshop Posted August 9, 2019 Share Posted August 9, 2019 On 12/14/2018 at 5:40 PM, lvlehdi said: didn't found a way for 1.7 but i did it for 1.6.1.18 with The least changes Reed Me.txt do these changes work on prestashop 1.7.5.2? Link to comment Share on other sites More sharing options...
slavenja Posted April 5, 2020 Share Posted April 5, 2020 (edited) Fractional Quantity for Prestashop 1.6.24 there are ready-made files + request https://intopsite.ru/drobnaja-korzina-dlja-prestashop.html Edited April 5, 2020 by slavenja (see edit history) Link to comment Share on other sites More sharing options...
diegovanz Posted November 4, 2020 Share Posted November 4, 2020 Buenas, alguien tiene el código modificado para PS 1.7+ que acepte compras por fracciones? se lo compro.. Gracias Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now