Jump to content
Sign in to follow this  
datadesignit

How do I prevent package-splitting by weight for some items?

Recommended Posts

If a customer orders small widget A, B, and C, these all get thrown in a box. If there's a lot of them, the order gets split into multiple boxes according to my maximum shipping weight. This is fine.

 

If, however, the customer orders five big, heavy widgets X, these are heavy only fit one to a box. In such an instance, I don't want to have OSC be 'smart' and divide up this part of the order into small packages as in the code below from shipping.php

--------

if ($shipping_weight > SHIPPING_MAX_WEIGHT) { // Split into many boxes

$shipping_num_boxes = ceil($shipping_weight/SHIPPING_MAX_WEIGHT);

$shipping_weight = $shipping_weight/$shipping_num_boxes;

}

----------

Is there some way to 'turn off' package splitting for specific products (i.e., X and Y) in an order, but keep package splitting 'on' for the majority of the products (i.e., A, B, and C) in the order? Is there a contribution for this to anyone's knowledge?

 

Thanks in advance!

Share this post


Link to post
Share on other sites

Hi Tom,

 

There is but I'm not able to direct you the in the right direction just yet until I know I'm giving you the right answer and not an incorrect answer causing you to waste your time.

 

In the contribution I had installed, I had the option to check box called 'Ready to Ship - Product can be shipped in its own container."

 

Lets assume the weights are as follows in pounds; A 1; B 2; C 3; X 10; Y 11.

 

So if your product X and Y had this box checked, and A B and C are not checked and 2 are ordered of A B C X and Y, the shipping would be calculated as 12+10+10+11+11+ handling.

 

I'm pretty sure this is what you are trying to achieve.

 

Now just recently I have installed Multi Vendor Shipping (awesome contrib) but I have lost the above functionality. I'm actually going to try and work out all of the changes and apply them to the MVS shipping module sometime hopefully next week.

 

I'll work out which shipping module I used originally and let you know.

 

Regards,

 

Mark

Share this post


Link to post
Share on other sites
At the time I used http://www.oscommerce.com/community/contributions,1323 Version 1.03 with the Dimensional Support as well and installed it all.

 

You could try the latest version and see how that goes for you.

 

 

Same problem with UPS and USPS still splits packaging. Say someone ordere 140 cases it will change ti to 70 cases. is there a way to change this

 

