Forums / General Questions / Free shipping on product when certain quantity is bought from a manufacturer.

Free shipping on product when certain quantity is bought from a manufacturer.

Started by necroside on 11 Apr 2017, 12:27 AM · Views: 380

Results 1 to 4 of 4
11 Apr 2017, 12:27 AM
#1
necroside avatar

necroside

New Zenner

Join Date:
Jun 2011
Posts:
8
Plugin Contributions:
0

Free shipping on product when certain quantity is bought from a manufacturer.

Hi all.

I am trying to add a condition to my store, whenever a customer buys certain amount from one selected manufacturer products from that manufacturer have free shipping.
The shipping module I use is ground shipping from Fedex web services. But I want this change to be shipping method agnostic.

I am not a Zencart expert, this is me second attempt for code modification. So I build an observer. Here is the code:

<?php
/**
 * Observer class used to add a free shipping to a product product to the cart 
 * if the user spends more than $x on products from an specific manufacturer
 */
class freeShippingOnManufacturer extends base {

  var $freeManufacturers = array();
  var $cartManufacturers = array();
  var $productsTotalInCart = array();
  /**
   * constructor method
   * 
   * Attaches our class to the $_SESSION['cart'] class and watches for 2 notifier events.
   */
  function __construct() {
    $_SESSION['cart']->attach($this, array('NOTIFIER_CART_ADD_CART_END', 'NOTIFIER_CART_REMOVE_END', 'NOTIFIER_CART_UPDATE_QUANTITY_END'));
  }
  /**
   * Update Method
   * 
   * Called by observed class when any of our notifiable events occur
   *
   * @param object $class
   * @param string $eventID
   */
  function update(&$class, $eventID, $paramsArray = array()) {
    global $db;
    
    //get manufacturers with free shipping option
    $getfreeshippingmanufacturerssql = "SELECT manufacturers_id, min_value FROM manufacturers_free_shipping WHERE active = '1'";
    $resultm = $db->Execute($getfreeshippingmanufacturerssql);
    $freeManIds = array();
    if ($resultm->RecordCount() > 0) {
      while (!$resultm->EOF) {
        $this->freeManufacturers[$resultm->fields['manufacturers_id']] = $resultm->fields['min_value'];
        $freeManIds[] =  $resultm->fields['manufacturers_id'];
        $resultm->MoveNext();
      }
    }
    foreach($_SESSION['cart']->contents as $key => $value){
      if(strstr($key,':')){
        $expl = explode(':',$key);
        $pid = $expl[0];
      }else{
        $pid = $key;
      }
      $manufacturersql = "SELECT manufacturers_id FROM " . TABLE_PRODUCTS ." WHERE products_id = '".$pid."'";
      $result = $db->Execute($manufacturersql);
      if ($result->RecordCount() > 0) {
        if(in_array($result->fields['manufacturers_id'],$freeManIds)){
          $this->cartManufacturers[$result->fields['manufacturers_id']] += $_SESSION['cart']->in_cart_product_total_price($pid);
          $free_shipping_products_price += $_SESSION['cart']->in_cart_product_total_price($pid);
          $weight_to_reduce += $_SESSION['cart']->in_cart_product_total_weight($key);
          $items_to_free +=  $value['qty'];
        }
      } 
    }
    if($this->freeManufacturers[$result->fields['manufacturers_id']] <= $this->cartManufacturers[$result->fields['manufacturers_id']]){
    
      $_SESSION['cart']->free_shipping_item = $_SESSION['cart']->free_shipping_item + $items_to_free;
      $_SESSION['cart']->weight = $_SESSION['cart']->weight - $weight_to_reduce;
      $_SESSION['cart']->free_shipping_weight = $_SESSION['cart']->free_shipping_weight + $weight_to_reduce;
      $_SESSION['cart']->free_shipping_price = $_SESSION['cart']->free_shipping_price + $free_shipping_products_price;
    }
    
  }  
}

I don't what else should be done other than modify the cart object to reflect the new free shipping values:

$_SESSION['cart']->free_shipping_item = $_SESSION['cart']->free_shipping_item + $items_to_free;
      $_SESSION['cart']->weight = $_SESSION['cart']->weight - $weight_to_reduce;
      $_SESSION['cart']->free_shipping_weight = $_SESSION['cart']->free_shipping_weight + $weight_to_reduce;
      $_SESSION['cart']->free_shipping_price = $_SESSION['cart']->free_shipping_price + $free_shipping_products_price;

The table manufacturers_free_shipping has the following structure:

CREATE TABLE IF NOT EXISTS `manufacturers_free_shipping` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `manufacturers_id` int(11) NOT NULL,
  `min_value` float NOT NULL,
  `active` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

I don't know what else should be done in order for those products to be free shipping.

Any help will be really appreciated :)

