Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

173
classes/stock/Stock.php Normal file
View File

@@ -0,0 +1,173 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Represents the products kept in warehouses.
*
* @deprecated since 9.0 and will be removed in 10.0, stock is now managed by new logic
*/
class StockCore extends ObjectModel
{
/** @var int identifier of the warehouse */
public $id_warehouse;
/** @var int identifier of the product */
public $id_product;
/** @var int identifier of the product attribute if necessary */
public $id_product_attribute;
/** @var string Product reference */
public $reference;
/** @var string Product EAN13 */
public $ean13;
/** @var string Product ISBN */
public $isbn;
/** @var string UPC */
public $upc;
/** @var string MPN */
public $mpn;
/** @var int the physical quantity in stock for the current product in the current warehouse */
public $physical_quantity;
/** @var int the usable quantity (for sale) of the current physical quantity */
public $usable_quantity;
/** @var float the unit price without tax forthe current product */
public $price_te;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'stock',
'primary' => 'id_stock',
'fields' => [
'id_warehouse' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_product_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'reference' => ['type' => self::TYPE_STRING, 'validate' => 'isReference', 'size' => 64],
'ean13' => ['type' => self::TYPE_STRING, 'validate' => 'isEan13', 'size' => 13],
'isbn' => ['type' => self::TYPE_STRING, 'validate' => 'isIsbn', 'size' => 32],
'upc' => ['type' => self::TYPE_STRING, 'validate' => 'isUpc', 'size' => 12],
'mpn' => ['type' => self::TYPE_STRING, 'validate' => 'isMpn', 'size' => 40],
'physical_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
'usable_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true],
'price_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
],
];
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = [
'fields' => [
'id_warehouse' => ['xlink_resource' => 'warehouses'],
'id_product' => ['xlink_resource' => 'products'],
'id_product_attribute' => ['xlink_resource' => 'combinations'],
'real_quantity' => ['getter' => 'getWsRealQuantity', 'setter' => false],
],
'hidden_fields' => [
],
];
/**
* @see ObjectModel::update()
*/
public function update($null_values = false)
{
$this->getProductInformations();
return parent::update($null_values);
}
/**
* @see ObjectModel::add()
*/
public function add($autodate = true, $null_values = false)
{
$this->getProductInformations();
return parent::add($autodate, $null_values);
}
/**
* Gets reference, ean13 , isbn, mpn and upc of the current product
* Stores it in stock for stock_mvt integrity and history purposes.
*/
protected function getProductInformations()
{
// if combinations
if ((int) $this->id_product_attribute > 0) {
$query = new DbQuery();
$query->select('reference, ean13, isbn, mpn, upc');
$query->from('product_attribute');
$query->where('id_product = ' . (int) $this->id_product);
$query->where('id_product_attribute = ' . (int) $this->id_product_attribute);
$rows = Db::getInstance()->executeS($query);
if (!is_array($rows)) {
return;
}
foreach ($rows as $row) {
$this->reference = $row['reference'];
$this->ean13 = $row['ean13'];
$this->isbn = $row['isbn'];
$this->upc = $row['upc'];
$this->mpn = $row['mpn'];
}
} else {
// else, simple product
$product = new Product((int) $this->id_product);
if (Validate::isLoadedObject($product)) {
$this->reference = $product->reference;
$this->ean13 = $product->ean13;
$this->isbn = $product->isbn;
$this->upc = $product->upc;
$this->mpn = $product->mpn;
}
}
}
/**
* Webservice : used to get the real quantity of a product.
*/
public function getWsRealQuantity()
{
$manager = StockManagerFactory::getManager();
$quantity = $manager->getProductRealQuantities($this->id_product, $this->id_product_attribute, $this->id_warehouse, true);
return $quantity;
}
public static function deleteStockByIds($id_product = null, $id_product_attribute = null)
{
if (!$id_product || !$id_product_attribute) {
return false;
}
return Db::getInstance()->execute('DELETE FROM ' . _DB_PREFIX_ . 'stock WHERE `id_product` = ' . (int) $id_product . ' AND `id_product_attribute` = ' . (int) $id_product_attribute);
}
public static function productIsPresentInStock($id_product = 0, $id_product_attribute = 0, $id_warehouse = 0)
{
if (!(int) $id_product && !is_int($id_product_attribute) && !(int) $id_warehouse) {
return false;
}
$result = Db::getInstance()->executeS('SELECT `id_stock` FROM ' . _DB_PREFIX_ . 'stock
WHERE `id_warehouse` = ' . (int) $id_warehouse . ' AND `id_product` = ' . (int) $id_product . ((int) $id_product_attribute ? ' AND `id_product_attribute` = ' . $id_product_attribute : ''));
return is_array($result) && !empty($result) ? true : false;
}
}

View File

@@ -0,0 +1,720 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
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.
*/
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 and will be removed in future version.
* 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()
{
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
*
* @deprecated Since 9.0 and will be removed in 10.0
*/
public static function synchronize($id_product, $order_id_shop = null)
{
@trigger_error(sprintf(
'%s is deprecated since 9.0 and will be removed in 10.0.',
__METHOD__
), E_USER_DEPRECATED);
return true;
}
/**
* 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)
{
$quantity = Hook::exec(
'actionOverrideQuantityAvailableByProduct',
[
'id_product' => $id_product,
'id_product_attribute' => $id_product_attribute,
'id_shop' => $id_shop,
],
null,
false,
true,
false,
null,
true
);
if (is_int($quantity)) {
return $quantity;
}
// 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();
}
/**
* Updates the total quantity of the given product.
*
* If a product has combinations, the quantity id_product = X, id_product_attribute = 0 entry
* is the sum of quantities of all the combinations. After a quantity of any combination has been
* updated, we also have to update this sum.
*
* @see StockAvailableCore::update()
* @see StockAvailableCore::add()
*/
public function postSave()
{
// If there are no combinations, we can just consider it finished
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);
}
// Get the total quantity of all combinations
$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)
);
// And write it to the id_product = X, id_product_attribute = 0 entry
$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|null $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;
}
// Try to set available quantity if product does not depend on physical 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, 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 out_of_stock flag
*/
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);
}
}

View File