if ($shipping_weight > SHIPPING_MAX_WEIGHT) { // Split into many boxes

$shipping_num_boxes = ceil($shipping_weight/SHIPPING_MAX_WEIGHT);

$shipping_weight = $shipping_weight/$shipping_num_boxes;

 

so that it just adds 1 box for each item orderd from the shop?

Share this post


Link to post
Share on other sites

I'm in a situaton that I ship items that weigh 7.125 each and 2 of them won't fit in 15kg, but 4 of them will just fit the maximum 31.5kg weight (which I label 30kg for convenience).

For some areas I can only ship 20kg in a box, so 3 items, really require a 20kg and a 10kg shipment and not 20kg and 2kg ...

 

Hope this will help you out. I think only these class changes are needed, but not 100% sure. So backup and give it a try ...

 

Below code is based on some UPS contribution, I've removed stuff (dimension) and changed (weight related) a little bit and my catalog/includes/classes.php looks like this:

 

 

<?php
/*
 $Id: shipping.php,v 1.23 2003/06/29 11:22:05 hpdl Exp $

 osCommerce, Open Source E-Commerce Solutions
 http://www.oscommerce.com

 Copyright (c) 2003 osCommerce

 Released under the GNU General Public License
*/

 class shipping {
var $modules;

// class constructor
function shipping($module = '') {
  global $language, $PHP_SELF;

  //BoF CB realistic number of boxes
  $this->items_qty = 0;
  //EoF CB realistic number of boxes

  if (defined('MODULE_SHIPPING_INSTALLED') && tep_not_null(MODULE_SHIPPING_INSTALLED)) {
	$this->modules = explode(';', MODULE_SHIPPING_INSTALLED);

	$include_modules = array();

	if ( (tep_not_null($module)) && (in_array(substr($module['id'], 0, strpos($module['id'], '_')) . '.' . substr($PHP_SELF, (strrpos($PHP_SELF, '.')+1)), $this->modules)) ) {
	  $include_modules[] = array('class' => substr($module['id'], 0, strpos($module['id'], '_')), 'file' => substr($module['id'], 0, strpos($module['id'], '_')) . '.' . substr($PHP_SELF, (strrpos($PHP_SELF, '.')+1)));
	} else {
	  reset($this->modules);
	  while (list(, $value) = each($this->modules)) {
		$class = substr($value, 0, strrpos($value, '.'));
		$include_modules[] = array('class' => $class, 'file' => $value);
	  }
	}

	for ($i=0, $n=sizeof($include_modules); $i<$n; $i++) {
	  include(DIR_WS_LANGUAGES . $language . '/modules/shipping/' . $include_modules[$i]['file']);
	  include(DIR_WS_MODULES . 'shipping/' . $include_modules[$i]['file']);

	  $GLOBALS[$include_modules[$i]['class']] = new $include_modules[$i]['class'];

//echo $include_modules[$i]['class'];

//print_r($GLOBALS);
	}
  }
}


function quote($method = '', $module = '') {
  global $total_weight, $shipping_weight, $shipping_quoted, $shipping_num_boxes;
  global $cart; //CB

  $quotes_array = array();

  if (is_array($this->modules)) {
	$shipping_quoted = '';
	$shipping_num_boxes = 1;
	$shipping_weight = $total_weight;

	if (SHIPPING_BOX_WEIGHT >= $shipping_weight*SHIPPING_BOX_PADDING/100) {
	  $shipping_weight = $shipping_weight+SHIPPING_BOX_WEIGHT;
	} else {
	  $shipping_weight = $shipping_weight + ($shipping_weight*SHIPPING_BOX_PADDING/100);
	}

	$productsArray = $cart->get_products();

	//BOF CB realistic number of boxes
	// sort $productsArray according to ready-to-ship (first) and not-ready-to-ship (last)
	usort($productsArray, "cmp");

	// Use packing algoritm to return the number of boxes we'll ship
	$boxesToShip = $this->packProducts($productsArray);
	// Quote for the number of boxes
	//EOF CB realistic number of boxes

	//hack for shipping.php estimates
	if (!$boxesToShip) {
		$boxesToShip[0]['current_weight'] = $total_weight;
	}

	$include_quotes = array();

	reset($this->modules);
	while (list(, $value) = each($this->modules)) {
//echo $value . '<br>';			
	  $class = substr($value, 0, strrpos($value, '.'));
//echo $class . '<br>' . $module;
	  if (tep_not_null($module)) {
		if ( ($module == $class) && ($GLOBALS[$class]->enabled) ) {
		  $include_quotes[] = $class;
		}
	  } elseif ($GLOBALS[$class]->enabled) {
//print_r($GLOBALS[$class]);
		$include_quotes[] = $class;
	  } else  {
//print_r($GLOBALS[$class]);
//			$include_quotes[] = $class;
		  }

	}
//print_r($boxesToShip);		

	$size = sizeof($include_quotes);
	for ($q=0; $q<$size; $q++) {
		  //BoF CB Realistic number of boxes
		$temp_cost = 0;
		$temp_weight = '';
		$temp_error = 0;

		for ($i = 0; $i < sizeof($boxesToShip); $i++) {
		   $shipping_weight = $boxesToShip[$i]['current_weight'];
		   $shipping_num_boxes = 1;

		   $quotes = $GLOBALS[$include_quotes[$q]]->quote($method);
		   if (is_array($quotes)) {
		   	 if (!$quotes['error']){
		   	   $temp_cost += $quotes['methods']['0']['cost'];
			   if ($temp_weight) $temp_weight .= ' + ';
			   $temp_weight .= $shipping_weight;
			  } else {
				  $temp_error = 1;
			  }
//print_r($quotes);
			 }
		}
		if (!$temp_error) {
		  $quotes['methods']['0']['cost'] = $temp_cost;
		  $quotes['methods']['0']['title'] = sizeof($boxesToShip). ' boxes - ' . $temp_weight .' kg';
		} else {
		  if ($quotes['error'] == '') { 
			  $quotes['error'] = 'An error occurred, probably one of the boxes is too big to ship this way'; 
		  }
		}

		$quotes_array[] = $quotes;

		//EoF CB Realistic number of boxes
	}
  }

  return $quotes_array;
}

function cheapest() {
  if (is_array($this->modules)) {
	$rates = array();

	reset($this->modules);
	while (list(, $value) = each($this->modules)) {
	  $class = substr($value, 0, strrpos($value, '.'));
	  if ($GLOBALS[$class]->enabled) {
		$quotes = $GLOBALS[$class]->quotes;
		for ($i=0, $n=sizeof($quotes['methods']); $i<$n; $i++) {
		  if (isset($quotes['methods'][$i]['cost']) && tep_not_null($quotes['methods'][$i]['cost'])
		  && ($quotes['methods'][$i]['cost'] > 0)
		  ) {
			$rates[] = array('id' => $quotes['id'] . '_' . $quotes['methods'][$i]['id'],
							 'title' => $quotes['module'] . ' (' . $quotes['methods'][$i]['title'] . ')',
							 'cost' => $quotes['methods'][$i]['cost']);
		  }
		}
	  }
	}

	$cheapest = false;
	for ($i=0, $n=sizeof($rates); $i<$n; $i++) {
	  if (is_array($cheapest)) {
		if ($rates[$i]['cost'] < $cheapest['cost']) {
		  $cheapest = $rates[$i];
		}
	  } else {
		$cheapest = $rates[$i];
	  }
	}

	return $cheapest;
  }
}

//********************************************
function getPackages() {
		$packages = array();
		$packages[] = array( 'id' => '1',				
							 'name' => 'Max 1 kg',
							 'description' => 'Box for 1 kg maximum',
							 'empty_weight' => '0.1',
							 'max_weight' => '1');

		$packages[] = array( 'id' => '2',				
							 'name' =>'Max 2 kg',
							 'description' => 'Box for 2 kg maximum',
							 'empty_weight' => '0.2',
							 'max_weight' => '2');

		$packages[] = array( 'id' => '5',				
							 'name' => 'Max 5 kg',
							 'description' => 'Box for 5 kg maximum',
							 'empty_weight' => '0.5',
							 'max_weight' => '5');

		$packages[] = array( 'id' => '10',				
							 'name' => 'Max 10 kg',
							 'description' => 'Box for 10 kg maximum',
							 'empty_weight' => '1',
							 'max_weight' => '10');

		$packages[] = array( 'id' => '15',				
							 'name' => 'Max 15 kg',
							 'description' => 'Box for 15 kg maximum',
							 'empty_weight' => '1.5',
							 'max_weight' => '15');

		$packages[] = array( 'id' => '20',				
							 'name' => 'Max 20 kg',
							 'description' => 'Box for 20 kg maximum',
							 'empty_weight' => '2',
							 'max_weight' => '20');

		$packages[] = array( 'id' => '30',				
							 'name' => 'Max 30 kg',
							 'description' => 'Box for 30 kg maximum',
							 'empty_weight' => '2',
							 'max_weight' => '31.5');

	return $packages;
}

//********************************

	function packProducts($productsArray) {
	// MOD FROM UPSXML
	// REMOVED ALL DIMENSIONAL DATA , ONLY KEPT WEIGHT
	// A very simple box packing algorithm. Given a list of packages, returns an array of boxes.
	// This algorithm is trivial. It works on the premise that you have selected boxes that fit your products, and that their volumes are resonable multiples
	// of the products they store. For example, if you sell CDs and these CDs are 5x5x0.5", your boxes should be 5x5x0.5 (1 CD mailer), 5x5x2.5 (5 CD mailer)
	// and 5x5x5 (10 CD mailer). No matter how many CDs a customer buys, this routine will always find the optimal packing.
	// Your milage may differ, depending on what variety of products you sell, and how they're boxed. I just made up this algorithm in a hurry to fill a small
	// niche. You are encouraged to find better algorithms. Better algorithms mean better packaging, resulting in higher quoting accuracy and less loss due to
	// inaccurate quoting. The algorithm proceeds as follows:
	// Get the first, smallest box, and try to put everything into it. If not all of it fits, try fitting it all into the next largest box. Keep increasing
	// the size of the box until no larger box can be obtained, then spill over into a second, smallest box. Once again, increase the box size until
	// everything fits, or spill over again. Repeat until everything is boxed. The cost of a box determines the order in which it is tried. There will definitely
	// be cases where it is cheaper to send two small packages rather than one larger one. In that case, you'll need a better algorithm.
	// Get the available packages and "prepare" empty boxes with weight and remaining volume counters. (Take existing box and add 'remaining_volume' and 'current_weight';
	$definedPackages = $this->getPackages();
	$emptyBoxesArray = array();
	for ($i = 0; $i < count($definedPackages); $i++) {
		$definedBox = $definedPackages[$i];
		$definedBox['current_weight'] = $definedBox['empty_weight'];
		$emptyBoxesArray[] = $definedBox;
	}
	$packedBoxesArray = array();
	$currentBox = NULL;
	// Get the product array and expand multiple qty items.
	$productsRemaining = array();
	for ($i = 0; $i < count($productsArray); $i++) {
		$product = $productsArray[$i];
		for ($j = 0; $j < $productsArray[$i]['quantity']; $j++) {
			$productsRemaining[] = $product;
		}
	}
	// Worst case, you'll need as many boxes as products ordered.
	while (count($productsRemaining)) {
		// Immediately set aside products that are already packed and ready.

// CB Not supported, but might be usefull for e.g. bundles
/*			if ($productsRemaining[0]['ready_to_ship'] == '1') {
			$packedBoxesArray[] = array (
			'length' => $productsRemaining[0]['length'],
			'width' => $productsRemaining[0]['width'],
			'height' => $productsRemaining[0]['height'],
			'current_weight' => $productsRemaining[0]['weight']);
			$productsRemaining = array_slice($productsRemaining, 1);
			continue;
		}
*/
		//Cylcle through boxes, increasing box size if all doesn't fit.
		if (count($emptyBoxesArray) == 0) {
			print_r("ERROR: No boxes to ship unpackaged product<br>");
			break;
		}
		for ($b = 0; $b < count($emptyBoxesArray); $b++) {
			$currentBox = $emptyBoxesArray[$b];
			//Try to fit each product in box
			for ($p = 0; $p < count($productsRemaining); $p++) {

				if ($this->fitsInBox($productsRemaining[$p], $currentBox)) {
					//It fits. Put it in the box.
					$currentBox = $this->putProductInBox($productsRemaining[$p], $currentBox);
					if ($p == count($productsRemaining) - 1) {
						$packedBoxesArray[] = $currentBox;
						$productsRemaining = array_slice($productsRemaining, $p + 1);
						break 2;
					}
				} else {
					if ($b == count($emptyBoxesArray) - 1) {
						//We're at the largest box already, and it's full. Keep what we've packed so far and get another box.
						$packedBoxesArray[] = $currentBox;
//							$productsRemaining = array_slice($productsRemaining, $p + 1);
						$productsRemaining = array_slice($productsRemaining, $p);
						break 2;
					}
					// Not all of them fit. Stop packing remaining products and try next box.
					break;
				}
			}
		}
	}

	return $packedBoxesArray;
}

//*****************************
function fitsInBox($product, $box) {
	if ($box['max_weight'] == 0 || ($box['current_weight'] + $product['weight'] <= $box['max_weight'])) {
		return true;
	}
	return false;
}

//***********************************
function putProductInBox($product, $box) {
	$box['products'][] = $product;
	$box['current_weight'] += $product['weight'];
	return $box;
}

//******************************************************************

 }
?>


KEEP CALM AND CARRY ON

I do not use the responsive bootstrap version since i coded my responsive version earlier, but i have bought every 28d of code package to support burts effort and keep this forum alive (albeit more like on life support).

So if you are still here ? What are you waiting for ?!

 

Find the most frequent unique errors to fix:

grep "PHP" php_error_log.txt | sed "s/^.* PHP/PHP/g" |grep "line" |sort | uniq -c | sort -r > counterrors.txt

Share this post


Link to post
Share on other sites

I tried the above code, and worked it and worked it but can not get it to work.

 

Has anyone ever resolved the ability to tag products as ready to ship or package individually that will work with FedEx real time quotes 2.04 by Steve Fatula?

 

Fedex quotes based on a the simple osc max weight per package is messed up and rarely accurate

 

Any help is greatly apprediated.


-Dave

Share this post


Link to post
Share on other sites
Has anyone ever resolved the ability to tag products as ready to ship or package individually that will work with FedEx real time quotes 2.04 by Steve Fatula?

 

I'd like to know this as well !

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×