Jump to content

Retrieving Ordered Quantity of a Product from hookActionUpdateQuantity ?


Eutanasio

Recommended Posts

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

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

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

  • Like 1
Link to comment
Share on other sites

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

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 by Eutanasio
​​​​​​​NOTE: PS v1.7.6 (see edit history)
Link to comment
Share on other sites

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

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

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 by WisQQ
edit (see edit history)
Link to comment
Share on other sites

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

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

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

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
×
×
  • Create New...