Skip to content

Odoo Negative Stock Quantity — 'Not Enough Stock' and Negative Inventory Fix

DeployMonkey Team · March 24, 2026 9 min read

Negative Stock Errors in Odoo

When processing deliveries or transfers, you encounter:

UserError: It is not possible to unreserve more products than you have in stock.

Warning: Not enough stock available for Product A.
Required: 10.0 | Available: 3.0

# Or in the inventory report:
Product A: On Hand = -5.0 (should never be negative)

Why Negative Stock Occurs

Negative inventory happens when more products are shipped out than exist in the system. This is caused by:

  • Deliveries processed without prior receipt confirmation
  • Manual stock adjustments that reduce quantity below zero
  • Inventory counts that mismatch with actual deliveries
  • The "Allow Negative Stock" setting enabled
  • Backdated stock moves processed after current transactions

Fix 1: Check and Disable Negative Stock Setting

# Odoo can be configured to allow or block negative stock

# Check current setting:
# Inventory > Configuration > Settings
# Under "Operations" section:
# "Allow Negative Stock" — should be UNCHECKED for most businesses

# Per product override:
# Product > Inventory tab > "Allow Negative Stock"
# Uncheck if enabled

# Per warehouse:
# Inventory > Configuration > Warehouses
# Check the delivery steps configuration

Fix 2: Correct Existing Negative Stock

# Option 1: Create an inventory adjustment
# Inventory > Operations > Physical Inventory
# Find the product with negative stock
# Set the "Counted Quantity" to the actual physical count (usually 0)
# Apply — this creates an adjustment move

# Option 2: Create a receipt to add missing stock
# Inventory > Operations > Receipts > Create
# Add the product with the quantity needed to reach 0
# This is appropriate if goods were physically received but not recorded

# Option 3: Via database (emergency only)
sudo -u postgres psql -d mydb -c "
  SELECT pp.id, pt.name, sq.quantity, sq.location_id, sl.complete_name
  FROM stock_quant sq
  JOIN product_product pp ON sq.product_id = pp.id
  JOIN product_template pt ON pp.product_tmpl_id = pt.id
  JOIN stock_location sl ON sq.location_id = sl.id
  WHERE sq.quantity < 0
  ORDER BY sq.quantity;
"

Fix 3: Find the Root Cause

# Trace the stock moves that caused negative stock:
sudo -u postgres psql -d mydb -c "
  SELECT sm.id, sm.date, sm.reference, sm.product_qty,
    sl_src.complete_name as source, sl_dst.complete_name as destination,
    sm.state
  FROM stock_move sm
  JOIN stock_location sl_src ON sm.location_id = sl_src.id
  JOIN stock_location sl_dst ON sm.location_dest_id = sl_dst.id
  WHERE sm.product_id = PRODUCT_ID
  AND sm.state = 'done'
  ORDER BY sm.date DESC
  LIMIT 20;
"

# Look for:
# - Outgoing moves (to customer) without prior incoming moves
# - Backdated moves that were processed out of sequence
# - Duplicate delivery orders for the same sale

Fix 4: Prevent Future Negative Stock

# 1. Use proper warehouse operations:
# - 2-step or 3-step delivery forces picking before shipping
# - Reservations prevent shipping more than available

# 2. Configure product routes correctly:
# Product > Inventory tab > Routes
# Ensure proper replenishment rules are set

# 3. Use the "Check Availability" button on delivery orders
# This reserves stock before allowing validation

# 4. Set reordering rules:
# Inventory > Configuration > Reordering Rules
# Set minimum stock levels to trigger automated purchase

Fix 5: Quant Reconciliation

# Stock quants track current quantities per location
# Sometimes quants get out of sync with stock moves

# Check quant vs move totals:
sudo -u postgres psql -d mydb -c "
  -- Current quant quantity
  SELECT SUM(quantity) as quant_total
  FROM stock_quant
  WHERE product_id = PRODUCT_ID
  AND location_id IN (SELECT id FROM stock_location WHERE usage = 'internal');
"

sudo -u postgres psql -d mydb -c "
  -- Calculated from moves
  SELECT
    SUM(CASE WHEN sl_dst.usage = 'internal' THEN sm.product_qty ELSE 0 END) -
    SUM(CASE WHEN sl_src.usage = 'internal' THEN sm.product_qty ELSE 0 END) as move_total
  FROM stock_move sm
  JOIN stock_location sl_src ON sm.location_id = sl_src.id
  JOIN stock_location sl_dst ON sm.location_dest_id = sl_dst.id
  WHERE sm.product_id = PRODUCT_ID
  AND sm.state = 'done';
"

# If they differ, run the quant fix:
# Inventory > Operations > Physical Inventory
# Count and adjust to match physical reality

Prevention

DeployMonkey's AI agent monitors stock levels and configures proper warehouse operations. Negative stock detection runs proactively, alerting before inventory discrepancies affect business operations.