* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ use PrestaShop\PrestaShop\Adapter\ServiceLocator; use PrestaShop\PrestaShop\Core\Domain\Product\Stock\StockSettings; /** * Represents quantities available * It is either synchronized with Stock or manualy set by the seller. * * @since 1.5.0 */ class StockAvailableCore extends ObjectModel { /** @var int identifier of the current product */ public $id_product; /** @var int identifier of product attribute if necessary */ public $id_product_attribute; /** @var int the shop associated to the current product and corresponding quantity */ public $id_shop; /** @var int the group shop associated to the current product and corresponding quantity */ public $id_shop_group; /** @var int the quantity available for sale */ public $quantity = 0; /** * @deprecated since 1.7.8 * This property was only relevant to advanced stock management and that feature is not maintained anymore * * @var bool determine if the available stock value depends on physical stock */ public $depends_on_stock = false; /** * Determine if a product is out of stock - it was previously in Product class * - O Deny orders * - 1 Allow orders * - 2 Use global setting * * @var int */ public $out_of_stock = 0; /** @var string the location of the stock for this product / combination */ public $location = ''; /** * @see ObjectModel::$definition */ public static $definition = [ 'table' => 'stock_available', 'primary' => 'id_stock_available', 'fields' => [ 'id_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], 'id_product_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], 'id_shop' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_shop_group' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'quantity' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true, 'range' => ['min' => StockSettings::INT_32_MAX_NEGATIVE, 'max' => StockSettings::INT_32_MAX_POSITIVE]], 'depends_on_stock' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool', 'required' => true], 'out_of_stock' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true], 'location' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'size' => 255], ], ]; /** * @see ObjectModel::$webserviceParameters */ protected $webserviceParameters = [ 'fields' => [ 'id_product' => ['xlink_resource' => 'products'], 'id_product_attribute' => ['xlink_resource' => 'combinations'], 'id_shop' => ['xlink_resource' => 'shops'], 'id_shop_group' => ['xlink_resource' => 'shop_groups'], ], 'hidden_fields' => [ ], 'objectMethods' => [ 'add' => 'addWs', 'update' => 'updateWs', ], ]; /** * @return bool */ public function updateWs() { if ($this->depends_on_stock) { return WebserviceRequest::getInstance()->setError( 500, $this->trans('You cannot update the available stock when it depends on stock.', [], 'Admin.Catalog.Notification'), 133 ); } return $this->update(); } public static function getStockAvailableIdByProductId($id_product, $id_product_attribute = null, $id_shop = null) { if (!Validate::isUnsignedId($id_product)) { return false; } $query = new DbQuery(); $query->select('id_stock_available'); $query->from('stock_available'); $query->where('id_product = ' . (int) $id_product); if ($id_product_attribute !== null) { $query->where('id_product_attribute = ' . (int) $id_product_attribute); } $query = StockAvailable::addSqlShopRestriction($query, $id_shop); return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query); } /** * For a given id_product, synchronizes StockAvailable::quantity with Stock::usable_quantity. * * @param int $id_product */ 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(); $order_warehouses = []; if ($order_id_shop !== null) { $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 = []; 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 = []; 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; } $product_quantity = $manager->getProductRealQuantities($id_product, 0, $allowed_warehouse_for_product_clean, true); Hook::exec( 'actionUpdateQuantity', [ 'id_product' => $id_product, 'id_product_attribute' => 0, 'quantity' => $product_quantity, '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 = []; 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 = [ 'table' => 'stock_available', 'data' => ['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 = [ 'table' => 'stock_available', 'data' => [ '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', [ 'id_product' => $id_product, 'id_product_attribute' => $id_product_attribute, 'quantity' => $quantity, '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 = [ 'table' => 'stock_available', 'data' => ['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', ['quantity' => 0], 'id_product = ' . (int) $id_product); } Cache::clean('StockAvailable::getQuantityAvailableByProduct_' . (int) $id_product . '*'); } /** * For a given id_product, sets if stock available depends on stock. * * @param int $id_product * @param bool $depends_on_stock Optional : true by default * @param int $id_shop Optional : gets context by default */ public static function setProductDependsOnStock($id_product, $depends_on_stock = true, $id_shop = null, $id_product_attribute = 0) { if (!Validate::isUnsignedId($id_product)) { return false; } $existing_id = StockAvailable::getStockAvailableIdByProductId((int) $id_product, (int) $id_product_attribute, $id_shop); if ($existing_id > 0) { Db::getInstance()->update('stock_available', [ 'depends_on_stock' => (int) $depends_on_stock, ], 'id_stock_available = ' . (int) $existing_id); } else { $params = [ 'depends_on_stock' => (int) $depends_on_stock, 'id_product' => (int) $id_product, 'id_product_attribute' => (int) $id_product_attribute, ]; StockAvailable::addSqlShopParams($params, $id_shop); Db::getInstance()->insert('stock_available', $params); } // depends on stock.. hence synchronizes if ($depends_on_stock) { StockAvailable::synchronize($id_product); } } /** * For a given id_product, sets if product is available out of stocks. * * @param int $id_product * @param int|bool $out_of_stock Optional false by default * @param int|null $id_shop Optional gets context by default * @param int $id_product_attribute */ public static function setProductOutOfStock($id_product, $out_of_stock = false, $id_shop = null, $id_product_attribute = 0) { if (!Validate::isUnsignedId($id_product)) { return false; } $existing_id = (int) StockAvailable::getStockAvailableIdByProductId((int) $id_product, (int) $id_product_attribute, $id_shop); if ($existing_id > 0) { Db::getInstance()->update( 'stock_available', ['out_of_stock' => (int) $out_of_stock], 'id_product = ' . (int) $id_product . (($id_product_attribute) ? ' AND id_product_attribute = ' . (int) $id_product_attribute : '') . StockAvailable::addSqlShopRestriction(null, $id_shop) ); } else { $params = [ 'out_of_stock' => (int) $out_of_stock, 'id_product' => (int) $id_product, 'id_product_attribute' => (int) $id_product_attribute, ]; StockAvailable::addSqlShopParams($params, $id_shop); Db::getInstance()->insert('stock_available', $params, false, true, Db::ON_DUPLICATE_KEY); } } /** * @param int $id_product * @param string $location * @param int $id_shop Optional * @param int $id_product_attribute Optional * * @return void * * @throws PrestaShopDatabaseException */ public static function setLocation($id_product, $location, $id_shop = null, $id_product_attribute = 0) { if ( false === Validate::isUnsignedId($id_product) || (((false === Validate::isUnsignedId($id_shop)) && (null !== $id_shop))) || (false === Validate::isUnsignedId($id_product_attribute)) || (false === Validate::isString($location)) ) { $serializedInputData = [ 'id_product' => $id_product, 'id_shop' => $id_shop, 'id_product_attribute' => $id_product_attribute, 'location' => $location, ]; throw new \InvalidArgumentException(sprintf('Could not update location as input data is not valid: %s', json_encode($serializedInputData))); } $existing_id = StockAvailable::getStockAvailableIdByProductId($id_product, $id_product_attribute, $id_shop); if ($existing_id > 0) { Db::getInstance()->update( 'stock_available', ['location' => pSQL($location)], 'id_product = ' . (int) $id_product . (($id_product_attribute) ? ' AND id_product_attribute = ' . (int) $id_product_attribute : '') . StockAvailable::addSqlShopRestriction(null, $id_shop) ); } else { $params = [ 'location' => pSQL($location), 'id_product' => (int) $id_product, 'id_product_attribute' => (int) $id_product_attribute, ]; StockAvailable::addSqlShopParams($params, $id_shop); Db::getInstance()->insert('stock_available', $params, false, true, Db::ON_DUPLICATE_KEY); } } /** * For a given id_product and id_product_attribute, gets its stock available. * * @param int $id_product * @param int $id_product_attribute Optional * @param int $id_shop Optional : gets context by default * * @return int Quantity */ public static function getQuantityAvailableByProduct($id_product = null, $id_product_attribute = null, $id_shop = null) { // if null, it's a product without attributes if ($id_product_attribute === null) { $id_product_attribute = 0; } $key = 'StockAvailable::getQuantityAvailableByProduct_' . (int) $id_product . '-' . (int) $id_product_attribute . '-' . (int) $id_shop; if (!Cache::isStored($key)) { $query = new DbQuery(); $query->select('SUM(quantity)'); $query->from('stock_available'); // if null, it's a product without attributes if ($id_product !== null) { $query->where('id_product = ' . (int) $id_product); } $query->where('id_product_attribute = ' . (int) $id_product_attribute); $query = StockAvailable::addSqlShopRestriction($query, $id_shop); $result = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query); Cache::store($key, $result); return $result; } return Cache::retrieve($key); } /** * Upgrades total_quantity_available after having saved. * * @see ObjectModel::add() */ public function add($autodate = true, $null_values = false) { if (!parent::add($autodate, $null_values)) { return false; } return $this->postSave(); } /** * Upgrades total_quantity_available after having update. * * @see ObjectModel::update() */ public function update($null_values = false) { if (!parent::update($null_values)) { return false; } return $this->postSave(); } /** * Upgrades total_quantity_available after having saved. * * @see StockAvailableCore::update() * @see StockAvailableCore::add() */ public function postSave() { if ($this->id_product_attribute == 0) { return true; } // If shop list was explicitly set we ignore the shop context if (count($this->id_shop_list)) { $id_shop = reset($this->id_shop_list); } else { $id_shop = (Shop::getContext() != Shop::CONTEXT_GROUP && $this->id_shop ? $this->id_shop : null); } if (!Configuration::get('PS_DISP_UNAVAILABLE_ATTR')) { $combination = new Combination((int) $this->id_product_attribute); if ($colors = $combination->getColorsAttributes()) { $product = new Product((int) $this->id_product); foreach ($colors as $color) { if ($product->isColorUnavailable((int) $color['id_attribute'], (int) $this->id_shop)) { Tools::clearColorListCache($product->id); break; } } } } $total_quantity = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( ' SELECT SUM(quantity) as quantity FROM ' . _DB_PREFIX_ . 'stock_available WHERE id_product = ' . (int) $this->id_product . ' AND id_product_attribute <> 0 ' . StockAvailable::addSqlShopRestriction(null, $id_shop) ); $this->setQuantity($this->id_product, 0, $total_quantity, $id_shop, false); return true; } /** * For a given id_product and id_product_attribute updates the quantity available * If $avoid_parent_pack_update is true, then packs containing the given product won't be updated. * * @param int $id_product * @param int $id_product_attribute Optional * @param int $delta_quantity The delta quantity to update * @param int $id_shop Optional * @param bool $add_movement Optional * @param array $params Optional */ public static function updateQuantity($id_product, $id_product_attribute, $delta_quantity, $id_shop = null, $add_movement = false, $params = []) { if (!Validate::isUnsignedId($id_product)) { return false; } $product = new Product((int) $id_product); if (!Validate::isLoadedObject($product)) { return false; } $stockManager = ServiceLocator::get('\\PrestaShop\\PrestaShop\\Core\\Stock\\StockManager'); $stockManager->updateQuantity($product, $id_product_attribute, $delta_quantity, $id_shop, $add_movement, $params); return true; } /** * For a given id_product and id_product_attribute sets the quantity available. * * @param int $id_product * @param int $id_product_attribute * @param int $quantity * @param int|null $id_shop * @param bool $add_movement * * @return bool|void */ public static function setQuantity($id_product, $id_product_attribute, $quantity, $id_shop = null, $add_movement = true) { if (!Validate::isUnsignedId($id_product)) { return false; } $context = Context::getContext(); // if there is no $id_shop, gets the context one if ($id_shop === null && Shop::getContext() != Shop::CONTEXT_GROUP) { $id_shop = (int) $context->shop->id; } $depends_on_stock = StockAvailable::dependsOnStock($id_product); //Try to set available quantity if product does not depend on physical stock if (!$depends_on_stock) { $stockManager = ServiceLocator::get('\\PrestaShop\\PrestaShop\\Core\\Stock\\StockManager'); $id_stock_available = (int) StockAvailable::getStockAvailableIdByProductId($id_product, $id_product_attribute, $id_shop); if ($id_stock_available) { $stock_available = new StockAvailable($id_stock_available); $deltaQuantity = (int) $quantity - (int) $stock_available->quantity; $stock_available->quantity = (int) $quantity; $stock_available->update(); if (true === $add_movement && 0 != $deltaQuantity) { $stockManager->saveMovement($id_product, $id_product_attribute, $deltaQuantity); } } else { $out_of_stock = StockAvailable::outOfStock($id_product, $id_shop); $stock_available = new StockAvailable(); $stock_available->out_of_stock = (int) $out_of_stock; $stock_available->id_product = (int) $id_product; $stock_available->id_product_attribute = (int) $id_product_attribute; $stock_available->quantity = (int) $quantity; if ($id_shop === null) { $shop_group = Shop::getContextShopGroup(); } else { $shop_group = new ShopGroup((int) Shop::getGroupFromShop((int) $id_shop)); } // if quantities are shared between shops of the group if ($shop_group->share_stock) { $stock_available->id_shop = 0; $stock_available->id_shop_group = (int) $shop_group->id; } else { $stock_available->id_shop = (int) $id_shop; $stock_available->id_shop_group = 0; } $stock_available->add(); if (true === $add_movement && 0 != $quantity) { $stockManager->saveMovement($id_product, $id_product_attribute, (int) $quantity); } } 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, ] ); } Cache::clean('StockAvailable::getQuantityAvailableByProduct_' . (int) $id_product . '*'); } /** * Removes a given product from the stock available. * * @param int $id_product * @param int|null $id_product_attribute Optional * @param Shop|int|null $shop Shop id or shop object Optional * * @return bool */ public static function removeProductFromStockAvailable($id_product, $id_product_attribute = null, $shop = null) { if (!Validate::isUnsignedId($id_product)) { return false; } if (null !== $shop) { if (!($shop instanceof Shop)) { $shop = new Shop($shop); } $groupSharedStock = (bool) $shop->getGroup()->share_stock; } else { $groupSharedStock = Shop::getContext() == Shop::CONTEXT_SHOP && (bool) Shop::getContextShopGroup()->share_stock; } // If stock is shared by group and the product is still associated to some shops from the group no need to delete the stock if ($groupSharedStock) { $pa_sql = ''; if ($id_product_attribute !== null) { $pa_sql = '_attribute'; $id_product_attribute_sql = $id_product_attribute; } else { $id_product_attribute_sql = $id_product; } if ((int) Db::getInstance()->getValue('SELECT COUNT(*) FROM ' . _DB_PREFIX_ . 'product' . $pa_sql . '_shop WHERE id_product' . $pa_sql . '=' . (int) $id_product_attribute_sql . ' AND id_shop IN (' . implode(',', array_map('intval', Shop::getContextListShopID(Shop::SHARE_STOCK))) . ')')) { return true; } } $res = Db::getInstance()->execute(' DELETE FROM ' . _DB_PREFIX_ . 'stock_available WHERE id_product = ' . (int) $id_product . ($id_product_attribute ? ' AND id_product_attribute = ' . (int) $id_product_attribute : '') . StockAvailable::addSqlShopRestriction(null, $shop)); if ($id_product_attribute) { if ($shop === null || !Validate::isLoadedObject($shop)) { $shop_datas = []; StockAvailable::addSqlShopParams($shop_datas); $id_shop = (int) $shop_datas['id_shop']; } else { $id_shop = (int) $shop->id; } $stock_available = new StockAvailable(); $stock_available->id_product = (int) $id_product; $stock_available->id_product_attribute = (int) $id_product_attribute; $stock_available->id_shop = (int) $id_shop; $stock_available->postSave(); } Cache::clean('StockAvailable::getQuantityAvailableByProduct_' . (int) $id_product . '*'); return $res; } /** * Removes all product quantities from all a group of shops * If stocks are shared, remoe all old available quantities for all shops of the group * Else remove all available quantities for the current group. * * @param ShopGroup $shop_group the ShopGroup object */ public static function resetProductFromStockAvailableByShopGroup(ShopGroup $shop_group) { $shop_list = $shop_group->share_stock ? Shop::getShops(false, $shop_group->id, true) : []; if (count($shop_list) > 0) { $id_shops_list = implode(', ', $shop_list); return Db::getInstance()->update('stock_available', ['quantity' => 0], 'id_shop IN (' . $id_shops_list . ')'); } return Db::getInstance()->update('stock_available', ['quantity' => 0], 'id_shop_group = ' . $shop_group->id); } /** * For a given product, tells if it depends on the physical (usable) stock. * * @param int $id_product * @param int $id_shop Optional : gets context if null @see Context::getContext() * * @return bool : depends on stock @see $depends_on_stock */ public static function dependsOnStock($id_product, $id_shop = null) { if (!Validate::isUnsignedId($id_product)) { return false; } $query = new DbQuery(); $query->select('depends_on_stock'); $query->from('stock_available'); $query->where('id_product = ' . (int) $id_product); $query->where('id_product_attribute = 0'); $query = StockAvailable::addSqlShopRestriction($query, $id_shop); return (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query); } /** * For a given product, get its "out of stock" flag. * * @param int $id_product * @param int|null $id_shop Optional : gets context if null @see Context::getContext() * * @return int|bool : depends on stock @see $depends_on_stock */ public static function outOfStock($id_product, $id_shop = null) { if (!Validate::isUnsignedId($id_product)) { return false; } $query = new DbQuery(); $query->select('out_of_stock'); $query->from('stock_available'); $query->where('id_product = ' . (int) $id_product); $query->where('id_product_attribute = 0'); $query = StockAvailable::addSqlShopRestriction($query, $id_shop); return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query); } /** * @param int $id_product * @param int|null $id_product_attribute Optional * @param int|null $id_shop Optional * * @return bool|string */ public static function getLocation($id_product, $id_product_attribute = null, $id_shop = null) { $id_product = (int) $id_product; if (null === $id_product_attribute) { $id_product_attribute = 0; } else { $id_product_attribute = (int) $id_product_attribute; } $query = new DbQuery(); $query->select('location'); $query->from('stock_available'); $query->where('id_product = ' . $id_product); $query->where('id_product_attribute = ' . $id_product_attribute); $query = StockAvailable::addSqlShopRestriction($query, $id_shop); return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query); } /** * Add an sql restriction for shops fields - specific to StockAvailable. * * @param DbQuery|string|null $sql Reference to the query object * @param Shop|int|null $shop Optional : The shop ID * @param string|null $alias Optional : The current table alias * * @return string|DbQuery DbQuery object or the sql restriction string */ public static function addSqlShopRestriction($sql = null, $shop = null, $alias = null) { $context = Context::getContext(); if (!empty($alias)) { $alias .= '.'; } // if there is no $id_shop, gets the context one // get shop group too if ($shop === null || $shop === $context->shop->id) { if (Shop::getContext() == Shop::CONTEXT_GROUP) { $shop_group = Shop::getContextShopGroup(); } else { $shop_group = $context->shop->getGroup(); } $shop = $context->shop; } elseif (is_object($shop)) { /** @var Shop $shop */ $shop_group = $shop->getGroup(); } else { $shop = new Shop($shop); $shop_group = $shop->getGroup(); } // if quantities are shared between shops of the group if ($shop_group->share_stock) { if (is_object($sql)) { $sql->where(pSQL($alias) . 'id_shop_group = ' . (int) $shop_group->id); $sql->where(pSQL($alias) . 'id_shop = 0'); } else { $sql = ' AND ' . pSQL($alias) . 'id_shop_group = ' . (int) $shop_group->id . ' '; $sql .= ' AND ' . pSQL($alias) . 'id_shop = 0 '; } } else { if (is_object($sql)) { $sql->where(pSQL($alias) . 'id_shop = ' . (int) $shop->id); $sql->where(pSQL($alias) . 'id_shop_group = 0'); } else { $sql = ' AND ' . pSQL($alias) . 'id_shop = ' . (int) $shop->id . ' '; $sql .= ' AND ' . pSQL($alias) . 'id_shop_group = 0 '; } } return $sql; } /** * Add sql params for shops fields - specific to StockAvailable. * * @param array $params Reference to the params array * @param int $id_shop Optional : The shop ID */ public static function addSqlShopParams(&$params, $id_shop = null) { $context = Context::getContext(); $group_ok = false; // if there is no $id_shop, gets the context one // get shop group too if ($id_shop === null) { if (Shop::getContext() == Shop::CONTEXT_GROUP) { $shop_group = Shop::getContextShopGroup(); } else { $shop_group = $context->shop->getGroup(); $id_shop = $context->shop->id; } } else { $shop = new Shop($id_shop); $shop_group = $shop->getGroup(); } // if quantities are shared between shops of the group if ($shop_group->share_stock) { $params['id_shop_group'] = (int) $shop_group->id; $params['id_shop'] = 0; $group_ok = true; } else { $params['id_shop_group'] = 0; } // if no group specific restriction, set simple shop restriction if (!$group_ok) { $params['id_shop'] = (int) $id_shop; } } /** * Copies stock available content table. * * @param int $src_shop_id * @param int $dst_shop_id * * @return bool */ public static function copyStockAvailableFromShopToShop($src_shop_id, $dst_shop_id) { if (!$src_shop_id || !$dst_shop_id) { return false; } $query = ' INSERT INTO ' . _DB_PREFIX_ . 'stock_available ( id_product, id_product_attribute, id_shop, id_shop_group, quantity, depends_on_stock, out_of_stock, location ) ( SELECT id_product, id_product_attribute, ' . (int) $dst_shop_id . ', 0, quantity, depends_on_stock, out_of_stock, location FROM ' . _DB_PREFIX_ . 'stock_available WHERE id_shop = ' . (int) $src_shop_id . ')'; return Db::getInstance()->execute($query); } }