diff --git a/lowcostexpress.php b/lowcostexpress.php index 1519bdd..94be631 100755 --- a/lowcostexpress.php +++ b/lowcostexpress.php @@ -64,7 +64,11 @@ class LowCostExpress extends CarrierModule 'MOD_LCE_PRICE_ROUND_INCREMENT', 'MOD_LCE_PRICE_SURCHARGE_STATIC', 'MOD_LCE_PRICE_SURCHARGE_PERCENT', - 'MOD_LCE_PRICE_TAX_RULES', ); + 'MOD_LCE_PRICE_TAX_RULES', + 'MOD_LCE_MAX_REAL_WEIGHT', + 'MOD_LCE_MAX_VOL_WEIGHT', + 'MOD_LCE_FORCE_WEIGHT_DIMS_TABLE', + ); public static $mandatory_settings = array('MOD_LCE_API_LOGIN', 'MOD_LCE_API_PASSWORD', @@ -90,7 +94,7 @@ public function __construct() { $this->name = 'lowcostexpress'; $this->tab = 'shipping_logistics'; - $this->version = '1.0.10'; + $this->version = '1.0.11'; $this->author = 'MY FLYING BOX SAS'; parent::__construct(); @@ -634,6 +638,9 @@ private function _displayContent($message) 'MOD_LCE_PRICE_SURCHARGE_STATIC' => Configuration::get('MOD_LCE_PRICE_SURCHARGE_STATIC'), 'MOD_LCE_PRICE_SURCHARGE_PERCENT' => Configuration::get('MOD_LCE_PRICE_SURCHARGE_PERCENT'), 'MOD_LCE_PRICE_TAX_RULES' => Configuration::get('MOD_LCE_PRICE_TAX_RULES'), + 'MOD_LCE_MAX_REAL_WEIGHT' => Configuration::get('MOD_LCE_MAX_REAL_WEIGHT'), + 'MOD_LCE_MAX_VOL_WEIGHT' => Configuration::get('MOD_LCE_MAX_VOL_WEIGHT'), + 'MOD_LCE_FORCE_WEIGHT_DIMS_TABLE' => Configuration::get('MOD_LCE_FORCE_WEIGHT_DIMS_TABLE'), )); } diff --git a/models/LceQuote.php b/models/LceQuote.php index e66333d..73a291b 100644 --- a/models/LceQuote.php +++ b/models/LceQuote.php @@ -73,6 +73,8 @@ public static function getLatestForCart($cart, $timelimit = true) public static function parcelDataFromCart($cart) { $parcels = array(); + $articles = array(); + $ignore_dimensions = false; $missing_dimension = false; $missing_dimensions_details = ''; $ignored_articles = 0; @@ -80,6 +82,7 @@ public static function parcelDataFromCart($cart) // First, we test whether we have dimensions for all articles. If not, // We fall back to the weight/dimensions table. // If some articles have dimensions and other have no dimensions at all (no weight either), then we totally ignore them + // The following loop initializes an array of articles with dimensions, that we can use later to determine final pack-list. foreach ($cart->getProducts() as $product) { $total_articles++; // Using the same strategy as Cart::getTotalWeight() for weight extraction @@ -91,9 +94,9 @@ public static function parcelDataFromCart($cart) // Some carriers check that length is long enough, but don't care much about other dimensions... $dims = array( - $product['depth'], - $product['width'], - $product['height'] + (int)$product['depth'], + (int)$product['width'], + (int)$product['height'] ); sort($dims); @@ -101,17 +104,22 @@ public static function parcelDataFromCart($cart) $width = $dims[1]; $height = $dims[0]; - // This product has no dimension at all. If other products have dimensions, then this - // one will be ignored. - // Otherwise, we will fall back to the correspondance table. if ($length <= 0 && $width <= 0 && $height <= 0 && $weight <= 0) { + // This product has no dimension at all, it will be ignored alltogether. $ignored_articles++; continue; + } else if (Configuration::get('MOD_LCE_FORCE_WEIGHT_DIMS_TABLE') ) { + // Forcing use of weight only + $ignore_dimensions = true; } else if ($length <= 0 || $width <= 0 || $height <= 0 || $weight <= 0) { + // Some dimensions are missing, so whatever the situation for other products, + // we will not use real dimensions for parcel simulation, but fall back + // to standard weight/dimensions correspondance table. + $ignore_dimensions = true; $missing_dimension = true; - $missing_dimensions_details .= "$length x $width x $height - $weight kg "; - break; + $missing_dimensions_details .= "$length x $width x $height - $weight kg "; // Used for debug output below. } else { + // We have all dimensions for this product. // Some carriers do not accept any parcel below 1cm on any side (DHL). Forcing 1cm mini dimension. if ($length < 1) { $length = 1; @@ -122,30 +130,23 @@ public static function parcelDataFromCart($cart) if ($height < 1) { $height = 1; } - // The same product can be added multiple times. We simulate one parcel per article. - for ($i=0; $i<$product['cart_quantity']; $i++) { - $parcels[] = array( - 'length' => $length, - 'height' => $height, - 'width' => $width, - 'weight' => $weight, - ); - } + } + // The same product can be added multiple times. We save articles unit by unit. + for ($i=0; $i<$product['cart_quantity']; $i++) { + $articles[] = array( + 'length' => $length, + 'height' => $height, + 'width' => $width, + 'weight' => $weight, + ); } } - // Some dimension was missing, we use the old method and override the - // $parcels array. Same if we have ignored all articles because they - // have no dimension set at all... - if ($missing_dimension || ($ignored_articles == $total_articles)) { - // if ($missing_dimension) { - // PrestaShopLogger::addLog("MFB LceQuote: falling back to weight/dimensions table do to missing dimensions ($missing_dimensions_details).", 1, null, 'Cart', (int)$cart->id, true); - // } else { - // PrestaShopLogger::addLog("MFB LceQuote: falling back to weight/dimensions as no article had dimensions set.", 1, null, 'Cart', (int)$cart->id, true); - // } + // If all articles were ignored, we just do our best with what we have, which means not much! + if ($ignored_articles == $total_articles) { $weight = round($cart->getTotalWeight($cart->getProducts()), 3); if ($weight <= 0) { - $weight = 0.1; + $weight = 0.1; // As ignored artices do not have weight, this will probably be the weight used. } $dimension = LceDimension::getForWeight($weight); $parcels = array( @@ -155,6 +156,144 @@ public static function parcelDataFromCart($cart) 'weight' => $weight, ), ); + } else if ($ignore_dimensions) { + // if ($missing_dimension) { + // PrestaShopLogger::addLog("MFB LceQuote: falling back to weight/dimensions table do to missing dimensions ($missing_dimensions_details).", 1, null, 'Cart', (int)$cart->id, true); + // } else { + // PrestaShopLogger::addLog("MFB LceQuote: falling back to weight/dimensions as no article had dimensions set.", 1, null, 'Cart', (int)$cart->id, true); + // } + + // In this case, two possibilities: + // - if we have a maximum weight per package set in the config, we + // have to spread articles in as many packages as needed. + // - otherwise, just use the default strategy: total weight + corresponding dimension based on table + $max_real_weight = Configuration::get('MOD_LCE_MAX_REAL_WEIGHT'); + if ($max_real_weight && $max_real_weight > 0) { + // We must now spread every article in virtual parcels, respecting + // the defined maximum real weight. + $parcels = array(); + foreach($articles as $key => $article) { + if (count($parcels) == 0 || bccomp($article['weight'], $max_real_weight, 3) > 0) { + // If first article, initialize new parcel. + // If article has a weight above the limit, it gets its own package. + $parcels[] = array('weight' => $article['weight']); + continue; + } else { + foreach($parcels as &$parcel) { + // Trying to fit the article in an existing parcel. + $cumulated_weight = bcadd($parcel['weight'], $article['weight'], 3); + if ($cumulated_weight <= $max_real_weight) { + $parcel['weight'] = $cumulated_weight; + unset($article); // Security, to avoid double treatment of the same article. + break; + } + } + unset($parcel); // Unsetting reference to last $parcel of the loop, to avoid any bad surprise later! + + // If we could not fit the article in any existing package, + // we simply initialize a new one, and that's it. + if (isset($article)) { + $parcels[] = array('weight' => $article['weight']); + continue; + } + } + } + // Article weight has been spread to relevant parcels. Now we must + // define parcel dimensions, based on weight. + foreach($parcels as &$parcel) { + // First, ensuring the weight is not zero! + if ($parcel['weight'] <= 0) { + $parcel['weight'] = 0.1; + } + $dimension = LceDimension::getForWeight($parcel['weight']); + $parcel['length'] = $dimension->length; + $parcel['height'] = $dimension->height; + $parcel['width'] = $dimension->width; + } + unset($parcel); // Unsetting reference to last $parcel of the loop, to avoid any bad surprise later! + + // Our parcels are now ready. + + } else { + // Simple case: no dimensions, and no maximum real weight. + // We just take the total weight and find the corresponding dimensions. + $weight = round($cart->getTotalWeight($cart->getProducts()), 3); + if ($weight <= 0) { + $weight = 0.1; + } + $dimension = LceDimension::getForWeight($weight); + $parcels = array( + array('length' => $dimension->length, + 'height' => $dimension->height, + 'width' => $dimension->width, + 'weight' => $weight, + ), + ); + } + } else { + // We have dimensions for all articles, so this is a bit more complex. + // We proceed like above, but we also take into account the dimensions of the articles, + // in two ways: to determine the dimensions of the packages, and to check, on this basis, the max + // volumetric weight of the package. + + $max_real_weight = Configuration::get('MOD_LCE_MAX_REAL_WEIGHT'); + $max_volumetric_weight = Configuration::get('MOD_LCE_MAX_VOL_WEIGHT'); + + if ($max_real_weight && $max_real_weight > 0 && $max_volumetric_weight && $max_volumetric_weight > 0) { + // We must now spread every article in virtual parcels, respecting + // the defined maximum real weight and volumetric weight, based on dimensions. + $parcels = array(); + foreach($articles as $key => $article) { + $article_volumetric_weight = $article['length']*$article['width']*$article['height']/5000; + if (count($parcels) == 0 || bccomp($article['weight'], $max_real_weight, 3) >= 0 || bccomp($article_volumetric_weight, $max_volumetric_weight, 3) >= 0) { + // If first article, initialize new parcel. + // If article has a weight above the limit, it gets its own package. + $parcels[] = array( + 'length' => $article['length'], + 'width' => $article['width'], + 'height' => $article['height'], + 'weight' => $article['weight'] + ); + continue; + } else { + foreach($parcels as &$parcel) { + // Trying to fit the article in an existing parcel. + $cumulated_weight = bcadd($parcel['weight'], $article['weight'], 3); + $new_parcel_length = max($parcel['length'], $article['length']); + $new_parcel_width = max($parcel['width'], $article['width']); + $new_parcel_height = (int)$parcel['height'] + (int)$article['height']; + $new_parcel_volumetric_weight = (int)$new_parcel_length*(int)$new_parcel_width*(int)$new_parcel_height/5000; + + if (bccomp($cumulated_weight, $max_real_weight, 3) <= 0 && bccomp($new_parcel_volumetric_weight, $max_volumetric_weight, 3) <= 0) { + $parcel['weight'] = $cumulated_weight; + $parcel['length'] = $new_parcel_length; + $parcel['width'] = $new_parcel_width; + $parcel['height'] = $new_parcel_height; + + unset($article); // Security, to avoid double treatment of the same article. + break; + } + } + unset($parcel); // Unsetting reference to last $parcel of the loop, to avoid any bad surprise later! + + // If we could not fit the article in any existing package, + // we simply initialize a new one, and that's it. + if (isset($article)) { + $parcels[] = array( + 'length' => $article['length'], + 'width' => $article['width'], + 'height' => $article['height'], + 'weight' => $article['weight'], + ); + continue; + } + } + } + } else { + // If we are here, it means we do not want to spread articles in parcels of specific characteristics. + // So we just have one parcel per article. + $parcels = $articles; + } } return $parcels; } diff --git a/upgrade/install-1.0.11.php b/upgrade/install-1.0.11.php new file mode 100644 index 0000000..5f21e76 --- /dev/null +++ b/upgrade/install-1.0.11.php @@ -0,0 +1,37 @@ + + * @copyright 2017 MyFlyingBox + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + * @version 1.0 + * + */ + +if (!defined('_PS_VERSION_')) { + exit; +} + +function upgrade_module_1_0_11($module) +{ + # Added a new option. Set to NULL by default. + Configuration::updateValue('MOD_LCE_MAX_REAL_WEIGHT', null); + Configuration::updateValue('MOD_LCE_MAX_VOL_WEIGHT', null); + Configuration::updateValue('MOD_LCE_FORCE_WEIGHT_DIMS_TABLE', false); + + return true; +} diff --git a/views/templates/admin/settings.bootstrap.tpl b/views/templates/admin/settings.bootstrap.tpl index d5805f4..3c78f7e 100644 --- a/views/templates/admin/settings.bootstrap.tpl +++ b/views/templates/admin/settings.bootstrap.tpl @@ -232,7 +232,27 @@
{l s='If checked, the price displayed to the customer will include the cost of insurance, based on cart value (max 2000€).' mod='lowcostexpress'}
- +{l s='In KG. Used to determine how to spread articles in a cart into several simulated parcels, based on real weight.' mod='lowcostexpress'}
+{l s='In KG. Used to determine how to spread articles in a cart into several simulated parcels, based on volumetric weight.' mod='lowcostexpress'}
+{l s='If checked, the module will ignore dimensions specificed in your product catalog and use only the weight and the table below to determine the dimensions of each parcel.' mod='lowcostexpress'}
+{l s='If checked, the price displayed to the customer will include the cost of insurance, based on cart value (max 2000€).' mod='lowcostexpress'}
+ +{l s='In KG. Used to determine how to spread articles in a cart into several simulated parcels, based on real weight.' mod='lowcostexpress'}
+{l s='In KG. Used to determine how to spread articles in a cart into several simulated parcels, based on volumetric weight.' mod='lowcostexpress'}
+{l s='If checked, the module will ignore dimensions specificed in your product catalog and use only the weight and the table below to determine the dimensions of each parcel.' mod='lowcostexpress'}
+@@ -372,4 +390,3 @@
-