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¶
| 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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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
Related Models¶
| 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.balancemodel - Product Costing:
stock.compute.costmodel - Stock Journals:
stock.journalconfiguration
This documentation is generated for developer onboarding and reference purposes.