Eutanasio Posted September 4, 2023 Share Posted September 4, 2023 Hi! I'm currently working with the hookActionUpdateQuantity hook, and I'm facing an issue where I need to retrieve the exact quantity of a product that was ordered. From the hook $params, I couldn't find a direct parameter that provides the ordered quantity. I've also tried using PrestaShop classes, but couldn't find a straightforward method to retrieve this information. Has anyone encountered this before or knows a way to efficiently obtain the ordered quantity without resorting to direct DB queries? Any guidance or insights would be greatly appreciated. Thank you! Link to comment Share on other sites More sharing options...
ventura Posted September 5, 2023 Share Posted September 5, 2023 You could use the hookActionValidateOrder hook for a better approach. Link to comment Share on other sites More sharing options...
Eutanasio Posted September 5, 2023 Author Share Posted September 5, 2023 36 minutes ago, ventura said: You could use the hookActionValidateOrder hook for a better approach. Thanks Ventura! the thing is that I'm trying to fix the module emailAlerts, which has some malfunctioning code for setting out of stock alerts, it uses a param that doesn't exist and it's crucial to all the logic inside hookActionProductQuantityUpdate (the $params['delta_quantity']). In order to calculate when to trigger the email alert we need to know the value of the stock variation. The module only actually plays with the Threshold and the currentStock. So as there's no real information for the function to know if stock variation went from above the Threshold to the threshold or bellow... what happens is that everytime the function gets triggered and the currentStock is at Threshold or bellow, keeps on sending alerts Link to comment Share on other sites More sharing options...
ventura Posted September 5, 2023 Share Posted September 5, 2023 I don't know what version of Prestashop you are using. eg. 1.7.8.7 hookActionUpdateQuantity params in classes/stock/StockAvailable.php function setQuantity Hook::exec( 'actionUpdateQuantity', array( 'id_product' => $id_product, 'id_product_attribute' => 0, 'quantity' => $product_quantity, 'id_shop' => $id_shop, ) ); should be Hook::exec( 'actionUpdateQuantity', [ 'id_product' => $id_product, 'id_product_attribute' => $id_product_attribute, 'quantity' => $stock_available->quantity, 'delta_quantity' => $deltaQuantity ?? null, 'id_shop' => $id_shop, ] ); } is modified in version 8 1 Link to comment Share on other sites More sharing options...
Eutanasio Posted September 5, 2023 Author Share Posted September 5, 2023 17 minutes ago, ventura said: I don't know what version of Prestashop you are using. eg. 1.7.8.7 hookActionUpdateQuantity params in classes/stock/StockAvailable.php function setQuantity Hook::exec( 'actionUpdateQuantity', array( 'id_product' => $id_product, 'id_product_attribute' => 0, 'quantity' => $product_quantity, 'id_shop' => $id_shop, ) ); should be Hook::exec( 'actionUpdateQuantity', [ 'id_product' => $id_product, 'id_product_attribute' => $id_product_attribute, 'quantity' => $stock_available->quantity, 'delta_quantity' => $deltaQuantity ?? null, 'id_shop' => $id_shop, ] ); } is modified in version 8 🤯 Whaaaat! I've spent many many hours trying many approaches with the emailAlert module and the issue was there?! so just modifying that the delta_quantity will finally be accessible for the hookActionProductQuantityUpdate function? THANKS in advance! Link to comment Share on other sites More sharing options...
Eutanasio Posted September 5, 2023 Author Share Posted September 5, 2023 (edited) I've have made that modification suggested by @ventura but still doesn't work when 'hookActionProductQuantityUpdate' is triggered, this code does nothing: if (isset($params['delta_quantity']) && (int) $params['delta_quantity'] === 0) { return; } I have opened in the admin a product that was already out of stock, did nothing, and saved it (so no stock variation), but I received the email alert anyway NOTE: PS v1.7.6 Edited September 5, 2023 by Eutanasio NOTE: PS v1.7.6 (see edit history) Link to comment Share on other sites More sharing options...
WisQQ Posted September 6, 2023 Share Posted September 6, 2023 16 godzin temu, Eutanasio napisał: I've have made that modification suggested by @ventura but still doesn't work when 'hookActionProductQuantityUpdate' is triggered, this code does nothing: if (isset($params['delta_quantity']) && (int) $params['delta_quantity'] === 0) { return; } I have opened in the admin a product that was already out of stock, did nothing, and saved it (so no stock variation), but I received the email alert anyway NOTE: PS v1.7.6 If you cannot rely on hookActionUpdateQuantity, why not use another hook like suggested ActionValidateOrder? You could also use HookactionObjectStockAvailableUpdateAfter. This hook is fired after data was saved to database through objectmodel. This way you will get final quantity of product saved into database(that would be delta of product variant). And you can acess id_product, id_product_attribute, quantity and location. Link to comment Share on other sites More sharing options...
Eutanasio Posted September 6, 2023 Author Share Posted September 6, 2023 2 hours ago, WisQQ said: If you cannot rely on hookActionUpdateQuantity, why not use another hook like suggested ActionValidateOrder? You could also use HookactionObjectStockAvailableUpdateAfter. This hook is fired after data was saved to database through objectmodel. This way you will get final quantity of product saved into database(that would be delta of product variant). And you can acess id_product, id_product_attribute, quantity and location. The problem is not with currentStock (which is the actual real stock after a stock variation for X reason) this value is correct. The issue is that emailAlerts module uses the function hookActionProductQuantityUpdate in which the param delta_quantity is used but has null value. As @ventura suggested, the issue was a bug in StockAvailable.php where the Hook::exec('actionUpdateQuantity' was missing this param. But despite adding it, the value keeps being null. I also checked further the code on StockAvailable.php to see where is calculated the delta_quantity param, which I found at public static function setQuantity as: $deltaQuantity = -1 * ((int) $stock_available->quantity - (int) $quantity); But this part of the code doesn't seem to be triggered on any of the processes (I used PS log to confirm this). Link to comment Share on other sites More sharing options...
WisQQ Posted September 6, 2023 Share Posted September 6, 2023 (edited) 1 godzinę temu, Eutanasio napisał: The problem is not with currentStock (which is the actual real stock after a stock variation for X reason) this value is correct. The issue is that emailAlerts module uses the function hookActionProductQuantityUpdate in which the param delta_quantity is used but has null value. As @ventura suggested, the issue was a bug in StockAvailable.php where the Hook::exec('actionUpdateQuantity' was missing this param. But despite adding it, the value keeps being null. I also checked further the code on StockAvailable.php to see where is calculated the delta_quantity param, which I found at public static function setQuantity as: $deltaQuantity = -1 * ((int) $stock_available->quantity - (int) $quantity); But this part of the code doesn't seem to be triggered on any of the processes (I used PS log to confirm this). I checked StockAvailable.php and $deltaQuantity is only used inside StockAvailable::setQuantity(). To fix your problem you will need to add $deltaQuantity to StockAvailable::synchronize(). This function runs after order is validated, and this one is also executing actionhookQuantityUpdate. You can find it inside class PaymentModule-> ValidateOrder(). From what i found $deltaQuantity is only used for stockmovements made by user in BackOffice. Rest is handled by StockManager Class. Edited September 6, 2023 by WisQQ edit (see edit history) Link to comment Share on other sites More sharing options...
Eutanasio Posted September 6, 2023 Author Share Posted September 6, 2023 Thanks! I found the solution, I had to override StockAvailable.php as "public static function synchronize" was missing things, this is the override: <?php class StockAvailable extends StockAvailableCore { public static function synchronize($id_product, $order_id_shop = null) { if (!Validate::isUnsignedId($id_product)) { return false; } //if product is pack sync recursivly product in pack if (Pack::isPack($id_product)) { if (Validate::isLoadedObject($product = new Product((int) $id_product))) { if ($product->pack_stock_type == Pack::STOCK_TYPE_PRODUCTS_ONLY || $product->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH || ($product->pack_stock_type == Pack::STOCK_TYPE_DEFAULT && Configuration::get('PS_PACK_STOCK_TYPE') > 0) ) { $products_pack = Pack::getItems($id_product, (int) Configuration::get('PS_LANG_DEFAULT')); foreach ($products_pack as $product_pack) { StockAvailable::synchronize($product_pack->id, $order_id_shop); } } } else { return false; } } // gets warehouse ids grouped by shops $ids_warehouse = Warehouse::getWarehousesGroupedByShops(); if ($order_id_shop !== null) { $order_warehouses = array(); $wh = Warehouse::getWarehouses(false, (int) $order_id_shop); foreach ($wh as $warehouse) { $order_warehouses[] = $warehouse['id_warehouse']; } } // gets all product attributes ids $ids_product_attribute = array(); foreach (Product::getProductAttributesIds($id_product) as $id_product_attribute) { $ids_product_attribute[] = $id_product_attribute['id_product_attribute']; } // Allow to order the product when out of stock? $out_of_stock = StockAvailable::outOfStock($id_product); $manager = StockManagerFactory::getManager(); // loops on $ids_warehouse to synchronize quantities foreach ($ids_warehouse as $id_shop => $warehouses) { // first, checks if the product depends on stock for the given shop $id_shop if (StockAvailable::dependsOnStock($id_product, $id_shop)) { // init quantity $product_quantity = 0; // if it's a simple product if (empty($ids_product_attribute)) { $allowed_warehouse_for_product = WareHouse::getProductWarehouseList((int) $id_product, 0, (int) $id_shop); $allowed_warehouse_for_product_clean = array(); foreach ($allowed_warehouse_for_product as $warehouse) { $allowed_warehouse_for_product_clean[] = (int) $warehouse['id_warehouse']; } $allowed_warehouse_for_product_clean = array_intersect($allowed_warehouse_for_product_clean, $warehouses); if ($order_id_shop != null && !count(array_intersect($allowed_warehouse_for_product_clean, $order_warehouses))) { continue; } // Fetch the real product quantities considering warehouses $product_quantity = $manager->getProductRealQuantities($id_product, null, $allowed_warehouse_for_product_clean, true); // NEW CODE // Fetch the current available quantity for the product $stock_available_quantity = StockAvailable::getQuantityAvailableByProduct($id_product, $id_product_attribute, $id_shop); // Check if the returned value is valid if ($stock_available_quantity === false || !is_int($stock_available_quantity)) { return; // Exit the function } // Calculate the change in quantity (delta) $deltaQuantity = -1 * ((int) $stock_available_quantity - (int) $product_quantity); // Check if deltaQuantity is valid if (!is_int($deltaQuantity)) { return; // Exit the function } //END NEW CODE Hook::exec( 'actionUpdateQuantity', array( 'id_product' => $id_product, 'id_product_attribute' => 0, 'quantity' => $product_quantity, 'delta_quantity' => $deltaQuantity ?? null, 'id_shop' => $id_shop, ) ); } else { // else this product has attributes, hence loops on $ids_product_attribute foreach ($ids_product_attribute as $id_product_attribute) { $allowed_warehouse_for_combination = WareHouse::getProductWarehouseList((int) $id_product, (int) $id_product_attribute, (int) $id_shop); $allowed_warehouse_for_combination_clean = array(); foreach ($allowed_warehouse_for_combination as $warehouse) { $allowed_warehouse_for_combination_clean[] = (int) $warehouse['id_warehouse']; } $allowed_warehouse_for_combination_clean = array_intersect($allowed_warehouse_for_combination_clean, $warehouses); if ($order_id_shop != null && !count(array_intersect($allowed_warehouse_for_combination_clean, $order_warehouses))) { continue; } $quantity = $manager->getProductRealQuantities($id_product, $id_product_attribute, $allowed_warehouse_for_combination_clean, true); $query = new DbQuery(); $query->select('COUNT(*)'); $query->from('stock_available'); $query->where('id_product = ' . (int) $id_product . ' AND id_product_attribute = ' . (int) $id_product_attribute . StockAvailable::addSqlShopRestriction(null, $id_shop)); if ((int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query)) { $query = array( 'table' => 'stock_available', 'data' => array('quantity' => $quantity), 'where' => 'id_product = ' . (int) $id_product . ' AND id_product_attribute = ' . (int) $id_product_attribute . StockAvailable::addSqlShopRestriction(null, $id_shop), ); Db::getInstance()->update($query['table'], $query['data'], $query['where']); } else { $query = array( 'table' => 'stock_available', 'data' => array( 'quantity' => $quantity, 'depends_on_stock' => 1, 'out_of_stock' => $out_of_stock, 'id_product' => (int) $id_product, 'id_product_attribute' => (int) $id_product_attribute, ), ); StockAvailable::addSqlShopParams($query['data'], $id_shop); Db::getInstance()->insert($query['table'], $query['data']); } $product_quantity += $quantity; Hook::exec( 'actionUpdateQuantity', array( 'id_product' => $id_product, 'id_product_attribute' => $id_product_attribute, 'quantity' => $quantity, 'delta_quantity' => $deltaQuantity ?? null, 'id_shop' => $id_shop, ) ); } } // updates // if $id_product has attributes, it also updates the sum for all attributes if (($order_id_shop != null && array_intersect($warehouses, $order_warehouses)) || $order_id_shop == null) { $query = array( 'table' => 'stock_available', 'data' => array('quantity' => $product_quantity), 'where' => 'id_product = ' . (int) $id_product . ' AND id_product_attribute = 0' . StockAvailable::addSqlShopRestriction(null, $id_shop), ); Db::getInstance()->update($query['table'], $query['data'], $query['where']); } } } // In case there are no warehouses, removes product from StockAvailable if (count($ids_warehouse) == 0 && StockAvailable::dependsOnStock((int) $id_product)) { Db::getInstance()->update('stock_available', array('quantity' => 0), 'id_product = ' . (int) $id_product); } Cache::clean('StockAvailable::getQuantityAvailableByProduct_' . (int) $id_product . '*'); } } Link to comment Share on other sites More sharing options...
WisQQ Posted September 7, 2023 Share Posted September 7, 2023 10 godzin temu, Eutanasio napisał: Thanks! I found the solution, I had to override StockAvailable.php as "public static function synchronize" was missing things, this is the override: <?php class StockAvailable extends StockAvailableCore { public static function synchronize($id_product, $order_id_shop = null) { if (!Validate::isUnsignedId($id_product)) { return false; } //if product is pack sync recursivly product in pack if (Pack::isPack($id_product)) { if (Validate::isLoadedObject($product = new Product((int) $id_product))) { if ($product->pack_stock_type == Pack::STOCK_TYPE_PRODUCTS_ONLY || $product->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH || ($product->pack_stock_type == Pack::STOCK_TYPE_DEFAULT && Configuration::get('PS_PACK_STOCK_TYPE') > 0) ) { $products_pack = Pack::getItems($id_product, (int) Configuration::get('PS_LANG_DEFAULT')); foreach ($products_pack as $product_pack) { StockAvailable::synchronize($product_pack->id, $order_id_shop); } } } else { return false; } } // gets warehouse ids grouped by shops $ids_warehouse = Warehouse::getWarehousesGroupedByShops(); if ($order_id_shop !== null) { $order_warehouses = array(); $wh = Warehouse::getWarehouses(false, (int) $order_id_shop); foreach ($wh as $warehouse) { $order_warehouses[] = $warehouse['id_warehouse']; } } // gets all product attributes ids $ids_product_attribute = array(); foreach (Product::getProductAttributesIds($id_product) as $id_product_attribute) { $ids_product_attribute[] = $id_product_attribute['id_product_attribute']; } // Allow to order the product when out of stock? $out_of_stock = StockAvailable::outOfStock($id_product); $manager = StockManagerFactory::getManager(); // loops on $ids_warehouse to synchronize quantities foreach ($ids_warehouse as $id_shop => $warehouses) { // first, checks if the product depends on stock for the given shop $id_shop if (StockAvailable::dependsOnStock($id_product, $id_shop)) { // init quantity $product_quantity = 0; // if it's a simple product if (empty($ids_product_attribute)) { $allowed_warehouse_for_product = WareHouse::getProductWarehouseList((int) $id_product, 0, (int) $id_shop); $allowed_warehouse_for_product_clean = array(); foreach ($allowed_warehouse_for_product as $warehouse) { $allowed_warehouse_for_product_clean[] = (int) $warehouse['id_warehouse']; } $allowed_warehouse_for_product_clean = array_intersect($allowed_warehouse_for_product_clean, $warehouses); if ($order_id_shop != null && !count(array_intersect($allowed_warehouse_for_product_clean, $order_warehouses))) { continue; } // Fetch the real product quantities considering warehouses $product_quantity = $manager->getProductRealQuantities($id_product, null, $allowed_warehouse_for_product_clean, true); // NEW CODE // Fetch the current available quantity for the product $stock_available_quantity = StockAvailable::getQuantityAvailableByProduct($id_product, $id_product_attribute, $id_shop); // Check if the returned value is valid if ($stock_available_quantity === false || !is_int($stock_available_quantity)) { return; // Exit the function } // Calculate the change in quantity (delta) $deltaQuantity = -1 * ((int) $stock_available_quantity - (int) $product_quantity); // Check if deltaQuantity is valid if (!is_int($deltaQuantity)) { return; // Exit the function } //END NEW CODE Hook::exec( 'actionUpdateQuantity', array( 'id_product' => $id_product, 'id_product_attribute' => 0, 'quantity' => $product_quantity, 'delta_quantity' => $deltaQuantity ?? null, 'id_shop' => $id_shop, ) ); } else { // else this product has attributes, hence loops on $ids_product_attribute foreach ($ids_product_attribute as $id_product_attribute) { $allowed_warehouse_for_combination = WareHouse::getProductWarehouseList((int) $id_product, (int) $id_product_attribute, (int) $id_shop); $allowed_warehouse_for_combination_clean = array(); foreach ($allowed_warehouse_for_combination as $warehouse) { $allowed_warehouse_for_combination_clean[] = (int) $warehouse['id_warehouse']; } $allowed_warehouse_for_combination_clean = array_intersect($allowed_warehouse_for_combination_clean, $warehouses); if ($order_id_shop != null && !count(array_intersect($allowed_warehouse_for_combination_clean, $order_warehouses))) { continue; } $quantity = $manager->getProductRealQuantities($id_product, $id_product_attribute, $allowed_warehouse_for_combination_clean, true); $query = new DbQuery(); $query->select('COUNT(*)'); $query->from('stock_available'); $query->where('id_product = ' . (int) $id_product . ' AND id_product_attribute = ' . (int) $id_product_attribute . StockAvailable::addSqlShopRestriction(null, $id_shop)); if ((int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query)) { $query = array( 'table' => 'stock_available', 'data' => array('quantity' => $quantity), 'where' => 'id_product = ' . (int) $id_product . ' AND id_product_attribute = ' . (int) $id_product_attribute . StockAvailable::addSqlShopRestriction(null, $id_shop), ); Db::getInstance()->update($query['table'], $query['data'], $query['where']); } else { $query = array( 'table' => 'stock_available', 'data' => array( 'quantity' => $quantity, 'depends_on_stock' => 1, 'out_of_stock' => $out_of_stock, 'id_product' => (int) $id_product, 'id_product_attribute' => (int) $id_product_attribute, ), ); StockAvailable::addSqlShopParams($query['data'], $id_shop); Db::getInstance()->insert($query['table'], $query['data']); } $product_quantity += $quantity; Hook::exec( 'actionUpdateQuantity', array( 'id_product' => $id_product, 'id_product_attribute' => $id_product_attribute, 'quantity' => $quantity, 'delta_quantity' => $deltaQuantity ?? null, 'id_shop' => $id_shop, ) ); } } // updates // if $id_product has attributes, it also updates the sum for all attributes if (($order_id_shop != null && array_intersect($warehouses, $order_warehouses)) || $order_id_shop == null) { $query = array( 'table' => 'stock_available', 'data' => array('quantity' => $product_quantity), 'where' => 'id_product = ' . (int) $id_product . ' AND id_product_attribute = 0' . StockAvailable::addSqlShopRestriction(null, $id_shop), ); Db::getInstance()->update($query['table'], $query['data'], $query['where']); } } } // In case there are no warehouses, removes product from StockAvailable if (count($ids_warehouse) == 0 && StockAvailable::dependsOnStock((int) $id_product)) { Db::getInstance()->update('stock_available', array('quantity' => 0), 'id_product = ' . (int) $id_product); } Cache::clean('StockAvailable::getQuantityAvailableByProduct_' . (int) $id_product . '*'); } } I think you forgot to add Code inside ELSE argument where products with combinations are handled. Right now your code will only work with simple products without combinations. Link to comment Share on other sites More sharing options...
Eutanasio Posted September 7, 2023 Author Share Posted September 7, 2023 25 minutes ago, WisQQ said: I think you forgot to add Code inside ELSE argument where products with combinations are handled. Right now your code will only work with simple products without combinations. I guess so, I didn't touch that part because I don't need it, but it's a good point so if someone is interested on this patch and uses combinations would take this into consideration. I'm not sure if Prestashop has fixed these problems on PS v8, but as for the last update of emailAlerts module nop! so for thos with PS v1.7.x I suggest overriding both ps_emailalerts.php and the class stockAvailable.php as mentioned. I'm still working on it, don't know why when I override the ps_emailalerts.php the function StockAvailableCore stops working... Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now