@@ -0,0 +1,821 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* StockManager : implementation of StockManagerInterface.
*
* @deprecated since 9.0 and will be removed in 10.0, stock is now managed by new logic
*/
class StockManagerCore implements StockManagerInterface
{
/**
* @see StockManagerInterface::isAvailable()
*/
public static function isAvailable()
{
// Default Manager : always available
return true;
}
/**
* @see StockManagerInterface::addProduct()
*
* @param int $id_product
* @param int $id_product_attribute
* @param Warehouse $warehouse
* @param int $quantity
* @param int|null $id_stock_mvt_reason
* @param float $price_te
* @param bool $is_usable
* @param int|null $id_supply_order
* @param Employee|null $employee
*
* @return bool
*
* @throws PrestaShopException
*/
public function addProduct(
$id_product,
$id_product_attribute,
Warehouse $warehouse,
$quantity,
$id_stock_mvt_reason,
$price_te,
$is_usable = true,
$id_supply_order = null,
$employee = null
) {
if (!Validate::isLoadedObject($warehouse) || !$quantity || !$id_product) {
return false;
}
$price_te = round((float) $price_te, 6);
if ($price_te <= 0.0) {
return false;
}
if (!StockMvtReason::exists($id_stock_mvt_reason)) {
$id_stock_mvt_reason = Configuration::get('PS_STOCK_MVT_INC_REASON_DEFAULT');
}
$context = Context::getContext();
$mvt_params = [
'id_stock' => null,
'physical_quantity' => $quantity,
'id_stock_mvt_reason' => $id_stock_mvt_reason,
'id_supply_order' => $id_supply_order,
'price_te' => $price_te,
'last_wa' => null,
'current_wa' => null,
'id_employee' => (int) $context->employee->id ? (int) $context->employee->id : $employee->id,
'employee_firstname' => $context->employee->firstname ? $context->employee->firstname : $employee->firstname,
'employee_lastname' => $context->employee->lastname ? $context->employee->lastname : $employee->lastname,
'sign' => 1,
];
$stock_exists = false;
// switch on MANAGEMENT_TYPE
switch ($warehouse->management_type) {
// case CUMP mode
case 'WA':
$stock_collection = $this->getStockCollection($id_product, $id_product_attribute, $warehouse->id);
// if this product is already in stock
if (count($stock_collection) > 0) {
$stock_exists = true;
/** @var Stock $stock */
// for a warehouse using WA, there is one and only one stock for a given product
$stock = $stock_collection->current();
// calculates WA price
$last_wa = $stock->price_te;
$current_wa = $this->calculateWA($stock, $quantity, $price_te);
$mvt_params['id_stock'] = $stock->id;
$mvt_params['last_wa'] = $last_wa;
$mvt_params['current_wa'] = $current_wa;
$stock_params = [
'physical_quantity' => ($stock->physical_quantity + $quantity),
'price_te' => $current_wa,
'usable_quantity' => ($is_usable ? ($stock->usable_quantity + $quantity) : $stock->usable_quantity),
'id_warehouse' => $warehouse->id,
];
// saves stock in warehouse
$stock->hydrate($stock_params);
$stock->update();
} else {
// else, the product is not in sock
$mvt_params['last_wa'] = 0;
$mvt_params['current_wa'] = $price_te;
}
break;
// case FIFO / LIFO mode
case 'FIFO':
case 'LIFO':
$stock_collection = $this->getStockCollection($id_product, $id_product_attribute, $warehouse->id, $price_te);
// if this product is already in stock
if (count($stock_collection) > 0) {
$stock_exists = true;
/** @var Stock $stock */
// there is one and only one stock for a given product in a warehouse and at the current unit price
$stock = $stock_collection->current();
$stock_params = [
'physical_quantity' => ($stock->physical_quantity + $quantity),
'usable_quantity' => ($is_usable ? ($stock->usable_quantity + $quantity) : $stock->usable_quantity),
];
// updates stock in warehouse
$stock->hydrate($stock_params);
$stock->update();
// sets mvt_params
$mvt_params['id_stock'] = $stock->id;
}
break;
default:
return false;
}
if (!$stock_exists) {
$stock = new Stock();
$stock_params = [
'id_product_attribute' => $id_product_attribute,
'id_product' => $id_product,
'physical_quantity' => $quantity,
'price_te' => $price_te,
'usable_quantity' => ($is_usable ? $quantity : 0),
'id_warehouse' => $warehouse->id,
];
// saves stock in warehouse
$stock->hydrate($stock_params);
$stock->add();
$mvt_params['id_stock'] = $stock->id;
}
// saves stock mvt
$stock_mvt = new StockMvt();
$stock_mvt->hydrate($mvt_params);
$stock_mvt->add();
return true;
}
/**
* @see StockManagerInterface::removeProduct()
*
* @param int $id_product
* @param int|null $id_product_attribute
* @param Warehouse $warehouse
* @param int $quantity
* @param int $id_stock_mvt_reason
* @param bool $is_usable
* @param int|null $id_order
* @param int $ignore_pack
* @param Employee|null $employee
*
* @return array|bool
*
* @throws PrestaShopException
*/
public function removeProduct(
$id_product,
$id_product_attribute,
Warehouse $warehouse,
$quantity,
$id_stock_mvt_reason,
$is_usable = true,
$id_order = null,
$ignore_pack = 0,
$employee = null
) {
$return = [];
if (!Validate::isLoadedObject($warehouse) || !$quantity || !$id_product) {
return $return;
}
if (!StockMvtReason::exists($id_stock_mvt_reason)) {
$id_stock_mvt_reason = Configuration::get('PS_STOCK_MVT_DEC_REASON_DEFAULT');
}
$context = Context::getContext();
// Special case of a pack
if (Pack::isPack((int) $id_product) && !$ignore_pack) {
if (Validate::isLoadedObject($product = new Product((int) $id_product))) {
if ($product->pack_stock_type == Pack::STOCK_TYPE_PACK_ONLY
|| $product->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH
|| (
$product->pack_stock_type == Pack::STOCK_TYPE_DEFAULT
&& (Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PACK_ONLY
|| Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PACK_BOTH)
)
) {
$return = array_merge($return, $this->removeProduct($id_product, $id_product_attribute, $warehouse, $quantity, $id_stock_mvt_reason, $is_usable, $id_order, 1, $employee));
}
} else {
return false;
}
} else {
// gets total quantities in stock for the current product
$physical_quantity_in_stock = (int) $this->getProductPhysicalQuantities($id_product, $id_product_attribute, [$warehouse->id], false);
$usable_quantity_in_stock = (int) $this->getProductPhysicalQuantities($id_product, $id_product_attribute, [$warehouse->id], true);
// check quantity if we want to decrement unusable quantity
if (!$is_usable) {
$quantity_in_stock = $physical_quantity_in_stock - $usable_quantity_in_stock;
} else {
$quantity_in_stock = $usable_quantity_in_stock;
}
// checks if it's possible to remove the given quantity
if ($quantity_in_stock < $quantity) {
return $return;
}
$stock_collection = $this->getStockCollection($id_product, $id_product_attribute, $warehouse->id);
$stock_collection->getAll();
// check if the collection is loaded
if (count($stock_collection) <= 0) {
return $return;
}
$stock_history_qty_available = [];
$mvt_params = [];
$stock_params = [];
$quantity_to_decrement_by_stock = [];
$global_quantity_to_decrement = $quantity;
// switch on MANAGEMENT_TYPE
switch ($warehouse->management_type) {
// case CUMP mode
case 'WA':
/** @var Stock $stock */
// There is one and only one stock for a given product in a warehouse in this mode
$stock = $stock_collection->current();
$mvt_params = [
'id_stock' => $stock->id,
'physical_quantity' => $quantity,
'id_stock_mvt_reason' => $id_stock_mvt_reason,
'id_order' => $id_order,
'price_te' => $stock->price_te,
'last_wa' => $stock->price_te,
'current_wa' => $stock->price_te,
'id_employee' => (int) $context->employee->id ? (int) $context->employee->id : $employee->id,
'employee_firstname' => $context->employee->firstname ? $context->employee->firstname : $employee->firstname,
'employee_lastname' => $context->employee->lastname ? $context->employee->lastname : $employee->lastname,
'sign' => -1,
];
$stock_params = [
'physical_quantity' => ($stock->physical_quantity - $quantity),
'usable_quantity' => ($is_usable ? ($stock->usable_quantity - $quantity) : $stock->usable_quantity),
];
// saves stock in warehouse
$stock->hydrate($stock_params);
$stock->update();
// saves stock mvt
$stock_mvt = new StockMvt();
$stock_mvt->hydrate($mvt_params);
$stock_mvt->save();
$return[$stock->id]['quantity'] = $quantity;
$return[$stock->id]['price_te'] = $stock->price_te;
break;
case 'LIFO':
case 'FIFO':
// for each stock, parse its mvts history to calculate the quantities left for each positive mvt,
// according to the instant available quantities for this stock
foreach ($stock_collection as $stock) {
/** @var Stock $stock */
$left_quantity_to_check = $stock->physical_quantity;
if ($left_quantity_to_check <= 0) {
continue;
}
$resource = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
'
SELECT sm.`id_stock_mvt`, sm.`date_add`, sm.`physical_quantity`,
IF ((sm2.`physical_quantity` is null), sm.`physical_quantity`, (sm.`physical_quantity` - SUM(sm2.`physical_quantity`))) as qty
FROM `' . _DB_PREFIX_ . 'stock_mvt` sm
LEFT JOIN `' . _DB_PREFIX_ . 'stock_mvt` sm2 ON sm2.`referer` = sm.`id_stock_mvt`
WHERE sm.`sign` = 1
AND sm.`id_stock` = ' . (int) $stock->id . '
GROUP BY sm.`id_stock_mvt`
ORDER BY sm.`date_add` DESC',
false
);
while ($row = Db::getInstance()->nextRow($resource)) {
// continue - in FIFO mode, we have to retreive the oldest positive mvts for which there are left quantities
if ($warehouse->management_type == 'FIFO') {
if ($row['qty'] == 0) {
continue;
}
}
// converts date to timestamp
$date = new DateTime($row['date_add']);
$timestamp = $date->format('U');
// history of the mvt
$stock_history_qty_available[$timestamp] = [
'id_stock' => $stock->id,
'id_stock_mvt' => (int) $row['id_stock_mvt'],
'qty' => (int) $row['qty'],
];
// break - in LIFO mode, checks only the necessary history to handle the global quantity for the current stock
if ($warehouse->management_type == 'LIFO') {
$left_quantity_to_check -= (int) $row['qty'];
if ($left_quantity_to_check <= 0) {
break;
}
}
}
}
if ($warehouse->management_type == 'LIFO') {
// orders stock history by timestamp to get newest history first
krsort($stock_history_qty_available);
} else {
// orders stock history by timestamp to get oldest history first
ksort($stock_history_qty_available);
}
// checks each stock to manage the real quantity to decrement for each of them
foreach ($stock_history_qty_available as $entry) {
if ($entry['qty'] >= $global_quantity_to_decrement) {
$quantity_to_decrement_by_stock[$entry['id_stock']][$entry['id_stock_mvt']] = $global_quantity_to_decrement;
$global_quantity_to_decrement = 0;
} else {
$quantity_to_decrement_by_stock[$entry['id_stock']][$entry['id_stock_mvt']] = $entry['qty'];
$global_quantity_to_decrement -= $entry['qty'];
}
if ($global_quantity_to_decrement <= 0) {
break;
}
}
// for each stock, decrements it and logs the mvts
foreach ($stock_collection as $stock) {
if (array_key_exists($stock->id, $quantity_to_decrement_by_stock) && is_array($quantity_to_decrement_by_stock[$stock->id])) {
$total_quantity_for_current_stock = 0;
foreach ($quantity_to_decrement_by_stock[$stock->id] as $id_mvt_referrer => $qte) {
$mvt_params = [
'id_stock' => $stock->id,
'physical_quantity' => $qte,
'id_stock_mvt_reason' => $id_stock_mvt_reason,
'id_order' => $id_order,
'price_te' => $stock->price_te,
'sign' => -1,
'referer' => $id_mvt_referrer,
'id_employee' => (int) $context->employee->id ? (int) $context->employee->id : $employee->id,
];
// saves stock mvt
$stock_mvt = new StockMvt();
$stock_mvt->hydrate($mvt_params);
$stock_mvt->save();
$total_quantity_for_current_stock += $qte;
}
$stock_params = [
'physical_quantity' => ($stock->physical_quantity - $total_quantity_for_current_stock),
'usable_quantity' => ($is_usable ? ($stock->usable_quantity - $total_quantity_for_current_stock) : $stock->usable_quantity),
];
$return[$stock->id]['quantity'] = $total_quantity_for_current_stock;
$return[$stock->id]['price_te'] = $stock->price_te;
// saves stock in warehouse
$stock->hydrate($stock_params);
$stock->update();
}
}
break;
}
if (Pack::isPacked($id_product, $id_product_attribute)) {
$packs = Pack::getPacksContainingItem($id_product, $id_product_attribute, (int) Configuration::get('PS_LANG_DEFAULT'));
foreach ($packs as $pack) {
// Decrease stocks of the pack only if pack is in linked stock mode (option called 'Decrement both')
if (!((int) $pack->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH)
&& !((int) $pack->pack_stock_type == Pack::STOCK_TYPE_DEFAULT
&& (int) Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PACK_BOTH)
) {
continue;
}
// Decrease stocks of the pack only if there is not enough items to constituate the actual pack stocks.
// How many packs can be constituated with the remaining product stocks
$quantity_by_pack = $pack->pack_item_quantity;
$stock_available_quantity = $quantity_in_stock - $quantity;
$max_pack_quantity = max([0, floor($stock_available_quantity / $quantity_by_pack)]);
$quantity_delta = Pack::getQuantity($pack->id) - $max_pack_quantity;
}
}
}
// if we remove a usable quantity, exec hook
if ($is_usable) {
Hook::exec(
'actionProductCoverage',
[
'id_product' => $id_product,
'id_product_attribute' => $id_product_attribute,
'warehouse' => $warehouse,
]
);
}
return $return;
}
/**
* @see StockManagerInterface::getProductPhysicalQuantities()
*/
public function getProductPhysicalQuantities($id_product, $id_product_attribute, $ids_warehouse = null, $usable = false)
{
if (null !== $ids_warehouse) {
// in case $ids_warehouse is not an array
if (!is_array($ids_warehouse)) {
$ids_warehouse = [$ids_warehouse];
}
// casts for security reason
$ids_warehouse = array_map('intval', $ids_warehouse);
if (!count($ids_warehouse)) {
return 0;
}
} else {
$ids_warehouse = [];
}
$query = new DbQuery();
$query->select('SUM(' . ($usable ? 's.usable_quantity' : 's.physical_quantity') . ')');
$query->from('stock', 's');
$query->where('s.id_product = ' . (int) $id_product);
if (0 != $id_product_attribute) {
$query->where('s.id_product_attribute = ' . (int) $id_product_attribute);
}
if (count($ids_warehouse)) {
$query->where('s.id_warehouse IN(' . implode(', ', $ids_warehouse) . ')');
}
return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
/**
* @see StockManagerInterface::getProductRealQuantities()
*/
public function getProductRealQuantities($id_product, $id_product_attribute, $ids_warehouse = null, $usable = false)
{
if (null !== $ids_warehouse) {
// in case $ids_warehouse is not an array
if (!is_array($ids_warehouse)) {
$ids_warehouse = [$ids_warehouse];
}
// casts for security reason
$ids_warehouse = array_map('intval', $ids_warehouse);
}
$client_orders_qty = 0;
// check if product is present in a pack
if (!Pack::isPack($id_product) && $in_pack = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
'SELECT id_product_pack, quantity FROM ' . _DB_PREFIX_ . 'pack
WHERE id_product_item = ' . (int) $id_product . '
AND id_product_attribute_item = ' . ($id_product_attribute ? (int) $id_product_attribute : '0')
)) {
foreach ($in_pack as $value) {
if (Validate::isLoadedObject($product = new Product((int) $value['id_product_pack']))
&& ($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))) {
$query = new DbQuery();
$query->select('od.product_quantity, od.product_quantity_refunded, pk.quantity');
$query->from('order_detail', 'od');
$query->leftjoin('orders', 'o', 'o.id_order = od.id_order');
$query->where('od.product_id = ' . (int) $value['id_product_pack']);
$query->leftJoin('order_history', 'oh', 'oh.id_order = o.id_order AND oh.id_order_state = o.current_state');
$query->leftJoin('order_state', 'os', 'os.id_order_state = oh.id_order_state');
$query->leftJoin('pack', 'pk', 'pk.id_product_item = ' . (int) $id_product . ' AND pk.id_product_attribute_item = ' . ($id_product_attribute ? (int) $id_product_attribute : '0') . ' AND id_product_pack = od.product_id');
$query->where('os.shipped != 1');
$query->where('o.valid = 1 OR (os.id_order_state != ' . (int) Configuration::get('PS_OS_ERROR') . '
AND os.id_order_state != ' . (int) Configuration::get('PS_OS_CANCELED') . ')');
$query->groupBy('od.id_order_detail');
if (count($ids_warehouse)) {
$query->where('od.id_warehouse IN(' . implode(', ', $ids_warehouse) . ')');
}
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
if (count($res)) {
foreach ($res as $row) {
$client_orders_qty += ($row['product_quantity'] - $row['product_quantity_refunded']) * $row['quantity'];
}
}
}
}
}
// skip if product is a pack without
$product = new Product((int) $id_product);
if (!Pack::isPack($id_product)
|| (
Validate::isLoadedObject($product)
&& $product->pack_stock_type == Pack::STOCK_TYPE_PACK_ONLY
|| $product->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH
|| (
$product->pack_stock_type == Pack::STOCK_TYPE_DEFAULT
&& (Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PACK_ONLY
|| Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PACK_BOTH
)
)
)
) {
// Gets client_orders_qty
$query = new DbQuery();
$query->select('od.product_quantity, od.product_quantity_refunded');
$query->from('order_detail', 'od');
$query->leftjoin('orders', 'o', 'o.id_order = od.id_order');
$query->where('od.product_id = ' . (int) $id_product);
if (0 != $id_product_attribute) {
$query->where('od.product_attribute_id = ' . (int) $id_product_attribute);
}
$query->leftJoin('order_history', 'oh', 'oh.id_order = o.id_order AND oh.id_order_state = o.current_state');
$query->leftJoin('order_state', 'os', 'os.id_order_state = oh.id_order_state');
$query->where('os.shipped != 1');
$query->where('o.valid = 1 OR (os.id_order_state != ' . (int) Configuration::get('PS_OS_ERROR') . '
AND os.id_order_state != ' . (int) Configuration::get('PS_OS_CANCELED') . ')');
$query->groupBy('od.id_order_detail');
if (count($ids_warehouse)) {
$query->where('od.id_warehouse IN(' . implode(', ', $ids_warehouse) . ')');
}
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
if (count($res)) {
foreach ($res as $row) {
$client_orders_qty += ($row['product_quantity'] - $row['product_quantity_refunded']);
}
}
}
// Gets supply_orders_qty
$query = new DbQuery();
$query->select('sod.quantity_expected, sod.quantity_received');
$query->from('supply_order', 'so');
$query->leftjoin('supply_order_detail', 'sod', 'sod.id_supply_order = so.id_supply_order');
$query->leftjoin('supply_order_state', 'sos', 'sos.id_supply_order_state = so.id_supply_order_state');
$query->where('sos.pending_receipt = 1');
$query->where('sod.id_product = ' . (int) $id_product . ' AND sod.id_product_attribute = ' . (int) $id_product_attribute);
if (null !== $ids_warehouse && count($ids_warehouse)) {
$query->where('so.id_warehouse IN(' . implode(', ', $ids_warehouse) . ')');
}
$supply_orders_qties = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
$supply_orders_qty = 0;
foreach ($supply_orders_qties as $qty) {
if ($qty['quantity_expected'] > $qty['quantity_received']) {
$supply_orders_qty += ($qty['quantity_expected'] - $qty['quantity_received']);
}
}
// Gets {physical OR usable}_qty
$qty = $this->getProductPhysicalQuantities($id_product, $id_product_attribute, $ids_warehouse, $usable);
// real qty = actual qty in stock - current client orders + current supply orders
return $qty - $client_orders_qty + $supply_orders_qty;
}
/**
* @see StockManagerInterface::transferBetweenWarehouses()
*/
public function transferBetweenWarehouses(
$id_product,
$id_product_attribute,
$quantity,
$id_warehouse_from,
$id_warehouse_to,
$usable_from = true,
$usable_to = true
) {
// Checks if this transfer is possible
if ($this->getProductPhysicalQuantities($id_product, $id_product_attribute, [$id_warehouse_from], $usable_from) < $quantity) {
return false;
}
if ($id_warehouse_from == $id_warehouse_to && $usable_from == $usable_to) {
return false;
}
// Checks if the given warehouses are available
$warehouse_from = new Warehouse($id_warehouse_from);
$warehouse_to = new Warehouse($id_warehouse_to);
if (!Validate::isLoadedObject($warehouse_from)
|| !Validate::isLoadedObject($warehouse_to)) {
return false;
}
// Removes from warehouse_from
$stocks = $this->removeProduct(
$id_product,
$id_product_attribute,
$warehouse_from,
$quantity,
(int) Configuration::get('PS_STOCK_MVT_TRANSFER_FROM'),
$usable_from
);
if (!count($stocks)) {
return false;
}
// Adds in warehouse_to
foreach ($stocks as $stock) {
$price = $stock['price_te'];
// convert product price to destination warehouse currency if needed
if ($warehouse_from->id_currency != $warehouse_to->id_currency) {
// First convert price to the default currency
$price_converted_to_default_currency = Tools::convertPrice($price, $warehouse_from->id_currency, false);
// Convert the new price from default currency to needed currency
$price = Tools::convertPrice($price_converted_to_default_currency, $warehouse_to->id_currency, true);
}
if (!$this->addProduct(
$id_product,
$id_product_attribute,
$warehouse_to,
$stock['quantity'],
(int) Configuration::get('PS_STOCK_MVT_TRANSFER_TO'),
$price,
$usable_to
)) {
return false;
}
}
return true;
}
/**
* @see StockManagerInterface::getProductCoverage()
* Here, $coverage is a number of days
*
* @return int number of days left (-1 if infinite)
*/
public function getProductCoverage($id_product, $id_product_attribute, $coverage, $id_warehouse = null)
{
if (!$id_product_attribute) {
$id_product_attribute = 0;
}
if ($coverage == 0) {
$coverage = 7;
} // Week by default
// gets all stock_mvt for the given coverage period
$query = '
SELECT SUM(view.quantity) as quantity_out
FROM
( SELECT sm.`physical_quantity` as quantity
FROM `' . _DB_PREFIX_ . 'stock_mvt` sm
LEFT JOIN `' . _DB_PREFIX_ . 'stock` s ON (sm.`id_stock` = s.`id_stock`)
LEFT JOIN `' . _DB_PREFIX_ . 'product` p ON (p.`id_product` = s.`id_product`)
' . Shop::addSqlAssociation('product', 'p') . '
LEFT JOIN `' . _DB_PREFIX_ . 'product_attribute` pa ON (p.`id_product` = pa.`id_product`)
' . Shop::addSqlAssociation('product_attribute', 'pa', false) . '
WHERE sm.`sign` = -1
AND sm.`id_stock_mvt_reason` != ' . Configuration::get('PS_STOCK_MVT_TRANSFER_FROM') . '
AND TO_DAYS("' . date('Y-m-d') . ' 00:00:00") - TO_DAYS(sm.`date_add`) <= ' . (int) $coverage . '
AND s.`id_product` = ' . (int) $id_product . '
AND s.`id_product_attribute` = ' . (int) $id_product_attribute .
($id_warehouse ? ' AND s.`id_warehouse` = ' . (int) $id_warehouse : '') . '
GROUP BY sm.`id_stock_mvt`
) as view';
$quantity_out = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
if (!$quantity_out) {
return -1;
}
$quantity_per_day = Tools::ps_round($quantity_out / $coverage);
$physical_quantity = $this->getProductPhysicalQuantities(
$id_product,
$id_product_attribute,
$id_warehouse ? [$id_warehouse] : null,
true
);
$time_left = ($quantity_per_day == 0) ? (-1) : Tools::ps_round($physical_quantity / $quantity_per_day);
return $time_left;
}
/**
* For a given stock, calculates its new WA(Weighted Average) price based on the new quantities and price
* Formula : (physicalStock * lastCump + quantityToAdd * unitPrice) / (physicalStock + quantityToAdd).
*
* @param Stock $stock
* @param int $quantity
* @param float $price_te
*
* @return float
*/
protected function calculateWA(Stock $stock, $quantity, $price_te)
{
return (float) Tools::ps_round((($stock->physical_quantity * $stock->price_te) + ($quantity * $price_te)) / ($stock->physical_quantity + $quantity), 6);
}
/**
* For a given product, retrieves the stock collection.
*
* @param int $id_product
* @param int $id_product_attribute
* @param int $id_warehouse Optional
* @param float|int|null $price_te Optional
*
* @return PrestaShopCollection Collection of Stock
*/
protected function getStockCollection($id_product, $id_product_attribute, $id_warehouse = null, $price_te = null)
{
$stocks = new PrestaShopCollection('Stock');
$stocks->where('id_product', '=', $id_product);
$stocks->where('id_product_attribute', '=', $id_product_attribute);
if ($id_warehouse) {
$stocks->where('id_warehouse', '=', $id_warehouse);
}
if ($price_te) {
$stocks->where('price_te', '=', $price_te);
}
return $stocks;
}
/**
* For a given product, retrieves the stock in function of the delivery option.
*
* @param int $id_product
* @param int $id_product_attribute optional
* @param array $delivery_option
*
* @return bool|int quantity
*/
public static function getStockByCarrier($id_product = 0, $id_product_attribute = 0, $delivery_option = null)
{
if (!(int) $id_product || !is_array($delivery_option) || !is_int($id_product_attribute)) {
return false;
}
$results = Warehouse::getWarehousesByProductId($id_product, $id_product_attribute);
$stock_quantity = 0;
foreach ($results as $result) {
if (isset($result['id_warehouse']) && (int) $result['id_warehouse']) {
$ws = new Warehouse((int) $result['id_warehouse']);
$carriers = $ws->getWsCarriers();
if (is_array($carriers) && !empty($carriers)) {
$stock_quantity += Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('SELECT SUM(s.`usable_quantity`) as quantity
FROM ' . _DB_PREFIX_ . 'stock s
LEFT JOIN ' . _DB_PREFIX_ . 'warehouse_carrier wc ON wc.`id_warehouse` = s.`id_warehouse`
LEFT JOIN ' . _DB_PREFIX_ . 'carrier c ON wc.`id_carrier` = c.`id_reference`
WHERE s.`id_product` = ' . (int) $id_product . ' AND s.`id_product_attribute` = ' . (int) $id_product_attribute . ' AND s.`id_warehouse` = ' . $result['id_warehouse'] . ' AND c.`id_carrier` IN (' . rtrim($delivery_option[(int) Context::getContext()->cart->id_address_delivery], ',') . ') GROUP BY s.`id_product`');
} else {
$stock_quantity += Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('SELECT SUM(s.`usable_quantity`) as quantity
FROM ' . _DB_PREFIX_ . 'stock s
WHERE s.`id_product` = ' . (int) $id_product . ' AND s.`id_product_attribute` = ' . (int) $id_product_attribute . ' AND s.`id_warehouse` = ' . $result['id_warehouse'] . ' GROUP BY s.`id_product`');
}
}
}
return $stock_quantity;
}
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* StockManagerFactory : factory of stock manager
*
* @deprecated since 9.0 and will be removed in 10.0, stock is now managed by new logic
*/
class StockManagerFactoryCore
{
/**
* @var StockManagerInterface|null Instance of the current StockManager
*/
protected static $stock_manager;
/**
* Returns a StockManager.
*
* @return StockManagerInterface
*/
public static function getManager()
{
if (!isset(StockManagerFactory::$stock_manager)) {
$stock_manager = StockManagerFactory::execHookStockManagerFactory();
if (!($stock_manager instanceof StockManagerInterface)) {
$stock_manager = new StockManager();
}
StockManagerFactory::$stock_manager = $stock_manager;
}
return StockManagerFactory::$stock_manager;
}
/**
* Looks for a StockManager in the modules list.
*
* @return StockManagerInterface
*/
public static function execHookStockManagerFactory()
{
$modules_infos = Hook::getModulesFromHook(Hook::getIdByName('stockManager'));
$stock_manager = false;
foreach ($modules_infos as $module_infos) {
$module_instance = Module::getInstanceByName($module_infos['name']);
if (is_callable([$module_instance, 'hookStockManager'])) {
$stock_manager = $module_instance->hookStockManager();
}
if ($stock_manager) {
break;
}
}
return $stock_manager;
}
}

View File

@@ -0,0 +1,110 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* StockManagerInterface : defines a way to manage stock.
*
* @deprecated since 9.0 and will be removed in 10.0, stock is now managed by new logic
*/
interface StockManagerInterface
{
/**
* Checks if the StockManager is available.
*
* @return bool
*/
public static function isAvailable();
/**
* For a given product, adds a given quantity.
*
* @param int $id_product
* @param int $id_product_attribute
* @param Warehouse $warehouse
* @param int $quantity
* @param int|null $id_stock_movement_reason
* @param float $price_te
* @param bool $is_usable
* @param int $id_supply_order optionnal
*
* @return bool
*/
public function addProduct($id_product, $id_product_attribute, Warehouse $warehouse, $quantity, $id_stock_movement_reason, $price_te, $is_usable = true, $id_supply_order = null);
/**
* For a given product, removes a given quantity.
*
* @param int $id_product
* @param int $id_product_attribute
* @param Warehouse $warehouse
* @param int $quantity
* @param int $id_stock_movement_reason
* @param bool $is_usable
* @param int $id_order Optionnal
*
* @return array - empty if an error occurred | details of removed products quantities with corresponding prices otherwise
*/
public function removeProduct($id_product, $id_product_attribute, Warehouse $warehouse, $quantity, $id_stock_movement_reason, $is_usable = true, $id_order = null);
/**
* For a given product, returns its physical quantity
* If the given product has combinations and $id_product_attribute is null, returns the sum for all combinations.
*
* @param int $id_product
* @param int $id_product_attribute
* @param array|int $ids_warehouse optional
* @param bool $usable false default - in this case we retrieve all physical quantities, otherwise we retrieve physical quantities flagged as usable
*
* @return int
*/
public function getProductPhysicalQuantities($id_product, $id_product_attribute, $ids_warehouse = null, $usable = false);
/**
* For a given product, returns its real quantity
* If the given product has combinations and $id_product_attribute is null, returns the sum for all combinations
* Real quantity : (physical_qty + supply_orders_qty - client_orders_qty)
* If $usable is defined, real quantity: usable_qty + supply_orders_qty - client_orders_qty.
*
* @param int $id_product
* @param int $id_product_attribute
* @param array|int $ids_warehouse optional
* @param bool $usable false by default
*
* @return int
*/
public function getProductRealQuantities($id_product, $id_product_attribute, $ids_warehouse = null, $usable = false);
/**
* For a given product, transfers quantities between two warehouses
* By default, it manages usable quantities
* It is also possible to transfer a usable quantity from warehouse 1 in an unusable quantity to warehouse 2
* It is also possible to transfer a usable quantity from warehouse 1 in an unusable quantity to warehouse 1.
*
* @param int $id_product
* @param int $id_product_attribute
* @param int $quantity
* @param int $warehouse_from
* @param int $warehouse_to
* @param bool $usable_from Optional, true by default
* @param bool $usable_to Optional, true by default
*
* @return bool
*/
public function transferBetweenWarehouses($id_product, $id_product_attribute, $quantity, $warehouse_from, $warehouse_to, $usable_from = true, $usable_to = true);
/**
* For a given product, returns the time left before being out of stock.
* By default, for the given product, it will use sum(quantities removed in all warehouses).
*
* @param int $id_product
* @param int $id_product_attribute
* @param int $coverage
* @param int $id_warehouse Optional
*
* @return int time
*/
public function getProductCoverage($id_product, $id_product_attribute, $coverage, $id_warehouse = null);
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @deprecated since 9.0 and will be removed in 10.0, stock is now managed by new logic
*/
abstract class StockManagerModuleCore extends Module
{
public $stock_manager_class;
public function install()
{
return parent::install() && $this->registerHook('stockManager');
}
public function hookStockManager()
{
$class_file = _PS_MODULE_DIR_ . '/' . $this->name . '/' . $this->stock_manager_class . '.php';
if (!isset($this->stock_manager_class) || !file_exists($class_file)) {
die($this->trans('Incorrect Stock Manager class [%s]', [htmlspecialchars($this->stock_manager_class)], 'Admin.Catalog.Notification'));
}
require_once $class_file;
if (!class_exists($this->stock_manager_class)) {
die($this->trans('Stock Manager class not found [%s]', [htmlspecialchars($this->stock_manager_class)], 'Admin.Catalog.Notification'));
}
$class = $this->stock_manager_class;
if (call_user_func([$class, 'isAvailable'])) {
return new $class();
}
return false;
}
}

202
classes/stock/StockMvt.php Normal file
View File

@@ -0,0 +1,202 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Defines stock movements
*
* @deprecated since 9.0 and will be removed in 10.0, this object model is no longer needed
*/
class StockMvtCore extends ObjectModel
{
public $id;
/**
* @var string The creation date of the movement
*/
public $date_add;
/**
* @var int The employee id, responsible of the movement
*/
public $id_employee;
/**
* @var string The first name of the employee responsible of the movement
*/
public $employee_firstname;
/**
* @var string The last name of the employee responsible of the movement
*/
public $employee_lastname;
/**
* @var int The stock id on wtich the movement is applied
*/
public $id_stock;
/**
* @var int the quantity of product with is moved
*/
public $physical_quantity;
/**
* @var int id of the movement reason assoiated to the movement
*/
public $id_stock_mvt_reason;
/**
* @var int Used when the movement is due to a customer order
*/
public $id_order = null;
/**
* @var int detrmine if the movement is a positive or negative operation
*/
public $sign;
/**
* @var int Used when the movement is due to a supplier order
*/
public $id_supply_order = null;
/**
* @var float Last value of the weighted-average method
*/
public $last_wa = null;
/**
* @var float Current value of the weighted-average method
*/
public $current_wa = null;
/**
* @var float The unit price without tax of the product associated to the movement
*/
public $price_te;
/**
* @var int Refers to an other id_stock_mvt : used for LIFO/FIFO implementation in StockManager
*/
public $referer;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'stock_mvt',
'primary' => 'id_stock_mvt',
'fields' => [
'id_employee' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'employee_firstname' => ['type' => self::TYPE_STRING, 'validate' => 'isName', 'size' => 255],
'employee_lastname' => ['type' => self::TYPE_STRING, 'validate' => 'isName', 'size' => 255],
'id_stock' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'physical_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
'id_stock_mvt_reason' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
'id_supply_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
'sign' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true],
'last_wa' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
'current_wa' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
'price_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
'referer' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true],
],
];
protected $webserviceParameters = [
'objectsNodeName' => 'stock_movements',
'objectNodeName' => 'stock_movement',
'fields' => [
'id_employee' => ['xlink_resource' => 'employees'],
'id_stock' => ['xlink_resource' => 'stock'],
'id_stock_mvt_reason' => ['xlink_resource' => 'stock_movement_reasons'],
'id_order' => ['xlink_resource' => 'orders'],
'id_supply_order' => ['xlink_resource' => 'supply_order'],
],
];
/**
* Gets the negative (decrements the stock) stock mvts that correspond to the given order, for :
* the given product, in the given quantity.
*
* @param int $id_order
* @param int $id_product
* @param int $id_product_attribute Use 0 if the product does not have attributes
* @param int $quantity
* @param int $id_warehouse Optional
*
* @return array mvts
*/
public static function getNegativeStockMvts($id_order, $id_product, $id_product_attribute, $quantity, $id_warehouse = null)
{
$movements = [];
$quantity_total = 0;
// preps query
$query = new DbQuery();
$query->select('sm.*, s.id_warehouse');
$query->from('stock_mvt', 'sm');
$query->innerJoin('stock', 's', 's.id_stock = sm.id_stock');
$query->where('sm.sign = -1');
$query->where('sm.id_order = ' . (int) $id_order);
$query->where('s.id_product = ' . (int) $id_product . ' AND s.id_product_attribute = ' . (int) $id_product_attribute);
// if filer by warehouse
if (null !== $id_warehouse) {
$query->where('s.id_warehouse = ' . (int) $id_warehouse);
}
// orders the movements by date
$query->orderBy('date_add DESC');
// gets the result
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query, false);
// fills the movements array
while ($row = Db::getInstance(_PS_USE_SQL_SLAVE_)->nextRow($res)) {
if ($quantity_total >= $quantity) {
break;
}
$quantity_total += (int) $row['physical_quantity'];
$movements[] = $row;
}
return $movements;
}
/**
* For a given product, gets the last positive stock mvt.
*
* @param int $id_product
* @param int $id_product_attribute Use 0 if the product does not have attributes
*
* @return bool|array
*/
public static function getLastPositiveStockMvt($id_product, $id_product_attribute)
{
$query = new DbQuery();
$query->select('sm.*, w.id_currency, (s.usable_quantity = sm.physical_quantity) as is_usable');
$query->from('stock_mvt', 'sm');
$query->innerJoin('stock', 's', 's.id_stock = sm.id_stock');
$query->innerJoin('warehouse', 'w', 'w.id_warehouse = s.id_warehouse');
$query->where('sm.sign = 1');
if ($id_product_attribute) {
$query->where('s.id_product = ' . (int) $id_product . ' AND s.id_product_attribute = ' . (int) $id_product_attribute);
} else {
$query->where('s.id_product = ' . (int) $id_product);
}
$query->orderBy('date_add DESC');
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
if ($res != false) {
return $res['0'];
}
return false;
}
}

