Skip to content

Stock Move API Documentation

Overview

The Stock Move module (stock.move) is the core transactional model for all inventory movements in the system. Every physical movement of stock - whether incoming, outgoing, or internal transfer - is recorded as a stock move. This model provides the foundation for inventory tracking, costing, and reporting.

Key Capabilities: - Records all stock transactions with full traceability - Supports lot/serial number tracking - Handles multi-location inventory - Manages product costing (FIFO, average, standard) - Integrates with accounting for perpetual inventory costing - Links to source documents (sales orders, purchase orders, stock counts, etc.)


Model Information

Model Name: stock.move
Display Name: Stock Movement
Name Field: number

Features

  • ✅ Multi-company support
  • ✅ Auto-sync disabled (manual control)
  • ❌ No unique key constraint
  • ✅ Date indexing for performance
  • ✅ Reference field search enabled

Movement States

draft → pending (planned) → approved → done (completed)
                                     voided (cancelled)
State Description
draft Initial state, editable
pending Planned movement, not yet executed
approved Authorized but not yet completed
forecast Forecasted movement (planning)
done Completed and posted
voided Cancelled movement

Key Fields Reference

Core Transaction Fields

Field Type Required Description
product_id Many2One Product being moved
location_from_id Many2One Source location
location_to_id Many2One Destination location
qty Decimal Quantity moved (6 decimal precision)
uom_id Many2One Unit of measure
date DateTime Transaction date/time
state Selection Current status
journal_id Many2One Stock journal

Tracking & Reference Fields

Field Type Description
lot_id Many2One Lot/Serial number
container_id Many2One Container reference
ref Char External reference
number Char Movement number
picking_id Many2One Parent picking document
related_id Reference Source document (SO/PO/etc)
contact_id Many2One Customer/Supplier

Costing Fields

Field Type Description
cost_price_cur Decimal Cost in picking currency
cost_price Decimal Cost in company currency
cost_amount Decimal Total cost amount
cost_fixed Boolean Cost manually fixed
sale_price Decimal Sales price (for issues)
sale_amount Decimal Sales amount (computed)

Additional Fields

Field Type Description
qty2 Decimal Secondary quantity
uom2_id Many2One Secondary UOM
sequence Integer Line sequence
notes Text Movement notes
net_weight Decimal Net weight
gross_weight Decimal Gross weight
received Boolean Received flag
picked Boolean Picked flag

API Methods

1. Create Movement

Method: create(vals, context)

Creates a new stock movement record.

Parameters:

vals = {
    "product_id": 10,              # Required
    "location_from_id": 5,         # Required
    "location_to_id": 8,           # Required
    "qty": 100,                    # Required
    "uom_id": 1,                   # Required
    "date": "2025-10-15 10:00:00",
    "journal_id": 2,
    "lot_id": 15,                  # Optional
    "cost_price": 25.50,
}

context = {
    "picking_id": 123,             # Links to parent picking
    "container_no": "CONT-001",    # Auto-assign from container
    "lot_number": "LOT-2025-001",  # Auto-link to lot
}

Returns: int - New move ID

Behavior: - Inherits date/journal from picking if picking_id provided - Auto-links to lot if lot_number provided in context - Auto-updates stock balance - Triggers lot synchronization if applicable

Example:

move_id = get_model("stock.move").create({
    "product_id": 100,
    "location_from_id": 5,
    "location_to_id": 10,
    "qty": 50,
    "uom_id": 1,
    "date": "2025-10-15 10:00:00",
    "journal_id": 2,
    "cost_price": 25.50
})


2. State Transitions

2.1 Set to Done (Validate)

Method: set_done(ids, context)
Method: set_done_fast(ids, context) - Optimized bulk version

Completes and validates stock movements.

Parameters:

context = {
    "no_post": False,  # Skip accounting posting
}

Behavior: - Creates lots automatically if product requires - Creates supplier invoices if applicable - Validates stock availability - Posts accounting entries (perpetual costing) - Updates lot information - Updates product cost prices (FIFO/average) - Checks for duplicate unique lots - Updates stock balances

Example:

# Standard validation
get_model("stock.move").set_done([123, 124])

# Fast validation (bulk, less validation)
get_model("stock.move").set_done_fast([123, 124, 125])


2.2 Revert to Draft

Method: to_draft(ids, context)

Reverts completed movements back to draft state.

Behavior: - Checks stock lock date - Voids and deletes accounting entries - Updates state to draft - Updates related borrow requests (if applicable)

Example:

get_model("stock.move").to_draft([123])


2.3 Reverse Movement

Method: reverse(ids, context)

Creates opposite movement to reverse original transaction.

Behavior: - Must be in "done" state - Swaps from/to locations - Copies quantities and costs - Creates new movement with "Reverse:" prefix - Automatically validates reversed movement

Example:

get_model("stock.move").reverse([123])


3. Costing Methods

3.1 Set Standard Cost

Method: set_standard_cost(ids, context)

Applies standard cost from product master to movements.

