Jump to content

Per item shipping


Recommended Posts

Hello,

The company I work for has a wide variety of products that are complex with respect to shipping based on weight and dimensions. Specifically, they range in weight from a few pounds up to several hundred and can be shipped parcel or on a pallet. The current shipping method simply doesn't work under these conditions.

I am going to implement a per item shipping by:

1) adding the following to the Carrier class:

    public function getDeliveryPriceByProduct($id_product, $id_zone) {

   } // getDeliveryPriceByProduct()


   public static function checkDeliveryPriceByProduct($id_carrier, $id_product, $id_zone) {

   } // checkDeliveryPriceByProduct()



2) adding another table ps_delivery_product:

DROP TABLE IF EXISTS `criterion_store`.`ps_delivery_product`;
CREATE TABLE  `criterion_store`.`ps_delivery_product` (
 `id_delivery_product` int(10) unsigned NOT NULL auto_increment,
 `id_carrier` int(10) unsigned NOT NULL,
 `id_product` int(10) unsigned NOT NULL,
 `id_zone` int(10) unsigned NOT NULL,
 `price` decimal(10,2) NOT NULL,
 PRIMARY KEY  (`id_delivery_product`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;



3) Adding another object DeliveryProduct that models the ps_delivery_product table

3) Altering the cart object. Specifically,

function getOrderShippingCost($id_carrier = NULL)



Does anyone have input, information or suggestions on this feature or implementation?

Link to comment
Share on other sites

Here is the DeliveryProduct class:

/**
* DeliveryProduct class, DeliveryProduct.php
* Delivery management on a per product basis
* 
* @category classes
*
* @author Finnian F Burn
*
* @license http://www.opensource.org/licenses/osl-3.0.php Open-source licence 3.0
* @version 1.0    
**/

class DeliveryProduct extends ObjectModel {

   /** @var integer */
   public $id_delivery_product;

   /** @var integer */
   public $id_carrier;

   /** @var integer */
   public $id_product;

   /** @var integer */    
   public $id_zone;

   /** @var float */    
   public $price;

   /** @var array */
   protected    $fieldsRequired = array ('id_carrier', 'id_product', 'id_zone', 'price');    

   /** @var array */
   protected    $fieldsValidate = array ('id_carrier' => 'isUnsignedId', 'id_product' => 'isUnsignedId', 
   'id_zone' => 'isUnsignedId', 'price' => 'isPrice');

   /** @var string */
   protected     $table = 'delivery_product';

   /** @var string */
   protected     $identifier = 'id_delivery_product';


   /**
    * retrieve the fields for this class
    * 
    * @return    array
   **/
   public function getFields() {

       // validate the fields
       parent::validateFields();

       // populate the fields array
       $fields['id_carrier'] = intval($this->id_carrier);
       $fields['id_product'] = intval($this->id_product);
       $fields['id_zone'] = intval($this->id_zone);
       $fields['price'] = floatval($this->price);

       // return the array
       return $fields;
   } // getFields()

} // DeliveryProduct

Link to comment
Share on other sites