View File

@@ -0,0 +1,124 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @deprecated since 9.0 and will be removed in 10.0, this object model is no longer needed
*/
class StockMvtReasonCore extends ObjectModel
{
/** @var int identifier of the movement reason */
public $id;
/** @var string the name of the movement reason */
public $name;
/** @var int detrmine if the movement reason correspond to a positive or negative operation */
public $sign;
/** @var string the creation date of the movement reason */
public $date_add;
/** @var string the last update date of the movement reason */
public $date_upd;
/** @var bool True if the movement reason has been deleted (staying in database as deleted) */
public $deleted = false;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'stock_mvt_reason',
'primary' => 'id_stock_mvt_reason',
'multilang' => true,
'fields' => [
'sign' => ['type' => self::TYPE_INT],
'deleted' => ['type' => self::TYPE_BOOL],
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 255],
],
];
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = [
'objectsNodeName' => 'stock_movement_reasons',
'objectNodeName' => 'stock_movement_reason',
'fields' => [
'sign' => [],
],
];
/**
* Gets Stock Mvt Reasons.
*
* @param int $id_lang
* @param int $sign Optionnal
*
* @return array
*/
public static function getStockMvtReasons($id_lang, $sign = null)
{
$query = new DbQuery();
$query->select('smrl.name, smr.id_stock_mvt_reason, smr.sign');
$query->from('stock_mvt_reason', 'smr');
$query->leftjoin('stock_mvt_reason_lang', 'smrl', 'smr.id_stock_mvt_reason = smrl.id_stock_mvt_reason AND smrl.id_lang=' . (int) $id_lang);
$query->where('smr.deleted = 0');
if ($sign != null) {
$query->where('smr.sign = ' . (int) $sign);
}
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* Same as StockMvtReason::getStockMvtReasons(), ignoring a specific lists of ids.
*
* @param int $id_lang
* @param array $ids_ignore
* @param int $sign optional
*/
public static function getStockMvtReasonsWithFilter($id_lang, $ids_ignore, $sign = null)
{
$query = new DbQuery();
$query->select('smrl.name, smr.id_stock_mvt_reason, smr.sign');
$query->from('stock_mvt_reason', 'smr');
$query->leftjoin('stock_mvt_reason_lang', 'smrl', 'smr.id_stock_mvt_reason = smrl.id_stock_mvt_reason AND smrl.id_lang=' . (int) $id_lang);
$query->where('smr.deleted = 0');
if ($sign != null) {
$query->where('smr.sign = ' . (int) $sign);
}
if (count($ids_ignore)) {
$ids_ignore = array_map('intval', $ids_ignore);
$query->where('smr.id_stock_mvt_reason NOT IN(' . implode(', ', $ids_ignore) . ')');
}
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* For a given id_stock_mvt_reason, tells if it exists.
*
* @param int $id_stock_mvt_reason
*
* @return bool
*/
public static function exists($id_stock_mvt_reason)
{
$query = new DbQuery();
$query->select('smr.id_stock_mvt_reason');
$query->from('stock_mvt_reason', 'smr');
$query->where('smr.id_stock_mvt_reason = ' . (int) $id_stock_mvt_reason);
$query->where('smr.deleted = 0');
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
}

View File

@@ -0,0 +1,284 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Webservice entity for stock movements.
*
* @deprecated since 9.0 and will be removed in 10.0, this object model is no longer needed
*/
class StockMvtWSCore extends ObjectModelCore
{
public $id;
/**
* @var string The creation date of the movement
*/
public $date_add;
/**
* @var int The employee id, responsible of the movement
*/
public $id_employee;
/**
* @var string The first name of the employee responsible of the movement
*/
public $employee_firstname;
/**
* @var string The last name of the employee responsible of the movement
*/
public $employee_lastname;
/**
* @var int The stock id on wtich the movement is applied
*/
public $id_stock;
/**
* @var int the quantity of product with is moved
*/
public $physical_quantity;
/**
* @var int id of the movement reason assoiated to the movement
*/
public $id_stock_mvt_reason;
/**
* @var int Used when the movement is due to a customer order
*/
public $id_order = null;
/**
* @var int detrmine if the movement is a positive or negative operation
*/
public $sign;
/**
* @var int Used when the movement is due to a supplier order
*/
public $id_supply_order = null;
/**
* @var float Last value of the weighted-average method
*/
public $last_wa = null;
/**
* @var float Current value of the weighted-average method
*/
public $current_wa = null;
/**
* @var float The unit price without tax of the product associated to the movement
*/
public $price_te;
/**
* @var int Refers to an other id_stock_mvt : used for LIFO/FIFO implementation in StockManager
*/
public $referer;
/**
* @var int id_product (@see Stock::id_product)
*/
public $id_product;
/**
* @var int id_product_attribute (@see Stock::id_product_attribute)
*/
public $id_product_attribute;
/**
* @var int id_warehouse (@see Stock::id_warehouse)
*/
public $id_warehouse;
/**
* @var int id_currency (@see Warehouse::id_currency)
*/
public $id_currency;
/**
* @var string management_type (@see Warehouse::management_type)
*/
public $management_type;
/**
* @var string : Name of the product (@see Product::getProductName)
*/
public $product_name;
/**
* @var string EAN13 of the product (@see Stock::product_ean13)
*/
public $ean13;
/**
* @var string UPC of the product (@see Stock::product_upc)
*/
public $upc;
/**
* @var string MPN of the product (@see Stock::product_mpn)
*/
public $mpn;
/**
* @var string Reference of the product (@see Stock::product_reference)
*/
public $reference;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'stock_mvt',
'primary' => 'id_stock_mvt',
'fields' => [
'id_employee' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'employee_firstname' => ['type' => self::TYPE_STRING, 'validate' => 'isName', 'size' => 255],
'employee_lastname' => ['type' => self::TYPE_STRING, 'validate' => 'isName', 'size' => 255],
'id_stock' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'physical_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
'id_stock_mvt_reason' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
'id_supply_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
'sign' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true],
'last_wa' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
'current_wa' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
'price_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
'referer' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true],
],
];
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = [
'fields' => [
'id_product' => ['xlink_resource' => 'products'],
'id_product_attribute' => ['xlink_resource' => 'combinations'],
'id_warehouse' => ['xlink_resource' => 'warehouses'],
'id_currency' => ['xlink_resource' => 'currencies'],
'management_type' => [],
'id_employee' => ['xlink_resource' => 'employees'],
'id_stock' => ['xlink_resource' => 'stocks'],
'id_stock_mvt_reason' => ['xlink_resource' => 'stock_movement_reasons'],
'id_order' => ['xlink_resource' => 'orders'],
'id_supply_order' => ['xlink_resource' => 'supply_orders'],
'product_name' => ['getter' => 'getWSProductName', 'i18n' => true],
'ean13' => [],
'upc' => [],
'reference' => [],
'mpn' => [],
],
'hidden_fields' => [
'referer',
'employee_firstname',
'employee_lastname',
],
];
/**
* Associations tables for attributes that require different tables than stated in ObjectModel::definition.
*
* @var array
*/
protected $tables_assoc = [
'id_product' => ['table' => 's'],
'id_product_attribute' => ['table' => 's'],
'id_warehouse' => ['table' => 's'],
'id_currency' => ['table' => 's'],
'management_type' => ['table' => 'w'],
'ean13' => ['table' => 's'],
'upc' => ['table' => 's'],
'mpn' => ['table' => 's'],
'reference' => ['table' => 's'],
];
/**
* @see ObjectModel
*/
public function __construct($id = null, $id_lang = null, $id_shop = null)
{
// calls parent
parent::__construct($id, $id_lang, $id_shop);
if ((int) $this->id != 0) {
$res = $this->getWebserviceObjectList('', ' AND ' . $this->def['primary'] . ' = ' . (int) $this->id, '', '', true);
if (isset($res[0])) {
foreach ($this->tables_assoc as $key => $param) {
$this->{$key} = $res[0][$key];
}
}
}
}
/**
* @see ObjectModel::getWebserviceObjectList()
* Added $full for this specific object
*/
public function getWebserviceObjectList($join, $filter, $sort, $limit, $full = false)
{
$query = 'SELECT DISTINCT main.' . $this->def['primary'] . ' ';
if ($full) {
$query .= ', s.id_product, s.id_product_attribute, s.id_warehouse, w.id_currency, w.management_type,
s.ean13, s.upc, s.mpn, s.reference ';
}
$old_filter = $filter;
if ($filter) {
foreach ($this->tables_assoc as $key => $value) {
$filter = str_replace('main.`' . $key . '`', $value['table'] . '.`' . $key . '`', $filter);
}
}
$query .= 'FROM ' . _DB_PREFIX_ . $this->def['table'] . ' as main ';
if ($filter !== $old_filter || $full) {
$query .= 'LEFT JOIN ' . _DB_PREFIX_ . 'stock s ON (s.id_stock = main.id_stock) ';
$query .= 'LEFT JOIN ' . _DB_PREFIX_ . 'warehouse w ON (w.id_warehouse = s.id_warehouse) ';
$query .= 'LEFT JOIN ' . _DB_PREFIX_ . 'currency c ON (c.id_currency = w.id_currency) ';
}
if ($join) {
$query .= $join;
}
$query .= 'WHERE 1 ';
if ($filter) {
$query .= $filter . ' ';
}
if ($sort) {
$query .= $sort . ' ';
}
if ($limit) {
$query .= $limit . ' ';
}
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* Webservice : getter for the product name.
*/
public function getWSProductName()
{
$res = [];
foreach (Language::getIDs(true) as $id_lang) {
$res[$id_lang] = Product::getProductName($this->id_product, $this->id_product_attribute, $id_lang);
}
return $res;
}
}