Behavior: - Only applies if product.cost_method == "standard" - Raises error if product missing cost_price - Calculates cost_amount = price × qty

Example:

get_model("stock.move").set_standard_cost([123])


3.2 Post to Accounting

Method: post(ids, context)

Creates accounting journal entries for stock movements (perpetual costing).

Requirements: - Settings: stock_cost_mode == "perpetual" - All movements must have same date - Accounts must be configured on locations/products

Behavior: - Groups movements by account/track/contact - Creates debit/credit entries for location transfers - Posts journal entry automatically - Links move_id to movements

Example:

get_model("stock.move").post([123, 124])


3.3 Update Cost Prices

Method: update_cost_prices(ids, context)

Updates product cost prices based on movement costs.

Behavior: - Only for FIFO and average costing methods - Only for receipts (from non-internal to internal) - Converts cost to product UOM - Writes to product.cost_price

Example:

get_model("stock.move").update_cost_prices([123])


4. Lot Management

4.1 Create Lots

Method: create_lots(ids, context)

Auto-creates lots from movement data.

Behavior: - Creates lot if supp_lot_no provided but no lot_id - Sets lot.expiry_date from exp_date - Sets lot.mfg_date from mfg_date - Links movement to created lot

Example:

get_model("stock.move").create_lots([123])


4.2 Update Lots

Method: update_lots(ids, context)

Updates lot information from movement data.

Behavior: - Sets lot.received_date for incoming movements - Sets lot.product_id if missing - Only updates for movements with lot_id

Example:

get_model("stock.move").update_lots([123])


5. Helper Methods

5.1 Update Balance

Method: update_balance(ids, context)

Marks affected stock balances for recalculation.

Behavior: - Extracts product_id and lot_id from movements - Inserts records into stock_balance_update table - Triggers balance recomputation asynchronously

Example:

get_model("stock.move").update_balance([123, 124])


5.2 Check Periods

Method: check_periods(ids, context)

Validates movements against closed stock periods.

Behavior: - Checks if movement date falls in posted period - Raises exception if period is closed - Prevents backdating into closed periods

Example:

get_model("stock.move").check_periods([123])


5.3 View Stock Transaction

Method: view_stock_transaction(ids, context)

Navigates to parent transaction document.

Returns: Navigation to appropriate form view

Example:

result = get_model("stock.move").view_stock_transaction([123])
# Returns: {"next": {"name": "pick_in", "mode": "form", "active_id": 456}}


5.4 Convert UOM

Method: convert_uom(ids, context)

Converts movement quantity to product's base UOM.

Example:

get_model("stock.move").convert_uom([123])


6. Bulk Operations

6.1 Validate Transaction (API)

Method: validate_transaction(journal_code, product_id, qty, qty2, weight, lot_id, new_lot, context)

High-level API to create and validate movement in one call.

Parameters:

journal_code = "GR-IN"       # Stock journal code
product_id = 100
qty = 50
qty2 = None                  # Optional secondary qty
weight = 10.5                # For lot creation
lot_id = None                # Existing lot
new_lot = True               # Create new lot

context = {
    "lot_sequence_name": "lot_seq",
    "lot_sequence_params": {"prefix": "L"}
}

Returns:

{
    "picking_id": 789,
    "number": "GR-2025-00123",
    "move_id": 456
}

Example:

result = get_model("stock.move").validate_transaction(
    journal_code="GR-IN",
    product_id=100,
    qty=50,
    weight=10.5,
    new_lot=True
)
print("Created picking:", result["number"])


7. Computed Field Functions

get_qty_stock(ids, context)

Returns available stock quantity at source location

get_sale_amount(ids, context)

Calculates sales amount = qty × sale_price

get_validate_qty(ids, context)

Returns validated quantity from validate_lines

get_avail_lots(ids, context)

Returns list of available lots for product

get_alloc_cost_amount(ids, context)

Returns allocated landed costs (from posted landed cost documents)

get_date_agg(ids, context)

Returns aggregated date fields (day, week, month)

get_index(ids, context)

Returns line sequence within picking


Model Relationship Description
stock.picking Many2One Parent picking document
product Many2One Product being moved
stock.location Many2One From/To locations
stock.lot Many2One Lot/serial number
stock.container Many2One Container
stock.journal Many2One Stock journal
uom Many2One Unit of measure
account.move Many2One Journal entry (perpetual)
contact Many2One Customer/Supplier
sale.order Reference Source sales order
purchase.order Reference Source purchase order
account.invoice Many2One Related invoice

Common Use Cases

Use Case 1: Simple Stock Receipt

# Receive 100 units of product from supplier
move_id = get_model("stock.move").create({
    "product_id": 100,
    "location_from_id": 5,    # Supplier location
    "location_to_id": 10,     # Warehouse location
    "qty": 100,
    "uom_id": 1,
    "journal_id": 2,          # Goods receipt journal
    "cost_price": 25.50,
    "date": time.strftime("%Y-%m-%d %H:%M:%S")
})

# Validate
get_model("stock.move").set_done([move_id])

Use Case 2: Stock Issue with Lot Tracking

