Skip to content

Odoo Stock Reservation Not Clearing — 'Reserved Quantity' Stuck Fix

DeployMonkey Team · March 24, 2026 9 min read

Stock Reservation Issues in Odoo

Stock reservations hold inventory for specific orders. When they get stuck:

Issues:
- Product shows 100 On Hand but 0 Available (all reserved)
- Cancelled order still shows reserved quantity
- Cannot reserve stock for new orders despite physical availability
- "Unreserve" button does nothing
- Reserved quantity exceeds on-hand quantity

Understanding Reservation in Odoo

# On Hand = total physical quantity in warehouse
# Reserved = quantity allocated to pending operations
# Available = On Hand - Reserved (what can be allocated to new orders)

# Reservations are tracked in stock.quant:
sudo -u postgres psql -d mydb -c "
  SELECT sq.product_id, pt.name, sq.quantity, sq.reserved_quantity,
    (sq.quantity - sq.reserved_quantity) as available,
    sl.complete_name as location
  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.product_id = PRODUCT_ID
  AND sl.usage = 'internal';
"

Fix 1: Find What Is Reserving the Stock

# Find all active stock moves reserving this product:
sudo -u postgres psql -d mydb -c "
  SELECT sm.id, sm.reference, sm.origin, sm.state,
    sm.product_uom_qty, sm.quantity as reserved,
    sp.name as picking_name, sp.state as picking_state
  FROM stock_move sm
  LEFT JOIN stock_picking sp ON sm.picking_id = sp.id
  WHERE sm.product_id = PRODUCT_ID
  AND sm.state IN ('assigned', 'partially_available', 'waiting', 'confirmed')
  ORDER BY sm.date;
"

# This shows all pending operations holding reservations
# Look for cancelled or obsolete orders that still hold stock

Fix 2: Unreserve from Cancelled Orders

# If an order was cancelled but reservations remain:

# Via UI:
# 1. Inventory > Operations > Transfers
# 2. Filter by state = "Ready" or "Waiting"
# 3. Find the stuck transfer
# 4. Click "Unreserve" button
# 5. Then cancel the transfer

# If Unreserve button doesn't work:
# The move may be in an inconsistent state
# Reset via database:
sudo -u postgres psql -d mydb -c "
  -- Find stuck moves
  SELECT id, reference, state, product_uom_qty, quantity
  FROM stock_move
  WHERE product_id = PRODUCT_ID
  AND state = 'assigned'
  AND picking_id IN (
    SELECT id FROM stock_picking WHERE state = 'cancel'
  );
"

# Set stuck moves to cancelled:
sudo -u postgres psql -d mydb -c "
  UPDATE stock_move SET state = 'cancel', quantity = 0
  WHERE id IN (STUCK_MOVE_IDS);
  
  -- Update the move lines too
  UPDATE stock_move_line SET state = 'cancel', quantity = 0
  WHERE move_id IN (STUCK_MOVE_IDS);
"

Fix 3: Recalculate Quant Reservations

# If reserved_quantity on quants is wrong:
# The reservation count should equal the sum of all active move line reservations

sudo -u postgres psql -d mydb -c "
  -- Calculate what reserved_quantity SHOULD be
  SELECT sq.id, sq.quantity, sq.reserved_quantity,
    COALESCE(SUM(sml.quantity), 0) as actual_reserved
  FROM stock_quant sq
  LEFT JOIN stock_move_line sml ON (
    sml.product_id = sq.product_id
    AND sml.location_id = sq.location_id
    AND sml.lot_id IS NOT DISTINCT FROM sq.lot_id
    AND sml.state IN ('assigned', 'partially_available')
  )
  WHERE sq.product_id = PRODUCT_ID
  AND sq.location_id IN (SELECT id FROM stock_location WHERE usage = 'internal')
  GROUP BY sq.id;
"

# Fix the quant reservation count:
sudo -u postgres psql -d mydb -c "
  UPDATE stock_quant sq SET reserved_quantity = (
    SELECT COALESCE(SUM(sml.quantity), 0)
    FROM stock_move_line sml
    WHERE sml.product_id = sq.product_id
    AND sml.location_id = sq.location_id
    AND sml.lot_id IS NOT DISTINCT FROM sq.lot_id
    AND sml.state IN ('assigned', 'partially_available')
  )
  WHERE sq.product_id = PRODUCT_ID;
"

Fix 4: Reservation Method Settings

# Odoo offers different reservation methods:
# Product > Inventory tab > Reservation Method:
# - "At Confirmation" — reserves when order is confirmed
# - "Manually" — reserves only when you click "Check Availability"
# - "Before scheduled date" — reserves X days before delivery

# If stock is reserved too early, change to Manual or Before scheduled date
# This frees stock for more urgent orders

Fix 5: Forced Unreserve via Odoo Shell

# For stubborn reservations, use the ORM:
# odoo shell -d mydb

moves = env['stock.move'].search([
    ('product_id', '=', PRODUCT_ID),
    ('state', '=', 'assigned'),
])
for move in moves:
    print(f"{move.reference}: {move.product_uom_qty} reserved")
    # Unreserve specific moves:
    # move._do_unreserve()

# Commit changes
env.cr.commit()

Prevention

DeployMonkey's AI agent monitors reservation health and detects stuck reservations automatically. The agent validates reservation consistency and alerts when available stock drops unexpectedly due to orphaned reservations.