View File

@@ -0,0 +1,563 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @deprecated since 9.0 and will be removed in 10.0
*/
class SupplyOrderCore extends ObjectModel
{
/**
* @var int Supplier
*/
public $id_supplier;
/**
* @var string Supplier Name
*/
public $supplier_name;
/**
* @var int The language id used on the delivery note
*/
public $id_lang;
/**
* @var int Warehouse where products will be delivered
*/
public $id_warehouse;
/**
* @var int Current state of the order
*/
public $id_supply_order_state;
/**
* @var int Currency used for the order
*/
public $id_currency;
/**
* @var int Currency used by default in main global configuration (i.e. by default for all shops)
*/
public $id_ref_currency;
/**
* @var string Reference of the order
*/
public $reference;
/**
* @var string Date when added
*/
public $date_add;
/**
* @var string Date when updated
*/
public $date_upd;
/**
* @var string Expected delivery date
*/
public $date_delivery_expected;
/**
* @var float Total price without tax
*/
public $total_te = 0;
/**
* @var float Total price after discount, without tax
*/
public $total_with_discount_te = 0;
/**
* @var float Total price with tax
*/
public $total_ti = 0;
/**
* @var float Total tax value
*/
public $total_tax = 0;
/**
* @var float Supplier discount rate (for the whole order)
*/
public $discount_rate = 0;
/**
* @var float Supplier discount value without tax (for the whole order)
*/
public $discount_value_te = 0;
/**
* @var int Tells if this order is a template
*/
public $is_template = 0;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'supply_order',
'primary' => 'id_supply_order',
'fields' => [
'id_supplier' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'supplier_name' => ['type' => self::TYPE_STRING, 'validate' => 'isCatalogName', 'required' => false],
'id_lang' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_warehouse' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_supply_order_state' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_ref_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'reference' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true],
'date_delivery_expected' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true],
'total_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
'total_with_discount_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
'total_ti' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
'total_tax' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
'discount_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => false],
'discount_value_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
'is_template' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
],
];
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = [
'fields' => [
'id_supplier' => ['xlink_resource' => 'suppliers'],
'id_lang' => ['xlink_resource' => 'languages'],
'id_warehouse' => ['xlink_resource' => 'warehouses'],
'id_supply_order_state' => ['xlink_resource' => 'supply_order_states'],
'id_currency' => ['xlink_resource' => 'currencies'],
],
'hidden_fields' => [
'id_ref_currency',
],
'associations' => [
'supply_order_details' => [
'resource' => 'supply_order_detail',
'fields' => [
'id' => [],
'id_product' => [],
'id_product_attribute' => [],
'supplier_reference' => [],
'product_name' => [],
],
],
],
];
/**
* @see ObjectModel::update()
*/
public function update($null_values = false)
{
$this->calculatePrices();
$res = parent::update($null_values);
if ($res && !$this->is_template) {
$this->addHistory();
}
return $res;
}
/**
* @see ObjectModel::add()
*/
public function add($autodate = true, $null_values = false)
{
$this->calculatePrices();
$res = parent::add($autodate, $null_values);
if ($res && !$this->is_template) {
$this->addHistory();
}
return $res;
}
/**
* Checks all products in this order and calculate prices
* Applies the global discount if necessary.
*/
protected function calculatePrices()
{
$this->total_te = 0;
$this->total_with_discount_te = 0;
$this->total_tax = 0;
$this->total_ti = 0;
$is_discount = false;
if (is_numeric($this->discount_rate) && (float) $this->discount_rate >= 0) {
$is_discount = true;
}
// gets all product entries in this order
/** @var array<SupplyOrderDetail> $entries */
$entries = $this->getEntriesCollection();
foreach ($entries as $entry) {
// applys global discount rate on each product if possible
if ($is_discount) {
$entry->applyGlobalDiscount((float) $this->discount_rate);
}
// adds new prices to the total
$this->total_te += $entry->price_with_discount_te;
$this->total_with_discount_te += $entry->price_with_order_discount_te;
$this->total_tax += $entry->tax_value_with_order_discount;
$this->total_ti = $this->total_tax + $this->total_with_discount_te;
}
// applies global discount rate if possible
if ($is_discount) {
$this->discount_value_te = $this->total_te - $this->total_with_discount_te;
}
}
/**
* Retrieves the product entries for the current order.
*
* @param int $id_lang Optional Id Lang - Uses Context::language::id by default
*
* @return array
*/
public function getEntries($id_lang = null)
{
if ($id_lang == null) {
$id_lang = Context::getContext()->language->id;
}
// build query
$query = new DbQuery();
$query->select('
s.*,
IFNULL(CONCAT(pl.name, \' : \', GROUP_CONCAT(agl.name, \' - \', al.name SEPARATOR \', \')), pl.name) as name_displayed');
$query->from('supply_order_detail', 's');
$query->innerjoin('product_lang', 'pl', 'pl.id_product = s.id_product AND pl.id_lang = ' . (int) $id_lang);
$query->leftjoin('product', 'p', 'p.id_product = s.id_product');
$query->leftjoin('product_attribute_combination', 'pac', 'pac.id_product_attribute = s.id_product_attribute');
$query->leftjoin('attribute', 'atr', 'atr.id_attribute = pac.id_attribute');
$query->leftjoin('attribute_lang', 'al', 'al.id_attribute = atr.id_attribute AND al.id_lang = ' . (int) $id_lang);
$query->leftjoin('attribute_group_lang', 'agl', 'agl.id_attribute_group = atr.id_attribute_group AND agl.id_lang = ' . (int) $id_lang);
$query->where('s.id_supply_order = ' . (int) $this->id);
$query->groupBy('s.id_supply_order_detail');
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* Retrieves the details entries (i.e. products) collection for the current order.
*
* @return PrestaShopCollection Collection of SupplyOrderDetail
*/
public function getEntriesCollection()
{
$details = new PrestaShopCollection('SupplyOrderDetail');
$details->where('id_supply_order', '=', $this->id);
return $details;
}
/**
* Check if the order has entries.
*
* @return bool Has/Has not
*/
public function hasEntries()
{
$query = new DbQuery();
$query->select('COUNT(*)');
$query->from('supply_order_detail', 's');
$query->where('s.id_supply_order = ' . (int) $this->id);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query) > 0;
}
/**
* Check if the current state allows to edit the current order.
*
* @return bool
*/
public function isEditable()
{
$query = new DbQuery();
$query->select('s.editable');
$query->from('supply_order_state', 's');
$query->where('s.id_supply_order_state = ' . (int) $this->id_supply_order_state);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query) == 1;
}
/**
* Checks if the current state allows to generate a delivery note for this order.
*
* @return bool
*/
public function isDeliveryNoteAvailable()
{
$query = new DbQuery();
$query->select('s.delivery_note');
$query->from('supply_order_state', 's');
$query->where('s.id_supply_order_state = ' . (int) $this->id_supply_order_state);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query) == 1;
}
/**
* Checks if the current state allows to add products in stock.
*
* @return bool
*/
public function isInReceiptState()
{
$query = new DbQuery();
$query->select('s.receipt_state');
$query->from('supply_order_state', 's');
$query->where('s.id_supply_order_state = ' . (int) $this->id_supply_order_state);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query) == 1;
}
/**
* Historizes the order : its id, its state, and the employee responsible for the current action.
*/
protected function addHistory()
{
$context = Context::getContext();
$history = new SupplyOrderHistory();
$history->id_supply_order = $this->id;
$history->id_state = $this->id_supply_order_state;
$history->id_employee = (int) $context->employee->id;
$history->employee_firstname = pSQL($context->employee->firstname);
$history->employee_lastname = pSQL($context->employee->lastname);
$history->save();
}
/**
* Removes all products from the order.
*/
public function resetProducts()
{
$products = $this->getEntriesCollection();
foreach ($products as $p) {
$p->delete();
}
}
/**
* For a given $id_warehouse, tells if it has pending supply orders.
*
* @param int $id_warehouse
*
* @return bool
*/
public static function warehouseHasPendingOrders($id_warehouse)
{
if (!$id_warehouse) {
return false;
}
$query = new DbQuery();
$query->select('COUNT(so.id_supply_order) as supply_orders');
$query->from('supply_order', 'so');
$query->leftJoin('supply_order_state', 'sos', 'so.id_supply_order_state = sos.id_supply_order_state');
$query->where('sos.enclosed != 1');
$query->where('so.id_warehouse = ' . (int) $id_warehouse);
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
return $res > 0;
}
/**
* For a given $id_supplier, tells if it has pending supply orders.
*
* @param int $id_supplier Id Supplier
*
* @return bool
*/
public static function supplierHasPendingOrders($id_supplier)
{
if (!$id_supplier) {
return false;
}
$query = new DbQuery();
$query->select('COUNT(so.id_supply_order) as supply_orders');
$query->from('supply_order', 'so');
$query->leftJoin('supply_order_state', 'sos', 'so.id_supply_order_state = sos.id_supply_order_state');
$query->where('sos.enclosed != 1');
$query->where('so.id_supplier = ' . (int) $id_supplier);
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
return $res > 0;
}
/**
* For a given id or reference, tells if the supply order exists.
*
* @param int|string $match Either the reference of the order, or the Id of the order
*
* @return bool|int SupplyOrder Id
*/
public static function exists($match)
{
if (!$match) {
return false;
}
$query = new DbQuery();
$query->select('id_supply_order');
$query->from('supply_order', 'so');
$query->where('so.id_supply_order = ' . (int) $match . ' OR so.reference = "' . pSQL($match) . '"');
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
return (int) $res;
}
/**
* For a given reference, returns the corresponding supply order.
*
* @param string $reference Reference of the order
*
* @return bool|SupplyOrder
*/
public static function getSupplyOrderByReference($reference)
{
if (!$reference) {
return false;
}
$query = new DbQuery();
$query->select('id_supply_order');
$query->from('supply_order', 'so');
$query->where('so.reference = "' . pSQL($reference) . '"');
$id_supply_order = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
if (!$id_supply_order) {
return false;
}
$supply_order = new SupplyOrder($id_supply_order);
return $supply_order;
}
/**
* @see ObjectModel::hydrate()
*/
public function hydrate(array $data, $id_lang = null)
{
$this->id_lang = $id_lang;
if (isset($data[$this->def['primary']])) {
$this->id = $data[$this->def['primary']];
}
foreach ($data as $key => $value) {
if (array_key_exists($key, get_object_vars($this))) {
// formats prices and floats
if ($this->def['fields'][$key]['validate'] == 'isFloat'
|| $this->def['fields'][$key]['validate'] == 'isPrice') {
$value = Tools::ps_round($value, 6);
}
$this->$key = $value;
}
}
}
/**
* Gets the reference of a given order.
*
* @param int $id_supply_order
*
* @return bool|string
*/
public static function getReferenceById($id_supply_order)
{
if (!$id_supply_order) {
return false;
}
$query = new DbQuery();
$query->select('so.reference');
$query->from('supply_order', 'so');
$query->where('so.id_supply_order = ' . (int) $id_supply_order);
$ref = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
return pSQL($ref);
}
public function getAllExpectedQuantity()
{
return Db::getInstance()->getValue(
'
SELECT SUM(`quantity_expected`)
FROM `' . _DB_PREFIX_ . 'supply_order_detail`
WHERE `id_supply_order` = ' . (int) $this->id
);
}
public function getAllReceivedQuantity()
{
return Db::getInstance()->getValue(
'
SELECT SUM(`quantity_received`)
FROM `' . _DB_PREFIX_ . 'supply_order_detail`
WHERE `id_supply_order` = ' . (int) $this->id
);
}
public function getAllPendingQuantity()
{
return Db::getInstance()->getValue(
'
SELECT (SUM(`quantity_expected`) - SUM(`quantity_received`))
FROM `' . _DB_PREFIX_ . 'supply_order_detail`
WHERE `id_supply_order` = ' . (int) $this->id
);
}
/*********************************\
*
* Webservices Specific Methods
*
*********************************/
/**
* Webservice : gets the ids supply_order_detail associated to this order.
*
* @return array
*/
public function getWsSupplyOrderDetails()
{
$query = new DbQuery();
$query->select('sod.id_supply_order_detail as id, sod.id_product,
sod.id_product_attribute,
sod.name as product_name, supplier_reference');
$query->from('supply_order_detail', 'sod');
$query->where('id_supply_order = ' . (int) $this->id);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
}

View File

@@ -0,0 +1,274 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Represents one product ordered.
*
* @deprecated since 9.0 and will be removed in 10.0
*/
class SupplyOrderDetailCore extends ObjectModel
{
/**
* @var int Supply order
*/
public $id_supply_order;
/**
* @var int Product ordered
*/
public $id_product;
/**
* @var int Product attribute ordered
*/
public $id_product_attribute;
/**
* @var string Product reference
*/
public $reference;
/**
* @var string Product supplier reference
*/
public $supplier_reference;
/**
* @var int Product name
*/
public $name;
/**
* @var int Product EAN13
*/
public $ean13;
/**
* @var string Product ISBN
*/
public $isbn;
/**
* @var string UPC
*/
public $upc;
/**
* @var string MPN
*/
public $mpn;
/**
* @var int Currency used to buy this particular product
*/
public $id_currency;
/**
* @var float Exchange rate between and SupplyOrder::$id_ref_currency, at the time
*/
public $exchange_rate;
/**
* @var float Unit price without discount, without tax
*/
public $unit_price_te = 0;
/**
* @var int Quantity ordered
*/
public $quantity_expected = 0;
/**
* @var int Quantity received
*/
public $quantity_received = 0;
/**
* @var float this defines the price of the product, considering the number of units to buy.
* ($unit_price_te * $quantity), without discount, without tax
*/
public $price_te = 0;
/**
* @var float Supplier discount rate for a given product
*/
public $discount_rate = 0;
/**
* @var float Supplier discount value (($discount_rate / 100) *), without tax
*/
public $discount_value_te = 0;
/**
* @var float ($price_te -), with discount, without tax
*/
public $price_with_discount_te = 0;
/**
* @var int Tax rate for the given product
*/
public $tax_rate = 0;
/**
* @var float Tax value for the given product
*/
public $tax_value = 0;
/**
* @var float ($price_with_discount_te +)
*/
public $price_ti = 0;
/**
* @var float Tax value of the given product after applying the global order discount (i.e. if SupplyOrder::discount_rate is set)
*/
public $tax_value_with_order_discount = 0;
/**
* @var float This is like, considering the global order discount.
* (i.e. if SupplyOrder::discount_rate is set)
*/
public $price_with_order_discount_te = 0;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'supply_order_detail',
'primary' => 'id_supply_order_detail',
'fields' => [
'id_supply_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_product_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'reference' => ['type' => self::TYPE_STRING, 'validate' => 'isReference'],
'supplier_reference' => ['type' => self::TYPE_STRING, 'validate' => 'isReference'],
'name' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true],
'ean13' => ['type' => self::TYPE_STRING, 'validate' => 'isEan13'],
'isbn' => ['type' => self::TYPE_STRING, 'validate' => 'isIsbn'],
'upc' => ['type' => self::TYPE_STRING, 'validate' => 'isUpc'],
'mpn' => ['type' => self::TYPE_STRING, 'validate' => 'isMpn'],
'id_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'exchange_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true],
'unit_price_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
'quantity_expected' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
'quantity_received' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'],
'price_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
'discount_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true],
'discount_value_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
'price_with_discount_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
'tax_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true],
'tax_value' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
'price_ti' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
'tax_value_with_order_discount' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true],
'price_with_order_discount_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
],
];
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = [
'objectsNodeName' => 'supply_order_details',
'objectNodeName' => 'supply_order_detail',
'fields' => [
'id_supply_order' => ['xlink_resource' => 'supply_orders'],
'id_product' => ['xlink_resource' => 'products'],
'id_product_attribute' => ['xlink_resource' => 'combinations'],
],
'hidden_fields' => [
'id_currency',
],
];
/**
* @see ObjectModel::update()
*/
public function update($null_values = false)
{
$this->calculatePrices();
return parent::update($null_values);
}
/**
* @see ObjectModel::add()
*/
public function add($autodate = true, $null_values = false)
{
$this->calculatePrices();
return parent::add($autodate, $null_values);
}
/**
* Calculates all prices for this product based on its quantity and unit price
* Applies discount if necessary
* Calculates tax value, function of tax rate.
*/
protected function calculatePrices()
{
// calculates entry price
$this->price_te = Tools::ps_round((float) $this->unit_price_te * (int) $this->quantity_expected, 6);
// calculates entry discount value
if ($this->discount_rate != null && is_numeric($this->discount_rate) && $this->discount_rate > 0) {
$this->discount_value_te = Tools::ps_round((float) $this->price_te * ($this->discount_rate / 100), 6);
}
// calculates entry price with discount
$this->price_with_discount_te = Tools::ps_round($this->price_te - $this->discount_value_te, 6);
// calculates tax value
$this->tax_value = Tools::ps_round($this->price_with_discount_te * ((float) $this->tax_rate / 100), 6);
$this->price_ti = Tools::ps_round($this->price_with_discount_te + $this->tax_value, 6);
// defines default values for order discount fields
$this->tax_value_with_order_discount = Tools::ps_round($this->tax_value, 6);
$this->price_with_order_discount_te = Tools::ps_round($this->price_with_discount_te, 6);
}
/**
* Applies a global order discount rate, for the current product (i.e detail)
* Calls ObjectModel::update().
*
* @param float|int $discount_rate The discount rate in percent (Ex. 5 for 5 percents)
*/
public function applyGlobalDiscount($discount_rate)
{
if ($discount_rate != null && is_numeric($discount_rate) && (float) $discount_rate > 0) {
// calculates new price, with global order discount, tax ecluded
$discount_value = $this->price_with_discount_te - (($this->price_with_discount_te * (float) $discount_rate) / 100);
$this->price_with_order_discount_te = Tools::ps_round($discount_value, 6);
// calculates new tax value, with global order discount
$this->tax_value_with_order_discount = Tools::ps_round($this->price_with_order_discount_te * ((float) $this->tax_rate / 100), 6);
parent::update();
}
}
/**
* @see ObjectModel::hydrate()
*/
public function hydrate(array $data, $id_lang = null)
{
$this->id_lang = $id_lang;
if (isset($data[$this->def['primary']])) {
$this->id = $data[$this->def['primary']];
}
foreach ($data as $key => $value) {
if (array_key_exists($key, get_object_vars($this))) {
// formats prices and floats
if ($this->def['fields'][$key]['validate'] == 'isFloat'
|| $this->def['fields'][$key]['validate'] == 'isPrice') {
$value = Tools::ps_round($value, 6);
}
$this->$key = $value;
}
}
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @deprecated since 9.0 and will be removed in 10.0
*/
class SupplyOrderHistoryCore extends ObjectModel
{
/**
* @var int Supply order Id
*/
public $id_supply_order;
/**
* @var int Employee Id
*/
public $id_employee;
/**
* @var string The first name of the employee responsible of the movement
*/
public $employee_firstname;
/**
* @var string The last name of the employee responsible of the movement
*/
public $employee_lastname;
/**
* @var int State of the supply order
*/
public $id_state;
/**
* @var string Date
*/
public $date_add;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'supply_order_history',
'primary' => 'id_supply_order_history',
'fields' => [
'id_supply_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_employee' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'employee_firstname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
'employee_lastname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
'id_state' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true],
],
];
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = [
'objectsNodeName' => 'supply_order_histories',
'objectNodeName' => 'supply_order_history',
'fields' => [
'id_supply_order' => ['xlink_resource' => 'supply_orders'],
'id_employee' => ['xlink_resource' => 'employees'],
'id_state' => ['xlink_resource' => 'supply_order_states'],
],
];
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* History of receipts.
*
* @deprecated since 9.0 and will be removed in 10.0
*/
class SupplyOrderReceiptHistoryCore extends ObjectModel
{
/**
* @var int Detail of the supply order (i.e. One particular product)
*/
public $id_supply_order_detail;
/**
* @var int Employee
*/
public $id_employee;
/**
* @var string The first name of the employee responsible of the movement
*/
public $employee_firstname;
/**
* @var string The last name of the employee responsible of the movement
*/
public $employee_lastname;
/**
* @var int State
*/
public $id_supply_order_state;
/**
* @var int Quantity delivered
*/
public $quantity;
/**
* @var string Date of delivery
*/
public $date_add;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'supply_order_receipt_history',
'primary' => 'id_supply_order_receipt_history',
'fields' => [
'id_supply_order_detail' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_supply_order_state' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_employee' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'employee_firstname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
'employee_lastname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
'quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
],
];
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = [
'objectsNodeName' => 'supply_order_receipt_histories',
'objectNodeName' => 'supply_order_receipt_history',
'fields' => [
'id_supply_order_detail' => ['xlink_resource' => 'supply_order_details'],
'id_employee' => ['xlink_resource' => 'employees'],
'id_supply_order_state' => ['xlink_resource' => 'supply_order_states'],
],
];
}

