first commit
This commit is contained in:
193
classes/stock/Stock.php
Normal file
193
classes/stock/Stock.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 string MPN */
|
||||
public $mpn;
|
||||
|
||||
/** @var int the physical quantity in stock for the current product in the current warehouse */
|
||||
public $physical_quantity;
|
||||
|
||||
/** @var int the usable quantity (for sale) of the current physical quantity */
|
||||
public $usable_quantity;
|
||||
|
||||
/** @var int the unit price without tax forthe current product */
|
||||
public $price_te;
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$definition
|
||||
*/
|
||||
public static $definition = [
|
||||
'table' => 'stock',
|
||||
'primary' => 'id_stock',
|
||||
'fields' => [
|
||||
'id_warehouse' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_product_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'reference' => ['type' => self::TYPE_STRING, 'validate' => 'isReference'],
|
||||
'ean13' => ['type' => self::TYPE_STRING, 'validate' => 'isEan13'],
|
||||
'isbn' => ['type' => self::TYPE_STRING, 'validate' => 'isIsbn'],
|
||||
'upc' => ['type' => self::TYPE_STRING, 'validate' => 'isUpc'],
|
||||
'mpn' => ['type' => self::TYPE_STRING, 'validate' => 'isMpn'],
|
||||
'physical_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
|
||||
'usable_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true],
|
||||
'price_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'fields' => [
|
||||
'id_warehouse' => ['xlink_resource' => 'warehouses'],
|
||||
'id_product' => ['xlink_resource' => 'products'],
|
||||
'id_product_attribute' => ['xlink_resource' => 'combinations'],
|
||||
'real_quantity' => ['getter' => 'getWsRealQuantity', 'setter' => false],
|
||||
],
|
||||
'hidden_fields' => [
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::update()
|
||||
*/
|
||||
public function update($null_values = false)
|
||||
{
|
||||
$this->getProductInformations();
|
||||
|
||||
return parent::update($null_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ObjectModel::add()
|
||||
*/
|
||||
public function add($autodate = true, $null_values = false)
|
||||
{
|
||||
$this->getProductInformations();
|
||||
|
||||
return parent::add($autodate, $null_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets reference, ean13 , isbn, mpn and upc of the current product
|
||||
* Stores it in stock for stock_mvt integrity and history purposes.
|
||||
*/
|
||||
protected function getProductInformations()
|
||||
{
|
||||
// if combinations
|
||||
if ((int) $this->id_product_attribute > 0) {
|
||||
$query = new DbQuery();
|
||||
$query->select('reference, ean13, isbn, mpn, upc');
|
||||
$query->from('product_attribute');
|
||||
$query->where('id_product = ' . (int) $this->id_product);
|
||||
$query->where('id_product_attribute = ' . (int) $this->id_product_attribute);
|
||||
$rows = Db::getInstance()->executeS($query);
|
||||
|
||||
if (!is_array($rows)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$this->reference = $row['reference'];
|
||||
$this->ean13 = $row['ean13'];
|
||||
$this->isbn = $row['isbn'];
|
||||
$this->upc = $row['upc'];
|
||||
$this->mpn = $row['mpn'];
|
||||
}
|
||||
} else {
|
||||
// else, simple product
|
||||
|
||||
$product = new Product((int) $this->id_product);
|
||||
if (Validate::isLoadedObject($product)) {
|
||||
$this->reference = $product->reference;
|
||||
$this->ean13 = $product->ean13;
|
||||
$this->isbn = $product->isbn;
|
||||
$this->upc = $product->upc;
|
||||
$this->mpn = $product->mpn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Webservice : used to get the real quantity of a product.
|
||||
*/
|
||||
public function getWsRealQuantity()
|
||||
{
|
||||
$manager = StockManagerFactory::getManager();
|
||||
$quantity = $manager->getProductRealQuantities($this->id_product, $this->id_product_attribute, $this->id_warehouse, true);
|
||||
|
||||
return $quantity;
|
||||
}
|
||||
|
||||
public static function deleteStockByIds($id_product = null, $id_product_attribute = null)
|
||||
{
|
||||
if (!$id_product || !$id_product_attribute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Db::getInstance()->execute('DELETE FROM ' . _DB_PREFIX_ . 'stock WHERE `id_product` = ' . (int) $id_product . ' AND `id_product_attribute` = ' . (int) $id_product_attribute);
|
||||
}
|
||||
|
||||
public static function productIsPresentInStock($id_product = 0, $id_product_attribute = 0, $id_warehouse = 0)
|
||||
{
|
||||
if (!(int) $id_product && !is_int($id_product_attribute) && !(int) $id_warehouse) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = Db::getInstance()->executeS('SELECT `id_stock` FROM ' . _DB_PREFIX_ . 'stock
|
||||
WHERE `id_warehouse` = ' . (int) $id_warehouse . ' AND `id_product` = ' . (int) $id_product . ((int) $id_product_attribute ? ' AND `id_product_attribute` = ' . $id_product_attribute : ''));
|
||||
|
||||
return is_array($result) && !empty($result) ? true : false;
|
||||
}
|
||||
}
|
||||
931
classes/stock/StockAvailable.php
Normal file
931
classes/stock/StockAvailable.php
Normal file
@@ -0,0 +1,931 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
use PrestaShop\PrestaShop\Adapter\ServiceLocator;
|
||||
|
||||
/**
|
||||
* Represents quantities available
|
||||
* It is either synchronized with Stock or manualy set by the seller.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
class StockAvailableCore extends ObjectModel
|
||||
{
|
||||
/** @var int identifier of the current product */
|
||||
public $id_product;
|
||||
|
||||
/** @var int identifier of product attribute if necessary */
|
||||
public $id_product_attribute;
|
||||
|
||||
/** @var int the shop associated to the current product and corresponding quantity */
|
||||
public $id_shop;
|
||||
|
||||
/** @var int the group shop associated to the current product and corresponding quantity */
|
||||
public $id_shop_group;
|
||||
|
||||
/** @var int the quantity available for sale */
|
||||
public $quantity = 0;
|
||||
|
||||
/**
|
||||
* @deprecated since 1.7.8
|
||||
* This property was only relevant to advanced stock management and that feature is not maintained anymore
|
||||
*
|
||||
* @var bool determine if the available stock value depends on physical stock
|
||||
*/
|
||||
public $depends_on_stock = false;
|
||||
|
||||
/**
|
||||
* Determine if a product is out of stock - it was previously in Product class
|
||||
* - O Deny orders
|
||||
* - 1 Allow orders
|
||||
* - 2 Use global setting
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $out_of_stock = 0;
|
||||
|
||||
/** @var string the location of the stock for this product / combination */
|
||||
public $location = '';
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$definition
|
||||
*/
|
||||
public static $definition = [
|
||||
'table' => 'stock_available',
|
||||
'primary' => 'id_stock_available',
|
||||
'fields' => [
|
||||
'id_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_product_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_shop' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
|
||||
'id_shop_group' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
|
||||
'quantity' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true, 'size' => 10],
|
||||
'depends_on_stock' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool', 'required' => true],
|
||||
'out_of_stock' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true],
|
||||
'location' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'size' => 255],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'fields' => [
|
||||
'id_product' => ['xlink_resource' => 'products'],
|
||||
'id_product_attribute' => ['xlink_resource' => 'combinations'],
|
||||
'id_shop' => ['xlink_resource' => 'shops'],
|
||||
'id_shop_group' => ['xlink_resource' => 'shop_groups'],
|
||||
],
|
||||
'hidden_fields' => [
|
||||
],
|
||||
'objectMethods' => [
|
||||
'add' => 'addWs',
|
||||
'update' => 'updateWs',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* 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.', [], '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 = [];
|
||||
$wh = Warehouse::getWarehouses(false, (int) $order_id_shop);
|
||||
foreach ($wh as $warehouse) {
|
||||
$order_warehouses[] = $warehouse['id_warehouse'];
|
||||
}
|
||||
}
|
||||
|
||||
// gets all product attributes ids
|
||||
$ids_product_attribute = [];
|
||||
foreach (Product::getProductAttributesIds($id_product) as $id_product_attribute) {
|
||||
$ids_product_attribute[] = $id_product_attribute['id_product_attribute'];
|
||||
}
|
||||
|
||||
// Allow to order the product when out of stock?
|
||||
$out_of_stock = StockAvailable::outOfStock($id_product);
|
||||
|
||||
$manager = StockManagerFactory::getManager();
|
||||
// loops on $ids_warehouse to synchronize quantities
|
||||
foreach ($ids_warehouse as $id_shop => $warehouses) {
|
||||
// first, checks if the product depends on stock for the given shop $id_shop
|
||||
if (StockAvailable::dependsOnStock($id_product, $id_shop)) {
|
||||
// init quantity
|
||||
$product_quantity = 0;
|
||||
|
||||
// if it's a simple product
|
||||
if (empty($ids_product_attribute)) {
|
||||
$allowed_warehouse_for_product = WareHouse::getProductWarehouseList((int) $id_product, 0, (int) $id_shop);
|
||||
$allowed_warehouse_for_product_clean = [];
|
||||
foreach ($allowed_warehouse_for_product as $warehouse) {
|
||||
$allowed_warehouse_for_product_clean[] = (int) $warehouse['id_warehouse'];
|
||||
}
|
||||
$allowed_warehouse_for_product_clean = array_intersect($allowed_warehouse_for_product_clean, $warehouses);
|
||||
if ($order_id_shop != null && !count(array_intersect($allowed_warehouse_for_product_clean, $order_warehouses))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$product_quantity = $manager->getProductRealQuantities($id_product, null, $allowed_warehouse_for_product_clean, true);
|
||||
|
||||
Hook::exec(
|
||||
'actionUpdateQuantity',
|
||||
[
|
||||
'id_product' => $id_product,
|
||||
'id_product_attribute' => 0,
|
||||
'quantity' => $product_quantity,
|
||||
'id_shop' => $id_shop,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// else this product has attributes, hence loops on $ids_product_attribute
|
||||
foreach ($ids_product_attribute as $id_product_attribute) {
|
||||
$allowed_warehouse_for_combination = WareHouse::getProductWarehouseList((int) $id_product, (int) $id_product_attribute, (int) $id_shop);
|
||||
$allowed_warehouse_for_combination_clean = [];
|
||||
foreach ($allowed_warehouse_for_combination as $warehouse) {
|
||||
$allowed_warehouse_for_combination_clean[] = (int) $warehouse['id_warehouse'];
|
||||
}
|
||||
$allowed_warehouse_for_combination_clean = array_intersect($allowed_warehouse_for_combination_clean, $warehouses);
|
||||
if ($order_id_shop != null && !count(array_intersect($allowed_warehouse_for_combination_clean, $order_warehouses))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$quantity = $manager->getProductRealQuantities($id_product, $id_product_attribute, $allowed_warehouse_for_combination_clean, true);
|
||||
|
||||
$query = new DbQuery();
|
||||
$query->select('COUNT(*)');
|
||||
$query->from('stock_available');
|
||||
$query->where('id_product = ' . (int) $id_product . ' AND id_product_attribute = ' . (int) $id_product_attribute .
|
||||
StockAvailable::addSqlShopRestriction(null, $id_shop));
|
||||
|
||||
if ((int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query)) {
|
||||
$query = [
|
||||
'table' => 'stock_available',
|
||||
'data' => ['quantity' => $quantity],
|
||||
'where' => 'id_product = ' . (int) $id_product . ' AND id_product_attribute = ' . (int) $id_product_attribute .
|
||||
StockAvailable::addSqlShopRestriction(null, $id_shop),
|
||||
];
|
||||
Db::getInstance()->update($query['table'], $query['data'], $query['where']);
|
||||
} else {
|
||||
$query = [
|
||||
'table' => 'stock_available',
|
||||
'data' => [
|
||||
'quantity' => $quantity,
|
||||
'depends_on_stock' => 1,
|
||||
'out_of_stock' => $out_of_stock,
|
||||
'id_product' => (int) $id_product,
|
||||
'id_product_attribute' => (int) $id_product_attribute,
|
||||
],
|
||||
];
|
||||
StockAvailable::addSqlShopParams($query['data'], $id_shop);
|
||||
Db::getInstance()->insert($query['table'], $query['data']);
|
||||
}
|
||||
|
||||
$product_quantity += $quantity;
|
||||
|
||||
Hook::exec(
|
||||
'actionUpdateQuantity',
|
||||
[
|
||||
'id_product' => $id_product,
|
||||
'id_product_attribute' => $id_product_attribute,
|
||||
'quantity' => $quantity,
|
||||
'id_shop' => $id_shop,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
// updates
|
||||
// if $id_product has attributes, it also updates the sum for all attributes
|
||||
if (($order_id_shop != null && array_intersect($warehouses, $order_warehouses)) || $order_id_shop == null) {
|
||||
$query = [
|
||||
'table' => 'stock_available',
|
||||
'data' => ['quantity' => $product_quantity],
|
||||
'where' => 'id_product = ' . (int) $id_product . ' AND id_product_attribute = 0' .
|
||||
StockAvailable::addSqlShopRestriction(null, $id_shop),
|
||||
];
|
||||
Db::getInstance()->update($query['table'], $query['data'], $query['where']);
|
||||
}
|
||||
}
|
||||
}
|
||||
// In case there are no warehouses, removes product from StockAvailable
|
||||
if (count($ids_warehouse) == 0 && StockAvailable::dependsOnStock((int) $id_product)) {
|
||||
Db::getInstance()->update('stock_available', ['quantity' => 0], 'id_product = ' . (int) $id_product);
|
||||
}
|
||||
|
||||
Cache::clean('StockAvailable::getQuantityAvailableByProduct_' . (int) $id_product . '*');
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given id_product, sets if stock available depends on stock.
|
||||
*
|
||||
* @param int $id_product
|
||||
* @param 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', [
|
||||
'depends_on_stock' => (int) $depends_on_stock,
|
||||
], 'id_stock_available = ' . (int) $existing_id);
|
||||
} else {
|
||||
$params = [
|
||||
'depends_on_stock' => (int) $depends_on_stock,
|
||||
'id_product' => (int) $id_product,
|
||||
'id_product_attribute' => (int) $id_product_attribute,
|
||||
];
|
||||
|
||||
StockAvailable::addSqlShopParams($params, $id_shop);
|
||||
|
||||
Db::getInstance()->insert('stock_available', $params);
|
||||
}
|
||||
|
||||
// depends on stock.. hence synchronizes
|
||||
if ($depends_on_stock) {
|
||||
StockAvailable::synchronize($id_product);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given id_product, sets if product is available out of stocks.
|
||||
*
|
||||
* @param int $id_product
|
||||
* @param int $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',
|
||||
['out_of_stock' => (int) $out_of_stock],
|
||||
'id_product = ' . (int) $id_product .
|
||||
(($id_product_attribute) ? ' AND id_product_attribute = ' . (int) $id_product_attribute : '') .
|
||||
StockAvailable::addSqlShopRestriction(null, $id_shop)
|
||||
);
|
||||
} else {
|
||||
$params = [
|
||||
'out_of_stock' => (int) $out_of_stock,
|
||||
'id_product' => (int) $id_product,
|
||||
'id_product_attribute' => (int) $id_product_attribute,
|
||||
];
|
||||
|
||||
StockAvailable::addSqlShopParams($params, $id_shop);
|
||||
Db::getInstance()->insert('stock_available', $params, false, true, Db::ON_DUPLICATE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id_product
|
||||
* @param string $location
|
||||
* @param int $id_shop Optional
|
||||
* @param int $id_product_attribute Optional
|
||||
*
|
||||
* @return 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',
|
||||
['location' => pSQL($location)],
|
||||
'id_product = ' . (int) $id_product .
|
||||
(($id_product_attribute) ? ' AND id_product_attribute = ' . (int) $id_product_attribute : '') .
|
||||
StockAvailable::addSqlShopRestriction(null, $id_shop)
|
||||
);
|
||||
} else {
|
||||
$params = [
|
||||
'location' => pSQL($location),
|
||||
'id_product' => (int) $id_product,
|
||||
'id_product_attribute' => (int) $id_product_attribute,
|
||||
];
|
||||
|
||||
StockAvailable::addSqlShopParams($params, $id_shop);
|
||||
Db::getInstance()->insert('stock_available', $params, false, true, Db::ON_DUPLICATE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given id_product and id_product_attribute, gets its stock available.
|
||||
*
|
||||
* @param int $id_product
|
||||
* @param int $id_product_attribute Optional
|
||||
* @param int $id_shop Optional : gets context by default
|
||||
*
|
||||
* @return int Quantity
|
||||
*/
|
||||
public static function getQuantityAvailableByProduct($id_product = null, $id_product_attribute = null, $id_shop = null)
|
||||
{
|
||||
// if null, it's a product without attributes
|
||||
if ($id_product_attribute === null) {
|
||||
$id_product_attribute = 0;
|
||||
}
|
||||
|
||||
$key = 'StockAvailable::getQuantityAvailableByProduct_' . (int) $id_product . '-' . (int) $id_product_attribute . '-' . (int) $id_shop;
|
||||
if (!Cache::isStored($key)) {
|
||||
$query = new DbQuery();
|
||||
$query->select('SUM(quantity)');
|
||||
$query->from('stock_available');
|
||||
|
||||
// if null, it's a product without attributes
|
||||
if ($id_product !== null) {
|
||||
$query->where('id_product = ' . (int) $id_product);
|
||||
}
|
||||
|
||||
$query->where('id_product_attribute = ' . (int) $id_product_attribute);
|
||||
$query = StockAvailable::addSqlShopRestriction($query, $id_shop);
|
||||
$result = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
|
||||
Cache::store($key, $result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return Cache::retrieve($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades total_quantity_available after having saved.
|
||||
*
|
||||
* @see ObjectModel::add()
|
||||
*/
|
||||
public function add($autodate = true, $null_values = false)
|
||||
{
|
||||
if (!$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 = [])
|
||||
{
|
||||
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 = (int) $quantity - (int) $stock_available->quantity;
|
||||
|
||||
$stock_available->quantity = (int) $quantity;
|
||||
$stock_available->update();
|
||||
|
||||
if (true === $add_movement && 0 != $deltaQuantity) {
|
||||
$stockManager->saveMovement($id_product, $id_product_attribute, $deltaQuantity);
|
||||
}
|
||||
} else {
|
||||
$out_of_stock = StockAvailable::outOfStock($id_product, $id_shop);
|
||||
$stock_available = new StockAvailable();
|
||||
$stock_available->out_of_stock = (int) $out_of_stock;
|
||||
$stock_available->id_product = (int) $id_product;
|
||||
$stock_available->id_product_attribute = (int) $id_product_attribute;
|
||||
$stock_available->quantity = (int) $quantity;
|
||||
if ($id_shop === null) {
|
||||
$shop_group = Shop::getContextShopGroup();
|
||||
} else {
|
||||
$shop_group = new ShopGroup((int) Shop::getGroupFromShop((int) $id_shop));
|
||||
}
|
||||
// if quantities are shared between shops of the group
|
||||
if ($shop_group->share_stock) {
|
||||
$stock_available->id_shop = 0;
|
||||
$stock_available->id_shop_group = (int) $shop_group->id;
|
||||
} else {
|
||||
$stock_available->id_shop = (int) $id_shop;
|
||||
$stock_available->id_shop_group = 0;
|
||||
}
|
||||
$stock_available->add();
|
||||
|
||||
if (true === $add_movement && 0 != $quantity) {
|
||||
$stockManager->saveMovement($id_product, $id_product_attribute, (int) $quantity);
|
||||
}
|
||||
}
|
||||
|
||||
Hook::exec(
|
||||
'actionUpdateQuantity',
|
||||
[
|
||||
'id_product' => $id_product,
|
||||
'id_product_attribute' => $id_product_attribute,
|
||||
'quantity' => $stock_available->quantity,
|
||||
]
|
||||
);
|
||||
}
|
||||
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 = [];
|
||||
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', ['quantity' => 0], 'id_shop IN (' . $id_shops_list . ')');
|
||||
} else {
|
||||
return Db::getInstance()->update('stock_available', ['quantity' => 0], 'id_shop_group = ' . $shop_group->id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given product, tells if it depends on the physical (usable) stock.
|
||||
*
|
||||
* @param int $id_product
|
||||
* @param int $id_shop Optional : gets context if null @see Context::getContext()
|
||||
*
|
||||
* @return bool : depends on stock @see $depends_on_stock
|
||||
*/
|
||||
public static function dependsOnStock($id_product, $id_shop = null)
|
||||
{
|
||||
if (!Validate::isUnsignedId($id_product)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = new DbQuery();
|
||||
$query->select('depends_on_stock');
|
||||
$query->from('stock_available');
|
||||
$query->where('id_product = ' . (int) $id_product);
|
||||
$query->where('id_product_attribute = 0');
|
||||
|
||||
$query = StockAvailable::addSqlShopRestriction($query, $id_shop);
|
||||
|
||||
return (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given product, get its "out of stock" flag.
|
||||
*
|
||||
* @param int $id_product
|
||||
* @param int $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);
|
||||
}
|
||||
}
|
||||
874
classes/stock/StockManager.php
Normal file
874
classes/stock/StockManager.php
Normal file
@@ -0,0 +1,874 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 = [
|
||||
'id_stock' => null,
|
||||
'physical_quantity' => $quantity,
|
||||
'id_stock_mvt_reason' => $id_stock_mvt_reason,
|
||||
'id_supply_order' => $id_supply_order,
|
||||
'price_te' => $price_te,
|
||||
'last_wa' => null,
|
||||
'current_wa' => null,
|
||||
'id_employee' => (int) $context->employee->id ? (int) $context->employee->id : $employee->id,
|
||||
'employee_firstname' => $context->employee->firstname ? $context->employee->firstname : $employee->firstname,
|
||||
'employee_lastname' => $context->employee->lastname ? $context->employee->lastname : $employee->lastname,
|
||||
'sign' => 1,
|
||||
];
|
||||
|
||||
$stock_exists = false;
|
||||
|
||||
// switch on MANAGEMENT_TYPE
|
||||
switch ($warehouse->management_type) {
|
||||
// case CUMP mode
|
||||
case 'WA':
|
||||
$stock_collection = $this->getStockCollection($id_product, $id_product_attribute, $warehouse->id);
|
||||
|
||||
// if this product is already in stock
|
||||
if (count($stock_collection) > 0) {
|
||||
$stock_exists = true;
|
||||
|
||||
/** @var Stock $stock */
|
||||
// for a warehouse using WA, there is one and only one stock for a given product
|
||||
$stock = $stock_collection->current();
|
||||
|
||||
// calculates WA price
|
||||
$last_wa = $stock->price_te;
|
||||
$current_wa = $this->calculateWA($stock, $quantity, $price_te);
|
||||
|
||||
$mvt_params['id_stock'] = $stock->id;
|
||||
$mvt_params['last_wa'] = $last_wa;
|
||||
$mvt_params['current_wa'] = $current_wa;
|
||||
|
||||
$stock_params = [
|
||||
'physical_quantity' => ($stock->physical_quantity + $quantity),
|
||||
'price_te' => $current_wa,
|
||||
'usable_quantity' => ($is_usable ? ($stock->usable_quantity + $quantity) : $stock->usable_quantity),
|
||||
'id_warehouse' => $warehouse->id,
|
||||
];
|
||||
|
||||
// saves stock in warehouse
|
||||
$stock->hydrate($stock_params);
|
||||
$stock->update();
|
||||
} else {
|
||||
// else, the product is not in sock
|
||||
|
||||
$mvt_params['last_wa'] = 0;
|
||||
$mvt_params['current_wa'] = $price_te;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// case FIFO / LIFO mode
|
||||
case 'FIFO':
|
||||
case 'LIFO':
|
||||
$stock_collection = $this->getStockCollection($id_product, $id_product_attribute, $warehouse->id, $price_te);
|
||||
|
||||
// if this product is already in stock
|
||||
if (count($stock_collection) > 0) {
|
||||
$stock_exists = true;
|
||||
|
||||
/** @var Stock $stock */
|
||||
// there is one and only one stock for a given product in a warehouse and at the current unit price
|
||||
$stock = $stock_collection->current();
|
||||
|
||||
$stock_params = [
|
||||
'physical_quantity' => ($stock->physical_quantity + $quantity),
|
||||
'usable_quantity' => ($is_usable ? ($stock->usable_quantity + $quantity) : $stock->usable_quantity),
|
||||
];
|
||||
|
||||
// updates stock in warehouse
|
||||
$stock->hydrate($stock_params);
|
||||
$stock->update();
|
||||
|
||||
// sets mvt_params
|
||||
$mvt_params['id_stock'] = $stock->id;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$stock_exists) {
|
||||
$stock = new Stock();
|
||||
|
||||
$stock_params = [
|
||||
'id_product_attribute' => $id_product_attribute,
|
||||
'id_product' => $id_product,
|
||||
'physical_quantity' => $quantity,
|
||||
'price_te' => $price_te,
|
||||
'usable_quantity' => ($is_usable ? $quantity : 0),
|
||||
'id_warehouse' => $warehouse->id,
|
||||
];
|
||||
|
||||
// saves stock in warehouse
|
||||
$stock->hydrate($stock_params);
|
||||
$stock->add();
|
||||
$mvt_params['id_stock'] = $stock->id;
|
||||
}
|
||||
|
||||
// saves stock mvt
|
||||
$stock_mvt = new StockMvt();
|
||||
$stock_mvt->hydrate($mvt_params);
|
||||
$stock_mvt->add();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StockManagerInterface::removeProduct()
|
||||
*
|
||||
* @param int $id_product
|
||||
* @param int|null $id_product_attribute
|
||||
* @param Warehouse $warehouse
|
||||
* @param int $quantity
|
||||
* @param int $id_stock_mvt_reason
|
||||
* @param bool $is_usable
|
||||
* @param int|null $id_order
|
||||
* @param int $ignore_pack
|
||||
* @param Employee|null $employee
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws PrestaShopException
|
||||
*/
|
||||
public function removeProduct(
|
||||
$id_product,
|
||||
$id_product_attribute,
|
||||
Warehouse $warehouse,
|
||||
$quantity,
|
||||
$id_stock_mvt_reason,
|
||||
$is_usable = true,
|
||||
$id_order = null,
|
||||
$ignore_pack = 0,
|
||||
$employee = null
|
||||
) {
|
||||
$return = [];
|
||||
|
||||
if (!Validate::isLoadedObject($warehouse) || !$quantity || !$id_product) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
if (!StockMvtReason::exists($id_stock_mvt_reason)) {
|
||||
$id_stock_mvt_reason = Configuration::get('PS_STOCK_MVT_DEC_REASON_DEFAULT');
|
||||
}
|
||||
|
||||
$context = Context::getContext();
|
||||
|
||||
// Special case of a pack
|
||||
if (Pack::isPack((int) $id_product) && !$ignore_pack) {
|
||||
if (Validate::isLoadedObject($product = new Product((int) $id_product))) {
|
||||
// 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, [$warehouse->id], false);
|
||||
$usable_quantity_in_stock = (int) $this->getProductPhysicalQuantities($id_product, $id_product_attribute, [$warehouse->id], true);
|
||||
|
||||
// check quantity if we want to decrement unusable quantity
|
||||
if (!$is_usable) {
|
||||
$quantity_in_stock = $physical_quantity_in_stock - $usable_quantity_in_stock;
|
||||
} else {
|
||||
$quantity_in_stock = $usable_quantity_in_stock;
|
||||
}
|
||||
|
||||
// checks if it's possible to remove the given quantity
|
||||
if ($quantity_in_stock < $quantity) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
$stock_collection = $this->getStockCollection($id_product, $id_product_attribute, $warehouse->id);
|
||||
$stock_collection->getAll();
|
||||
|
||||
// check if the collection is loaded
|
||||
if (count($stock_collection) <= 0) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
$stock_history_qty_available = [];
|
||||
$mvt_params = [];
|
||||
$stock_params = [];
|
||||
$quantity_to_decrement_by_stock = [];
|
||||
$global_quantity_to_decrement = $quantity;
|
||||
|
||||
// switch on MANAGEMENT_TYPE
|
||||
switch ($warehouse->management_type) {
|
||||
// case CUMP mode
|
||||
case 'WA':
|
||||
/** @var Stock $stock */
|
||||
// There is one and only one stock for a given product in a warehouse in this mode
|
||||
$stock = $stock_collection->current();
|
||||
|
||||
$mvt_params = [
|
||||
'id_stock' => $stock->id,
|
||||
'physical_quantity' => $quantity,
|
||||
'id_stock_mvt_reason' => $id_stock_mvt_reason,
|
||||
'id_order' => $id_order,
|
||||
'price_te' => $stock->price_te,
|
||||
'last_wa' => $stock->price_te,
|
||||
'current_wa' => $stock->price_te,
|
||||
'id_employee' => (int) $context->employee->id ? (int) $context->employee->id : $employee->id,
|
||||
'employee_firstname' => $context->employee->firstname ? $context->employee->firstname : $employee->firstname,
|
||||
'employee_lastname' => $context->employee->lastname ? $context->employee->lastname : $employee->lastname,
|
||||
'sign' => -1,
|
||||
];
|
||||
$stock_params = [
|
||||
'physical_quantity' => ($stock->physical_quantity - $quantity),
|
||||
'usable_quantity' => ($is_usable ? ($stock->usable_quantity - $quantity) : $stock->usable_quantity),
|
||||
];
|
||||
|
||||
// saves stock in warehouse
|
||||
$stock->hydrate($stock_params);
|
||||
$stock->update();
|
||||
|
||||
// saves stock mvt
|
||||
$stock_mvt = new StockMvt();
|
||||
$stock_mvt->hydrate($mvt_params);
|
||||
$stock_mvt->save();
|
||||
|
||||
$return[$stock->id]['quantity'] = $quantity;
|
||||
$return[$stock->id]['price_te'] = $stock->price_te;
|
||||
|
||||
break;
|
||||
|
||||
case 'LIFO':
|
||||
case 'FIFO':
|
||||
// for each stock, parse its mvts history to calculate the quantities left for each positive mvt,
|
||||
// according to the instant available quantities for this stock
|
||||
foreach ($stock_collection as $stock) {
|
||||
/** @var Stock $stock */
|
||||
$left_quantity_to_check = $stock->physical_quantity;
|
||||
if ($left_quantity_to_check <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$resource = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
|
||||
'
|
||||
SELECT sm.`id_stock_mvt`, sm.`date_add`, sm.`physical_quantity`,
|
||||
IF ((sm2.`physical_quantity` is null), sm.`physical_quantity`, (sm.`physical_quantity` - SUM(sm2.`physical_quantity`))) as qty
|
||||
FROM `' . _DB_PREFIX_ . 'stock_mvt` sm
|
||||
LEFT JOIN `' . _DB_PREFIX_ . 'stock_mvt` sm2 ON sm2.`referer` = sm.`id_stock_mvt`
|
||||
WHERE sm.`sign` = 1
|
||||
AND sm.`id_stock` = ' . (int) $stock->id . '
|
||||
GROUP BY sm.`id_stock_mvt`
|
||||
ORDER BY sm.`date_add` DESC',
|
||||
false
|
||||
);
|
||||
|
||||
while ($row = Db::getInstance()->nextRow($resource)) {
|
||||
// continue - in FIFO mode, we have to retreive the oldest positive mvts for which there are left quantities
|
||||
if ($warehouse->management_type == 'FIFO') {
|
||||
if ($row['qty'] == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// converts date to timestamp
|
||||
$date = new DateTime($row['date_add']);
|
||||
$timestamp = $date->format('U');
|
||||
|
||||
// history of the mvt
|
||||
$stock_history_qty_available[$timestamp] = [
|
||||
'id_stock' => $stock->id,
|
||||
'id_stock_mvt' => (int) $row['id_stock_mvt'],
|
||||
'qty' => (int) $row['qty'],
|
||||
];
|
||||
|
||||
// break - in LIFO mode, checks only the necessary history to handle the global quantity for the current stock
|
||||
if ($warehouse->management_type == 'LIFO') {
|
||||
$left_quantity_to_check -= (int) $row['qty'];
|
||||
if ($left_quantity_to_check <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($warehouse->management_type == 'LIFO') {
|
||||
// orders stock history by timestamp to get newest history first
|
||||
krsort($stock_history_qty_available);
|
||||
} else {
|
||||
// orders stock history by timestamp to get oldest history first
|
||||
ksort($stock_history_qty_available);
|
||||
}
|
||||
|
||||
// checks each stock to manage the real quantity to decrement for each of them
|
||||
foreach ($stock_history_qty_available as $entry) {
|
||||
if ($entry['qty'] >= $global_quantity_to_decrement) {
|
||||
$quantity_to_decrement_by_stock[$entry['id_stock']][$entry['id_stock_mvt']] = $global_quantity_to_decrement;
|
||||
$global_quantity_to_decrement = 0;
|
||||
} else {
|
||||
$quantity_to_decrement_by_stock[$entry['id_stock']][$entry['id_stock_mvt']] = $entry['qty'];
|
||||
$global_quantity_to_decrement -= $entry['qty'];
|
||||
}
|
||||
|
||||
if ($global_quantity_to_decrement <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// for each stock, decrements it and logs the mvts
|
||||
foreach ($stock_collection as $stock) {
|
||||
if (array_key_exists($stock->id, $quantity_to_decrement_by_stock) && is_array($quantity_to_decrement_by_stock[$stock->id])) {
|
||||
$total_quantity_for_current_stock = 0;
|
||||
|
||||
foreach ($quantity_to_decrement_by_stock[$stock->id] as $id_mvt_referrer => $qte) {
|
||||
$mvt_params = [
|
||||
'id_stock' => $stock->id,
|
||||
'physical_quantity' => $qte,
|
||||
'id_stock_mvt_reason' => $id_stock_mvt_reason,
|
||||
'id_order' => $id_order,
|
||||
'price_te' => $stock->price_te,
|
||||
'sign' => -1,
|
||||
'referer' => $id_mvt_referrer,
|
||||
'id_employee' => (int) $context->employee->id ? (int) $context->employee->id : $employee->id,
|
||||
];
|
||||
|
||||
// saves stock mvt
|
||||
$stock_mvt = new StockMvt();
|
||||
$stock_mvt->hydrate($mvt_params);
|
||||
$stock_mvt->save();
|
||||
|
||||
$total_quantity_for_current_stock += $qte;
|
||||
}
|
||||
|
||||
$stock_params = [
|
||||
'physical_quantity' => ($stock->physical_quantity - $total_quantity_for_current_stock),
|
||||
'usable_quantity' => ($is_usable ? ($stock->usable_quantity - $total_quantity_for_current_stock) : $stock->usable_quantity),
|
||||
];
|
||||
|
||||
$return[$stock->id]['quantity'] = $total_quantity_for_current_stock;
|
||||
$return[$stock->id]['price_te'] = $stock->price_te;
|
||||
|
||||
// saves stock in warehouse
|
||||
$stock->hydrate($stock_params);
|
||||
$stock->update();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (Pack::isPacked($id_product, $id_product_attribute)) {
|
||||
$packs = Pack::getPacksContainingItem($id_product, $id_product_attribute, (int) Configuration::get('PS_LANG_DEFAULT'));
|
||||
foreach ($packs as $pack) {
|
||||
// Decrease stocks of the pack only if pack is in linked stock mode (option called 'Decrement both')
|
||||
if (!((int) $pack->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH)
|
||||
&& !((int) $pack->pack_stock_type == Pack::STOCK_TYPE_DEFAULT
|
||||
&& (int) Configuration::get('PS_PACK_STOCK_TYPE') == Pack::STOCK_TYPE_PACK_BOTH)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decrease stocks of the pack only if there is not enough items to constituate the actual pack stocks.
|
||||
|
||||
// How many packs can be constituated with the remaining product stocks
|
||||
$quantity_by_pack = $pack->pack_item_quantity;
|
||||
$stock_available_quantity = $quantity_in_stock - $quantity;
|
||||
$max_pack_quantity = max([0, floor($stock_available_quantity / $quantity_by_pack)]);
|
||||
$quantity_delta = Pack::getQuantity($pack->id) - $max_pack_quantity;
|
||||
|
||||
if ($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',
|
||||
[
|
||||
'id_product' => $id_product,
|
||||
'id_product_attribute' => $id_product_attribute,
|
||||
'warehouse' => $warehouse,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StockManagerInterface::getProductPhysicalQuantities()
|
||||
*/
|
||||
public function getProductPhysicalQuantities($id_product, $id_product_attribute, $ids_warehouse = null, $usable = false)
|
||||
{
|
||||
if (null !== $ids_warehouse) {
|
||||
// in case $ids_warehouse is not an array
|
||||
if (!is_array($ids_warehouse)) {
|
||||
$ids_warehouse = [$ids_warehouse];
|
||||
}
|
||||
|
||||
// casts for security reason
|
||||
$ids_warehouse = array_map('intval', $ids_warehouse);
|
||||
if (!count($ids_warehouse)) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$ids_warehouse = [];
|
||||
}
|
||||
|
||||
$query = new DbQuery();
|
||||
$query->select('SUM(' . ($usable ? 's.usable_quantity' : 's.physical_quantity') . ')');
|
||||
$query->from('stock', 's');
|
||||
$query->where('s.id_product = ' . (int) $id_product);
|
||||
if (0 != $id_product_attribute) {
|
||||
$query->where('s.id_product_attribute = ' . (int) $id_product_attribute);
|
||||
}
|
||||
|
||||
if (count($ids_warehouse)) {
|
||||
$query->where('s.id_warehouse IN(' . implode(', ', $ids_warehouse) . ')');
|
||||
}
|
||||
|
||||
return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see StockManagerInterface::getProductRealQuantities()
|
||||
*/
|
||||
public function getProductRealQuantities($id_product, $id_product_attribute, $ids_warehouse = null, $usable = false)
|
||||
{
|
||||
if (null !== $ids_warehouse) {
|
||||
// in case $ids_warehouse is not an array
|
||||
if (!is_array($ids_warehouse)) {
|
||||
$ids_warehouse = [$ids_warehouse];
|
||||
}
|
||||
|
||||
// casts for security reason
|
||||
$ids_warehouse = array_map('intval', $ids_warehouse);
|
||||
}
|
||||
|
||||
$client_orders_qty = 0;
|
||||
|
||||
// check if product is present in a pack
|
||||
if (!Pack::isPack($id_product) && $in_pack = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
|
||||
'SELECT id_product_pack, quantity FROM ' . _DB_PREFIX_ . 'pack
|
||||
WHERE id_product_item = ' . (int) $id_product . '
|
||||
AND id_product_attribute_item = ' . ($id_product_attribute ? (int) $id_product_attribute : '0')
|
||||
)) {
|
||||
foreach ($in_pack as $value) {
|
||||
if (Validate::isLoadedObject($product = new Product((int) $value['id_product_pack'])) &&
|
||||
($product->pack_stock_type == Pack::STOCK_TYPE_PRODUCTS_ONLY || $product->pack_stock_type == Pack::STOCK_TYPE_PACK_BOTH || ($product->pack_stock_type == Pack::STOCK_TYPE_DEFAULT && Configuration::get('PS_PACK_STOCK_TYPE') > 0))) {
|
||||
$query = new DbQuery();
|
||||
$query->select('od.product_quantity, od.product_quantity_refunded, pk.quantity');
|
||||
$query->from('order_detail', 'od');
|
||||
$query->leftjoin('orders', 'o', 'o.id_order = od.id_order');
|
||||
$query->where('od.product_id = ' . (int) $value['id_product_pack']);
|
||||
$query->leftJoin('order_history', 'oh', 'oh.id_order = o.id_order AND oh.id_order_state = o.current_state');
|
||||
$query->leftJoin('order_state', 'os', 'os.id_order_state = oh.id_order_state');
|
||||
$query->leftJoin('pack', 'pk', 'pk.id_product_item = ' . (int) $id_product . ' AND pk.id_product_attribute_item = ' . ($id_product_attribute ? (int) $id_product_attribute : '0') . ' AND id_product_pack = od.product_id');
|
||||
$query->where('os.shipped != 1');
|
||||
$query->where('o.valid = 1 OR (os.id_order_state != ' . (int) Configuration::get('PS_OS_ERROR') . '
|
||||
AND os.id_order_state != ' . (int) Configuration::get('PS_OS_CANCELED') . ')');
|
||||
$query->groupBy('od.id_order_detail');
|
||||
if (count($ids_warehouse)) {
|
||||
$query->where('od.id_warehouse IN(' . implode(', ', $ids_warehouse) . ')');
|
||||
}
|
||||
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
|
||||
if (count($res)) {
|
||||
foreach ($res as $row) {
|
||||
$client_orders_qty += ($row['product_quantity'] - $row['product_quantity_refunded']) * $row['quantity'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skip if product is a pack without
|
||||
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, [$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 ? [$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;
|
||||
}
|
||||
}
|
||||
80
classes/stock/StockManagerFactory.php
Normal file
80
classes/stock/StockManagerFactory.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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([$module_instance, 'hookStockManager'])) {
|
||||
$stock_manager = $module_instance->hookStockManager();
|
||||
}
|
||||
|
||||
if ($stock_manager) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $stock_manager;
|
||||
}
|
||||
}
|
||||
130
classes/stock/StockManagerInterface.php
Normal file
130
classes/stock/StockManagerInterface.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
60
classes/stock/StockManagerModule.php
Normal file
60
classes/stock/StockManagerModule.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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]', [$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]', [$this->stock_manager_class], 'Admin.Catalog.Notification'));
|
||||
}
|
||||
|
||||
$class = $this->stock_manager_class;
|
||||
if (call_user_func([$class, 'isAvailable'])) {
|
||||
return new $class();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
270
classes/stock/StockMvt.php
Normal file
270
classes/stock/StockMvt.php
Normal file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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 = [
|
||||
'table' => 'stock_mvt',
|
||||
'primary' => 'id_stock_mvt',
|
||||
'fields' => [
|
||||
'id_employee' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'employee_firstname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
|
||||
'employee_lastname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
|
||||
'id_stock' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'physical_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
|
||||
'id_stock_mvt_reason' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
|
||||
'id_supply_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
|
||||
'sign' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true],
|
||||
'last_wa' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
|
||||
'current_wa' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
|
||||
'price_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
|
||||
'referer' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
|
||||
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true],
|
||||
],
|
||||
];
|
||||
|
||||
protected $webserviceParameters = [
|
||||
'objectsNodeName' => 'stock_movements',
|
||||
'objectNodeName' => 'stock_movement',
|
||||
'fields' => [
|
||||
'id_employee' => ['xlink_resource' => 'employees'],
|
||||
'id_stock' => ['xlink_resource' => 'stock'],
|
||||
'id_stock_mvt_reason' => ['xlink_resource' => 'stock_movement_reasons'],
|
||||
'id_order' => ['xlink_resource' => 'orders'],
|
||||
'id_supply_order' => ['xlink_resource' => 'supply_order'],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @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 = [];
|
||||
$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;
|
||||
}
|
||||
}
|
||||
145
classes/stock/StockMvtReason.php
Normal file
145
classes/stock/StockMvtReason.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
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 = [
|
||||
'table' => 'stock_mvt_reason',
|
||||
'primary' => 'id_stock_mvt_reason',
|
||||
'multilang' => true,
|
||||
'fields' => [
|
||||
'sign' => ['type' => self::TYPE_INT],
|
||||
'deleted' => ['type' => self::TYPE_BOOL],
|
||||
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 255],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'objectsNodeName' => 'stock_movement_reasons',
|
||||
'objectNodeName' => 'stock_movement_reason',
|
||||
'fields' => [
|
||||
'sign' => [],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets Stock Mvt Reasons.
|
||||
*
|
||||
* @param int $id_lang
|
||||
* @param int $sign Optionnal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getStockMvtReasons($id_lang, $sign = null)
|
||||
{
|
||||
$query = new DbQuery();
|
||||
$query->select('smrl.name, smr.id_stock_mvt_reason, smr.sign');
|
||||
$query->from('stock_mvt_reason', 'smr');
|
||||
$query->leftjoin('stock_mvt_reason_lang', 'smrl', 'smr.id_stock_mvt_reason = smrl.id_stock_mvt_reason AND smrl.id_lang=' . (int) $id_lang);
|
||||
$query->where('smr.deleted = 0');
|
||||
|
||||
if ($sign != null) {
|
||||
$query->where('smr.sign = ' . (int) $sign);
|
||||
}
|
||||
|
||||
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as StockMvtReason::getStockMvtReasons(), ignoring a specific lists of ids.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
304
classes/stock/StockMvtWS.php
Normal file
304
classes/stock/StockMvtWS.php
Normal file
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 MPN of the product (@see Stock::product_mpn)
|
||||
*/
|
||||
public $mpn;
|
||||
|
||||
/**
|
||||
* @var string Reference of the product (@see Stock::product_reference)
|
||||
*/
|
||||
public $reference;
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$definition
|
||||
*/
|
||||
public static $definition = [
|
||||
'table' => 'stock_mvt',
|
||||
'primary' => 'id_stock_mvt',
|
||||
'fields' => [
|
||||
'id_employee' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'employee_firstname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
|
||||
'employee_lastname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
|
||||
'id_stock' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'physical_quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
|
||||
'id_stock_mvt_reason' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
|
||||
'id_supply_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
|
||||
'sign' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true],
|
||||
'last_wa' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
|
||||
'current_wa' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
|
||||
'price_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
|
||||
'referer' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'],
|
||||
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'fields' => [
|
||||
'id_product' => ['xlink_resource' => 'products'],
|
||||
'id_product_attribute' => ['xlink_resource' => 'combinations'],
|
||||
'id_warehouse' => ['xlink_resource' => 'warehouses'],
|
||||
'id_currency' => ['xlink_resource' => 'currencies'],
|
||||
'management_type' => [],
|
||||
'id_employee' => ['xlink_resource' => 'employees'],
|
||||
'id_stock' => ['xlink_resource' => 'stocks'],
|
||||
'id_stock_mvt_reason' => ['xlink_resource' => 'stock_movement_reasons'],
|
||||
'id_order' => ['xlink_resource' => 'orders'],
|
||||
'id_supply_order' => ['xlink_resource' => 'supply_orders'],
|
||||
'product_name' => ['getter' => 'getWSProductName', 'i18n' => true],
|
||||
'ean13' => [],
|
||||
'upc' => [],
|
||||
'reference' => [],
|
||||
'mpn' => [],
|
||||
],
|
||||
'hidden_fields' => [
|
||||
'referer',
|
||||
'employee_firstname',
|
||||
'employee_lastname',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Associations tables for attributes that require different tables than stated in ObjectModel::definition.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tables_assoc = [
|
||||
'id_product' => ['table' => 's'],
|
||||
'id_product_attribute' => ['table' => 's'],
|
||||
'id_warehouse' => ['table' => 's'],
|
||||
'id_currency' => ['table' => 's'],
|
||||
'management_type' => ['table' => 'w'],
|
||||
'ean13' => ['table' => 's'],
|
||||
'upc' => ['table' => 's'],
|
||||
'mpn' => ['table' => 's'],
|
||||
'reference' => ['table' => 's'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel
|
||||
*/
|
||||
public function __construct($id = null, $id_lang = null, $id_shop = null)
|
||||
{
|
||||
// calls parent
|
||||
parent::__construct($id, $id_lang, $id_shop);
|
||||
|
||||
if ((int) $this->id != 0) {
|
||||
$res = $this->getWebserviceObjectList(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.mpn, s.reference ';
|
||||
}
|
||||
|
||||
$old_filter = $filter;
|
||||
if ($filter) {
|
||||
foreach ($this->tables_assoc as $key => $value) {
|
||||
$filter = str_replace('main.`' . $key . '`', $value['table'] . '.`' . $key . '`', $filter);
|
||||
}
|
||||
}
|
||||
|
||||
$query .= 'FROM ' . _DB_PREFIX_ . $this->def['table'] . ' as main ';
|
||||
|
||||
if ($filter !== $old_filter || $full) {
|
||||
$query .= 'LEFT JOIN ' . _DB_PREFIX_ . 'stock s ON (s.id_stock = main.id_stock) ';
|
||||
$query .= 'LEFT JOIN ' . _DB_PREFIX_ . 'warehouse w ON (w.id_warehouse = s.id_warehouse) ';
|
||||
$query .= 'LEFT JOIN ' . _DB_PREFIX_ . 'currency c ON (c.id_currency = w.id_currency) ';
|
||||
}
|
||||
|
||||
if ($join) {
|
||||
$query .= $join;
|
||||
}
|
||||
|
||||
$query .= 'WHERE 1 ';
|
||||
|
||||
if ($filter) {
|
||||
$query .= $filter . ' ';
|
||||
}
|
||||
|
||||
if ($sort) {
|
||||
$query .= $sort . ' ';
|
||||
}
|
||||
|
||||
if ($limit) {
|
||||
$query .= $limit . ' ';
|
||||
}
|
||||
|
||||
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Webservice : getter for the product name.
|
||||
*/
|
||||
public function getWSProductName()
|
||||
{
|
||||
$res = [];
|
||||
foreach (Language::getIDs(true) as $id_lang) {
|
||||
$res[$id_lang] = Product::getProductName($this->id_product, $this->id_product_attribute, $id_lang);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
583
classes/stock/SupplyOrder.php
Normal file
583
classes/stock/SupplyOrder.php
Normal file
@@ -0,0 +1,583 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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 = [
|
||||
'table' => 'supply_order',
|
||||
'primary' => 'id_supply_order',
|
||||
'fields' => [
|
||||
'id_supplier' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'supplier_name' => ['type' => self::TYPE_STRING, 'validate' => 'isCatalogName', 'required' => false],
|
||||
'id_lang' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_warehouse' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_supply_order_state' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_ref_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'reference' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true],
|
||||
'date_delivery_expected' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true],
|
||||
'total_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
|
||||
'total_with_discount_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
|
||||
'total_ti' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
|
||||
'total_tax' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
|
||||
'discount_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => false],
|
||||
'discount_value_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice'],
|
||||
'is_template' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'fields' => [
|
||||
'id_supplier' => ['xlink_resource' => 'suppliers'],
|
||||
'id_lang' => ['xlink_resource' => 'languages'],
|
||||
'id_warehouse' => ['xlink_resource' => 'warehouses'],
|
||||
'id_supply_order_state' => ['xlink_resource' => 'supply_order_states'],
|
||||
'id_currency' => ['xlink_resource' => 'currencies'],
|
||||
],
|
||||
'hidden_fields' => [
|
||||
'id_ref_currency',
|
||||
],
|
||||
'associations' => [
|
||||
'supply_order_details' => [
|
||||
'resource' => 'supply_order_detail',
|
||||
'fields' => [
|
||||
'id' => [],
|
||||
'id_product' => [],
|
||||
'id_product_attribute' => [],
|
||||
'supplier_reference' => [],
|
||||
'product_name' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::update()
|
||||
*/
|
||||
public function update($null_values = false)
|
||||
{
|
||||
$this->calculatePrices();
|
||||
|
||||
$res = parent::update($null_values);
|
||||
|
||||
if ($res && !$this->is_template) {
|
||||
$this->addHistory();
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ObjectModel::add()
|
||||
*/
|
||||
public function add($autodate = true, $null_values = false)
|
||||
{
|
||||
$this->calculatePrices();
|
||||
|
||||
$res = parent::add($autodate, $null_values);
|
||||
|
||||
if ($res && !$this->is_template) {
|
||||
$this->addHistory();
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks all products in this order and calculate prices
|
||||
* Applies the global discount if necessary.
|
||||
*/
|
||||
protected function calculatePrices()
|
||||
{
|
||||
$this->total_te = 0;
|
||||
$this->total_with_discount_te = 0;
|
||||
$this->total_tax = 0;
|
||||
$this->total_ti = 0;
|
||||
$is_discount = false;
|
||||
|
||||
if (is_numeric($this->discount_rate) && (float) $this->discount_rate >= 0) {
|
||||
$is_discount = true;
|
||||
}
|
||||
|
||||
// gets all product entries in this order
|
||||
$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, get_object_vars($this))) {
|
||||
// formats prices and floats
|
||||
if ($this->def['fields'][$key]['validate'] == 'isFloat' ||
|
||||
$this->def['fields'][$key]['validate'] == 'isPrice') {
|
||||
$value = Tools::ps_round($value, 6);
|
||||
}
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reference of a given order.
|
||||
*
|
||||
* @param int $id_supply_order
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function getReferenceById($id_supply_order)
|
||||
{
|
||||
if (!$id_supply_order) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = new DbQuery();
|
||||
$query->select('so.reference');
|
||||
$query->from('supply_order', 'so');
|
||||
$query->where('so.id_supply_order = ' . (int) $id_supply_order);
|
||||
$ref = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
|
||||
|
||||
return pSQL($ref);
|
||||
}
|
||||
|
||||
public function getAllExpectedQuantity()
|
||||
{
|
||||
return Db::getInstance()->getValue(
|
||||
'
|
||||
SELECT SUM(`quantity_expected`)
|
||||
FROM `' . _DB_PREFIX_ . 'supply_order_detail`
|
||||
WHERE `id_supply_order` = ' . (int) $this->id
|
||||
);
|
||||
}
|
||||
|
||||
public function getAllReceivedQuantity()
|
||||
{
|
||||
return Db::getInstance()->getValue(
|
||||
'
|
||||
SELECT SUM(`quantity_received`)
|
||||
FROM `' . _DB_PREFIX_ . 'supply_order_detail`
|
||||
WHERE `id_supply_order` = ' . (int) $this->id
|
||||
);
|
||||
}
|
||||
|
||||
public function getAllPendingQuantity()
|
||||
{
|
||||
return Db::getInstance()->getValue(
|
||||
'
|
||||
SELECT (SUM(`quantity_expected`) - SUM(`quantity_received`))
|
||||
FROM `' . _DB_PREFIX_ . 'supply_order_detail`
|
||||
WHERE `id_supply_order` = ' . (int) $this->id
|
||||
);
|
||||
}
|
||||
|
||||
/*********************************\
|
||||
*
|
||||
* Webservices Specific Methods
|
||||
*
|
||||
*********************************/
|
||||
|
||||
/**
|
||||
* Webservice : gets the ids supply_order_detail associated to this order.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getWsSupplyOrderDetails()
|
||||
{
|
||||
$query = new DbQuery();
|
||||
$query->select('sod.id_supply_order_detail as id, sod.id_product,
|
||||
sod.id_product_attribute,
|
||||
sod.name as product_name, supplier_reference');
|
||||
$query->from('supply_order_detail', 'sod');
|
||||
$query->where('id_supply_order = ' . (int) $this->id);
|
||||
|
||||
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
|
||||
}
|
||||
}
|
||||
371
classes/stock/SupplyOrderDetail.php
Normal file
371
classes/stock/SupplyOrderDetail.php
Normal file
@@ -0,0 +1,371 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 string MPN
|
||||
*/
|
||||
public $mpn;
|
||||
|
||||
/**
|
||||
* @var int Currency used to buy this particular product
|
||||
*/
|
||||
public $id_currency;
|
||||
|
||||
/**
|
||||
* @var float Exchange rate between and SupplyOrder::$id_ref_currency, at the time
|
||||
*/
|
||||
public $exchange_rate;
|
||||
|
||||
/**
|
||||
* @var float Unit price without discount, without tax
|
||||
*/
|
||||
public $unit_price_te = 0;
|
||||
|
||||
/**
|
||||
* @var int Quantity ordered
|
||||
*/
|
||||
public $quantity_expected = 0;
|
||||
|
||||
/**
|
||||
* @var int Quantity received
|
||||
*/
|
||||
public $quantity_received = 0;
|
||||
|
||||
/**
|
||||
* @var float this defines the price of the product, considering the number of units to buy.
|
||||
* ($unit_price_te * $quantity), without discount, without tax
|
||||
*/
|
||||
public $price_te = 0;
|
||||
|
||||
/**
|
||||
* @var float Supplier discount rate for a given product
|
||||
*/
|
||||
public $discount_rate = 0;
|
||||
|
||||
/**
|
||||
* @var float Supplier discount value (($discount_rate / 100) *), without tax
|
||||
*/
|
||||
public $discount_value_te = 0;
|
||||
|
||||
/**
|
||||
* @var float ($price_te -), with discount, without tax
|
||||
*/
|
||||
public $price_with_discount_te = 0;
|
||||
|
||||
/**
|
||||
* @var int Tax rate for the given product
|
||||
*/
|
||||
public $tax_rate = 0;
|
||||
|
||||
/**
|
||||
* @var float Tax value for the given product
|
||||
*/
|
||||
public $tax_value = 0;
|
||||
|
||||
/**
|
||||
* @var float ($price_with_discount_te +)
|
||||
*/
|
||||
public $price_ti = 0;
|
||||
|
||||
/**
|
||||
* @var float Tax value of the given product after applying the global order discount (i.e. if SupplyOrder::discount_rate is set)
|
||||
*/
|
||||
public $tax_value_with_order_discount = 0;
|
||||
|
||||
/**
|
||||
* @var float This is like, considering the global order discount.
|
||||
* (i.e. if SupplyOrder::discount_rate is set)
|
||||
*/
|
||||
public $price_with_order_discount_te = 0;
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$definition
|
||||
*/
|
||||
public static $definition = [
|
||||
'table' => 'supply_order_detail',
|
||||
'primary' => 'id_supply_order_detail',
|
||||
'fields' => [
|
||||
'id_supply_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_product_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'reference' => ['type' => self::TYPE_STRING, 'validate' => 'isReference'],
|
||||
'supplier_reference' => ['type' => self::TYPE_STRING, 'validate' => 'isReference'],
|
||||
'name' => ['type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true],
|
||||
'ean13' => ['type' => self::TYPE_STRING, 'validate' => 'isEan13'],
|
||||
'isbn' => ['type' => self::TYPE_STRING, 'validate' => 'isIsbn'],
|
||||
'upc' => ['type' => self::TYPE_STRING, 'validate' => 'isUpc'],
|
||||
'mpn' => ['type' => self::TYPE_STRING, 'validate' => 'isMpn'],
|
||||
'id_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'exchange_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true],
|
||||
'unit_price_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
|
||||
'quantity_expected' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
|
||||
'quantity_received' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'],
|
||||
'price_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
|
||||
'discount_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true],
|
||||
'discount_value_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
|
||||
'price_with_discount_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
|
||||
'tax_rate' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true],
|
||||
'tax_value' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
|
||||
'price_ti' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
|
||||
'tax_value_with_order_discount' => ['type' => self::TYPE_FLOAT, 'validate' => 'isFloat', 'required' => true],
|
||||
'price_with_order_discount_te' => ['type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'objectsNodeName' => 'supply_order_details',
|
||||
'objectNodeName' => 'supply_order_detail',
|
||||
'fields' => [
|
||||
'id_supply_order' => ['xlink_resource' => 'supply_orders'],
|
||||
'id_product' => ['xlink_resource' => 'products'],
|
||||
'id_product_attribute' => ['xlink_resource' => 'combinations'],
|
||||
],
|
||||
'hidden_fields' => [
|
||||
'id_currency',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::update()
|
||||
*/
|
||||
public function update($null_values = false)
|
||||
{
|
||||
$this->calculatePrices();
|
||||
|
||||
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 = [];
|
||||
|
||||
/* 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.',
|
||||
[
|
||||
'<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).',
|
||||
[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.', [], '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.', [], '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.', [], '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.', [], '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, get_object_vars($this))) {
|
||||
// formats prices and floats
|
||||
if ($this->def['fields'][$key]['validate'] == 'isFloat' ||
|
||||
$this->def['fields'][$key]['validate'] == 'isPrice') {
|
||||
$value = Tools::ps_round($value, 6);
|
||||
}
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
classes/stock/SupplyOrderHistory.php
Normal file
90
classes/stock/SupplyOrderHistory.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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 = [
|
||||
'table' => 'supply_order_history',
|
||||
'primary' => 'id_supply_order_history',
|
||||
'fields' => [
|
||||
'id_supply_order' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_employee' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'employee_firstname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
|
||||
'employee_lastname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
|
||||
'id_state' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => true],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'objectsNodeName' => 'supply_order_histories',
|
||||
'objectNodeName' => 'supply_order_history',
|
||||
'fields' => [
|
||||
'id_supply_order' => ['xlink_resource' => 'supply_orders'],
|
||||
'id_employee' => ['xlink_resource' => 'employees'],
|
||||
'id_state' => ['xlink_resource' => 'supply_order_states'],
|
||||
],
|
||||
];
|
||||
}
|
||||
98
classes/stock/SupplyOrderReceiptHistory.php
Normal file
98
classes/stock/SupplyOrderReceiptHistory.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 = [
|
||||
'table' => 'supply_order_receipt_history',
|
||||
'primary' => 'id_supply_order_receipt_history',
|
||||
'fields' => [
|
||||
'id_supply_order_detail' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_supply_order_state' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_employee' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'employee_firstname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
|
||||
'employee_lastname' => ['type' => self::TYPE_STRING, 'validate' => 'isName'],
|
||||
'quantity' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true],
|
||||
'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'objectsNodeName' => 'supply_order_receipt_histories',
|
||||
'objectNodeName' => 'supply_order_receipt_history',
|
||||
'fields' => [
|
||||
'id_supply_order_detail' => ['xlink_resource' => 'supply_order_details'],
|
||||
'id_employee' => ['xlink_resource' => 'employees'],
|
||||
'id_supply_order_state' => ['xlink_resource' => 'supply_order_states'],
|
||||
],
|
||||
];
|
||||
}
|
||||
176
classes/stock/SupplyOrderState.php
Normal file
176
classes/stock/SupplyOrderState.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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 = [
|
||||
'table' => 'supply_order_state',
|
||||
'primary' => 'id_supply_order_state',
|
||||
'multilang' => true,
|
||||
'fields' => [
|
||||
'delivery_note' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'editable' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'receipt_state' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'pending_receipt' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'enclosed' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'],
|
||||
'color' => ['type' => self::TYPE_STRING, 'validate' => 'isColor'],
|
||||
'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 128],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'objectsNodeName' => 'supply_order_states',
|
||||
'objectNodeName' => 'supply_order_state',
|
||||
'fields' => [
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets the list of supply order statuses.
|
||||
*
|
||||
* @param int $id_state_referrer Optional, used to know what state is available after this one
|
||||
* @param int $id_lang Optional Id Language
|
||||
*
|
||||
* @return array States
|
||||
*/
|
||||
public static function getSupplyOrderStates($id_state_referrer = null, $id_lang = null)
|
||||
{
|
||||
if ($id_lang == null) {
|
||||
$id_lang = Context::getContext()->language->id;
|
||||
}
|
||||
|
||||
$query = new DbQuery();
|
||||
$query->select('sl.name, s.id_supply_order_state');
|
||||
$query->from('supply_order_state', 's');
|
||||
$query->leftjoin('supply_order_state_lang', 'sl', 's.id_supply_order_state = sl.id_supply_order_state AND sl.id_lang=' . (int) $id_lang);
|
||||
|
||||
if (null !== $id_state_referrer) {
|
||||
$is_receipt_state = false;
|
||||
$is_editable = false;
|
||||
$is_delivery_note = false;
|
||||
$is_pending_receipt = false;
|
||||
|
||||
//check current state to see what state is available
|
||||
$state = new SupplyOrderState((int) $id_state_referrer);
|
||||
if (Validate::isLoadedObject($state)) {
|
||||
$is_receipt_state = $state->receipt_state;
|
||||
$is_editable = $state->editable;
|
||||
$is_delivery_note = $state->delivery_note;
|
||||
$is_pending_receipt = $state->pending_receipt;
|
||||
}
|
||||
|
||||
$query->where('s.id_supply_order_state <> ' . (int) $id_state_referrer);
|
||||
|
||||
//check first if the order is editable
|
||||
if ($is_editable) {
|
||||
$query->where('s.editable = 1 OR s.delivery_note = 1 OR s.enclosed = 1');
|
||||
} elseif ($is_delivery_note || $is_pending_receipt) {
|
||||
//check if the delivery note is available or if the state correspond to a pending receipt state
|
||||
$query->where('(s.delivery_note = 0 AND s.editable = 0) OR s.enclosed = 1');
|
||||
} elseif ($is_receipt_state) {
|
||||
//check if the state correspond to a receipt state
|
||||
$query->where('s.receipt_state = 1');
|
||||
}
|
||||
}
|
||||
|
||||
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of supply order statuses.
|
||||
*
|
||||
* @param array $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 = [];
|
||||
}
|
||||
|
||||
$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
646
classes/stock/Warehouse.php
Normal file
@@ -0,0 +1,646 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 = [
|
||||
'table' => 'warehouse',
|
||||
'primary' => 'id_warehouse',
|
||||
'fields' => [
|
||||
'id_address' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'reference' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true, 'size' => 64],
|
||||
'name' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true, 'size' => 45],
|
||||
'id_employee' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'management_type' => ['type' => self::TYPE_STRING, 'validate' => 'isStockManagement', 'required' => true],
|
||||
'id_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'deleted' => ['type' => self::TYPE_BOOL],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'fields' => [
|
||||
'id_address' => ['xlink_resource' => 'addresses'],
|
||||
'id_employee' => ['xlink_resource' => 'employees'],
|
||||
'id_currency' => ['xlink_resource' => 'currencies'],
|
||||
'valuation' => ['getter' => 'getWsStockValue', 'setter' => false],
|
||||
'deleted' => [],
|
||||
],
|
||||
'associations' => [
|
||||
'stocks' => [
|
||||
'resource' => 'stock',
|
||||
'fields' => [
|
||||
'id' => [],
|
||||
],
|
||||
],
|
||||
'carriers' => [
|
||||
'resource' => 'carrier',
|
||||
'fields' => [
|
||||
'id' => [],
|
||||
],
|
||||
],
|
||||
'shops' => [
|
||||
'resource' => 'shop',
|
||||
'fields' => [
|
||||
'id' => [],
|
||||
'name' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets the shops associated to the current warehouse.
|
||||
*
|
||||
* @return array Shops (id, name)
|
||||
*/
|
||||
public function getShops()
|
||||
{
|
||||
$query = new DbQuery();
|
||||
$query->select('ws.id_shop, s.name');
|
||||
$query->from('warehouse_shop', 'ws');
|
||||
$query->leftJoin('shop', 's', 's.id_shop = ws.id_shop');
|
||||
$query->where($this->def['primary'] . ' = ' . (int) $this->id);
|
||||
|
||||
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the carriers associated to the current warehouse.
|
||||
*
|
||||
* @return array Ids of the associated carriers
|
||||
*/
|
||||
public function getCarriers($return_reference = false)
|
||||
{
|
||||
$ids_carrier = [];
|
||||
|
||||
$query = new DbQuery();
|
||||
if ($return_reference) {
|
||||
$query->select('wc.id_carrier');
|
||||
} else {
|
||||
$query->select('c.id_carrier');
|
||||
}
|
||||
$query->from('warehouse_carrier', 'wc');
|
||||
$query->innerJoin('carrier', 'c', 'c.id_reference = wc.id_carrier');
|
||||
$query->where($this->def['primary'] . ' = ' . (int) $this->id);
|
||||
$query->where('c.deleted = 0');
|
||||
$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
|
||||
|
||||
if (!is_array($res)) {
|
||||
return $ids_carrier;
|
||||
}
|
||||
|
||||
foreach ($res as $carriers) {
|
||||
foreach ($carriers as $carrier) {
|
||||
$ids_carrier[$carrier] = $carrier;
|
||||
}
|
||||
}
|
||||
|
||||
return $ids_carrier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the carriers associated to the current warehouse.
|
||||
*
|
||||
* @param array $ids_carriers
|
||||
*/
|
||||
public function setCarriers($ids_carriers)
|
||||
{
|
||||
if (!is_array($ids_carriers)) {
|
||||
$ids_carriers = [];
|
||||
}
|
||||
|
||||
$row_to_insert = [];
|
||||
foreach ($ids_carriers as $id_carrier) {
|
||||
$row_to_insert[] = [$this->def['primary'] => $this->id, 'id_carrier' => (int) $id_carrier];
|
||||
}
|
||||
|
||||
Db::getInstance()->execute('
|
||||
DELETE FROM ' . _DB_PREFIX_ . 'warehouse_carrier
|
||||
WHERE ' . $this->def['primary'] . ' = ' . (int) $this->id);
|
||||
|
||||
if ($row_to_insert) {
|
||||
Db::getInstance()->insert('warehouse_carrier', $row_to_insert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given carrier, removes it from the warehouse/carrier association
|
||||
* If $id_warehouse is set, it only removes the carrier for this warehouse.
|
||||
*
|
||||
* @param int $id_carrier Id of the carrier to remove
|
||||
* @param int $id_warehouse optional Id of the warehouse to filter
|
||||
*/
|
||||
public static function removeCarrier($id_carrier, $id_warehouse = null)
|
||||
{
|
||||
Db::getInstance()->execute('
|
||||
DELETE FROM ' . _DB_PREFIX_ . 'warehouse_carrier
|
||||
WHERE id_carrier = ' . (int) $id_carrier .
|
||||
($id_warehouse ? ' AND id_warehouse = ' . (int) $id_warehouse : ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a warehouse is empty - i.e. has no stock.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
$query = new DbQuery();
|
||||
$query->select('SUM(s.physical_quantity)');
|
||||
$query->from('stock', 's');
|
||||
$query->where($this->def['primary'] . ' = ' . (int) $this->id);
|
||||
|
||||
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given warehouse exists.
|
||||
*
|
||||
* @param int $id_warehouse
|
||||
*
|
||||
* @return bool Exists/Does not exist
|
||||
*/
|
||||
public static function exists($id_warehouse)
|
||||
{
|
||||
$query = new DbQuery();
|
||||
$query->select('id_warehouse');
|
||||
$query->from('warehouse');
|
||||
$query->where('id_warehouse = ' . (int) $id_warehouse);
|
||||
$query->where('deleted = 0');
|
||||
|
||||
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given {product, product attribute} sets its location in the given warehouse
|
||||
* First, for the given parameters, it cleans the database before updating.
|
||||
*
|
||||
* @param int $id_product ID of the product
|
||||
* @param int $id_product_attribute Use 0 if this product does not have attributes
|
||||
* @param int $id_warehouse ID of the warehouse
|
||||
* @param string $location Describes the location (no lang id required)
|
||||
*
|
||||
* @return bool Success/Failure
|
||||
*/
|
||||
public static function setProductLocation($id_product, $id_product_attribute, $id_warehouse, $location)
|
||||
{
|
||||
Db::getInstance()->execute('
|
||||
DELETE FROM `' . _DB_PREFIX_ . 'warehouse_product_location`
|
||||
WHERE `id_product` = ' . (int) $id_product . '
|
||||
AND `id_product_attribute` = ' . (int) $id_product_attribute . '
|
||||
AND `id_warehouse` = ' . (int) $id_warehouse);
|
||||
|
||||
$row_to_insert = [
|
||||
'id_product' => (int) $id_product,
|
||||
'id_product_attribute' => (int) $id_product_attribute,
|
||||
'id_warehouse' => (int) $id_warehouse,
|
||||
'location' => pSQL($location),
|
||||
];
|
||||
|
||||
return Db::getInstance()->insert('warehouse_product_location', $row_to_insert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all product locations for this warehouse.
|
||||
*/
|
||||
public function resetProductsLocations()
|
||||
{
|
||||
Db::getInstance()->execute('
|
||||
DELETE FROM `' . _DB_PREFIX_ . 'warehouse_product_location`
|
||||
WHERE `id_warehouse` = ' . (int) $this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given {product, product attribute} gets its location in the given warehouse.
|
||||
*
|
||||
* @param int $id_product ID of the product
|
||||
* @param int $id_product_attribute Use 0 if this product does not have attributes
|
||||
* @param int $id_warehouse ID of the warehouse
|
||||
*
|
||||
* @return string Location of the product
|
||||
*/
|
||||
public static function getProductLocation($id_product, $id_product_attribute, $id_warehouse)
|
||||
{
|
||||
$query = new DbQuery();
|
||||
$query->select('location');
|
||||
$query->from('warehouse_product_location');
|
||||
$query->where('id_warehouse = ' . (int) $id_warehouse);
|
||||
$query->where('id_product = ' . (int) $id_product);
|
||||
$query->where('id_product_attribute = ' . (int) $id_product_attribute);
|
||||
|
||||
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given {product, product attribute} gets warehouse list.
|
||||
*
|
||||
* @param int $id_product ID of the product
|
||||
* @param int $id_product_attribute Optional, uses 0 if this product does not have attributes
|
||||
* @param int $id_shop Optional, ID of the shop. Uses the context shop id (@see Context::shop)
|
||||
*
|
||||
* @return array Warehouses (ID, reference/name concatenated)
|
||||
*/
|
||||
public static function getProductWarehouseList($id_product, $id_product_attribute = 0, $id_shop = null)
|
||||
{
|
||||
// if it's a pack, returns warehouses if and only if some products use the advanced stock management
|
||||
$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 = [(int) $id_shop];
|
||||
}
|
||||
|
||||
$query = new DbQuery();
|
||||
$query->select('wpl.id_warehouse, CONCAT(w.reference, " - ", w.name) as name');
|
||||
$query->from('warehouse_product_location', 'wpl');
|
||||
$query->innerJoin('warehouse_shop', 'ws', 'ws.id_warehouse = wpl.id_warehouse AND id_shop IN (' . implode(',', array_map('intval', $ids_shop)) . ')');
|
||||
$query->innerJoin('warehouse', 'w', 'ws.id_warehouse = w.id_warehouse');
|
||||
$query->where('id_product = ' . (int) $id_product);
|
||||
$query->where('id_product_attribute = ' . (int) $id_product_attribute);
|
||||
$query->where('w.deleted = 0');
|
||||
$query->groupBy('wpl.id_warehouse');
|
||||
|
||||
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets available warehouses
|
||||
* It is possible via ignore_shop and id_shop to filter the list with shop id.
|
||||
*
|
||||
* @param bool $ignore_shop Optional, false by default - Allows to get only the warehouses that are associated to one/some shops (@see $id_shop)
|
||||
* @param int $id_shop optional, Context::shop::Id by default - Allows to define a specific shop to filter
|
||||
*
|
||||
* @return array Warehouses (ID, reference/name concatenated)
|
||||
*/
|
||||
public static function getWarehouses($ignore_shop = false, $id_shop = null)
|
||||
{
|
||||
if (!$ignore_shop) {
|
||||
if (null === $id_shop) {
|
||||
$id_shop = Context::getContext()->shop->id;
|
||||
}
|
||||
}
|
||||
|
||||
$query = new DbQuery();
|
||||
$query->select('w.id_warehouse, CONCAT(reference, \' - \', name) as name');
|
||||
$query->from('warehouse', 'w');
|
||||
$query->where('deleted = 0');
|
||||
$query->orderBy('reference ASC');
|
||||
if (!$ignore_shop) {
|
||||
$query->innerJoin('warehouse_shop', 'ws', 'ws.id_warehouse = w.id_warehouse AND ws.id_shop = ' . (int) $id_shop);
|
||||
}
|
||||
|
||||
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets warehouses grouped by shops.
|
||||
*
|
||||
* @return array (of array) Warehouses ID are grouped by shops ID
|
||||
*/
|
||||
public static function getWarehousesGroupedByShops()
|
||||
{
|
||||
$ids_warehouse = [];
|
||||
$query = new DbQuery();
|
||||
$query->select('id_warehouse, id_shop');
|
||||
$query->from('warehouse_shop');
|
||||
$query->orderBy('id_shop');
|
||||
|
||||
// queries to get warehouse ids grouped by shops
|
||||
foreach (Db::getInstance()->executeS($query) as $row) {
|
||||
$ids_warehouse[$row['id_shop']][] = $row['id_warehouse'];
|
||||
}
|
||||
|
||||
return $ids_warehouse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of products in the current warehouse.
|
||||
*
|
||||
* @return int Number of different id_stock
|
||||
*/
|
||||
public function getNumberOfProducts()
|
||||
{
|
||||
$query = '
|
||||
SELECT COUNT(t.id_stock)
|
||||
FROM
|
||||
(
|
||||
SELECT s.id_stock
|
||||
FROM ' . _DB_PREFIX_ . 'stock s
|
||||
WHERE s.id_warehouse = ' . (int) $this->id . '
|
||||
GROUP BY s.id_product, s.id_product_attribute
|
||||
) as t';
|
||||
|
||||
return 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 [];
|
||||
}
|
||||
|
||||
$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 = [];
|
||||
|
||||
// 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] = [];
|
||||
// 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 = [];
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
145
classes/stock/WarehouseProductLocation.php
Normal file
145
classes/stock/WarehouseProductLocation.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
/**
|
||||
* @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 = [
|
||||
'table' => 'warehouse_product_location',
|
||||
'primary' => 'id_warehouse_product_location',
|
||||
'fields' => [
|
||||
'location' => ['type' => self::TYPE_STRING, 'validate' => 'isReference', 'size' => 64],
|
||||
'id_product' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_product_attribute' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
'id_warehouse' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$webserviceParameters
|
||||
*/
|
||||
protected $webserviceParameters = [
|
||||
'fields' => [
|
||||
'id_product' => ['xlink_resource' => 'products'],
|
||||
'id_product_attribute' => ['xlink_resource' => 'combinations'],
|
||||
'id_warehouse' => ['xlink_resource' => 'warehouses'],
|
||||
],
|
||||
'hidden_fields' => [
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* For a given product and warehouse, gets the location.
|
||||
*
|
||||
* @param int $id_product product ID
|
||||
* @param int $id_product_attribute product attribute ID
|
||||
* @param int $id_warehouse warehouse ID
|
||||
*
|
||||
* @return string $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
34
classes/stock/index.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* 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.md.
|
||||
* 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://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
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;
|
||||
Reference in New Issue
Block a user