Skip to content

Stock Count Add Documentation

Overview

The Stock Count Add module (stock.count.add) is a transient wizard model that provides a user-friendly interface for batch-adding count lines to stock counts. It simplifies the process of populating count sessions by offering filtering options (product, category, UOM, lot type) and configuration choices for initializing quantities and prices. This is the base wizard used by both desktop and mobile count interfaces.


Model Information

Model Name: stock.count.add
Display Name: Stock Count Add
Transient: ✅ Yes (temporary wizard data)

Features

  • ✅ Transient model - No permanent database storage
  • ✅ Wizard interface - Simplified batch configuration
  • ✅ Multiple filters - Product, category, UOM, lot type
  • ✅ Quantity/price options - Configure initialization behavior
  • ✅ Used by variants - Base for mobile.add and custom.add

Understanding the Add Lines Wizard

Purpose

The wizard solves the problem of manually creating count lines for many products:

Without Wizard:

# Manual: Create 100 lines individually
for product in products:
    get_model("stock.count.line").create({
        "count_id": count_id,
        "product_id": product.id,
        "prev_qty": get_balance(...),
        "new_qty": ...,
        # ... tedious for each product
    })

With Wizard:

# Wizard: Configure once, create many
wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "categ_id": category_id,
    "qty_type": "previous",
    "price_type": "product"
})
get_model("stock.count.add").add_lines([wizard_id])
# Creates all matching lines automatically


Key Fields Reference

Required Fields

Field Type Description
stock_count_id Many2One Target stock count for line addition

Filter Fields (Optional)

Field Type Description
product_id Many2One Specific product to add (if set, only this product)
categ_id Many2One Product category filter (all products in category)
sale_invoice_uom_id Many2One Unit of measure filter (products sold in this UOM)
lot_type Selection Lot tracking filter (with_lot / without_lot / all)

Configuration Fields (Optional)

Field Type Description
qty_type Selection How to initialize new quantities
price_type Selection How to initialize cost prices

Field Options Explained

Lot Type Options

Value Label Behavior
with_lot With Lot Only add products that require lot/serial tracking
without_lot Without Lot Only add products without lot/serial tracking
(null) All Add both lot-tracked and non-tracked products

Example:

# Count only serialized electronics
wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "categ_id": electronics_categ_id,
    "lot_type": "with_lot"  # Only serialized items
})


Quantity Type Options

Value Label Behavior
previous Copy Previous Qty Sets new_qty = prev_qty (verification mode)
reset Set Qty To Zero Sets new_qty = 0 (blind count mode)

When to use: - previous: For verification counts where users confirm/adjust expected quantities - reset: For blind counts where users only enter what they physically see

Example:

# Blind count (start at zero)
wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "qty_type": "reset"  # All quantities start at 0
})


Price Type Options

Value Label Behavior
previous Copy Previous Cost Price Uses current stock balance cost
product Copy Cost Price From Product Uses product master cost_price
reset Set Cost Price To Zero Sets unit_price = 0

Recommendation: Use previous for regular counts, product for fresh inventory valuation.


API Methods

add_lines(ids, context)

Executes the wizard to add filtered lines to the stock count.

Parameters: - ids (list): Wizard record ID (transient) - context (dict): Additional context options

Behavior: 1. Reads wizard configuration 2. Builds filter context 3. Calls stock.count.add_lines() with configured parameters 4. Returns navigation to count form

Returns: Dictionary with navigation to count form

Example:

# Create and execute wizard
wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "categ_id": category_id,
    "lot_type": "with_lot",
    "qty_type": "previous",
    "price_type": "product"
})

result = get_model("stock.count.add").add_lines([wizard_id])
# result = {
#     "next": {
#         "name": "stock_count",
#         "mode": "form",
#         "active_id": count_id
#     }
# }


Common Use Cases

Use Case 1: Add Category with Verification

# Add all products in category with expected quantities

# Step 1: Create stock count
count_id = get_model("stock.count").create({
    "location_id": warehouse_id,
    "date": time.strftime("%Y-%m-%d %H:%M:%S"),
    "memo": "Electronics Category Count"
})

# Step 2: Create wizard for electronics
wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "categ_id": electronics_category_id,
    "qty_type": "previous",  # Show expected qty
    "price_type": "previous"  # Use current costs
})

# Step 3: Execute
result = get_model("stock.count.add").add_lines([wizard_id])

# Lines added with expected quantities for verification

Use Case 2: Blind Count Setup

# Setup for blind counting (no expected quantities shown)

wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "categ_id": warehouse_supplies_id,
    "qty_type": "reset",  # Start at zero (blind)
    "price_type": "product"
})

get_model("stock.count.add").add_lines([wizard_id])

# Result: All lines have new_qty=0
# Users count from scratch without bias

Use Case 3: Single Product Count

# Count all lots of a specific product

# High-value item with multiple serial numbers
product_id = 123  # Laptop model

wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "product_id": product_id,  # Specific product only
    "lot_type": "with_lot",     # Has serial numbers
    "qty_type": "previous",
    "price_type": "product"
})

get_model("stock.count.add").add_lines([wizard_id])

# Result: Lines created for all laptop serial numbers

Use Case 4: UOM-Filtered Count

# Count items sold by specific unit (e.g., boxes)

# Get "Box" UOM
box_uom_id = get_model("uom").search([["name", "=", "Box"]])[0]

wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "sale_invoice_uom_id": box_uom_id,  # Only items sold by box
    "qty_type": "previous",
    "price_type": "previous"
})

get_model("stock.count.add").add_lines([wizard_id])

# Useful for warehouse organized by UOM type

Use Case 5: Multiple Filter Combination

# Complex filtering: Category + Lot Type + Price Source

wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "categ_id": medical_equipment_id,  # Filter 1: Category
    "lot_type": "with_lot",             # Filter 2: Only serialized
    "qty_type": "reset",                # Blind count
    "price_type": "product"             # Fresh product costs
})

get_model("stock.count.add").add_lines([wizard_id])

# Result: Only serialized medical equipment with fresh costs

Best Practices

1. Choose Appropriate Qty Type

# Good: Match qty_type to counting method

# For VERIFICATION (users check expected):
wizard_config = {
    "qty_type": "previous",  # Show what system expects
    "price_type": "previous"
}

# For BLIND COUNT (no bias):
wizard_config = {
    "qty_type": "reset",  # Start at zero
    "price_type": "product"
}

# Bad: Using previous qty for blind count
# (Defeats purpose of blind counting)

2. Filter to Manageable Size

# Good: Use filters to create focused counts

# Count by section/category
wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "categ_id": section_a_category_id,  # Narrow scope
    "qty_type": "previous"
})

# Bad: No filters for large warehouse
wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id
    # No filters - adds ALL products (could be thousands!)
})

3. Execute Wizard Immediately

# Good: Create and execute in same session

wizard_id = get_model("stock.count.add").create({...})
result = get_model("stock.count.add").add_lines([wizard_id])
# Wizard data cleaned up automatically

# Bad: Storing wizard_id for later use
# (Transient data may expire)

4. Validate Count State Before Using

# Good: Check count is editable

count = get_model("stock.count").browse(count_id)
if count.state != "draft":
    raise Exception("Cannot add lines to completed count")

wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    # ...
})

# Bad: Not checking count state
# (May fail if count already validated)

5. Provide User Feedback

# Good: Show what was added

wizard_id = get_model("stock.count.add").create({...})
result = get_model("stock.count.add").add_lines([wizard_id])

# Check result
count = get_model("stock.count").browse(count_id)
print(f"Added {len(count.lines)} lines to count")

# Guide user to next step
print(f"Ready to count {count.num_lines} items")

Wizard Variants

This base wizard has three variants used in different contexts:

Wizard Model Context Usage
stock.count.add Desktop Base desktop interface
stock.count.mobile.add Mobile Mobile/tablet interface
custom.stock.count.add Custom Custom counting workflows

All three share identical: - Field structure - Functionality - Behavior

Only difference: Registration name for different UI contexts


Performance Considerations

Wizard Efficiency

# Transient wizards are lightweight
# No database storage overhead
# Automatic cleanup after use

# Good: One wizard per action
wizard_id = create_wizard(...)
execute_wizard(wizard_id)
# Done - no cleanup needed

# Bad: Creating persistent records for temporary config
# (Use transient for temporary data)

Large Batch Processing

# When adding many lines, wizard delegates to stock.count.add_lines()
# That method handles:
# - Background job support
# - Progress tracking
# - Efficient batch creation

# Configure for large counts:
wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    "categ_id": large_category_id
})

# Execute (may take time for large datasets)
result = get_model("stock.count.add").add_lines([wizard_id], context={
    "job_id": job_id  # For progress tracking
})

Troubleshooting

"stock_count_id required"

Cause: Wizard created without target count.
Solution: Always specify stock_count_id:

wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,  # Required!
    "categ_id": category_id
})

No Lines Added

Cause: Filters too restrictive, no products match criteria.
Solution: Verify products exist matching filters:

# Check products in category
products = get_model("product").search([
    ["categ_id", "=", category_id],
    ["type", "=", "stock"]
])
print(f"Products in category: {len(products)}")

# Check stock balances exist
balances = get_model("stock.balance").search([
    ["location_id", "=", location_id],
    ["product_id.categ_id", "=", category_id]
])
print(f"Stock balances found: {len(balances)}")

Wizard Execution Error

Cause: Count in wrong state or missing required data.
Solution: Validate before executing:

count = get_model("stock.count").browse(count_id)

# Check state
if count.state != "draft":
    raise Exception("Count must be in draft state")

# Check location
if not count.location_id:
    raise Exception("Count has no location")

# Now safe to use wizard
wizard_id = get_model("stock.count.add").create({
    "stock_count_id": count_id,
    # ...
})


Integration with UI

Desktop Interface

# Typical desktop workflow:
# 1. User opens stock count form
# 2. Clicks "Add Lines" button
# 3. Wizard dialog opens
# 4. User selects filters and options
# 5. Clicks "Add" button
# 6. Wizard executes, returns to count form
# 7. Lines appear in count

Programmatic Usage

# For batch operations or automation:

def auto_populate_count(count_id, category_id):
    """Automatically populate count with category"""

    wizard_id = get_model("stock.count.add").create({
        "stock_count_id": count_id,
        "categ_id": category_id,
        "qty_type": "previous",
        "price_type": "product"
    })

    result = get_model("stock.count.add").add_lines([wizard_id])

    # Verify lines added
    count = get_model("stock.count").browse(count_id)
    return len(count.lines)

# Usage
lines_added = auto_populate_count(count_id, electronics_id)
print(f"Auto-populated {lines_added} lines")

Model Relationship Description
stock.count Many2One Target count
product Many2One Product filter
product.categ Many2One Category filter
uom Many2One UOM filter
stock.count.line Created Lines added by wizard
stock.balance Referenced Source of quantities

Version History

Last Updated: 2024-10-27
Model Version: stock_count_add.py
Framework: Netforce


Additional Resources

  • Stock Count Documentation: stock.count
  • Stock Count Line Documentation: stock.count.line
  • Stock Count Mobile Add: stock.count.mobile.add (identical variant)
  • Custom Stock Count Add: custom.stock.count.add (identical variant)
  • Transient Models Guide

This documentation is generated for developer onboarding and reference purposes.