View File

@@ -0,0 +1,156 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @deprecated since 9.0 and will be removed in 10.0
*/
class SupplyOrderStateCore extends ObjectModel
{
/**
* @var string Name of the state
*/
public $name;
/**
* @var bool Tells if a delivery note can be issued (i.e. the order has been validated)
*/
public $delivery_note;
/**
* @var bool Tells if the order is still editable by an employee (i.e. you can add products)
*/
public $editable;
/**
* @var bool Tells if the the order has been delivered
*/
public $receipt_state;
/**
* @var bool Tells if the the order is in a state corresponding to a product pending receipt
*/
public $pending_receipt;
/**
* @var bool Tells if the the order is in an enclosed state (i.e. terminated, canceled)
*/
public $enclosed;
/**
* @var string Color used to display the state in the specified color (Ex. #FFFF00)
*/
public $color;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'supply_order_state',
'primary' => 'id_supply_order_state',
'multilang' => true,
'fields' => [
'delivery_note' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'editable' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'receipt_state' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'pending_receipt' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'enclosed' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
'color' => ['type' => self::TYPE_STRING, 'validate' => 'isColor'],
'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 128],
],
];
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = [
'objectsNodeName' => 'supply_order_states',
'objectNodeName' => 'supply_order_state',
'fields' => [
],
];
/**
* Gets the list of supply order statuses.
*
* @param int $id_state_referrer Optional, used to know what state is available after this one
* @param int $id_lang Optional Id Language
*
* @return array States
*/
public static function getSupplyOrderStates($id_state_referrer = null, $id_lang = null)
{
if ($id_lang == null) {
$id_lang = Context::getContext()->language->id;
}
$query = new DbQuery();
$query->select('sl.name, s.id_supply_order_state');
$query->from('supply_order_state', 's');
$query->leftjoin('supply_order_state_lang', 'sl', 's.id_supply_order_state = sl.id_supply_order_state AND sl.id_lang=' . (int) $id_lang);
if (null !== $id_state_referrer) {
$is_receipt_state = false;
$is_editable = false;
$is_delivery_note = false;
$is_pending_receipt = false;
// check current state to see what state is available
$state = new SupplyOrderState((int) $id_state_referrer);
if (Validate::isLoadedObject($state)) {
$is_receipt_state = $state->receipt_state;
$is_editable = $state->editable;
$is_delivery_note = $state->delivery_note;
$is_pending_receipt = $state->pending_receipt;
}
$query->where('s.id_supply_order_state <> ' . (int) $id_state_referrer);
// check first if the order is editable
if ($is_editable) {
$query->where('s.editable = 1 OR s.delivery_note = 1 OR s.enclosed = 1');
} elseif ($is_delivery_note || $is_pending_receipt) {
// check if the delivery note is available or if the state correspond to a pending receipt state
$query->where('(s.delivery_note = 0 AND s.editable = 0) OR s.enclosed = 1');
} elseif ($is_receipt_state) {
// check if the state correspond to a receipt state
$query->where('s.receipt_state = 1');
}
}
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* Gets the list of supply order statuses.
*
* @param array|null $ids Optional Do not include these ids in the result
* @param int $id_lang Optional
*
* @return array
*/
public static function getStates($ids = null, $id_lang = null)
{
if ($id_lang == null) {
$id_lang = Context::getContext()->language->id;
}
if (!is_array($ids)) {
$ids = [];
}
$query = new DbQuery();
$query->select('sl.name, s.id_supply_order_state');
$query->from('supply_order_state', 's');
$query->leftjoin('supply_order_state_lang', 'sl', 's.id_supply_order_state = sl.id_supply_order_state AND sl.id_lang=' . (int) $id_lang);
if ($ids) {
$query->where('s.id_supply_order_state NOT IN(' . implode(',', array_map('intval', $ids)) . ')');
}
$query->orderBy('sl.name ASC');
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
}

616
classes/stock/Warehouse.php Normal file
View File

@@ -0,0 +1,616 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Holds Stock.
*
* @deprecated since 9.0 and will be removed in 10.0
*/
class WarehouseCore extends ObjectModel
{
/** @var int identifier of the warehouse */
public $id;
/** @var int Id of the address associated to the warehouse */
public $id_address;
/** @var string Reference of the warehouse */
public $reference;
/** @var string Name of the warehouse */
public $name;
/** @var int Id of the employee who manages the warehouse */
public $id_employee;
/** @var int Id of the valuation currency of the warehouse */
public $id_currency;
/** @var bool True if warehouse has been deleted (hence, no deletion in DB) */
public $deleted = false;
/**
* Describes the way a Warehouse is managed.
*
* @var string enum WA|LIFO|FIFO
*/
public $management_type;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'warehouse',
'primary' => 'id_warehouse',
'fields' => [
'id_address' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'reference' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true, 'size' => 64],
'name' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true, 'size' => 45],
'id_employee' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'management_type' => ['type' => self::TYPE_STRING, 'validate' => 'isStockManagement', 'required' => true],
'id_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'deleted' => ['type' => self::TYPE_BOOL],
],
];
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = [
'fields' => [
'id_address' => ['xlink_resource' => 'addresses'],
'id_employee' => ['xlink_resource' => 'employees'],
'id_currency' => ['xlink_resource' => 'currencies'],
'valuation' => ['getter' => 'getWsStockValue', 'setter' => false],
'deleted' => [],
],
'associations' => [
'stocks' => [
'resource' => 'stock',
'fields' => [
'id' => [],
],
],
'carriers' => [
'resource' => 'carrier',
'fields' => [
'id' => [],
],
],
'shops' => [
'resource' => 'shop',
'fields' => [
'id' => [],
'name' => [],
],
],
],
];
/**
* Gets the shops associated to the current warehouse.
*
* @return array Shops (id, name)
*/
public function getShops()
{
$query = new DbQuery();
$query->select('ws.id_shop, s.name');
$query->from('warehouse_shop', 'ws');
$query->leftJoin('shop', 's', 's.id_shop = ws.id_shop');
$query->where($this->def['primary'] . ' = ' . (int) $this->id);
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
return $res;
}
/**
* Gets the carriers associated to the current warehouse.
*
* @return array Ids of the associated carriers
*/
public function getCarriers($return_reference = false)
{
$ids_carrier = [];
$query = new DbQuery();
if ($return_reference) {
$query->select('wc.id_carrier');
} else {
$query->select('c.id_carrier');
}
$query->from('warehouse_carrier', 'wc');
$query->innerJoin('carrier', 'c', 'c.id_reference = wc.id_carrier');
$query->where($this->def['primary'] . ' = ' . (int) $this->id);
$query->where('c.deleted = 0');
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
if (!is_array($res)) {
return $ids_carrier;
}
foreach ($res as $carriers) {
foreach ($carriers as $carrier) {
$ids_carrier[$carrier] = $carrier;
}
}
return $ids_carrier;
}
/**
* Sets the carriers associated to the current warehouse.
*
* @param array $ids_carriers
*/
public function setCarriers($ids_carriers)
{
if (!is_array($ids_carriers)) {
$ids_carriers = [];
}
$row_to_insert = [];
foreach ($ids_carriers as $id_carrier) {
$row_to_insert[] = [$this->def['primary'] => $this->id, 'id_carrier' => (int) $id_carrier];
}
Db::getInstance()->execute('
DELETE FROM ' . _DB_PREFIX_ . 'warehouse_carrier
WHERE ' . $this->def['primary'] . ' = ' . (int) $this->id);
if ($row_to_insert) {
Db::getInstance()->insert('warehouse_carrier', $row_to_insert);
}
}
/**
* For a given carrier, removes it from the warehouse/carrier association
* If $id_warehouse is set, it only removes the carrier for this warehouse.
*
* @param int $id_carrier Id of the carrier to remove
* @param int $id_warehouse optional Id of the warehouse to filter
*/
public static function removeCarrier($id_carrier, $id_warehouse = null)
{
Db::getInstance()->execute('
DELETE FROM ' . _DB_PREFIX_ . 'warehouse_carrier
WHERE id_carrier = ' . (int) $id_carrier .
($id_warehouse ? ' AND id_warehouse = ' . (int) $id_warehouse : ''));
}
/**
* Checks if a warehouse is empty - i.e. has no stock.
*
* @return bool
*/
public function isEmpty()
{
$query = new DbQuery();
$query->select('SUM(s.physical_quantity)');
$query->from('stock', 's');
$query->where($this->def['primary'] . ' = ' . (int) $this->id);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query) == 0;
}
/**
* Checks if the given warehouse exists.
*
* @param int $id_warehouse
*
* @return bool Exists/Does not exist
*/
public static function exists($id_warehouse)
{
$query = new DbQuery();
$query->select('id_warehouse');
$query->from('warehouse');
$query->where('id_warehouse = ' . (int) $id_warehouse);
$query->where('deleted = 0');
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query, false);
}
/**
* For a given {product, product attribute} sets its location in the given warehouse
* First, for the given parameters, it cleans the database before updating.
*
* @param int $id_product ID of the product
* @param int $id_product_attribute Use 0 if this product does not have attributes
* @param int $id_warehouse ID of the warehouse
* @param string $location Describes the location (no lang id required)
*
* @return bool Success/Failure
*/
public static function setProductLocation($id_product, $id_product_attribute, $id_warehouse, $location)
{
Db::getInstance()->execute('
DELETE FROM `' . _DB_PREFIX_ . 'warehouse_product_location`
WHERE `id_product` = ' . (int) $id_product . '
AND `id_product_attribute` = ' . (int) $id_product_attribute . '
AND `id_warehouse` = ' . (int) $id_warehouse);
$row_to_insert = [
'id_product' => (int) $id_product,
'id_product_attribute' => (int) $id_product_attribute,
'id_warehouse' => (int) $id_warehouse,
'location' => pSQL($location),
];
return Db::getInstance()->insert('warehouse_product_location', $row_to_insert);
}
/**
* Resets all product locations for this warehouse.
*/
public function resetProductsLocations()
{
Db::getInstance()->execute('
DELETE FROM `' . _DB_PREFIX_ . 'warehouse_product_location`
WHERE `id_warehouse` = ' . (int) $this->id);
}
/**
* For a given {product, product attribute} gets its location in the given warehouse.
*
* @param int $id_product ID of the product
* @param int $id_product_attribute Use 0 if this product does not have attributes
* @param int $id_warehouse ID of the warehouse
*
* @return string Location of the product
*/
public static function getProductLocation($id_product, $id_product_attribute, $id_warehouse)
{
$query = new DbQuery();
$query->select('location');
$query->from('warehouse_product_location');
$query->where('id_warehouse = ' . (int) $id_warehouse);
$query->where('id_product = ' . (int) $id_product);
$query->where('id_product_attribute = ' . (int) $id_product_attribute);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
/**
* For a given {product, product attribute} gets warehouse list.
*
* @param int $id_product ID of the product
* @param int $id_product_attribute Optional, uses 0 if this product does not have attributes
* @param int $id_shop Optional, ID of the shop. Uses the context shop id (@see Context::shop)
*
* @return array Warehouses (ID, reference/name concatenated)
*/
public static function getProductWarehouseList($id_product, $id_product_attribute = 0, $id_shop = null)
{
// if it's a pack, returns warehouses if and only if some products use the advanced stock management
if ($id_shop === null) {
if (Shop::getContext() == Shop::CONTEXT_GROUP) {
$shop_group = Shop::getContextShopGroup();
$shop_group_id = (int) $shop_group->id;
} else {
$shop_group = Context::getContext()->shop->getGroup();
$shop_group_id = (int) $shop_group->id;
$id_shop = (int) Context::getContext()->shop->id;
}
$share_stock = $shop_group->share_stock;
} else {
$shop_group = Shop::getGroupFromShop($id_shop, false);
$share_stock = $shop_group['share_stock'];
$shop_group_id = (int) $shop_group['id'];
}
if ($share_stock && $shop_group_id) {
$ids_shop = Shop::getShops(true, $shop_group_id, true);
} else {
$ids_shop = [(int) $id_shop];
}
$query = new DbQuery();
$query->select('wpl.id_warehouse, CONCAT(w.reference, " - ", w.name) as name');
$query->from('warehouse_product_location', 'wpl');
$query->innerJoin('warehouse_shop', 'ws', 'ws.id_warehouse = wpl.id_warehouse AND id_shop IN (' . implode(',', array_map('intval', $ids_shop)) . ')');
$query->innerJoin('warehouse', 'w', 'ws.id_warehouse = w.id_warehouse');
$query->where('id_product = ' . (int) $id_product);
$query->where('id_product_attribute = ' . (int) $id_product_attribute);
$query->where('w.deleted = 0');
$query->groupBy('wpl.id_warehouse');
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* Gets available warehouses
* It is possible via ignore_shop and id_shop to filter the list with shop id.
*
* @param bool $ignore_shop Optional, false by default - Allows to get only the warehouses that are associated to one/some shops (@see $id_shop)
* @param int $id_shop optional, Context::shop::Id by default - Allows to define a specific shop to filter
*
* @return array Warehouses (ID, reference/name concatenated)
*/
public static function getWarehouses($ignore_shop = false, $id_shop = null)
{
if (!$ignore_shop) {
if (null === $id_shop) {
$id_shop = Context::getContext()->shop->id;
}
}
$query = new DbQuery();
$query->select('w.id_warehouse, CONCAT(reference, \' - \', name) as name');
$query->from('warehouse', 'w');
$query->where('deleted = 0');
$query->orderBy('reference ASC');
if (!$ignore_shop) {
$query->innerJoin('warehouse_shop', 'ws', 'ws.id_warehouse = w.id_warehouse AND ws.id_shop = ' . (int) $id_shop);
}
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* Gets warehouses grouped by shops.
*
* @return array (of array) Warehouses ID are grouped by shops ID
*/
public static function getWarehousesGroupedByShops()
{
$ids_warehouse = [];
$query = new DbQuery();
$query->select('id_warehouse, id_shop');
$query->from('warehouse_shop');
$query->orderBy('id_shop');
// queries to get warehouse ids grouped by shops
foreach (Db::getInstance()->executeS($query) as $row) {
$ids_warehouse[$row['id_shop']][] = $row['id_warehouse'];
}
return $ids_warehouse;
}
/**
* Gets the number of products in the current warehouse.
*
* @return int Number of different id_stock
*/
public function getNumberOfProducts()
{
$query = 'SELECT COUNT(t.id_stock) FROM
(
SELECT s.id_stock
FROM ' . _DB_PREFIX_ . 'stock s
WHERE s.id_warehouse = ' . (int) $this->id . '
GROUP BY s.id_product, s.id_product_attribute
) as t';
return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
/**
* Gets the number of quantities - for all products - in the current warehouse.
*
* @return int Total Quantity
*/
public function getQuantitiesOfProducts()
{
$query = '
SELECT SUM(s.physical_quantity)
FROM ' . _DB_PREFIX_ . 'stock s
WHERE s.id_warehouse = ' . (int) $this->id;
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
return $res ? $res : 0;
}
/**
* Gets the value of the stock in the current warehouse.
*
* @return float Value of the stock
*/
public function getStockValue()
{
$query = new DbQuery();
$query->select('SUM(s.`price_te` * s.`physical_quantity`)');
$query->from('stock', 's');
$query->where('s.`id_warehouse` = ' . (int) $this->id);
return (float) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
/**
* For a given employee, gets the warehouse(s) he/she manages.
*
* @param int $id_employee Manager ID
*
* @return array ids_warehouse Ids of the warehouses
*/
public static function getWarehousesByEmployee($id_employee)
{
$query = new DbQuery();
$query->select('w.id_warehouse');
$query->from('warehouse', 'w');
$query->where('w.id_employee = ' . (int) $id_employee);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* For a given product, returns the warehouses it is stored in.
*
* @param int $id_product Product Id
* @param int $id_product_attribute Optional, Product Attribute Id - 0 by default (no attribues)
*
* @return array Warehouses Ids and names
*/
public static function getWarehousesByProductId($id_product, $id_product_attribute = 0)
{
if (!$id_product && !$id_product_attribute) {
return [];
}
$query = new DbQuery();
$query->select('DISTINCT w.id_warehouse, CONCAT(w.reference, " - ", w.name) as name');
$query->from('warehouse', 'w');
$query->leftJoin('warehouse_product_location', 'wpl', 'wpl.id_warehouse = w.id_warehouse');
if ($id_product) {
$query->where('wpl.id_product = ' . (int) $id_product);
}
if ($id_product_attribute) {
$query->where('wpl.id_product_attribute = ' . (int) $id_product_attribute);
}
$query->orderBy('w.reference ASC');
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* For a given $id_warehouse, returns its name.
*
* @param int $id_warehouse Warehouse Id
*
* @return string Name
*/
public static function getWarehouseNameById($id_warehouse)
{
$query = new DbQuery();
$query->select('name');
$query->from('warehouse');
$query->where('id_warehouse = ' . (int) $id_warehouse);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
/**
* For a given pack, returns the warehouse it can be shipped from.
*
* @param int $id_product
*
* @return array|bool id_warehouse or false
*/
public static function getPackWarehouses($id_product, $id_shop = null)
{
if (!Pack::isPack($id_product)) {
return false;
}
if (null === $id_shop) {
$id_shop = Context::getContext()->shop->id;
}
// warehouses of the pack
$pack_warehouses = WarehouseProductLocation::getCollection((int) $id_product);
// array with all warehouses id to check
$list = [];
// fills $list
foreach ($pack_warehouses as $pack_warehouse) {
/* @var WarehouseProductLocation $pack_warehouse */
$list['pack_warehouses'][] = (int) $pack_warehouse->id_warehouse;
}
$res = false;
// returns final list
if (!empty($list)) {
$res = call_user_func_array('array_intersect', $list);
}
return $res;
}
/**
* @deprecated Since 9.0 and will be removed in 10.0
*/
public function resetStockAvailable()
{
@trigger_error(sprintf(
'%s is deprecated since 9.0 and will be removed in 10.0.',
__METHOD__
), E_USER_DEPRECATED);
return true;
}
/*********************************\
*
* Webservices Specific Methods
*
*********************************/
/**
* Webservice : gets the value of the warehouse.
*
* @return float
*/
public function getWsStockValue()
{
return $this->getStockValue();
}
/**
* Webservice : gets the ids stock associated to this warehouse.
*
* @return array
*/
public function getWsStocks()
{
$query = new DbQuery();
$query->select('s.id_stock as id');
$query->from('stock', 's');
$query->where('s.id_warehouse =' . (int) $this->id);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
}
/**
* Webservice : gets the ids shops associated to this warehouse.
*
* @return array
*/
public function getWsShops()
{
$query = new DbQuery();
$query->select('ws.id_shop as id, s.name');
$query->from('warehouse_shop', 'ws');
$query->leftJoin('shop', 's', 's.id_shop = ws.id_shop');
$query->where($this->def['primary'] . ' = ' . (int) $this->id);
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
return $res;
}
/**
* Webservice : gets the ids carriers associated to this warehouse.
*
* @return array
*/
public function getWsCarriers()
{
$ids_carrier = [];
$query = new DbQuery();
$query->select('wc.id_carrier as id');
$query->from('warehouse_carrier', 'wc');
$query->where($this->def['primary'] . ' = ' . (int) $this->id);
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
if (!is_array($res)) {
return $ids_carrier;
}
foreach ($res as $carriers) {
foreach ($carriers as $carrier) {
$ids_carrier[] = $carrier;
}
}
return $ids_carrier;
}
}

View File

@@ -0,0 +1,125 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* @deprecated since 9.0 and will be removed in 10.0
*/
class WarehouseProductLocationCore extends ObjectModel
{
/**
* @var int product ID
* */
public $id_product;
/**
* @var int product attribute ID
* */
public $id_product_attribute;
/**
* @var int warehouse ID
* */
public $id_warehouse;
/**
* @var string location of the product
* */
public $location;
/**
* @see ObjectModel::$definition
*/
public static $definition = [
'table' => 'warehouse_product_location',
'primary' => 'id_warehouse_product_location',
'fields' => [
'location' => ['type' => self::TYPE_STRING, 'validate' => 'isReference', 'size' => 64],
'id_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_product_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
'id_warehouse' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
],
];
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = [
'fields' => [
'id_product' => ['xlink_resource' => 'products'],
'id_product_attribute' => ['xlink_resource' => 'combinations'],
'id_warehouse' => ['xlink_resource' => 'warehouses'],
],
'hidden_fields' => [
],
];
/**
* For a given product and warehouse, gets the location.
*
* @param int $id_product product ID
* @param int $id_product_attribute product attribute ID
* @param int $id_warehouse warehouse ID
*
* @return string|false $location Location of the product
*/
public static function getProductLocation($id_product, $id_product_attribute, $id_warehouse)
{
// build query
$query = new DbQuery();
$query->select('wpl.location');
$query->from('warehouse_product_location', 'wpl');
$query->where(
'wpl.id_product = ' . (int) $id_product . '
AND wpl.id_product_attribute = ' . (int) $id_product_attribute . '
AND wpl.id_warehouse = ' . (int) $id_warehouse
);
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
/**
* For a given product and warehouse, gets the WarehouseProductLocation corresponding ID.
*
* @param int $id_product
* @param int $id_product_attribute
* @param int $id_warehouse
*
* @return int $id_warehouse_product_location ID of the WarehouseProductLocation
*/
public static function getIdByProductAndWarehouse($id_product, $id_product_attribute, $id_warehouse)
{
// build query
$query = new DbQuery();
$query->select('wpl.id_warehouse_product_location');
$query->from('warehouse_product_location', 'wpl');
$query->where(
'wpl.id_product = ' . (int) $id_product . '
AND wpl.id_product_attribute = ' . (int) $id_product_attribute . '
AND wpl.id_warehouse = ' . (int) $id_warehouse
);
return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
/**
* For a given product, gets its warehouses.
*
* @param int $id_product
*
* @return PrestaShopCollection The type of the collection is WarehouseProductLocation
*/
public static function getCollection($id_product)
{
$collection = new PrestaShopCollection('WarehouseProductLocation');
$collection->where('id_product', '=', (int) $id_product);
return $collection;
}
public static function getProducts($id_warehouse)
{
return Db::getInstance()->executeS('SELECT DISTINCT id_product FROM ' . _DB_PREFIX_ . 'warehouse_product_location WHERE id_warehouse=' . (int) $id_warehouse);
}
}

14
classes/stock/index.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../../');
exit;