/** * @see StockManagerInterface::removeProduct() */ public function removeProduct($id_product, $id_product_attribute = null, Warehouse $warehouse, $quantity, $id_stock_mvt_reason, $is_usable = true, $id_order = null) { $return = array(); if (!Validate::isLoadedObject($warehouse) || !$quantity || !$id_product) { return $return; } if (!StockMvtReason::exists($id_stock_mvt_reason)) { $id_stock_mvt_reason = Configuration::get('PS_STOCK_MVT_DEC_REASON_DEFAULT'); } $context = Context::getContext(); // Special case of a pack if (Pack::isPack((int) $id_product)) { // Gets items $products_pack = Pack::getItems((int) $id_product, (int) Configuration::get('PS_LANG_DEFAULT')); // Foreach item foreach ($products_pack as $product_pack) { $pack_id_product_attribute = Product::getDefaultAttribute($product_pack->id, 1); if ($product_pack->advanced_stock_management == 1) { $this->removeProduct($product_pack->id, $pack_id_product_attribute, $warehouse, $product_pack->pack_quantity * $quantity, $id_stock_mvt_reason, $is_usable, $id_order); } } } else { // gets total quantities in stock for the current product $physical_quantity_in_stock = (int) $this->getProductPhysicalQuantities($id_product, $id_product_attribute, array($warehouse->id), false); $usable_quantity_in_stock = (int) $this->getProductPhysicalQuantities($id_product, $id_product_attribute, array($warehouse->id), true); // check quantity if we want to decrement unusable quantity if (!$is_usable) { $quantity_in_stock = $physical_quantity_in_stock - $usable_quantity_in_stock; } else { $quantity_in_stock = $usable_quantity_in_stock; } // checks if it's possible to remove the given quantity if ($quantity_in_stock < $quantity) { return $return; } $stock_collection = $this->getStockCollection($id_product, $id_product_attribute, $warehouse->id); $stock_collection->getAll(); // check if the collection is loaded if (count($stock_collection) <= 0) { return $return; } $stock_history_qty_available = array(); $mvt_params = array(); $stock_params = array(); $quantity_to_decrement_by_stock = array(); $global_quantity_to_decrement = $quantity; // switch on MANAGEMENT_TYPE switch ($warehouse->management_type) { // case CUMP mode case 'WA': // There is one and only one stock for a given product in a warehouse in this mode $stock = $stock_collection->current(); $mvt_params = array('id_stock' => $stock->id, 'physical_quantity' => $quantity, 'id_stock_mvt_reason' => $id_stock_mvt_reason, 'id_order' => $id_order, 'price_te' => $stock->price_te, 'last_wa' => $stock->price_te, 'current_wa' => $stock->price_te, 'id_employee' => $context->employee->id, 'employee_firstname' => $context->employee->firstname, 'employee_lastname' => $context->employee->lastname, 'sign' => -1); $stock_params = array('physical_quantity' => $stock->physical_quantity - $quantity, 'usable_quantity' => $is_usable ? $stock->usable_quantity - $quantity : $stock->usable_quantity); // saves stock in warehouse $stock->hydrate($stock_params); $stock->update(); // saves stock mvt $stock_mvt = new StockMvt(); $stock_mvt->hydrate($mvt_params); $stock_mvt->save(); $return[$stock->id]['quantity'] = $quantity; $return[$stock->id]['price_te'] = $stock->price_te; break; case 'LIFO': case 'FIFO': // for each stock, parse its mvts history to calculate the quantities left for each positive mvt, // according to the instant available quantities for this stock foreach ($stock_collection as $stock) { $left_quantity_to_check = $stock->physical_quantity; if ($left_quantity_to_check <= 0) { continue; } $resource = Db::getInstance(_PS_USE_SQL_SLAVE_)->query(' 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'); while ($row = Db::getInstance()->nextRow($resource)) { // break - 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) { break; } } // converts date to timestamp $date = new DateTime($row['date_add']); $timestamp = $date->format('U'); // history of the mvt $stock_history_qty_available[$timestamp] = array('id_stock' => $stock->id, 'id_stock_mvt' => (int) $row['id_stock_mvt'], 'qty' => (int) $row['qty']); // break - in LIFO mode, checks only the necessary history to handle the global quantity for the current stock if ($warehouse->management_type == 'LIFO') { $left_quantity_to_check -= (int) $row['physical_quantity']; if ($left_quantity_to_check <= 0) { break; } } } } if ($warehouse->management_type == 'LIFO') { // orders stock history by timestamp to get newest history first krsort($stock_history_qty_available); } else { // orders stock history by timestamp to get oldest history first ksort($stock_history_qty_available); } // checks each stock to manage the real quantity to decrement for each of them foreach ($stock_history_qty_available as $entry) { if ($entry['qty'] >= $global_quantity_to_decrement) { $quantity_to_decrement_by_stock[$entry['id_stock']][$entry['id_stock_mvt']] = $global_quantity_to_decrement; $global_quantity_to_decrement = 0; } else { $quantity_to_decrement_by_stock[$entry['id_stock']][$entry['id_stock_mvt']] = $entry['qty']; $global_quantity_to_decrement -= $entry['qty']; } if ($global_quantity_to_decrement <= 0) { break; } } // for each stock, decrements it and logs the mvts foreach ($stock_collection as $stock) { if (array_key_exists($stock->id, $quantity_to_decrement_by_stock) && is_array($quantity_to_decrement_by_stock[$stock->id])) { $total_quantity_for_current_stock = 0; foreach ($quantity_to_decrement_by_stock[$stock->id] as $id_mvt_referrer => $qte) { $mvt_params = array('id_stock' => $stock->id, 'physical_quantity' => $qte, 'id_stock_mvt_reason' => $id_stock_mvt_reason, 'id_order' => $id_order, 'price_te' => $stock->price_te, 'sign' => -1, 'referer' => $id_mvt_referrer, 'id_employee' => $context->employee->id); // saves stock mvt $stock_mvt = new StockMvt(); $stock_mvt->hydrate($mvt_params); $stock_mvt->save(); $total_quantity_for_current_stock += $qte; } $stock_params = array('physical_quantity' => $stock->physical_quantity - $total_quantity_for_current_stock, 'usable_quantity' => $is_usable ? $stock->usable_quantity - $total_quantity_for_current_stock : $stock->usable_quantity); $return[$stock->id]['quantity'] = $total_quantity_for_current_stock; $return[$stock->id]['price_te'] = $stock->price_te; // saves stock in warehouse $stock->hydrate($stock_params); $stock->update(); } } break; } } // if we remove a usable quantity, exec hook if ($is_usable) { Hook::exec('actionProductCoverage', array('id_product' => $id_product, 'id_product_attribute' => $id_product_attribute, 'warehouse' => $warehouse)); } return $return; }
/** * AdminController::postProcess() override * @see AdminController::postProcess() */ public function postProcess() { parent::postProcess(); // Checks access if (Tools::isSubmit('addStock') && !($this->tabAccess['add'] === '1')) { $this->errors[] = Tools::displayError('You do not have the required permission to add stock.'); } if (Tools::isSubmit('removeStock') && !($this->tabAccess['delete'] === '1')) { $this->errors[] = Tools::displayError('You do not have the required permission to delete stock'); } if (Tools::isSubmit('transferStock') && !($this->tabAccess['edit'] === '1')) { $this->errors[] = Tools::displayError('You do not have the required permission to transfer stock.'); } if (count($this->errors)) { return; } // Global checks when add / remove / transfer product if ((Tools::isSubmit('addstock') || Tools::isSubmit('removestock') || Tools::isSubmit('transferstock')) && Tools::isSubmit('is_post')) { // get product ID $id_product = (int) Tools::getValue('id_product', 0); if ($id_product <= 0) { $this->errors[] = Tools::displayError('The selected product is not valid.'); } // get product_attribute ID $id_product_attribute = (int) Tools::getValue('id_product_attribute', 0); // check the product hash $check = Tools::getValue('check', ''); $check_valid = md5(_COOKIE_KEY_ . $id_product . $id_product_attribute); if ($check != $check_valid) { $this->errors[] = Tools::displayError('The selected product is not valid.'); } // get quantity and check that the post value is really an integer // If it's not, we have nothing to do $quantity = Tools::getValue('quantity', 0); if (!is_numeric($quantity) || (int) $quantity <= 0) { $this->errors[] = Tools::displayError('The quantity value is not valid.'); } $quantity = (int) $quantity; $token = Tools::getValue('token') ? Tools::getValue('token') : $this->token; $redirect = self::$currentIndex . '&token=' . $token; } // Global checks when add / remove product if ((Tools::isSubmit('addstock') || Tools::isSubmit('removestock')) && Tools::isSubmit('is_post')) { // get warehouse id $id_warehouse = (int) Tools::getValue('id_warehouse', 0); if ($id_warehouse <= 0 || !Warehouse::exists($id_warehouse)) { $this->errors[] = Tools::displayError('The selected warehouse is not valid.'); } // get stock movement reason id $id_stock_mvt_reason = (int) Tools::getValue('id_stock_mvt_reason', 0); if ($id_stock_mvt_reason <= 0 || !StockMvtReason::exists($id_stock_mvt_reason)) { $this->errors[] = Tools::displayError('The reason is not valid.'); } // get usable flag $usable = Tools::getValue('usable', null); if (is_null($usable)) { $this->errors[] = Tools::displayError('You have to specify whether the product quantity is usable for sale on shops or not.'); } $usable = (bool) $usable; } if (Tools::isSubmit('addstock') && Tools::isSubmit('is_post')) { // get product unit price $price = str_replace(',', '.', Tools::getValue('price', 0)); if (!is_numeric($price)) { $this->errors[] = Tools::displayError('The product price is not valid.'); } $price = round(floatval($price), 6); // get product unit price currency id $id_currency = (int) Tools::getValue('id_currency', 0); if ($id_currency <= 0 || (!($result = Currency::getCurrency($id_currency)) || empty($result))) { $this->errors[] = Tools::displayError('The selected currency is not valid.'); } // if all is ok, add stock if (count($this->errors) == 0) { $warehouse = new Warehouse($id_warehouse); // convert price to warehouse currency if needed if ($id_currency != $warehouse->id_currency) { // First convert price to the default currency $price_converted_to_default_currency = Tools::convertPrice($price, $id_currency, false); // Convert the new price from default currency to needed currency $price = Tools::convertPrice($price_converted_to_default_currency, $warehouse->id_currency, true); } // add stock $stock_manager = StockManagerFactory::getManager(); if ($stock_manager->addProduct($id_product, $id_product_attribute, $warehouse, $quantity, $id_stock_mvt_reason, $price, $usable)) { // Create warehouse_product_location entry if we add stock to a new warehouse $id_wpl = (int) WarehouseProductLocation::getIdByProductAndWarehouse($id_product, $id_product_attribute, $id_warehouse); if (!$id_wpl) { $wpl = new WarehouseProductLocation(); $wpl->id_product = (int) $id_product; $wpl->id_product_attribute = (int) $id_product_attribute; $wpl->id_warehouse = (int) $id_warehouse; $wpl->save(); } StockAvailable::synchronize($id_product); if (Tools::isSubmit('addstockAndStay')) { $redirect = self::$currentIndex . '&id_product=' . (int) $id_product; if ($id_product_attribute) { $redirect .= '&id_product_attribute=' . (int) $id_product_attribute; } $redirect .= '&addstock&token=' . $token; } Tools::redirectAdmin($redirect . '&conf=1'); } else { $this->errors[] = Tools::displayError('An error occurred. No stock was added.'); } } } if (Tools::isSubmit('removestock') && Tools::isSubmit('is_post')) { // if all is ok, remove stock if (count($this->errors) == 0) { $warehouse = new Warehouse($id_warehouse); // remove stock $stock_manager = StockManagerFactory::getManager(); $removed_products = $stock_manager->removeProduct($id_product, $id_product_attribute, $warehouse, $quantity, $id_stock_mvt_reason, $usable); if (count($removed_products) > 0) { StockAvailable::synchronize($id_product); Tools::redirectAdmin($redirect . '&conf=2'); } else { $physical_quantity_in_stock = (int) $stock_manager->getProductPhysicalQuantities($id_product, $id_product_attribute, array($warehouse->id), false); $usable_quantity_in_stock = (int) $stock_manager->getProductPhysicalQuantities($id_product, $id_product_attribute, array($warehouse->id), true); $not_usable_quantity = $physical_quantity_in_stock - $usable_quantity_in_stock; if ($usable_quantity_in_stock < $quantity) { $this->errors[] = sprintf(Tools::displayError('You don\'t have enough usable quantity. Cannot remove %d items out of %d.'), (int) $quantity, (int) $usable_quantity_in_stock); } elseif ($not_usable_quantity < $quantity) { $this->errors[] = sprintf(Tools::displayError('You don\'t have enough usable quantity. Cannot remove %d items out of %d.'), (int) $quantity, (int) $not_usable_quantity); } else { $this->errors[] = Tools::displayError('It is not possible to remove the specified quantity. Therefore no stock was removed.'); } } } } if (Tools::isSubmit('transferstock') && Tools::isSubmit('is_post')) { // get source warehouse id $id_warehouse_from = (int) Tools::getValue('id_warehouse_from', 0); if ($id_warehouse_from <= 0 || !Warehouse::exists($id_warehouse_from)) { $this->errors[] = Tools::displayError('The source warehouse is not valid.'); } // get destination warehouse id $id_warehouse_to = (int) Tools::getValue('id_warehouse_to', 0); if ($id_warehouse_to <= 0 || !Warehouse::exists($id_warehouse_to)) { $this->errors[] = Tools::displayError('The destination warehouse is not valid.'); } // get usable flag for source warehouse $usable_from = Tools::getValue('usable_from', null); if (is_null($usable_from)) { $this->errors[] = Tools::displayError('You have to specify whether the product quantity in your source warehouse(s) is ready for sale or not.'); } $usable_from = (bool) $usable_from; // get usable flag for destination warehouse $usable_to = Tools::getValue('usable_to', null); if (is_null($usable_to)) { $this->errors[] = Tools::displayError('You have to specify whether the product quantity in your destination warehouse(s) is ready for sale or not.'); } $usable_to = (bool) $usable_to; // if we can process stock transfers if (count($this->errors) == 0) { // transfer stock $stock_manager = StockManagerFactory::getManager(); $is_transfer = $stock_manager->transferBetweenWarehouses($id_product, $id_product_attribute, $quantity, $id_warehouse_from, $id_warehouse_to, $usable_from, $usable_to); StockAvailable::synchronize($id_product); if ($is_transfer) { Tools::redirectAdmin($redirect . '&conf=3'); } else { $this->errors[] = Tools::displayError('It is not possible to transfer the specified quantity. No stock was transferred.'); } } } }
public function postProcess() { $this->adminControllerPostProcess(); if (Tools::isSubmit('addStock') && !($this->tabAccess['add'] === '1')) { $this->errors[] = Tools::displayError('You do not have the required permission to add stock.'); } if (Tools::isSubmit('removeStock') && !($this->tabAccess['delete'] === '1')) { $this->errors[] = Tools::displayError('You do not have the required permission to delete stock'); } if (Tools::isSubmit('transferStock') && !($this->tabAccess['edit'] === '1')) { $this->errors[] = Tools::displayError('You do not have the required permission to transfer stock.'); } if (count($this->errors)) { return; } if ((Tools::isSubmit('addstock') || Tools::isSubmit('removestock') || Tools::isSubmit('transferstock')) && Tools::isSubmit('is_post')) { $id_product = (int) Tools::getValue('id_product', 0); if ($id_product <= 0) { $this->errors[] = Tools::displayError('The selected product is not valid.'); } $id_product_attribute = (int) Tools::getValue('id_product_attribute', 0); $check = Tools::getValue('check', ''); $check_valid = md5(_COOKIE_KEY_ . $id_product . $id_product_attribute); if ($check != $check_valid) { $this->errors[] = Tools::displayError('The selected product is not valid.'); } $quantity = Tools::getValue('quantity', 0); $quantity = PP::normalizeProductQty($quantity, $id_product); if (!is_numeric($quantity) || $quantity <= 0) { $this->errors[] = Tools::displayError('The quantity value is not valid.'); } $token = Tools::getValue('token') ? Tools::getValue('token') : $this->token; $redirect = self::$currentIndex . '&token=' . $token; } if ((Tools::isSubmit('addstock') || Tools::isSubmit('removestock')) && Tools::isSubmit('is_post')) { $id_warehouse = (int) Tools::getValue('id_warehouse', 0); if ($id_warehouse <= 0 || !Warehouse::exists($id_warehouse)) { $this->errors[] = Tools::displayError('The selected warehouse is not valid.'); } $id_stock_mvt_reason = (int) Tools::getValue('id_stock_mvt_reason', 0); if ($id_stock_mvt_reason <= 0 || !StockMvtReason::exists($id_stock_mvt_reason)) { $this->errors[] = Tools::displayError('The reason is not valid.'); } $usable = Tools::getValue('usable', null); if (is_null($usable)) { $this->errors[] = Tools::displayError('You have to specify whether the product quantity is usable for sale on shops or not.'); } $usable = (bool) $usable; } if (Tools::isSubmit('addstock') && Tools::isSubmit('is_post')) { $price = str_replace(',', '.', Tools::getValue('price', 0)); if (!is_numeric($price)) { $this->errors[] = Tools::displayError('The product price is not valid.'); } $price = round((double) $price, 6); $id_currency = (int) Tools::getValue('id_currency', 0); if ($id_currency <= 0 || (!($result = Currency::getCurrency($id_currency)) || empty($result))) { $this->errors[] = Tools::displayError('The selected currency is not valid.'); } if (count($this->errors) == 0) { $warehouse = new Warehouse($id_warehouse); if ($id_currency != $warehouse->id_currency) { $price_converted_to_default_currency = Tools::convertPrice($price, $id_currency, false); $price = Tools::convertPrice($price_converted_to_default_currency, $warehouse->id_currency, true); } $stock_manager = StockManagerFactory::getManager(); if ($stock_manager->addProduct($id_product, $id_product_attribute, $warehouse, $quantity, $id_stock_mvt_reason, $price, $usable)) { $id_wpl = (int) WarehouseProductLocation::getIdByProductAndWarehouse($id_product, $id_product_attribute, $id_warehouse); if (!$id_wpl) { $wpl = new WarehouseProductLocation(); $wpl->id_product = (int) $id_product; $wpl->id_product_attribute = (int) $id_product_attribute; $wpl->id_warehouse = (int) $id_warehouse; $wpl->save(); } StockAvailable::synchronize($id_product); if (Tools::isSubmit('addstockAndStay')) { $redirect = self::$currentIndex . '&id_product=' . (int) $id_product; if ($id_product_attribute) { $redirect .= '&id_product_attribute=' . (int) $id_product_attribute; } $redirect .= '&addstock&token=' . $token; } Tools::redirectAdmin($redirect . '&conf=1'); } else { $this->errors[] = Tools::displayError('An error occurred. No stock was added.'); } } } if (Tools::isSubmit('removestock') && Tools::isSubmit('is_post')) { if (count($this->errors) == 0) { $warehouse = new Warehouse($id_warehouse); $stock_manager = StockManagerFactory::getManager(); $removed_products = $stock_manager->removeProduct($id_product, $id_product_attribute, $warehouse, $quantity, $id_stock_mvt_reason, $usable); if (count($removed_products) > 0) { StockAvailable::synchronize($id_product); Tools::redirectAdmin($redirect . '&conf=2'); } else { $physical_quantity_in_stock = (int) $stock_manager->getProductPhysicalQuantities($id_product, $id_product_attribute, array($warehouse->id), false); $usable_quantity_in_stock = (int) $stock_manager->getProductPhysicalQuantities($id_product, $id_product_attribute, array($warehouse->id), true); $not_usable_quantity = $physical_quantity_in_stock - $usable_quantity_in_stock; if ($usable_quantity_in_stock < $quantity) { $this->errors[] = sprintf(Tools::displayError('You don\'t have enough usable quantity. Cannot remove %d items out of %d.'), (int) $quantity, (int) $usable_quantity_in_stock); } elseif ($not_usable_quantity < $quantity) { $this->errors[] = sprintf(Tools::displayError('You don\'t have enough usable quantity. Cannot remove %d items out of %d.'), (int) $quantity, (int) $not_usable_quantity); } else { $this->errors[] = Tools::displayError('It is not possible to remove the specified quantity. Therefore no stock was removed.'); } } } } if (Tools::isSubmit('transferstock') && Tools::isSubmit('is_post')) { $id_warehouse_from = (int) Tools::getValue('id_warehouse_from', 0); if ($id_warehouse_from <= 0 || !Warehouse::exists($id_warehouse_from)) { $this->errors[] = Tools::displayError('The source warehouse is not valid.'); } $id_warehouse_to = (int) Tools::getValue('id_warehouse_to', 0); if ($id_warehouse_to <= 0 || !Warehouse::exists($id_warehouse_to)) { $this->errors[] = Tools::displayError('The destination warehouse is not valid.'); } $usable_from = Tools::getValue('usable_from', null); if (is_null($usable_from)) { $this->errors[] = Tools::displayError('You have to specify whether the product quantity in your source warehouse(s) is ready for sale or not.'); } $usable_from = (bool) $usable_from; $usable_to = Tools::getValue('usable_to', null); if (is_null($usable_to)) { $this->errors[] = Tools::displayError('You have to specify whether the product quantity in your destination warehouse(s) is ready for sale or not.'); } $usable_to = (bool) $usable_to; if (count($this->errors) == 0) { $stock_manager = StockManagerFactory::getManager(); $is_transfer = $stock_manager->transferBetweenWarehouses($id_product, $id_product_attribute, $quantity, $id_warehouse_from, $id_warehouse_to, $usable_from, $usable_to); StockAvailable::synchronize($id_product); if ($is_transfer) { Tools::redirectAdmin($redirect . '&conf=3'); } else { $this->errors[] = Tools::displayError('It is not possible to transfer the specified quantity. No stock was transferred.'); } } } }