Initial commit

This commit is contained in:
2020-10-07 10:37:15 +02:00
commit ce5f440392
28157 changed files with 4429172 additions and 0 deletions

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

@@ -0,0 +1,187 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* Represents the products kept in warehouses.
*
* @since 1.5.0
*/
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 int Product EAN13 */
public $ean13;
/** @var string Product ISBN */
public $isbn;
/** @var string UPC */
public $upc;
/** @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 int the unit price without tax forthe current product */
public $price_te;
/**
* @see ObjectModel::$definition
*/
public static $definition = array(
'table' => 'stock',
'primary' => 'id_stock',
'fields' => array(
'id_warehouse' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_product' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_product_attribute' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'reference' => array('type' => self::TYPE_STRING, 'validate' => 'isReference'),
'ean13' => array('type' => self::TYPE_STRING, 'validate' => 'isEan13'),
'isbn' => array('type' => self::TYPE_STRING, 'validate' => 'isIsbn'),
'upc' => array('type' => self::TYPE_STRING, 'validate' => 'isUpc'),
'physical_quantity' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true),
'usable_quantity' => array('type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true),
'price_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'fields' => array(
'id_warehouse' => array('xlink_resource' => 'warehouses'),
'id_product' => array('xlink_resource' => 'products'),
'id_product_attribute' => array('xlink_resource' => 'combinations'),
'real_quantity' => array('getter' => 'getWsRealQuantity', 'setter' => false),
),
'hidden_fields' => array(
),
);
/**
* @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 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, 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'];
}
} 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;
}
}
}
/**
* 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,922 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
use PrestaShop\PrestaShop\Adapter\ServiceLocator;
/**
* Represents quantities available
* It is either synchronized with Stock or manualy set by the seller.
*
* @since 1.5.0
*/
class StockAvailableCore extends ObjectModel
{
/** @var int identifier of the current product */
public $id_product;
/** @var int identifier of product attribute if necessary */
public $id_product_attribute;
/** @var int the shop associated to the current product and corresponding quantity */
public $id_shop;
/** @var int the group shop associated to the current product and corresponding quantity */
public $id_shop_group;
/** @var int the quantity available for sale */
public $quantity = 0;
/** @var bool determine if the available stock value depends on physical stock */
public $depends_on_stock = false;
/** @var bool determine if a product is out of stock - it was previously in Product class */
public $out_of_stock = false;
/** @var string the location of the stock for this product / combination */
public $location = '';
/**
* @see ObjectModel::$definition
*/
public static $definition = array(
'table' => 'stock_available',
'primary' => 'id_stock_available',
'fields' => array(
'id_product' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_product_attribute' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_shop' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'id_shop_group' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'quantity' => array('type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true),
'depends_on_stock' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool', 'required' => true),
'out_of_stock' => array('type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true),
'location' => array('type' => self::TYPE_STRING, 'validate' => 'isString', 'size' => 255),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'fields' => array(
'id_product' => array('xlink_resource' => 'products'),
'id_product_attribute' => array('xlink_resource' => 'combinations'),
'id_shop' => array('xlink_resource' => 'shops'),
'id_shop_group' => array('xlink_resource' => 'shop_groups'),
),
'hidden_fields' => array(
),
'objectMethods' => array(
'add' => 'addWs',
'update' => 'updateWs',
),
);
/**
* For a given {id_product, id_product_attribute and id_shop}, gets the stock available id associated.
*
* @param int $id_product
* @param int $id_product_attribute Optional
* @param int $id_shop Optional
*
* @return int
*/
public function updateWs()
{
if ($this->depends_on_stock) {
return WebserviceRequest::getInstance()->setError(500, $this->trans('You cannot update the available stock when it depends on stock.', array(), 'Admin.Catalog.Notification'), 133);
}
return $this->update();
}
public static function getStockAvailableIdByProductId($id_product, $id_product_attribute = null, $id_shop = null)
{
if (!Validate::isUnsignedId($id_product)) {
return false;
}
$query = new DbQuery();
$query->select('id_stock_available');
$query->from('stock_available');
$query->where('id_product = ' . (int) $id_product);
if ($id_product_attribute !== null) {
$query->where('id_product_attribute = ' . (int) $id_product_attribute);
}
$query = StockAvailable::addSqlShopRestriction($query, $id_shop);
return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
/**
* For a given id_product, synchronizes StockAvailable::quantity with Stock::usable_quantity.
*
* @param int $id_product
*/
public static function synchronize($id_product, $order_id_shop = null)
{
if (!Validate::isUnsignedId($id_product)) {
return false;
}
//if product is pack sync recursivly product in pack
if (Pack::isPack($id_product)) {
if (Validate::isLoadedObject($product = new Product((int) $id_product))) {
if ($product->pack_stock_type == Pack::STOCK_TYPE_PRODUCTS_ONLY
|| $product->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH
|| ($product->pack_stock_type == Pack::STOCK_TYPE_DEFAULT
&& Configuration::get('PS_PACK_STOCK_TYPE') > 0)
) {
$products_pack = Pack::getItems($id_product, (int) Configuration::get('PS_LANG_DEFAULT'));
foreach ($products_pack as $product_pack) {
StockAvailable::synchronize($product_pack->id, $order_id_shop);
}
}
} else {
return false;
}
}
// gets warehouse ids grouped by shops
$ids_warehouse = Warehouse::getWarehousesGroupedByShops();
if ($order_id_shop !== null) {
$order_warehouses = array();
$wh = Warehouse::getWarehouses(false, (int) $order_id_shop);
foreach ($wh as $warehouse) {
$order_warehouses[] = $warehouse['id_warehouse'];
}
}
// gets all product attributes ids
$ids_product_attribute = array();
foreach (Product::getProductAttributesIds($id_product) as $id_product_attribute) {
$ids_product_attribute[] = $id_product_attribute['id_product_attribute'];
}
// Allow to order the product when out of stock?
$out_of_stock = StockAvailable::outOfStock($id_product);
$manager = StockManagerFactory::getManager();
// loops on $ids_warehouse to synchronize quantities
foreach ($ids_warehouse as $id_shop => $warehouses) {
// first, checks if the product depends on stock for the given shop $id_shop
if (StockAvailable::dependsOnStock($id_product, $id_shop)) {
// init quantity
$product_quantity = 0;
// if it's a simple product
if (empty($ids_product_attribute)) {
$allowed_warehouse_for_product = WareHouse::getProductWarehouseList((int) $id_product, 0, (int) $id_shop);
$allowed_warehouse_for_product_clean = array();
foreach ($allowed_warehouse_for_product as $warehouse) {
$allowed_warehouse_for_product_clean[] = (int) $warehouse['id_warehouse'];
}
$allowed_warehouse_for_product_clean = array_intersect($allowed_warehouse_for_product_clean, $warehouses);
if ($order_id_shop != null && !count(array_intersect($allowed_warehouse_for_product_clean, $order_warehouses))) {
continue;
}
$product_quantity = $manager->getProductRealQuantities($id_product, null, $allowed_warehouse_for_product_clean, true);
Hook::exec(
'actionUpdateQuantity',
array(
'id_product' => $id_product,
'id_product_attribute' => 0,
'quantity' => $product_quantity,
'id_shop' => $id_shop,
)
);
} else {
// else this product has attributes, hence loops on $ids_product_attribute
foreach ($ids_product_attribute as $id_product_attribute) {
$allowed_warehouse_for_combination = WareHouse::getProductWarehouseList((int) $id_product, (int) $id_product_attribute, (int) $id_shop);
$allowed_warehouse_for_combination_clean = array();
foreach ($allowed_warehouse_for_combination as $warehouse) {
$allowed_warehouse_for_combination_clean[] = (int) $warehouse['id_warehouse'];
}
$allowed_warehouse_for_combination_clean = array_intersect($allowed_warehouse_for_combination_clean, $warehouses);
if ($order_id_shop != null && !count(array_intersect($allowed_warehouse_for_combination_clean, $order_warehouses))) {
continue;
}
$quantity = $manager->getProductRealQuantities($id_product, $id_product_attribute, $allowed_warehouse_for_combination_clean, true);
$query = new DbQuery();
$query->select('COUNT(*)');
$query->from('stock_available');
$query->where('id_product = ' . (int) $id_product . ' AND id_product_attribute = ' . (int) $id_product_attribute .
StockAvailable::addSqlShopRestriction(null, $id_shop));
if ((int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query)) {
$query = array(
'table' => 'stock_available',
'data' => array('quantity' => $quantity),
'where' => 'id_product = ' . (int) $id_product . ' AND id_product_attribute = ' . (int) $id_product_attribute .
StockAvailable::addSqlShopRestriction(null, $id_shop),
);
Db::getInstance()->update($query['table'], $query['data'], $query['where']);
} else {
$query = array(
'table' => 'stock_available',
'data' => array(
'quantity' => $quantity,
'depends_on_stock' => 1,
'out_of_stock' => $out_of_stock,
'id_product' => (int) $id_product,
'id_product_attribute' => (int) $id_product_attribute,
),
);
StockAvailable::addSqlShopParams($query['data'], $id_shop);
Db::getInstance()->insert($query['table'], $query['data']);
}
$product_quantity += $quantity;
Hook::exec(
'actionUpdateQuantity',
array(
'id_product' => $id_product,
'id_product_attribute' => $id_product_attribute,
'quantity' => $quantity,
'id_shop' => $id_shop,
)
);
}
}
// updates
// if $id_product has attributes, it also updates the sum for all attributes
if (($order_id_shop != null && array_intersect($warehouses, $order_warehouses)) || $order_id_shop == null) {
$query = array(
'table' => 'stock_available',
'data' => array('quantity' => $product_quantity),
'where' => 'id_product = ' . (int) $id_product . ' AND id_product_attribute = 0' .
StockAvailable::addSqlShopRestriction(null, $id_shop),
);
Db::getInstance()->update($query['table'], $query['data'], $query['where']);
}
}
}
// In case there are no warehouses, removes product from StockAvailable
if (count($ids_warehouse) == 0 && StockAvailable::dependsOnStock((int) $id_product)) {
Db::getInstance()->update('stock_available', array('quantity' => 0), 'id_product = ' . (int) $id_product);
}
Cache::clean('StockAvailable::getQuantityAvailableByProduct_' . (int) $id_product . '*');
}
/**
* For a given id_product, sets if stock available depends on stock.
*
* @param int $id_product
* @param int $depends_on_stock Optional : true by default
* @param int $id_shop Optional : gets context by default
*/
public static function setProductDependsOnStock($id_product, $depends_on_stock = true, $id_shop = null, $id_product_attribute = 0)
{
if (!Validate::isUnsignedId($id_product)) {
return false;
}
$existing_id = StockAvailable::getStockAvailableIdByProductId((int) $id_product, (int) $id_product_attribute, $id_shop);
if ($existing_id > 0) {
Db::getInstance()->update('stock_available', array(
'depends_on_stock' => (int) $depends_on_stock,
), 'id_stock_available = ' . (int) $existing_id);
} else {
$params = array(
'depends_on_stock' => (int) $depends_on_stock,
'id_product' => (int) $id_product,
'id_product_attribute' => (int) $id_product_attribute,
);
StockAvailable::addSqlShopParams($params, $id_shop);
Db::getInstance()->insert('stock_available', $params);
}
// depends on stock.. hence synchronizes
if ($depends_on_stock) {
StockAvailable::synchronize($id_product);
}
}
/**
* For a given id_product, sets if product is available out of stocks.
*
* @param int $id_product
* @param int $out_of_stock Optional false by default
* @param int $id_shop Optional gets context by default
*/
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',
array('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 = array(
'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 bool
*
* @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',
array('location' => $location),
'id_product = ' . $id_product .
(($id_product_attribute) ? ' AND id_product_attribute = ' . $id_product_attribute : '') .
StockAvailable::addSqlShopRestriction(null, $id_shop)
);
} else {
$params = array(
'location' => $location,
'id_product' => $id_product,
'id_product_attribute' => $id_product_attribute,
);
StockAvailable::addSqlShopParams($params, $id_shop);
Db::getInstance()->insert('stock_available', $params, false, true, Db::ON_DUPLICATE_KEY);
}
}
/**
* For a given id_product and id_product_attribute, gets its stock available.
*
* @param int $id_product
* @param int $id_product_attribute Optional
* @param int $id_shop Optional : gets context by default
*
* @return int Quantity
*/
public static function getQuantityAvailableByProduct($id_product = null, $id_product_attribute = null, $id_shop = null)
{
// if null, it's a product without attributes
if ($id_product_attribute === null) {
$id_product_attribute = 0;
}
$key = 'StockAvailable::getQuantityAvailableByProduct_' . (int) $id_product . '-' . (int) $id_product_attribute . '-' . (int) $id_shop;
if (!Cache::isStored($key)) {
$query = new DbQuery();
$query->select('SUM(quantity)');
$query->from('stock_available');
// if null, it's a product without attributes
if ($id_product !== null) {
$query->where('id_product = ' . (int) $id_product);
}
$query->where('id_product_attribute = ' . (int) $id_product_attribute);
$query = StockAvailable::addSqlShopRestriction($query, $id_shop);
$result = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
Cache::store($key, $result);
return $result;
}
return Cache::retrieve($key);
}
/**
* Upgrades total_quantity_available after having saved.
*
* @see ObjectModel::add()
*/
public function add($autodate = true, $null_values = false)
{
if (!$result = parent::add($autodate, $null_values)) {
return false;
}
$result &= $this->postSave();
return $result;
}
/**
* Upgrades total_quantity_available after having update.
*
* @see ObjectModel::update()
*/
public function update($null_values = false)
{
if (!$result = parent::update($null_values)) {
return false;
}
$result &= $this->postSave();
return $result;
}
/**
* Upgrades total_quantity_available after having saved.
*
* @see StockAvailableCore::update()
* @see StockAvailableCore::add()
*/
public function postSave()
{
if ($this->id_product_attribute == 0) {
return true;
}
$id_shop = (Shop::getContext() != Shop::CONTEXT_GROUP && $this->id_shop ? $this->id_shop : null);
if (!Configuration::get('PS_DISP_UNAVAILABLE_ATTR')) {
$combination = new Combination((int) $this->id_product_attribute);
if ($colors = $combination->getColorsAttributes()) {
$product = new Product((int) $this->id_product);
foreach ($colors as $color) {
if ($product->isColorUnavailable((int) $color['id_attribute'], (int) $this->id_shop)) {
Tools::clearColorListCache($product->id);
break;
}
}
}
}
$total_quantity = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(
'
SELECT SUM(quantity) as quantity
FROM ' . _DB_PREFIX_ . 'stock_available
WHERE id_product = ' . (int) $this->id_product . '
AND id_product_attribute <> 0 ' .
StockAvailable::addSqlShopRestriction(null, $id_shop)
);
$this->setQuantity($this->id_product, 0, $total_quantity, $id_shop, false);
return true;
}
/**
* For a given id_product and id_product_attribute updates the quantity available
* If $avoid_parent_pack_update is true, then packs containing the given product won't be updated.
*
* @param int $id_product
* @param int $id_product_attribute Optional
* @param int $delta_quantity The delta quantity to update
* @param int $id_shop Optional
* @param bool $add_movement Optional
* @param array $params Optional
*/
public static function updateQuantity($id_product, $id_product_attribute, $delta_quantity, $id_shop = null, $add_movement = false, $params = array())
{
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 $id_product
* @param $id_product_attribute
* @param $quantity
* @param null $id_shop
* @param bool $add_movement
*
* @return bool
*/
public static function setQuantity($id_product, $id_product_attribute, $quantity, $id_shop = null, $add_movement = true)
{
if (!Validate::isUnsignedId($id_product)) {
return false;
}
$context = Context::getContext();
// if there is no $id_shop, gets the context one
if ($id_shop === null && Shop::getContext() != Shop::CONTEXT_GROUP) {
$id_shop = (int) $context->shop->id;
}
$depends_on_stock = StockAvailable::dependsOnStock($id_product);
//Try to set available quantity if product does not depend on physical stock
if (!$depends_on_stock) {
$stockManager = ServiceLocator::get('\\PrestaShop\\PrestaShop\\Core\\Stock\\StockManager');
$id_stock_available = (int) StockAvailable::getStockAvailableIdByProductId($id_product, $id_product_attribute, $id_shop);
if ($id_stock_available) {
$stock_available = new StockAvailable($id_stock_available);
$deltaQuantity = -1 * ((int) $stock_available->quantity - (int) $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',
array(
'id_product' => $id_product,
'id_product_attribute' => $id_product_attribute,
'quantity' => $stock_available->quantity,
)
);
}
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|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 (Shop::getContext() == SHOP::CONTEXT_SHOP) {
if (Shop::getContextShopGroup()->share_stock == 1) {
$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 = array();
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)
{
if ($shop_group->share_stock) {
$shop_list = Shop::getShops(false, $shop_group->id, true);
}
if (count($shop_list) > 0) {
$id_shops_list = implode(', ', $shop_list);
return Db::getInstance()->update('stock_available', array('quantity' => 0), 'id_shop IN (' . $id_shops_list . ')');
} else {
return Db::getInstance()->update('stock_available', array('quantity' => 0), 'id_shop_group = ' . $shop_group->id);
}
}
/**
* For a given product, tells if it depends on the physical (usable) stock.
*
* @param int $id_product
* @param int $id_shop Optional : gets context if null @see Context::getContext()
*
* @return bool : depends on stock @see $depends_on_stock
*/
public static function dependsOnStock($id_product, $id_shop = null)
{
if (!Validate::isUnsignedId($id_product)) {
return false;
}
$query = new DbQuery();
$query->select('depends_on_stock');
$query->from('stock_available');
$query->where('id_product = ' . (int) $id_product);
$query->where('id_product_attribute = 0');
$query = StockAvailable::addSqlShopRestriction($query, $id_shop);
return (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
/**
* For a given product, get its "out of stock" flag.
*
* @param int $id_product
* @param int $id_shop Optional : gets context if null @see Context::getContext()
*
* @return bool : depends on stock @see $depends_on_stock
*/
public static function outOfStock($id_product, $id_shop = null)
{
if (!Validate::isUnsignedId($id_product)) {
return false;
}
$query = new DbQuery();
$query->select('out_of_stock');
$query->from('stock_available');
$query->where('id_product = ' . (int) $id_product);
$query->where('id_product_attribute = 0');
$query = StockAvailable::addSqlShopRestriction($query, $id_shop);
return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
}
/**
* @param int $id_product
* @param int id_product_attribute Optional
* @param int $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,874 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* StockManager : implementation of StockManagerInterface.
*
* @since 1.5.0
*/
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 $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 = array(
'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 = array(
'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 = array(
'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;
break;
}
if (!$stock_exists) {
$stock = new Stock();
$stock_params = array(
'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
*
* @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 = array();
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))) {
// Gets items
if ($product->pack_stock_type == Pack::STOCK_TYPE_PRODUCTS_ONLY
|| $product->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH
|| ($product->pack_stock_type == Pack::STOCK_TYPE_DEFAULT
&& Configuration::get('PS_PACK_STOCK_TYPE') > 0)
) {
$products_pack = Pack::getItems((int) $id_product, (int) Configuration::get('PS_LANG_DEFAULT'));
// Foreach item
foreach ($products_pack as $product_pack) {
if ($product_pack->advanced_stock_management == 1) {
$product_warehouses = Warehouse::getProductWarehouseList($product_pack->id, $product_pack->id_pack_product_attribute);
$warehouse_stock_found = false;
foreach ($product_warehouses as $product_warehouse) {
if (!$warehouse_stock_found) {
if (Warehouse::exists($product_warehouse['id_warehouse'])) {
$current_warehouse = new Warehouse($product_warehouse['id_warehouse']);
$return[] = $this->removeProduct($product_pack->id, $product_pack->id_pack_product_attribute, $current_warehouse, $product_pack->pack_quantity * $quantity, $id_stock_mvt_reason, $is_usable, $id_order, $ignore_pack, $employee);
// The product was found on this warehouse. Stop the stock searching.
$warehouse_stock_found = !empty($return[count($return) - 1]);
}
}
}
}
}
}
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, array($warehouse->id), false);
$usable_quantity_in_stock = (int) $this->getProductPhysicalQuantities($id_product, $id_product_attribute, array($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 = array();
$mvt_params = array();
$stock_params = array();
$quantity_to_decrement_by_stock = array();
$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 = array(
'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 = array(
'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] = array(
'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 = array(
'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 = array(
'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(array(0, floor($stock_available_quantity / $quantity_by_pack)));
$quantity_delta = Pack::getQuantity($pack->id) - $max_pack_quantity;
if ($pack->advanced_stock_management == 1 && $quantity_delta > 0) {
$product_warehouses = Warehouse::getPackWarehouses($pack->id);
$warehouse_stock_found = false;
foreach ($product_warehouses as $product_warehouse) {
if (!$warehouse_stock_found) {
if (Warehouse::exists($product_warehouse)) {
$current_warehouse = new Warehouse($product_warehouse);
$return[] = $this->removeProduct($pack->id, null, $current_warehouse, $quantity_delta, $id_stock_mvt_reason, $is_usable, $id_order, 1);
// The product was found on this warehouse. Stop the stock searching.
$warehouse_stock_found = !empty($return[count($return) - 1]);
}
}
}
}
}
}
}
// if we remove a usable quantity, exec hook
if ($is_usable) {
Hook::exec(
'actionProductCoverage',
array(
'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 = array($ids_warehouse);
}
// casts for security reason
$ids_warehouse = array_map('intval', $ids_warehouse);
if (!count($ids_warehouse)) {
return 0;
}
} else {
$ids_warehouse = array();
}
$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 = array($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
if (!Pack::isPack($id_product) || (Pack::isPack($id_product) && Validate::isLoadedObject($product = new Product((int) $id_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, array($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,
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'],
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) {
$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 = 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 ? array($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|PrestaShopCollection $stock
* @param int $quantity
* @param float $price_te
*
* @return int WA
*/
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 int $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 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,80 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/*
* StockManagerFactory : factory of stock manager
* @since 1.5.0
*/
class StockManagerFactoryCore
{
/**
* @var : 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(array($module_instance, 'hookStockManager'))) {
$stock_manager = $module_instance->hookStockManager();
}
if ($stock_manager) {
break;
}
}
return $stock_manager;
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* StockManagerInterface : defines a way to manage stock.
*
* @since 1.5.0
*/
interface StockManagerInterface
{
/**
* Checks if the StockManager is available.
*
* @return StockManagerInterface
*/
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 $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,60 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* @since 1.5.0
*/
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]', array($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]', array($this->stock_manager_class), 'Admin.Catalog.Notification'));
}
$class = $this->stock_manager_class;
if (call_user_func(array($class, 'isAvailable'))) {
return new $class();
}
return false;
}
}

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

@@ -0,0 +1,270 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* @since 1.5.0 It now defines stock movements when the advanced stock management system is available
*/
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;
/**
* @since 1.5.0
*
* @var string The first name of the employee responsible of the movement
*/
public $employee_firstname;
/**
* @since 1.5.0
*
* @var string The last name of the employee responsible of the movement
*/
public $employee_lastname;
/**
* @since 1.5.0
*
* @var int The stock id on wtich the movement is applied
*/
public $id_stock;
/**
* @since 1.5.0
*
* @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;
/**
* @since 1.5.0
*
* @var int detrmine if the movement is a positive or negative operation
*/
public $sign;
/**
* @since 1.5.0
*
* @var int Used when the movement is due to a supplier order
*/
public $id_supply_order = null;
/**
* @since 1.5.0
*
* @var float Last value of the weighted-average method
*/
public $last_wa = null;
/**
* @since 1.5.0
*
* @var float Current value of the weighted-average method
*/
public $current_wa = null;
/**
* @since 1.5.0
*
* @var float The unit price without tax of the product associated to the movement
*/
public $price_te;
/**
* @since 1.5.0
*
* @var int Refers to an other id_stock_mvt : used for LIFO/FIFO implementation in StockManager
*/
public $referer;
/**
* @deprecated since 1.5.0
* @deprecated stock movement will not be updated anymore
*/
public $date_upd;
/**
* @deprecated since 1.5.0
* @see physical_quantity
*
* @var int
*/
public $quantity;
/**
* @see ObjectModel::$definition
*/
public static $definition = array(
'table' => 'stock_mvt',
'primary' => 'id_stock_mvt',
'fields' => array(
'id_employee' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'employee_firstname' => array('type' => self::TYPE_STRING, 'validate' => 'isName'),
'employee_lastname' => array('type' => self::TYPE_STRING, 'validate' => 'isName'),
'id_stock' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'physical_quantity' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true),
'id_stock_mvt_reason' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'id_supply_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'sign' => array('type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true),
'last_wa' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
'current_wa' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
'price_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'referer' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true),
),
);
protected $webserviceParameters = array(
'objectsNodeName' => 'stock_movements',
'objectNodeName' => 'stock_movement',
'fields' => array(
'id_employee' => array('xlink_resource' => 'employees'),
'id_stock' => array('xlink_resource' => 'stock'),
'id_stock_mvt_reason' => array('xlink_resource' => 'stock_movement_reasons'),
'id_order' => array('xlink_resource' => 'orders'),
'id_supply_order' => array('xlink_resource' => 'supply_order'),
),
);
/**
* @deprecated since 1.5.0
*
* This method no longer exists.
* There is no equivalent or replacement, considering that this should be handled by inventories.
*/
public static function addMissingMvt($id_employee)
{
// display that this method is deprecated
Tools::displayAsDeprecated();
}
/**
* Gets the negative (decrements the stock) stock mvts that correspond to the given order, for :
* the given product, in the given quantity.
*
* @since 1.5.0
*
* @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 = array();
$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.
*
* @since 1.5.0
*
* @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,145 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
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 = 0;
/**
* @since 1.5.0
* @see ObjectModel::$definition
*/
public static $definition = array(
'table' => 'stock_mvt_reason',
'primary' => 'id_stock_mvt_reason',
'multilang' => true,
'fields' => array(
'sign' => array('type' => self::TYPE_INT),
'deleted' => array('type' => self::TYPE_BOOL),
'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
'name' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 255),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'objectsNodeName' => 'stock_movement_reasons',
'objectNodeName' => 'stock_movement_reason',
'fields' => array(
'sign' => array(),
),
);
/**
* 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.
*
* @since 1.5.0
*
* @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.
*
* @since 1.5.0
*
* @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,297 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* Webservice entity for stock movements.
*
* @since 1.5.0
*/
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 Reference of the product (@see Stock::product_reference)
*/
public $reference;
/**
* @see ObjectModel::$definition
*/
public static $definition = array(
'table' => 'stock_mvt',
'primary' => 'id_stock_mvt',
'fields' => array(
'id_employee' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'employee_firstname' => array('type' => self::TYPE_STRING, 'validate' => 'isName'),
'employee_lastname' => array('type' => self::TYPE_STRING, 'validate' => 'isName'),
'id_stock' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'physical_quantity' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true),
'id_stock_mvt_reason' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'id_supply_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'sign' => array('type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true),
'last_wa' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
'current_wa' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
'price_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'referer' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'fields' => array(
'id_product' => array('xlink_resource' => 'products'),
'id_product_attribute' => array('xlink_resource' => 'combinations'),
'id_warehouse' => array('xlink_resource' => 'warehouses'),
'id_currency' => array('xlink_resource' => 'currencies'),
'management_type' => array(),
'id_employee' => array('xlink_resource' => 'employees'),
'id_stock' => array('xlink_resource' => 'stocks'),
'id_stock_mvt_reason' => array('xlink_resource' => 'stock_movement_reasons'),
'id_order' => array('xlink_resource' => 'orders'),
'id_supply_order' => array('xlink_resource' => 'supply_orders'),
'product_name' => array('getter' => 'getWSProductName', 'i18n' => true),
'ean13' => array(),
'upc' => array(),
'reference' => array(),
),
'hidden_fields' => array(
'referer',
'employee_firstname',
'employee_lastname',
),
);
/**
* Associations tables for attributes that require different tables than stated in ObjectModel::definition.
*
* @var array
*/
protected $tables_assoc = array(
'id_product' => array('table' => 's'),
'id_product_attribute' => array('table' => 's'),
'id_warehouse' => array('table' => 's'),
'id_currency' => array('table' => 's'),
'management_type' => array('table' => 'w'),
'ean13' => array('table' => 's'),
'upc' => array('table' => 's'),
'reference' => array('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(null, (' AND ' . $this->def['primary'] . ' = ' . (int) $this->id), null, null, 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.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 = array();
foreach (Language::getIDs(true) as $id_lang) {
$res[$id_lang] = Product::getProductName($this->id_product, $this->id_product_attribute, $id_lang);
}
return $res;
}
}

583
classes/stock/SupplyOrder.php Executable file
View File

@@ -0,0 +1,583 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* @since 1.5.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 = array(
'table' => 'supply_order',
'primary' => 'id_supply_order',
'fields' => array(
'id_supplier' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'supplier_name' => array('type' => self::TYPE_STRING, 'validate' => 'isCatalogName', 'required' => false),
'id_lang' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_warehouse' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_supply_order_state' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_currency' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_ref_currency' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'reference' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true),
'date_delivery_expected' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true),
'total_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
'total_with_discount_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
'total_ti' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
'total_tax' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
'discount_rate' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => false),
'discount_value_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
'is_template' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'fields' => array(
'id_supplier' => array('xlink_resource' => 'suppliers'),
'id_lang' => array('xlink_resource' => 'languages'),
'id_warehouse' => array('xlink_resource' => 'warehouses'),
'id_supply_order_state' => array('xlink_resource' => 'supply_order_states'),
'id_currency' => array('xlink_resource' => 'currencies'),
),
'hidden_fields' => array(
'id_ref_currency',
),
'associations' => array(
'supply_order_details' => array(
'resource' => 'supply_order_detail',
'fields' => array(
'id' => array(),
'id_product' => array(),
'id_product_attribute' => array(),
'supplier_reference' => array(),
'product_name' => array(),
),
),
),
);
/**
* @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
$entries = $this->getEntriesCollection();
foreach ($entries as $entry) {
/* @var SupplyOrderDetail $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 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, $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,365 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* Represents one product ordered.
*
* @since 1.5.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 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 = array(
'table' => 'supply_order_detail',
'primary' => 'id_supply_order_detail',
'fields' => array(
'id_supply_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_product' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_product_attribute' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'reference' => array('type' => self::TYPE_STRING, 'validate' => 'isReference'),
'supplier_reference' => array('type' => self::TYPE_STRING, 'validate' => 'isReference'),
'name' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true),
'ean13' => array('type' => self::TYPE_STRING, 'validate' => 'isEan13'),
'isbn' => array('type' => self::TYPE_STRING, 'validate' => 'isIsbn'),
'upc' => array('type' => self::TYPE_STRING, 'validate' => 'isUpc'),
'id_currency' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'exchange_rate' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true),
'unit_price_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'quantity_expected' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true),
'quantity_received' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
'price_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'discount_rate' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true),
'discount_value_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'price_with_discount_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'tax_rate' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true),
'tax_value' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'price_ti' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'tax_value_with_order_discount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true),
'price_with_order_discount_te' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'objectsNodeName' => 'supply_order_details',
'objectNodeName' => 'supply_order_detail',
'fields' => array(
'id_supply_order' => array('xlink_resource' => 'supply_orders'),
'id_product' => array('xlink_resource' => 'products'),
'id_product_attribute' => array('xlink_resource' => 'combinations'),
),
'hidden_fields' => array(
'id_currency',
),
);
/**
* @see ObjectModel::update()
*/
public function update($null_values = false)
{
$this->calculatePrices();
parent::update($null_values);
}
/**
* @see ObjectModel::add()
*/
public function add($autodate = true, $null_values = false)
{
$this->calculatePrices();
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_float($this->discount_rate) || 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::validateController()
*
* @param bool $htmlentities Optional
*
* @return array Errors, if any..
*/
public function validateController($htmlentities = true)
{
$errors = array();
/* required fields */
$fields_required = $this->fieldsRequired;
$objectName = $this->getObjectName();
if (isset(self::$fieldsRequiredDatabase[$objectName])) {
$fields_required = array_merge(
$this->fieldsRequired,
self::$fieldsRequiredDatabase[$objectName]
);
}
foreach ($fields_required as $field) {
if (($value = $this->{$field}) == false && (string) $value != '0') {
if (!$this->id || $field != 'passwd') {
$errors[] = $this->trans(
'%s is required.',
array(
'<b>' . SupplyOrderDetail::displayFieldName($field, get_class($this), $htmlentities) . '</b>',
),
'Shop.Notifications.Error'
);
}
}
}
/* Checks maximum fields sizes */
foreach ($this->fieldsSize as $field => $max_length) {
if ($value = $this->{$field} && Tools::strlen($value) > $max_length) {
$errors[] = $this->trans(
'The %1$s field is too long (%2$d chars max).',
array(SupplyOrderDetail::displayFieldName($field, get_class($this), $htmlentities), $max_length),
'Shop.Notifications.Error'
);
}
}
/* Checks fields validity */
foreach ($this->fieldsValidate as $field => $function) {
if ($value = $this->{$field}) {
if (!Validate::$function($value) && (!empty($value) || in_array($field, $this->fieldsRequired))) {
$errors[] = '<b>' . SupplyOrderDetail::displayFieldName($field, get_class($this), $htmlentities) . '</b> ' . $this->trans('is invalid.', array(), 'Shop.Notifications.Error');
} elseif ($field == 'passwd') {
if ($value = Tools::getValue($field)) {
$this->{$field} = Tools::hash($value);
} else {
$this->{$field} = $value;
}
}
}
}
if ($this->quantity_expected <= 0) {
$errors[] = '<b>' . SupplyOrderDetail::displayFieldName('quantity_expected', get_class($this)) . '</b> ' . $this->trans('is invalid.', array(), 'Shop.Notifications.Error');
}
if ($this->tax_rate < 0 || $this->tax_rate > 100) {
$errors[] = '<b>' . SupplyOrderDetail::displayFieldName('tax_rate', get_class($this)) . '</b> ' . $this->trans('is invalid.', array(), 'Shop.Notifications.Error');
}
if ($this->discount_rate < 0 || $this->discount_rate > 100) {
$errors[] = '<b>' . SupplyOrderDetail::displayFieldName('discount_rate', get_class($this)) . '</b> ' . $this->trans('is invalid.', array(), 'Shop.Notifications.Error');
}
return $errors;
}
/**
* @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, $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,90 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* @since 1.5.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 = array(
'table' => 'supply_order_history',
'primary' => 'id_supply_order_history',
'fields' => array(
'id_supply_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_employee' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'employee_firstname' => array('type' => self::TYPE_STRING, 'validate' => 'isName'),
'employee_lastname' => array('type' => self::TYPE_STRING, 'validate' => 'isName'),
'id_state' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'objectsNodeName' => 'supply_order_histories',
'objectNodeName' => 'supply_order_history',
'fields' => array(
'id_supply_order' => array('xlink_resource' => 'supply_orders'),
'id_employee' => array('xlink_resource' => 'employees'),
'id_state' => array('xlink_resource' => 'supply_order_states'),
),
);
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* History of receipts.
*
* @since 1.5.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 = array(
'table' => 'supply_order_receipt_history',
'primary' => 'id_supply_order_receipt_history',
'fields' => array(
'id_supply_order_detail' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_supply_order_state' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_employee' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'employee_firstname' => array('type' => self::TYPE_STRING, 'validate' => 'isName'),
'employee_lastname' => array('type' => self::TYPE_STRING, 'validate' => 'isName'),
'quantity' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true),
'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'objectsNodeName' => 'supply_order_receipt_histories',
'objectNodeName' => 'supply_order_receipt_history',
'fields' => array(
'id_supply_order_detail' => array('xlink_resource' => 'supply_order_details'),
'id_employee' => array('xlink_resource' => 'employees'),
'id_supply_order_state' => array('xlink_resource' => 'supply_order_states'),
),
);
}

View File

@@ -0,0 +1,176 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* @since 1.5.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 = array(
'table' => 'supply_order_state',
'primary' => 'id_supply_order_state',
'multilang' => true,
'fields' => array(
'delivery_note' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
'editable' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
'receipt_state' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
'pending_receipt' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
'enclosed' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
'color' => array('type' => self::TYPE_STRING, 'validate' => 'isColor'),
'name' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 128),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'objectsNodeName' => 'supply_order_states',
'objectNodeName' => 'supply_order_state',
'fields' => array(
),
);
/**
* 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 $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 ($ids && !is_array($ids)) {
$ids = array();
}
$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);
}
}

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

@@ -0,0 +1,646 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* Holds Stock.
*
* @since 1.5.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 = 0;
/**
* Describes the way a Warehouse is managed.
*
* @var string enum WA|LIFO|FIFO
*/
public $management_type;
/**
* @see ObjectModel::$definition
*/
public static $definition = array(
'table' => 'warehouse',
'primary' => 'id_warehouse',
'fields' => array(
'id_address' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'reference' => array('type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true, 'size' => 64),
'name' => array('type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true, 'size' => 45),
'id_employee' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'management_type' => array('type' => self::TYPE_STRING, 'validate' => 'isStockManagement', 'required' => true),
'id_currency' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'deleted' => array('type' => self::TYPE_BOOL),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'fields' => array(
'id_address' => array('xlink_resource' => 'addresses'),
'id_employee' => array('xlink_resource' => 'employees'),
'id_currency' => array('xlink_resource' => 'currencies'),
'valuation' => array('getter' => 'getWsStockValue', 'setter' => false),
'deleted' => array(),
),
'associations' => array(
'stocks' => array(
'resource' => 'stock',
'fields' => array(
'id' => array(),
),
),
'carriers' => array(
'resource' => 'carrier',
'fields' => array(
'id' => array(),
),
),
'shops' => array(
'resource' => 'shop',
'fields' => array(
'id' => array(),
'name' => array(),
),
),
),
);
/**
* 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 = array();
$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 = array();
}
$row_to_insert = array();
foreach ($ids_carriers as $id_carrier) {
$row_to_insert[] = array($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);
}
/**
* 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 = array(
'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
$share_stock = false;
if ($id_shop === null) {
if (Shop::getContext() == Shop::CONTEXT_GROUP) {
$shop_group = Shop::getContextShopGroup();
} else {
$shop_group = Context::getContext()->shop->getGroup();
$id_shop = (int) Context::getContext()->shop->id;
}
$share_stock = $shop_group->share_stock;
} else {
$shop_group = Shop::getGroupFromShop($id_shop);
$share_stock = $shop_group['share_stock'];
}
if ($share_stock) {
$ids_shop = Shop::getShops(true, (int) $shop_group->id, true);
} else {
$ids_shop = array((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 = array();
$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 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 int 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 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 array();
}
$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);
// products in the pack
$products = Pack::getItems((int) $id_product, Configuration::get('PS_LANG_DEFAULT'));
// array with all warehouses id to check
$list = array();
// fills $list
foreach ($pack_warehouses as $pack_warehouse) {
/* @var WarehouseProductLocation $pack_warehouse */
$list['pack_warehouses'][] = (int) $pack_warehouse->id_warehouse;
}
// for each products in the pack
foreach ($products as $product) {
if ($product->advanced_stock_management) {
// gets the warehouses of one product
$product_warehouses = Warehouse::getProductWarehouseList((int) $product->id, (int) $product->cache_default_attribute, (int) $id_shop);
$list[(int) $product->id] = array();
// fills array with warehouses for this product
foreach ($product_warehouses as $product_warehouse) {
$list[(int) $product->id][] = $product_warehouse['id_warehouse'];
}
}
}
$res = false;
// returns final list
if (count($list) > 1) {
$res = call_user_func_array('array_intersect', $list);
}
return $res;
}
public function resetStockAvailable()
{
$products = WarehouseProductLocation::getProducts((int) $this->id);
foreach ($products as $product) {
StockAvailable::synchronize((int) $product['id_product']);
}
}
/*********************************\
*
* Webservices Specific Methods
*
*********************************/
/**
* Webservice : gets the value of the warehouse.
*
* @return int
*/
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 = array();
$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,145 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
/**
* @since 1.5.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 = array(
'table' => 'warehouse_product_location',
'primary' => 'id_warehouse_product_location',
'fields' => array(
'location' => array('type' => self::TYPE_STRING, 'validate' => 'isReference', 'size' => 64),
'id_product' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_product_attribute' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_warehouse' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
),
);
/**
* @see ObjectModel::$webserviceParameters
*/
protected $webserviceParameters = array(
'fields' => array(
'id_product' => array('xlink_resource' => 'products'),
'id_product_attribute' => array('xlink_resource' => 'combinations'),
'id_warehouse' => array('xlink_resource' => 'warehouses'),
),
'hidden_fields' => array(
),
);
/**
* 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 $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_supplier
*
* @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 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);
}
}

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

@@ -0,0 +1,34 @@
<?php
/**
* 2007-2019 PrestaShop and Contributors
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://www.prestashop.com for more information.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2019 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
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;