Skip to content

Commit

Permalink
Advanced mechanisms to simulate packlist with max real and volumetric…
Browse files Browse the repository at this point in the history
… weight.

Bump to version 1.0.11.
  • Loading branch information
tbelliard committed May 15, 2018
1 parent 30b7322 commit d7974e8
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 32 deletions.
11 changes: 9 additions & 2 deletions lowcostexpress.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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();
Expand Down Expand Up @@ -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'),
));
}

Expand Down
193 changes: 166 additions & 27 deletions models/LceQuote.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,16 @@ 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;
$total_articles = 0;
// 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
Expand All @@ -91,27 +94,32 @@ 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);

$length = $dims[2];
$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;
Expand All @@ -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(
Expand All @@ -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;
}
Expand Down
37 changes: 37 additions & 0 deletions upgrade/install-1.0.11.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License (AFL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/afl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade your module to newer
* versions in the future.
*
* @author MyFlyingBox <[email protected]>
* @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;
}
24 changes: 22 additions & 2 deletions views/templates/admin/settings.bootstrap.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,27 @@
<p class="help-block">{l s='If checked, the price displayed to the customer will include the cost of insurance, based on cart value (max 2000€).' mod='lowcostexpress'}</p>
</div>
</div>

<div class="form-group">
<label for="MOD_LCE_MAX_REAL_WEIGHT" class="control-label col-lg-3">{l s='Max real weight per parcel:' mod='lowcostexpress'}</label>
<div class="col-lg-8">
<input id="MOD_LCE_MAX_REAL_WEIGHT" name="MOD_LCE_MAX_REAL_WEIGHT" type="text" value="{$MOD_LCE_MAX_REAL_WEIGHT|escape:'htmlall':'UTF-8'}" />
<p class="help-block">{l s='In KG. Used to determine how to spread articles in a cart into several simulated parcels, based on real weight.' mod='lowcostexpress'}</p>
</div>
</div>
<div class="form-group">
<label for="MOD_LCE_MAX_VOL_WEIGHT" class="control-label col-lg-3">{l s='Max volumetric weight per parcel:' mod='lowcostexpress'}</label>
<div class="col-lg-8">
<input id="MOD_LCE_MAX_VOL_WEIGHT" name="MOD_LCE_MAX_VOL_WEIGHT" type="text" value="{$MOD_LCE_MAX_VOL_WEIGHT|escape:'htmlall':'UTF-8'}" />
<p class="help-block">{l s='In KG. Used to determine how to spread articles in a cart into several simulated parcels, based on volumetric weight.' mod='lowcostexpress'}</p>
</div>
</div>
<div class="form-group">
<label for="MOD_LCE_FORCE_WEIGHT_DIMS_TABLE" class="control-label col-lg-3">{l s='Force use of weight/dimensions table:' mod='lowcostexpress'}</label>
<div class="col-lg-8">
<input id="MOD_LCE_FORCE_WEIGHT_DIMS_TABLE" name="MOD_LCE_FORCE_WEIGHT_DIMS_TABLE" type="checkbox" value="1"{if $MOD_LCE_FORCE_WEIGHT_DIMS_TABLE eq true} CHECKED{/if} />
<p class="help-block">{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'}</p>
</div>
</div>
<div class="form-group" style="text-align:center">
<input id="submit_{$module_name|escape:'htmlall':'UTF-8'}" name="submit_{$module_name|escape:'htmlall':'UTF-8'}" type="submit" value="{l s='Save' mod='lowcostexpress'}" class="btn btn-default" />
</div>
Expand Down Expand Up @@ -418,4 +438,4 @@
</div>
</form>
</fieldset>
</div>
</div>
Loading

0 comments on commit d7974e8

Please sign in to comment.