Here's the Carrier->checkDeliveryPriceByProduct method:

    public static function checkDeliveryPriceByProduct($id_carrier, $id_product, $id_zone) {

       $result = Db::getInstance()->getRow('
       SELECT dp.`price`
       FROM `'._DB_PREFIX_.'delivery_product` dp
       WHERE dp.`id_zone` = '.intval($id_zone).'
       AND dp.`id_product` = '.intval($id_product).'
       AND dp.`id_carrier` = '.intval($id_carrier));

       if (!isset($result['price']))
           return false;

       return true;
   } // checkDeliveryPriceByProduct()

Link to comment
Share on other sites

OK, I feel like a total hack. The maximal effort required to make these features work within the prestashop world is extraordinary. So many things are linked together that modifying something as fundamental as shipping becomes cost prohibitive.

Forget the stuff above, this is my quick and VERY dirty solution:

Run the following SQL:

INSERT INTO ps_configuration SET name='PS_DELIVERY_PRODUCT', value='1', date_add=NOW(), date_upd=NOW()



in cart->getOrderShippingCost() add/change the following code (around line 535):

if (Configuration::get('PS_DELIVERY_PRODUCT') == 1) {

   $products = $this->getProducts();

   foreach ($products as $row) {
   $shipping_cost += $row['shipping_cost'];
   } // foreach    
} // if    
// Get shipping cost using correct method
else if ($carrier->range_behavior)



Change the Cart->getProducts() method SQL follows:


$sql = '
SELECT cp.`id_product_attribute`, cp.`id_product`, cp.`quantity` AS cart_quantity, pl.`name`,
pl.`description_short`, pl.`availability`, p.`id_product`, p.`id_supplier`, p.`id_manufacturer`, p.`id_tax`, p.`on_sale`, p.`ecotax`,
p.`quantity`, p.`price`, p.`reduction_price`, p.`reduction_percent`, p.`weight`, p.`out_of_stock`, p.`active`, p.`date_add`, p.`date_upd`, p.`shipping_cost`,
t.`id_tax`, tl.`name` AS tax, t.`rate`, pa.`price` AS price_attribute, pa.`weight` AS weight_attribute, pa.`quantity` AS quantity_attribute,
pa.`ecotax` AS ecotax_attr, i.`id_image`, il.`legend`, pl.`link_rewrite`,
IF (IFNULL(pa.`reference`, \'\') = \'\', p.`reference`, pa.`reference`) AS reference,
IF (IFNULL(pa.`supplier_reference`, \'\') = \'\', p.`supplier_reference`, pa.`supplier_reference`) AS supplier_reference,
IF (IFNULL(pa.`weight`, \'\') = \'\', p.`weight`, pa.`weight`) AS weight_attribute,
IF (IFNULL(pa.`ean13`, \'\') = \'\', p.`ean13`, pa.`ean13`) AS ean13
FROM `'._DB_PREFIX_.'cart_product` cp
LEFT JOIN `'._DB_PREFIX_.'product` p ON p.`id_product` = cp.`id_product`
LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product` AND pl.`id_lang` = '.intval($this->id_lang).')
LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa ON (pa.`id_product_attribute` = cp.`id_product_attribute`)
LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = p.`id_tax`)
LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (t.`id_tax` = tl.`id_tax` AND tl.`id_lang` = '.intval($this->id_lang).')
LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = cp.`id_product` AND (IF(pa.`id_image`, pa.`id_image` = i.`id_image`, i.`cover` = 1)))
LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.intval($this->id_lang).')
WHERE `id_cart` = '.intval($this->id).'
'.($id_product ? ' AND cp.`id_product` = '.intval($id_product) : '').'
AND p.`id_product` IS NOT NULL';
$result = Db::getInstance()->ExecuteS($sql);


And here...:

            $row['features'] = Product::getFeaturesStatic(intval($row['id_product']));
           $row['shipping_cost'] = floatval($row['shipping_cost']) * intval($row['quantity']);



Update the Product class to feature a per product shipping:


    public        $shipping_cost;

   /** @var array tables */
   protected $tables = array ('product', 'product_lang');

   protected $fieldsRequired = array('id_tax', 'quantity', 'price');
   protected $fieldsSize = array('reference' => 32, 'supplier_reference' => 32, 'ean13' => 13);
   protected $fieldsValidate = array(
       'id_tax' => 'isUnsignedId',
...................................
       'active' => 'isBool',
       'ean13' => 'isEan13',
       'shipping_cost' => 'isPrice'
   );


   public function getFields()
   {
       parent::validateFields();

................................
       $fields['date_upd'] = pSQL($this->date_upd);
       $fields['shipping_cost'] = floatval($this->shipping_cost);
       return $fields;
   }




Update the ps_product table:

DROP TABLE IF EXISTS `criterion_store`.`ps_product`;
CREATE TABLE  `criterion_store`.`ps_product` (
 `id_product` int(10) unsigned NOT NULL auto_increment,
 `id_supplier` int(10) unsigned default NULL,
 `id_manufacturer` int(10) unsigned default NULL,
 `id_tax` int(10) unsigned NOT NULL,
 `id_category_default` int(10) unsigned default NULL,
 `id_color_default` int(10) unsigned default NULL,
 `on_sale` tinyint(1) unsigned NOT NULL default '0',
 `ean13` varchar(13) default NULL,
 `ecotax` decimal(10,2) NOT NULL default '0.00',
 `quantity` int(10) unsigned NOT NULL default '0',
 `price` decimal(13,6) NOT NULL default '0.000000',
 `wholesale_price` decimal(13,6) NOT NULL default '0.000000',
 `reduction_price` decimal(10,2) default NULL,
 `reduction_percent` float default NULL,
 `reduction_from` date default NULL,
 `reduction_to` date default NULL,
 `reference` varchar(32) default NULL,
 `supplier_reference` varchar(32) default NULL,
 `weight` float NOT NULL default '0',
 `out_of_stock` int(10) unsigned NOT NULL default '2',
 `quantity_discount` tinyint(1) default '0',
 `active` tinyint(1) unsigned NOT NULL default '0',
 `date_add` datetime NOT NULL,
 `date_upd` datetime NOT NULL,
 `shipping_cost` decimal(10,2) default NULL,
 PRIMARY KEY  (`id_product`),
 KEY `product_supplier` (`id_supplier`),
 KEY `product_manufacturer` (`id_manufacturer`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;



The rest in the next message...

Link to comment
Share on other sites

For the back-office, we need to update the AdminProduct tab...

Here's the changes to copyFromPost():

    protected function copyFromPost(&$object, $table)
   {
       parent::copyFromPost($object, $table);

       /* Additional fields */
       $languages = Language::getLanguages();
       foreach ($languages as $language)
           if (isset($_POST['meta_keywords_'.$language['id_lang']]))
               $_POST['meta_keywords_'.$language['id_lang']] = preg_replace('/ *,? +,*/', ',', strtolower($_POST['meta_keywords_'.$language['id_lang']]));
       $_POST['weight'] = empty($_POST['weight']) ? '0' : str_replace(',', '.', $_POST['weight']);
       if ($_POST['reduction_price'] != NULL) $object->reduction_price = str_replace(',', '.', $_POST['reduction_price']);
       if ($_POST['reduction_percent'] != NULL) $object->reduction_percent = str_replace(',', '.', $_POST['reduction_percent']);
       if ($_POST['ecotax'] != NULL) $object->ecotax = str_replace(',', '.', $_POST['ecotax']);
       $object->active = (!isset($_POST['active']) OR $_POST['active']) ? true : false;
       $object->on_sale = (!isset($_POST['on_sale']) ? false : true);
       $object->shipping_cost = $_POST['shipping'] != NULL ? $_POST['shipping'] : '0';
   }



Add the last of the following table rows (the other is for reference):

                        '.$this->l('Weight:').'

                           <input size="3" maxlength="4" name="weight" type="text" value="'.htmlentities($this->getFieldValue($obj, 'weight'), ENT_COMPAT, 'UTF-8').'" onKeyUp="[removed]this.value = this.value.replace(/,/g, \'.\');" /> '.Configuration::get('PS_WEIGHT_UNIT').'




'.$this->l('Shipping:').'
$
                           <input size="5" maxlength="6" name="shipping" type="text" value="'.htmlentities($this->getFieldValue($obj, 'shipping_cost'), ENT_COMPAT, 'UTF-8').'" style="width: 110px;  margin-right: 44px;" />

';



Phew!

I know it's not elegant, but it works. It's getting time for me to finish up for the day, but tomorrow I will update the AdminShipping tabs so that you can choose to switch between the three different shipping methods.

As much as possible I have tested the code today, and it works smoothly on my linux boxes. I'm going to put this into production in the next couple weeks. Anyone who is interested in additional help implementing this solution until the prestashop team adds this functionality, please feel free to contact me.

I'm not particularly good with regular expressions, so if some one wants to contribute verification for the shipping costs in the copyFromPost above, I'd love it.

Have a good evening (for those in my time zone)!

Link to comment
Share on other sites

Thanks for this, it looks like something i could do with.

Could you please make the code above a little easier to implement, by adding something like.

In file blah.php, After "blah blah blah" around line 1234 add the following

etc etc.

I have tried to follow the above instructions, but it isnt making much sense.

Hope this makes sense to you :)

Link to comment
Share on other sites

Hi everyone!

Very nice contribution fburn :-)
It's not a so dirty way ;-)
It's just a shame that it converts the shipping costs of the entire shop :S

I think you can resolve it by replacing :

if (Configuration::get('PS_DELIVERY_PRODUCT') == 1) {

   $products = $this->getProducts();

   foreach ($products as $row) {
   $shipping_cost += $row['shipping_cost'];
   } // foreach    
}



by

   $products = $this->getProducts();

   foreach ($products as $row) {
     if ($product['shipping_cost'])
       $shipping_cost += floatval($row['shipping_cost']);
   } // foreach    

Link to comment
Share on other sites

Mattheiu,

Great idea! I've been looking for a way to handle this so it doesn't disable the rest of the shipping methods.

There must be a lot of users out there who require per item shipping, if anyone else needs help implementing this, please feel free to email me at [email protected].

For the prestashop team, what about including this code or a variant in the next release?

Link to comment
Share on other sites

  • 2 months later...

From my understanding (and looking thru all the code) this will work with 1.1, but requires some modification. Hopefully fburn can chime in with some updated instructions, but I'm also planning to try installing on a demo box today and trying to make it work if I can.

Link to comment
Share on other sites

Try as I might I can't get it to work. I've gone thru and compared the supplied code to the 1.1 source and attempted to get the two to work together, but no dice.

If anyone has this working on 1.1 I would love to talk to you. I love PrestaShop and I know it's free, but I can't deploy this without a shipping method like this.

Thanks to anyone who may be able to help.

Link to comment
Share on other sites

After much frustration with not being able to make this work I downgraded (ie reinstalled) 1.0 on my live server out of desperation for a working solution. Unfortunately at that point it would work there either. At that point I realized there must be a PEBKAC error and I started again.

This time I was able to get the mod working on a 1.0 install AS WELL as a demo 1.1 install.

Tomorrow I am going to reinstall the the "live" server with 1.1 and try my luck again with the modification. If that works I will post some more detailed instructions as well as the actual PHP files themselves.

The next step though is to get the additional modification Matthieu Biart provided working because so far that is not.

Link to comment
Share on other sites

  • 3 weeks later...

I SO hope that the fine makers of PrestaShop will make a module for this dilemma! I have a collectibles and antique store. I have different shipping needs for each item. One item may only require $5.00 shipping fee, while another may be $25.00 shipping fee. It would be nice to see "Enter Shipping Amount" on each product entry page! PLEASE come out with a module for this that allows individual shipping fees. I really am a BIG fan of PrestaShop, (I left ZenCart because it was way too bloated and difficult to figure out) just need this "individual item shipping" addition. In the mean time, how do I allow for individual shipping?

Link to comment
Share on other sites

I have this same problem - but just a field with fixed price its not enough.
What about many items in cart? If I buy 10 I must pay 10 shipping price per ich item?

Just think over and easy way to fix price - use weight fields as a delivery price and add this value product.weight to delivery price in delivery.php

When stanard delivery will by set to 0, this gives You weight as a delivery price

Buy this don solve many items shipping ....

Link to comment
Share on other sites

I just think a module that will dsplay a shipping charges box, on each products page will be the answer. Then have a tick box that says "Charge shipping on multiple orders"..... if you don't tick the box, it will only charge a one time shipping fee. Good idea folks?

Link to comment
Share on other sites

  • 1 month later...

Hello,

are there news for this point ?

I also have to find a solution : in my case, each manufacturer will send the products to the buyers. So each product must have a shipping cost. Except if the products come from the same manufacturers...

I don t know how to solve this question now... if somenone has an idea...?

sorry for my english, i am very bad !

have a good day

Link to comment
Share on other sites

  • 4 months later...
  • 9 months later...
×
×
  • Create New...