[ Index ]

PHP Cross Reference of osCMax 2.0.4

title

Body

[close]

/includes/classes/ -> packing.php (source)

   1  <?php
   2  /*
   3    $Id: packing.php, v1.0 2007/12/24 JanZ Exp $
   4  
   5    osCommerce, Open Source E-Commerce Solutions
   6    http://www.oscommerce.com
   7  
   8    Copyright (c) 2007 osCommerce
   9  
  10    Released under the GNU General Public License
  11    Adapted from the UPSXML contribution
  12    
  13    dimensions support = 0: no dimensions support
  14    dimensions support = 1: ready-to-ship support only
  15    dimensions support = 2: full dimensions support
  16  */
  17  
  18    class packing {
  19      var $item, $totalWeight, $items_qty;
  20  
  21      function packing() {
  22      global $shipping_weight, $shipping_num_boxes, $total_weight, $boxcount, $cart, $order;
  23  
  24          $this->unit_weight = SHIPPING_UNIT_WEIGHT;
  25          $this->unit_length = SHIPPING_UNIT_LENGTH;
  26          $this->items_qty = 0;
  27          $this->totalWeight = 0;
  28          $this->item = array();
  29  
  30          
  31          if (defined('SHIPPING_DIMENSIONS_SUPPORT') && SHIPPING_DIMENSIONS_SUPPORT == 'Ready-to-ship only') {
  32            $this->dimensions_support = 1;
  33          } elseif (defined('SHIPPING_DIMENSIONS_SUPPORT') && SHIPPING_DIMENSIONS_SUPPORT == 'With product dimensions') {
  34            $this->dimensions_support = 2;
  35          } else {
  36            $this->dimensions_support = 0;
  37          }
  38  
  39          if (defined('SHIPPING_STORE_BOXES_USED') && SHIPPING_STORE_BOXES_USED == 'true') {
  40            $this->store_boxes_used = 1; 
  41          } else {
  42            $this->store_boxes_used = 0;
  43          }
  44          
  45          if (method_exists($cart, 'get_products_for_packaging') ) {
  46            $productsArray = $cart->get_products_for_packaging();
  47          } else {
  48            $productsArray = $cart->get_products();
  49          }
  50          if ($this->dimensions_support > 0) {
  51            $productsArray = $this->more_dimensions_to_productsArray($productsArray);
  52            // debug only
  53            // echo '<pre>Products to pack:<br>';
  54            // print_r($productsArray);
  55           // exit; 
  56          } 
  57  
  58          if ($this->dimensions_support == 2) {
  59              // sort $productsArray according to ready-to-ship (first) and not-ready-to-ship (last)
  60              usort($productsArray, ready_to_shipCmp);
  61              // Use packing algorithm to return the number of boxes we'll ship
  62              $boxesToShip = $this->packProducts($productsArray);
  63              /* echo '<pre>Boxes to ship:<br>';
  64               print_r($boxesToShip);
  65               exit; */
  66              if ($this->store_boxes_used == 1) {
  67              $storeBoxesToShip = base64_encode(serialize($boxesToShip));
  68              $storeQueryArray = array('date' => 'now()', 'customers_id' => $_SESSION['customer_id'], 'boxes' => $storeBoxesToShip);
  69              tep_db_perform(TABLE_UPS_BOXES_USED, $storeQueryArray);
  70              }
  71              // Quote for the number of boxes
  72              for ($i = 0; $i < count($boxesToShip); $i++) {
  73                  $this->_addItem($boxesToShip[$i]['length'], $boxesToShip[$i]['width'], $boxesToShip[$i]['height'], $boxesToShip[$i]['current_weight'], $boxesToShip[$i]['price']);
  74                  $this->totalWeight += $boxesToShip[$i]['current_weight'];
  75              }
  76          } elseif ($this->dimensions_support == 1) {
  77              $this->totalWeight = 0;
  78              $total_non_ready_to_ship_weight = 0;
  79              $total_non_ready_to_ship_value = 0;
  80              // sort $productsArray according to ready-to-ship (first) and not-ready-to-ship (last)
  81              usort($productsArray, ready_to_shipCmp);
  82              $non_ready_to_shipArray = array();
  83              // walk through the productsArray, separate the items ready-to-ship and add them to
  84              // the items (boxes) list, add the weight to the totalWeight
  85              // and add the other items to a separate array
  86              for ($i = 0; $i < count($productsArray); $i++) {
  87                  if ($productsArray[$i]['ready_to_ship'] == '1') {
  88                      for ($z = 0 ; $z < $productsArray[$i]['quantity']; $z++) {
  89                          $this->_addItem($productsArray[$i]['length'], $productsArray[$i]['width'], $productsArray[$i]['height'], $productsArray[$i]['weight'], $productsArray[$i]['final_price']);
  90                          $this->totalWeight += $productsArray[$i]['weight'];
  91                      } // end for ($z = 0 ; $z < $productsArray[$i]['quantity']; $z++)
  92                  } // end if($productsArray['ready_to_ship'] == '1')
  93                  else {
  94                      $non_ready_to_shipArray[] = $productsArray[$i];
  95                  }
  96              } // end for ($i = 0; $i < count($productsArray); $i++)
  97              // Ready_to_ship items out of the way, now assess remaining weight and remaining value of products
  98  
  99              for ($x = 0 ; $x < count($non_ready_to_shipArray) ; $x++) {
 100                  $total_non_ready_to_ship_weight += ($non_ready_to_shipArray[$x]['weight'] * $non_ready_to_shipArray[$x]['quantity']);
 101                  $total_non_ready_to_ship_value += ($non_ready_to_shipArray[$x]['final_price'] * $non_ready_to_shipArray[$x]['quantity']);
 102              } // end for ($x = 0 ; count($non_ready_to_shipArray) ; $x++)
 103        
 104              if (tep_not_null($non_ready_to_shipArray)) {
 105                  // adapted code from includes/classes/shipping.php
 106                  $shipping_non_ready_to_ship_boxes = 1;
 107                  $shipping_non_ready_to_ship_weight = $total_non_ready_to_ship_weight;
 108                  if (SHIPPING_BOX_WEIGHT >= $total_non_ready_to_ship_weight*SHIPPING_BOX_PADDING/100) {
 109                    $total_non_ready_to_ship_weight = $total_non_ready_to_ship_weight+SHIPPING_BOX_WEIGHT;
 110                  } else {
 111                    $total_non_ready_to_ship_weight += $total_non_ready_to_ship_weight*SHIPPING_BOX_PADDING/100;
 112                  }
 113                  if ($total_non_ready_to_ship_weight > SHIPPING_MAX_WEIGHT) { // Split into many boxes
 114                      $shipping_non_ready_to_ship_boxes = ceil($total_non_ready_to_ship_weight/SHIPPING_MAX_WEIGHT);
 115                      $shipping_non_ready_to_ship_weight = round($total_non_ready_to_ship_weight/$shipping_non_ready_to_ship_boxes,1);
 116                  }
 117                  // end adapted code from includes/classes/shipping.php
 118                  // weight and number of boxes of non-ready-to-ship is determined, now add them to the items list
 119                  for ($y = 0; $y < $shipping_non_ready_to_ship_boxes ; $y++) {
 120                      $this->_addItem(0, 0, 0, $shipping_non_ready_to_ship_weight, number_format(($total_non_ready_to_ship_value/$shipping_non_ready_to_ship_boxes), 2, '.', ''));
 121                      $this->totalWeight += $shipping_non_ready_to_ship_weight;
 122                  } // end for ($y = 0; $y < $shipping_non_ready_to_ship_boxes ; $y++)
 123              } // end if (tep_not_null($non_ready_to_shipArray))
 124         } // if/else ($this->dimensions_support == '#')
 125      } // end function packing($dimensions_support = '0')
 126      
 127      //********************************************
 128      function _addItem($length, $width, $height, $weight, $price = 0 ) {
 129          // Add box or item to shipment list. Round weights to 1 decimal places.
 130          if ((float)$weight < 1.0) {
 131              $weight = 1;
 132          } else {
 133              $weight = round($weight, 1);
 134          }
 135          $index = $this->items_qty;
 136          $this->item[$index]['item_length'] = ($length ? (string)$length : '0' );
 137          $this->item[$index]['item_width'] = ($width ? (string)$width : '0' );
 138          $this->item[$index]['item_height'] = ($height ? (string)$height : '0' );
 139          $this->item[$index]['item_weight'] = ($weight ? (string)$weight : '0' );
 140          $this->item[$index]['item_price'] = $price;
 141          $this->items_qty++;
 142      }
 143  
 144      //********************
 145      function getPackagesByVol() {
 146          $packages = array();
 147          $packages_query = tep_db_query("select *, (package_length * package_width * package_height) as volume from " . TABLE_PACKAGING . " order by volume");
 148          $counter = 0;
 149          while ($package = tep_db_fetch_array($packages_query)) {
 150              $packages[] = array(
 151              'id' => $package['package_id'],
 152              'name' => $package['package_name'],
 153              'description' => $package['package_description'],
 154              'length' => $package['package_length'],
 155              'width' => $package['package_width'],
 156              'height' => $package['package_height'],
 157              'empty_weight' => $package['package_empty_weight'],
 158              'max_weight' => $package['package_max_weight'],
 159              'volume' => $package['volume']);
 160  // sort dimensions from low to high, used in the function fitsInBox
 161              $dimensions = array($package['package_length'], $package['package_width'], $package['package_height']);
 162              sort($dimensions);
 163                foreach($dimensions as $key => $value) {
 164                  if ($key == 0 ) { $packages[$counter]['x'] = $value; }
 165                  if ($key == 1 ) { $packages[$counter]['y'] = $value; }
 166                  if ($key == 2 ) { $packages[$counter]['z'] = $value; }
 167                }
 168              $counter++;
 169          }
 170          return $packages;
 171      }
 172  
 173      //********************************
 174      function packProducts($productsArray) {
 175          $definedPackages = $this->getPackagesByVol();
 176          $emptyBoxesArray = array();
 177          for ($i = 0; $i < count($definedPackages); $i++) {
 178              $definedBox = $definedPackages[$i];
 179              $definedBox['remaining_volume'] = $definedBox['volume'];
 180              $definedBox['current_weight'] = $definedBox['empty_weight'];
 181              $emptyBoxesArray[] = $definedBox;
 182          }
 183            if (count($emptyBoxesArray) == 0) {
 184               print("ERROR: No boxes to ship unpackaged product<br />\n");
 185               break;
 186            }
 187  
 188          $packedBoxesArray = array();
 189          $currentBox = NULL;
 190          $index_of_largest_box = count($emptyBoxesArray)-1;
 191          // Get the product array and expand multiple qty items.
 192          $productsRemaining = array();
 193          for ($i = 0; $i < count($productsArray); $i++) {
 194            $product = $productsArray[$i];
 195              // sanity checks on the product, no need for ready-to-ship items
 196              if ((int)$product['ready_to_ship'] == 0) {
 197                $product['ready_to_ship'] = '1';
 198                   for ($x = 0; $x <= $index_of_largest_box; $x++) {
 199                     if ($this->fitsInBox($product, $emptyBoxesArray[$x])) {
 200                       $product['ready_to_ship'] = '0';
 201                       $product['largest_box_it_will_fit'] = $x;
 202                     } 
 203                   } // end for ($x = 0; $x <= $index_of_largest_box; $x++) 
 204              } // end if ((int)$product['ready_to_ship'] == 0)
 205  
 206              for ($j = 0; $j < $productsArray[$i]['quantity']; $j++) {
 207                  $productsRemaining[] = $product;
 208              }
 209          } // end for ($i = 0; $i < count($productsArray); $i++)
 210          // make sure the products that did not fit the largest box and are now set as ready-to-ship
 211          // are out of the way as soon as possible
 212          usort($productsRemaining, ready_to_shipCmp);
 213          // Worst case, you'll need as many boxes as products ordered
 214          $index_of_largest_box_to_use = count($emptyBoxesArray) -1;
 215          while (count($productsRemaining)) {
 216              // Immediately set aside products that are already packed and ready.
 217              if ($productsRemaining[0]['ready_to_ship'] == '1') {
 218                  $packedBoxesArray[] = array (
 219                  'length' => $productsRemaining[0]['length'],
 220                  'width' => $productsRemaining[0]['width'],
 221                  'height' => $productsRemaining[0]['height'],
 222                  'current_weight' => $productsRemaining[0]['weight'],
 223                  'price' => $productsRemaining[0]['final_price']);
 224                  $productsRemaining = array_slice($productsRemaining, 1);
 225                  continue;
 226              }
 227              // Cycle through boxes, increasing box size if all doesn't fit
 228              // but if the remaining products only fit in a box of smaller size, use that one to pack it away
 229              for ($b = 0; $b < count($emptyBoxesArray) && tep_not_null($productsRemaining); $b++) {
 230                  $result = $this->fitProductsInBox($productsRemaining, $emptyBoxesArray[$b], $packedBoxesArray, $b, $index_of_largest_box_to_use);
 231                  $packedBoxesArray = $result['packed_boxes'];
 232                  $productsRemaining = $result['remaining'];
 233                  if (isset($result['index_of_largest_box_to_use']) && $result['index_of_largest_box_to_use'] >= 0 ) {
 234                    $index_of_largest_box_to_use = $result['index_of_largest_box_to_use'];
 235                  }
 236              }
 237          } // end while
 238  
 239          return $packedBoxesArray;
 240      }
 241  
 242      //*****************************
 243      function fitsInBox($product, $box) {
 244          if ($product['x'] > $box['x'] || $product['y'] > $box['y'] || $product['z'] > $box['z']) {
 245              return false;
 246          } 
 247  
 248          if ($product['volume'] <= $box['remaining_volume']) {
 249              if ($box['max_weight'] == 0 || ($box['current_weight'] + $product['weight'] <= $box['max_weight'])) {
 250                  return true;
 251              }
 252          }
 253          return false;
 254      }
 255  
 256      //***********************************
 257      function putProductInBox($product, $box) {
 258          $box['remaining_volume'] -= $product['volume'];
 259          $box['products'][] = $product;
 260          $box['current_weight'] += $product['weight'];
 261          $box['price'] += $product['final_price'];
 262          return $box;
 263      } 
 264      //*********************    
 265      function fitProductsInBox($productsRemaining, $emptyBox, $packedBoxesArray, $box_no, $index_of_largest_box) { 
 266          $currentBox = $emptyBox;
 267          $productsRemainingSkipped = array();
 268          $productsRemainingNotSkipped = array();
 269          $largest_box_in_skipped_products = -1;
 270          // keep apart products that will not fit this box anyway
 271          for ($p = 0; $p < count($productsRemaining); $p++) {
 272            if ($productsRemaining[$p]['largest_box_it_will_fit'] < $box_no) {
 273              $productsRemainingSkipped[] = $productsRemaining[$p];
 274              // check on skipped products: if they will not fit in the largest box
 275              // the $index_of_largest_box should be the one they *will* fit
 276              // otherwise the packing algorithm gets stuck in a loop
 277              if ($productsRemaining[$p]['largest_box_it_will_fit'] > $largest_box_in_skipped_products) {
 278                $largest_box_in_skipped_products = $productsRemaining[$p]['largest_box_it_will_fit'];
 279              }
 280            } else {
 281              $productsRemainingNotSkipped[] = $productsRemaining[$p];
 282            }
 283          }
 284  
 285          unset($productsRemaining);
 286          $productsRemaining = $productsRemainingNotSkipped;
 287          unset($productsRemainingNotSkipped);
 288          if (count($productsRemaining) == 0) {
 289            // products remaining are the ones that will not fit this box (productsRemaimingSkipped)
 290              $result_array = array('remaining' => $productsRemainingSkipped, 'box_no' => $box_no, 'packed_boxes' => $packedBoxesArray, 'index_of_largest_box_to_use' => $largest_box_in_skipped_products);
 291              return ($result_array);
 292          }
 293  
 294          //Try to fit each product that can fit in box
 295          for ($p = 0; $p < count($productsRemaining); $p++) {
 296              if ($this->fitsInBox($productsRemaining[$p], $currentBox)) {
 297                  //It fits. Put it in the box.
 298                  $currentBox = $this->putProductInBox($productsRemaining[$p], $currentBox);
 299                  if ($p == count($productsRemaining) - 1) {
 300                      $packedBoxesArray[] = $currentBox;
 301                      $productsRemaining = array_slice($productsRemaining, $p + 1);
 302                      $productsRemaining = array_merge($productsRemaining, $productsRemainingSkipped);
 303  
 304                      $result_array = array('remaining' => $productsRemaining, 'box_no' => $box_no, 'packed_boxes' => $packedBoxesArray);
 305                      return ($result_array);
 306                  }
 307              } else {
 308                  if ($box_no == $index_of_largest_box) {
 309                      //We're at the largest box already, and it's full. Keep what we've packed so far and get another box.
 310                      $packedBoxesArray[] = $currentBox;
 311                      $productsRemaining = array_slice($productsRemaining, $p);
 312                      $productsRemaining = array_merge($productsRemaining, $productsRemainingSkipped);
 313                      $result_array = array('remaining' => $productsRemaining, 'box_no' => $box_no, 'packed_boxes' => $packedBoxesArray);
 314                      return ($result_array);
 315                  }
 316                  // Not all of them fit. Stop packing remaining products and try next box.
 317                  $productsRemaining = array_merge($productsRemaining, $productsRemainingSkipped);
 318                  $result_array = array('remaining' => $productsRemaining, 'box_no' => $box_no, 'packed_boxes' => $packedBoxesArray);
 319                  return ($result_array);
 320              } // end else
 321          } // end for ($p = 0; $p < count($productsRemaining); $p++)
 322      } // end function fitProductsInBox
 323      
 324  // ******************************
 325    function more_dimensions_to_productsArray($productsArray) {
 326      $counter = 0;
 327        foreach ($productsArray as $key => $product) {
 328          // in case by accident or by choice length, width or height is not set
 329          // we will estimate it by using a set density and the product['weight'] variable
 330          // will only be used in the check for whether it fits the largest box
 331          // after that it will already be set, if product['weight'] is set at least
 332          if ($product['length'] == 0 || $product['width'] == 0 || $product['height'] == 0) {
 333              $density = 0.7;
 334              if ($this->unit_length == 'CM') {
 335                  $product['length']=$product['width']=$product['height']= round(10*(pow($product['weight']/$density, 1/3)),1);
 336              } else {
 337                  // non-metric: inches and pounds
 338                  $product['length']=$product['width']=$product['height']= round(pow($product['weight']*27.67/$density, 1/3),1);
 339              }
 340          } // end if ($product['length'] == 0 || $product['width'] == 0 etc.
 341  // sort dimensions from low to high, used in the function fitsInBox
 342          $dimensions = array($product['length'], $product['width'], $product['height']);
 343          sort($dimensions);
 344            foreach($dimensions as $key => $value) {
 345              if ($key == 0 ) { $productsArray[$counter]['x'] = $value; }
 346              if ($key == 1 ) { $productsArray[$counter]['y'] = $value; }
 347              if ($key == 2 ) { $productsArray[$counter]['z'] = $value; }
 348             }
 349          $productsArray[$counter]['volume'] = $product['length'] * $product['width'] * $product['height'];
 350          $counter++;
 351    } // end foreach ($productsArray as $key => $product)
 352      return($productsArray);
 353    }
 354  
 355    function getPackedBoxes() {
 356      return $this->item;
 357    }
 358  
 359    function getTotalWeight() {
 360      return $this->totalWeight;
 361    }
 362  
 363    function getNumberOfBoxes() {
 364      return $this->items_qty;
 365    }
 366  
 367    } // end class packing
 368  // ******************************
 369  function ready_to_shipCmp( $a, $b) {
 370      if ( $a['ready_to_ship'] == $b['ready_to_ship'] )
 371      return 0;
 372      if ( $a['ready_to_ship'] > $b['ready_to_ship'] )
 373      return -1;
 374      return 1;
 375  }
 376  ?>


Generated: Fri Jan 1 13:43:16 2010 Cross-referenced by PHPXref 0.7