Jump to content

Error You don't have enough usable quantity.


Recommended Posts

Hi Guys,

 

Ive looked around but can find a solution to a stock management problem. I have started to add stock to the warehouse I created in Prestashop. While adding stock we sold an item. Now when I go to remove that item I get an error "You don't have enough usable quantity. Cannot remove 1 out of 0." I had 2 of these items in stock and want to remove 1 leaving 1 left. Why would it say I cant remove 1 out of 0 items.

 

Any help or advice would be useful

 

Thanks

  • Like 1
Link to comment
Share on other sites

  • 4 months later...
  • 3 weeks later...

Just upgraded to 1.6.0.11 and I still have this problem. Stock Management screen shows qty = 1, instant stock status shows physical, usable, real qty all = 1. But when I try to remove stock, I get the error "You don't have enough usable quantity. Cannot remove 1 out of 0."

Anyone else having this issue? If so I'll raise a bug

Link to comment
Share on other sites

  • 3 weeks later...
  • 1 month later...

Im trying to see if i can find the code bug

 

I found the place in code where something is wrong

 

file: StockManager.php

			// check quantity if we want to decrement unusable quantity
			if (!$is_usable){
				$quantity_in_stock = $physical_quantity_in_stock - $usable_quantity_in_stock;
				PrestaShopLogger::addLog('removeProduct physical_quantity_in_stock='.$physical_quantity_in_stock.' usable_quantity_in_stock='.$usable_quantity_in_stock, 1, null, null, null, true);
			} else {
				$quantity_in_stock = $usable_quantity_in_stock;
			}

As you can see i put PrestaShopLogger in there and that reveals that 
"removeProduct physical_quantity_in_stock=8 usable_quantity_in_stock=8"
when im looking at stock prestahop tells me that i have 6 usable and 2 unusable.
so i guess the correct should be:
"removeProduct physical_quantity_in_stock=8 usable_quantity_in_stock=6"

Edited by michaelhjulskov (see edit history)
  • Like 1
Link to comment
Share on other sites

  • 1 month later...
  • 3 months later...

Hi

 

I cant say if this is a bug in 1.6.0.9 but in my 1.6.0.11 i just had to import the quantity data into the  stock_mvt table.

 

The problem was that I only imported data into tables stock and stock_available but not into stock_mvt. (I was upgrading my shop from 1.4.7 to 1.6.0.11, and imported all of the products etc), and if there is no "start" data in the stock_mvt table then the advanced stock system thinks there is 0 on stock when removing from stock, and of course you cant remove something you dont have :)

 

Hope that explains it, just try with one product and see if that works.

 

insert into YOURPREFIX_stock_mvt (id_stock, id_order, id_supply_order, id_stock_mvt_reason, id_employee, employee_lastname, employee_firstname,physical_quantity, date_add, sign, price_te, referer)
 
values (id_stock, 0, 0, 1, 1, 'employee lastname', 'employee firstname', quantity, now(),1,price,0)
Edited by ragnabt (see edit history)
Link to comment
Share on other sites

  • 2 months later...

 

Hi

 

I cant say if this is a bug in 1.6.0.9 but in my 1.6.0.11 i just had to import the quantity data into the  stock_mvt table.

 

The problem was that I only imported data into tables stock and stock_available but not into stock_mvt. (I was upgrading my shop from 1.4.7 to 1.6.0.11, and imported all of the products etc), and if there is no "start" data in the stock_mvt table then the advanced stock system thinks there is 0 on stock when removing from stock, and of course you cant remove something you dont have :)

 

Hope that explains it, just try with one product and see if that works.

 

insert into YOURPREFIX_stock_mvt (id_stock, id_order, id_supply_order, id_stock_mvt_reason, id_employee, employee_lastname, employee_firstname,physical_quantity, date_add, sign, price_te, referer)
 
values (id_stock, 0, 0, 1, 1, 'employee lastname', 'employee firstname', quantity, now(),1,price,0)

 

Hi there

Is this a solution that actually works or is it a temp fix?

Link to comment
Share on other sites

I think I might have found the bug after spending many hours envestigating

 

PS 1.6.0.9

 

replaced "break;" with "continue;" 

 

I made contrib here

 

I made this override

 

override/classes/stock/StockManager.php

<?php
/*
* override Michael Hjulskov
 */
class StockManager extends StockManagerCore
{
	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)
									continue; // Michael Hjulskov - here it was originally: 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;
	}
}
Edited by fransjaeger (see edit history)
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...