# Issue stock with specific lot
move_id = get_model("stock.move").create({
    "product_id": 100,
    "location_from_id": 10,   # Warehouse
    "location_to_id": 15,     # Customer location
    "qty": 50,
    "uom_id": 1,
    "journal_id": 3,          # Goods issue journal
    "lot_id": 25,             # Specific lot
    "date": time.strftime("%Y-%m-%d %H:%M:%S")
})

get_model("stock.move").set_done([move_id])

Use Case 3: Internal Transfer with Container

# Transfer stock between warehouses
move_id = get_model("stock.move").create({
    "product_id": 100,
    "location_from_id": 10,   # Warehouse A
    "location_to_id": 20,     # Warehouse B
    "qty": 75,
    "uom_id": 1,
    "journal_id": 4,          # Transfer journal
    "container_id": 30,       # Container ID
    "date": time.strftime("%Y-%m-%d %H:%M:%S")
})

get_model("stock.move").set_done([move_id])

Use Case 4: Bulk Movement Validation

# Create multiple movements
move_ids = []
for product_id in [100, 101, 102]:
    move_id = get_model("stock.move").create({
        "product_id": product_id,
        "location_from_id": 5,
        "location_to_id": 10,
        "qty": 100,
        "uom_id": 1,
        "journal_id": 2,
    })
    move_ids.append(move_id)

# Validate all at once (fast method)
get_model("stock.move").set_done_fast(move_ids)

Use Case 5: API Transaction

# Complete transaction in one API call
result = get_model("stock.move").validate_transaction(
    journal_code="GR-IN",
    product_id=100,
    qty=50,
    weight=10.5,
    new_lot=True,
    context={
        "lot_sequence_name": "lot_custom",
        "lot_sequence_params": {"prefix": "BATCH"}
    }
)

print(f"Created picking {result['number']} with move {result['move_id']}")

Database Constraints

None explicitly defined, but: - Foreign key constraints on all Many2One fields - State must be one of defined values - Qty precision limited to 6 decimals - Date field indexed for performance


Best Practices

1. Always Use Parent Picking

# Good: Create within picking context
pick_id = get_model("stock.picking").create({...})
move_id = get_model("stock.move").create({
    "picking_id": pick_id,
    ...
})

# Avoid: Standalone movements
move_id = get_model("stock.move").create({...})  # Missing context

2. Validate in Batches

# Good: Bulk validation
move_ids = [1, 2, 3, 4, 5]
get_model("stock.move").set_done_fast(move_ids)

# Avoid: Loop validation
for move_id in move_ids:
    get_model("stock.move").set_done([move_id])  # Slow

3. Handle Costing Correctly

# For receipts, always set cost
get_model("stock.move").create({
    "product_id": 100,
    "qty": 50,
    "cost_price": 25.50,  # ✅ Important for costing
    ...
})

# For issues, cost is calculated automatically
get_model("stock.move").create({
    "product_id": 100,
    "qty": 50,
    # cost_price calculated from stock
    ...
})

4. Check Stock Lock Dates

# Check settings before backdating
settings = get_model("settings").browse(1)
if settings.stock_lock_date:
    if move_date < settings.stock_lock_date:
        raise Exception("Cannot create movement before lock date")

Troubleshooting

"Before stock lock date"

Cause: Attempting to create/modify movement before configured lock date
Solution: Check settings.stock_lock_date, use current date or update settings

"Invalid product type"

Cause: Product type is not stock/consumable/bundle
Solution: Verify product.type is valid for stock movements

"Missing cost price in product"

Cause: Standard costing enabled but product.cost_price is null
Solution: Set cost price on product master data

"Missing input/output account"

Cause: Location or product missing accounting configuration
Solution: Configure accounts on locations and product categories

"Source/Destination location is a view location"

Cause: Trying to move to/from a view-only location
Solution: Use child locations, not parent view locations


Performance Tips

1. Use Fast Methods for Bulk Operations

# Instead of this:
for move_id in large_list:
    get_model("stock.move").set_done([move_id])

# Do this:
get_model("stock.move").set_done_fast(large_list)

2. Disable Auto-Posting When Needed

# Skip accounting posts for bulk operations
get_model("stock.move").set_done(ids, context={"no_post": True})

# Post manually after all movements
get_model("stock.move").post(ids)

3. Batch Balance Updates

Stock balances are updated asynchronously via stock_balance_update table. The system automatically batches these updates.


Security Considerations

Access Control

  • Multi-company isolation via company_id
  • User tracking via user_id
  • Lock dates prevent backdating

Data Integrity

  • Balance updates ensure consistency
  • Accounting integration for audit trail
  • State workflow prevents unauthorized changes

Version History

Last Updated: October 2025
Model Version: stock_move.py
Framework: Netforce


Additional Resources

  • Stock Picking Documentation: appendix-stock-picking.md
  • Stock Balance: stock.balance model
  • Product Costing: stock.compute.cost model
  • Stock Journals: stock.journal configuration

This documentation is generated for developer onboarding and reference purposes.