Thanks

12 Apr 2017, 4:17 PM
#2
mc12345678 avatar

mc12345678

Totally Zenned

Join Date:
Jul 2012
Posts:
16,908
Plugin Contributions:
2

Re: Free shipping on product when certain quantity is bought from a manufacturer.

necroside:

Hi all.

I am trying to add a condition to my store, whenever a customer buys certain amount from one selected manufacturer products from that manufacturer have free shipping.
The shipping module I use is ground shipping from Fedex web services. But I want this change to be shipping method agnostic.

I am not a Zencart expert, this is me second attempt for code modification. So I build an observer. Here is the code:

<?php /** * Observer class used to add a free shipping to a product product to the cart * if the user spends more than $x on products from an specific manufacturer */ class freeShippingOnManufacturer extends base { var $freeManufacturers = array(); var $cartManufacturers = array(); var $productsTotalInCart = array(); /** * constructor method * * Attaches our class to the $_SESSION['cart'] class and watches for 2 notifier events. */ function __construct() { $_SESSION['cart']->attach($this, array('NOTIFIER_CART_ADD_CART_END', 'NOTIFIER_CART_REMOVE_END', 'NOTIFIER_CART_UPDATE_QUANTITY_END')); } /** * Update Method * * Called by observed class when any of our notifiable events occur * * @param object $class * @param string $eventID */ function update(&$class, $eventID, $paramsArray = array()) { global $db; //get manufacturers with free shipping option $getfreeshippingmanufacturerssql = "SELECT manufacturers_id, min_value FROM manufacturers_free_shipping WHERE active = '1'"; $resultm = $db->Execute($getfreeshippingmanufacturerssql); $freeManIds = array(); if ($resultm->RecordCount() > 0) { while (!$resultm->EOF) { $this->freeManufacturers[$resultm->fields['manufacturers_id']] = $resultm->fields['min_value']; $freeManIds[] = $resultm->fields['manufacturers_id']; $resultm->MoveNext(); } } foreach($_SESSION['cart']->contents as $key => $value){ if(strstr($key,':')){ $expl = explode(':',$key); $pid = $expl[0]; }else{ $pid = $key; } $manufacturersql = "SELECT manufacturers_id FROM " . TABLE_PRODUCTS ." WHERE products_id = '".$pid."'"; $result = $db->Execute($manufacturersql); if ($result->RecordCount() > 0) { if(in_array($result->fields['manufacturers_id'],$freeManIds)){ $this->cartManufacturers[$result->fields['manufacturers_id']] += $_SESSION['cart']->in_cart_product_total_price($pid); $free_shipping_products_price += $_SESSION['cart']->in_cart_product_total_price($pid); $weight_to_reduce += $_SESSION['cart']->in_cart_product_total_weight($key); $items_to_free += $value['qty']; } } } if($this->freeManufacturers[$result->fields['manufacturers_id']] <= $this->cartManufacturers[$result->fields['manufacturers_id']]){ $_SESSION['cart']->free_shipping_item = $_SESSION['cart']->free_shipping_item + $items_to_free; $_SESSION['cart']->weight = $_SESSION['cart']->weight - $weight_to_reduce; $_SESSION['cart']->free_shipping_weight = $_SESSION['cart']->free_shipping_weight + $weight_to_reduce; $_SESSION['cart']->free_shipping_price = $_SESSION['cart']->free_shipping_price + $free_shipping_products_price; } } } ``` > > I don't what else should be done other than modify the cart object to reflect the new free shipping values: > > ```php $_SESSION['cart']->free_shipping_item = $_SESSION['cart']->free_shipping_item + $items_to_free; $_SESSION['cart']->weight = $_SESSION['cart']->weight - $weight_to_reduce; $_SESSION['cart']->free_shipping_weight = $_SESSION['cart']->free_shipping_weight + $weight_to_reduce; $_SESSION['cart']->free_shipping_price = $_SESSION['cart']->free_shipping_price + $free_shipping_products_price; ``` > > The table manufacturers_free_shipping has the following structure: > > ``` CREATE TABLE IF NOT EXISTS `manufacturers_free_shipping` ( `id` int(11) NOT NULL AUTO_INCREMENT, `manufacturers_id` int(11) NOT NULL, `min_value` float NOT NULL, `active` tinyint(1) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ; ``` > > I don't know what else should be done in order for those products to be free shipping. > > Any help will be really appreciated :) > > Thanks Oye... A lot going on here, and I'd like to try to explain all of the aspects that appear to not have yet been learned about observers, but I'm likely to miss something at least as it relates to correcting the above code. Couple of things for starters. The ZC version has not been identified, so for now am going to consider continuing to offer direction using some of the older techniques of working with observers (pre ZC 1.5.3) which remain possible in ZC 1.5.3 and above, but the older versions miss out on some improvements/added capabilities. So first. Generally speaking, an observer observes all of ZC operation, not just the file/class that happens to have a notifier within it at least as far as the file/class being observed extends base or incorporates the notifier system. The shopping_cart class does extend base and therefore there is no need and could be problematic to specifically attach to the object that is the shopping cart class. Next there is the fact that the update function triggers for any observed operation not just one or the other. Therefore, to ensure that only the applicable code is executed, within the update function it is important to know which notifier has been triggered and to take only the action applicable to that notifier. In here, there are three notifiers being listened for, and currently, regardless of the notifier all of the same operations are being taken. So even if a remove operation is being performed, the actions of an addition (appears that current code is adding product) are being performed rather than a removal operation... But, as the code is refined, this may not be an issue. To be honest, I haven't looked at the bigger picture of what you are trying to do as compared to what the code is actually doing... Some of that is because, even if what is being done won't work, there is an area of learning/improvement to be had which in my mind is more important and will guide you down future success of doing whatever it is you want to do and discovery of whether the action being taken will be successful or not. Ie. learn more from trying and possibly failing than from being told the entire solution. :) Okay, so next there is the fact that when a class is involved with a notifier, that the notifier/observer code actually makes the class accessible to the update related function. Therefore the observer can actually manipulate the calling class' data without having to reference an existing object's data. Next to that is the overall final variable(s) being modified, these are the part into which I haven't looked to see if the appropriate variable(s) are being modified to support your targeted goal, but even in so doing, there is another aspect of consideration/concern which follows. What happens or rather what is expected to happen if a cart is to have not only product from this one manufacturer but also product from another? Does the entire cart become free shipping regardless of other items that are included or are only the items associated with that one manufacturer the ones that are shipped for free and some other process (would have to identify) will address the remainder of the shipping "agnostically". Oh, and I guess lastly... When referencing items within the cart, the cart generally needs every bit of the information related to the products_id as relates to attributes (part of the id that begins with a colon and has the remainder of information) in order to look up the product. There are some functions in the shopping cart class that may truncate the number to identify how many total (variants) of a product, but then there are others that will take what they are given... So if for example product 5 is added to the cart without attributes, its products_id will be '5'... If it is added with attributes, then it will be similar (longer) to '5:234fabedae' typically having more data than that, but it is an example. So, one can ask the cart, how many of the physical product are present (5), what is the total value of them, total weight, etc... Or can ask, how many of variant 1 are present? When/if such a function is not present to ask the how many of the physical product are present, then as the cart is parsed, any time (int)$products_id is found, then process the associated data of that products_id (in whole and not truncated) to relate the data back to the base calculation. So regarding the provided code, I have the following recommendations to address some of the issues seen: By no means would I say that this is your solution, but it should provide some further insight on observer usage and help get you to where you are trying to get. Unless you have installed something that adds functionality to ZC, there are at least two functions "identified" to the shopping_cart class that do not exist in a basic install of ZC: in_cart_product_total_price and in_cart_product_total_weight. Below I have kept the full products_id associated with those functions considering that those functions look at the total price and total weight of the specific product not the generic product. Consider this: product 5 is in the cart two times, one with attribute set A and one with attribute set B. If say the in_cart_product_total_price function looks through the entire cart for all product 5 items, then when looping through the cart, that total will be added twice, once when the first instance of product 5 (attribute set A) is found and once with product 5 (attribute set B) is found. Further, if product 5 is in the cart only with attributes and those functions need to know about the attributes, sending just the value/integer 5 will cause those functions to return nothing because there would be no product in the cart that has a products_id of just the value of 5. Long and short of that is would need to know what the input and output is of those functions which could be better understood by seeing the code that is within each function... For now though, I am assuming that each function looks up the price/weight of the product the single variant that is provided and not just getting the total price/weight of all variations of the product. ``` <?php /** * Observer class used to add a free shipping to a product product to the cart * if the user spends more than $x on products from an specific manufacturer */ class freeShippingOnManufacturer extends base { var $freeManufacturers = array(); var $cartManufacturers = array(); var $productsTotalInCart = array(); /** * constructor method * * Attaches our class to the base class and watches for 3 notifier events that are within the shopping_cart class. */ function __construct() { $this->attach($this, array('NOTIFIER_CART_ADD_CART_END', 'NOTIFIER_CART_REMOVE_END', 'NOTIFIER_CART_UPDATE_QUANTITY_END')); } /** * Update Method * * Called by observed class when any of our notifiable events occur * * @param object $class <- The class that contains the notifier that triggered this code. * @param string $eventID <- The event id (notifier) that has initiated this observer code. */ function update(&$class, $eventID, $paramsArray = array()) { global $db; if ($eventID == 'NOTIFIER_CART_ADD_CART_END') { //get manufacturers with free shipping option $getfreeshippingmanufacturerssql = "SELECT manufacturers_id, min_value FROM manufacturers_free_shipping WHERE active = '1'"; $resultm = $db->Execute($getfreeshippingmanufacturerssql); $freeManIds = array(); if ($resultm->RecordCount() > 0) { while (!$resultm->EOF) { $this->freeManufacturers[$resultm->fields['manufacturers_id']] = $resultm->fields['min_value']; $freeManIds[] = $resultm->fields['manufacturers_id']; $resultm->MoveNext(); } } foreach($class->contents as $key => $value){ /* if(strstr($key,':')){ $expl = explode(':',$key); $pid = $expl[0]; }else{ $pid = $key; }*/ $pid = (int)$key; $manufacturersql = "SELECT manufacturers_id FROM " . TABLE_PRODUCTS ." WHERE products_id = ".(int)$key; $result = $db->Execute($manufacturersql); if ($result->RecordCount() > 0) { if(in_array($result->fields['manufacturers_id'],$freeManIds)){ $this->cartManufacturers[$result->fields['manufacturers_id']] += $class->in_cart_product_total_price($key); $free_shipping_products_price += $class->in_cart_product_total_price($key); $weight_to_reduce += $class->in_cart_product_total_weight($key); $items_to_free += $value['qty']; } } } if($this->freeManufacturers[$result->fields['manufacturers_id']] <= $this->cartManufacturers[$result->fields['manufacturers_id']]){ $class->free_shipping_item = $class->free_shipping_item + $items_to_free; $class->weight = $class->weight - $weight_to_reduce; $class->free_shipping_weight = $class->free_shipping_weight + $weight_to_reduce; $class->free_shipping_price = $class->free_shipping_price + $free_shipping_products_price; } } // EOF if 'NOTIFIER_CART_ADD_CART_END' } // EOF function update } ``` Now... Having modified all of that... There is an issue... While product is being added to the cart (as performed by the call to the function add_cart that has the notifier NOTIFIER_CART_ADD_CART_END), the applicable free_shipping_item, weight, free_shipping_weight, and free_shipping_price are being modified and can be "seen" on the store front, the problem is that a call to the shopping cart functions: reset and calculate restart the counter to 0 and the same operation(s) are not performed after that call to calculate... Calculate is called as part of moving through the checkout process as well as other operations, so... The above observer does not provide the benefit that it is intended to offer, specifically at the point of checkout_shipping, the values would have been recalculated in absence of the code in the above observer... Areas where calculate are called (in a default ZC 1.5.5 version) are in the shopping_cart class itself and include: show_total(), show_total_before_discounts(), show_weight(), free_shipping_items(), free_shipping_prices(), free_shipping_weight(), and download_counts(). Some of those offer notifiers that could possibly be used to address modification of the totals, but others do not and it depends on when those functions are called as to when they will reset the values as calculated by the shopping_cart class' calculate function (which does not by default have any notifiers in it). So one could step back a little and maybe implement cart changes at earlier points to provide the desired outcome, but it also depends on the sequence of calling those as to when/if they will edit the outcome when used... Generally speaking... Modifications such like this are made at the shipping method level such that each shipping method made available behaves/carries out the desired shipping modifications rather than at the cart level before approaching the shipping method(s)... Doesn't mean something like that can not be done, but may require a different approach than what has been proposed here.

ZC Installation/Maintenance Support <- Site
Contribution for contributions welcome...

25 Apr 2017, 1:56 AM
#3
necroside avatar

necroside

New Zenner

Join Date:
Jun 2011
Posts:
8
Plugin Contributions:
0

Re: Free shipping on product when certain quantity is bought from a manufacturer.

Thank you!

I wasn't working on this the last week because something with higher priority arose. This was the complete introduction and tutorial on how to proper work with observers. Thank you again.

I am checking the shipping method right now, right now we are using only one, so I am building some helper functions to keep the method modifications at a minimum. I'll let you know how it goes.

For the third time, thanks again for your help and really deep explanation.

25 Apr 2017, 4:03 AM
#4
mc12345678 avatar

mc12345678

Totally Zenned

Join Date:
Jul 2012
Posts:
16,908
Plugin Contributions:
2

Re: Free shipping on product when certain quantity is bought from a manufacturer.

Was a pleasure. Obvious that you're trying, sometimes just need nudges. :) Also, while I would say I learned much of what I know about ZC through some of the wiki articles, trial and error in some/many cases certainly has taught a lot including that unfortunately for the documentation some of it is a bit out of date to where what seemed important in the day is not as much now with the way the core team has worked so diligently on expansion/adaptability.

Hope the mini novella helps in some other way than just saying nice try, but that won't work. :)

ZC Installation/Maintenance Support <- Site
Contribution for contributions welcome...