Skip to content

Stock Count Mobile Line Documentation

Overview

The Stock Count Mobile Line module (stock.count.mobile.line) represents individual count line records used in mobile counting interfaces. This is the same model as stock.count.line but registered under a mobile-specific context. Each line captures product count details including previous system quantities, new physical counts, cost information, and lot/serial numbers—all optimized for mobile device display and interaction.


Model Information

Model Name: stock.count.line (shared with desktop)
Display Name: Stock Count Line (Mobile Context)
Key Fields: None (detail records)

Features

  • ❌ No audit logging (parent count is audited)
  • ❌ No multi-company (inherits from parent)
  • ✅ Cascade delete (removed when count deleted)
  • Mobile-optimized display fields
  • ✅ Computed cost amounts
  • ✅ Lot/serial tracking support
  • Touch-friendly data entry

Mobile-Specific Considerations

Display Optimization for Mobile

Mobile interfaces require: - Minimal fields shown: Only essential information visible - Large touch targets: Easy tap/select on small screens - Progressive disclosure: Details shown only when needed - Offline support: Lines cached for offline editing

Key Differences: Mobile vs Desktop Line View

Aspect Desktop View Mobile View
Fields Shown All fields visible Essential fields only
Layout Multi-column table Single-column list
Edit Mode Inline editing Full-screen form
Sorting Complex multi-field Simple single field
Actions Right-click menus Swipe gestures

Key Fields Reference

Essential Mobile Fields (Priority Display)

Field Type Mobile Priority Description
product_id Many2One ⭐⭐⭐ Product name/code - PRIMARY
new_qty Decimal ⭐⭐⭐ Physical count - EDITABLE
prev_qty Decimal ⭐⭐ System quantity - READ ONLY
lot_id Many2One ⭐⭐ Serial/lot number (if applicable)
uom_id Many2One Unit of measure

Secondary Mobile Fields (Detail View)

Field Type Description
bin_location Char Warehouse location/bin
prev_cost_price Decimal Previous cost per unit (computed)
unit_price Decimal New cost per unit
prev_cost_amount Decimal Previous total cost
new_cost_amount Decimal New total cost (computed)

Supporting Fields

Field Type Description
count_id Many2One Parent count (cascade delete)
lot_weight Decimal Weight from lot (if tracked)
cyclecount_id Many2One Associated cycle count program

Mobile Display Patterns

Pattern 1: List View (Scan Mode)

┌─────────────────────────┐
│ PROD-123               ⭐│ ← Current item (just scanned)
│ Laptop Computer         │
│ Qty: 5 (Expected: 8)    │ ← Variance highlight
├─────────────────────────┤
│ PROD-124                │
│ Desktop Monitor         │
│ Qty: 12 (Expected: 12)  │ ← Match (green)
├─────────────────────────┤
│ PROD-125                │
│ Keyboard - USB          │
│ Qty: 0 (Expected: 25)   │ ← Large variance (red)
└─────────────────────────┘
  [Scan Next Item]

Pattern 2: Detail View (Edit Mode)

┌─────────────────────────┐
│ ← Back   PROD-123   ⋮   │
├─────────────────────────┤
│ Laptop Computer         │
│ Model: ThinkPad X1      │
│                         │
│ Bin Location: A-03-15   │
│ Lot: LOT-2024-001       │
│                         │
│ System Qty: 8 units     │
│                         │
│ Physical Count:         │
│ ┌─────────────────────┐ │
│ │        5            │ │ ← Large number pad
│ └─────────────────────┘ │
│                         │
│ Variance: -3 units      │ ← Calculated automatically
│                         │
│  [ SAVE ]  [ CANCEL ]   │
└─────────────────────────┘

Pattern 3: Quick Entry (Barcode Focus)

┌─────────────────────────┐
│  Items: 47    Total: 234│
├─────────────────────────┤
│                         │
│  Last Scanned:          │
│  PROD-123 → Qty: 5      │ ← Instant feedback
│                         │
│  [  SCAN NEXT  ]        │ ← Primary action
│                         │
│  Quick Actions:         │
│  [+1] [-1] [Edit]       │ ← Touch buttons
│                         │
└─────────────────────────┘

Computed Fields Functions

get_prev_cost_price(ids, context)

Calculates previous average cost per unit for mobile display.

Mobile Usage:

# Display in detail view
line = get_model("stock.count.line").browse(line_id)
mobile_display = {
    "product": line.product_id.name,
    "prev_qty": line.prev_qty,
    "prev_cost": f"${line.prev_cost_price:.2f}/unit"  # Computed
}

get_new_cost_amount(ids, context)

Calculates new total cost for mobile summary.

Mobile Usage:

# Show in count summary
total_value = sum(line.new_cost_amount for line in count.lines)
mobile_summary = {
    "items": len(count.lines),
    "total_value": f"${total_value:,.2f}"
}


Mobile API Patterns

1. Get Line for Mobile Display

def get_mobile_line_data(line_id):
    """Format line data for mobile display"""
    line = get_model("stock.count.line").browse(line_id)

    # Calculate variance
    variance = (line.new_qty or 0) - (line.prev_qty or 0)
    variance_pct = (variance / line.prev_qty * 100) if line.prev_qty else 0

    return {
        "id": line.id,
        "product_code": line.product_id.code,
        "product_name": line.product_id.name,
        "bin_location": line.bin_location or "N/A",
        "lot_number": line.lot_id.number if line.lot_id else None,
        "system_qty": float(line.prev_qty or 0),
        "counted_qty": float(line.new_qty or 0),
        "variance": float(variance),
        "variance_pct": f"{variance_pct:+.1f}%",
        "status": "match" if variance == 0 else "variance",
        "uom": line.uom_id.name
    }

# Mobile API endpoint
mobile_data = get_mobile_line_data(line_id)

2. Update Line from Mobile

def update_mobile_line(line_id, new_qty):
    """Update count line from mobile input"""
    line = get_model("stock.count.line").browse(line_id)

    # Validate count state
    if line.count_id.state != "draft":
        return {
            "success": False,
            "error": "Count is locked"
        }

    # Update quantity
    line.write({"new_qty": new_qty})

    # Return updated data for mobile
    return {
        "success": True,
        "line": get_mobile_line_data(line_id),
        "count_total": sum(l.new_qty for l in line.count_id.lines)
    }

# Mobile app calls this
result = update_mobile_line(line_id, 5.0)

3. Batch Load for Mobile

def get_mobile_count_lines(count_id, limit=50, offset=0):
    """
    Load count lines for mobile (paginated)
    """
    lines = get_model("stock.count.line").search_browse([
        ["count_id", "=", count_id]
    ], limit=limit, offset=offset, order="id")

    return {
        "lines": [get_mobile_line_data(l.id) for l in lines],
        "total": len(get_model("stock.count.line").search([
            ["count_id", "=", count_id]
        ])),
        "limit": limit,
        "offset": offset
    }

# Mobile scrolling loads more
page1 = get_mobile_count_lines(count_id, limit=50, offset=0)
page2 = get_mobile_count_lines(count_id, limit=50, offset=50)

Mobile-Optimized Use Cases

Use Case 1: Mobile Line Edit

# User taps line in mobile app to edit

def mobile_edit_line_handler(line_id):
    """Handle mobile line tap"""

    # Load line data
    line = get_model("stock.count.line").browse(line_id)

    # Format for mobile editor
    mobile_form = {
        "title": line.product_id.code,
        "fields": [
            {
                "label": "Product",
                "value": line.product_id.name,
                "readonly": True
            },
            {
                "label": "Bin Location",
                "value": line.bin_location,
                "readonly": True
            },
            {
                "label": "Expected Qty",
                "value": float(line.prev_qty),
                "readonly": True,
                "hint": f"{line.uom_id.name}"
            },
            {
                "label": "Physical Count",
                "value": float(line.new_qty),
                "type": "number",
                "editable": True,
                "focus": True,  # Auto-focus for quick entry
                "keyboard": "numeric"  # Mobile numeric keyboard
            }
        ],
        "actions": [
            {"label": "Save", "action": "save"},
            {"label": "Cancel", "action": "cancel"}
        ]
    }

    return mobile_form

# Mobile app displays form
form_data = mobile_edit_line_handler(line_id)

Use Case 2: Mobile Variance Highlighting

# Highlight variances in mobile list

def get_mobile_lines_with_status(count_id):
    """Get lines with visual status indicators"""
    lines = get_model("stock.count.line").search_browse([
        ["count_id", "=", count_id]
    ])

    mobile_lines = []
    for line in lines:
        variance = (line.new_qty or 0) - (line.prev_qty or 0)
        variance_pct = (variance / line.prev_qty * 100) if line.prev_qty else 0

        # Determine mobile display status
        if variance == 0:
            status = "match"
            color = "green"
            icon = "✓"
        elif abs(variance_pct) < 5:
            status = "minor"
            color = "yellow"
            icon = "⚠"
        else:
            status = "major"
            color = "red"
            icon = "❗"

        mobile_lines.append({
            "id": line.id,
            "product": line.product_id.code,
            "name": line.product_id.name,
            "counted": float(line.new_qty),
            "expected": float(line.prev_qty),
            "variance": float(variance),
            "status": status,
            "color": color,
            "icon": icon
        })

    return mobile_lines

# Mobile UI applies color coding
lines_display = get_mobile_lines_with_status(count_id)

Use Case 3: Mobile Quick Adjust

# Quick +/- buttons on mobile

def mobile_quick_adjust(line_id, adjustment):
    """
    Quick increment/decrement from mobile
    adjustment: +1, -1, +10, etc.
    """
    line = get_model("stock.count.line").browse(line_id)

    # Calculate new quantity
    new_qty = max(0, (line.new_qty or 0) + adjustment)

    # Update
    line.write({"new_qty": new_qty})

    # Mobile feedback
    return {
        "success": True,
        "new_qty": float(new_qty),
        "adjustment": adjustment,
        "feedback": f"{'↑' if adjustment > 0 else '↓'} {abs(adjustment)}"
    }

# Mobile UI buttons
# [+10] [+1] [ 5 ] [-1] [-10]
result = mobile_quick_adjust(line_id, +1)  # Increment by 1

Use Case 4: Mobile Barcode Line Lookup

# Find line by barcode scan

def mobile_find_line_by_barcode(count_id, barcode):
    """
    Locate count line by barcode for editing
    """
    # Lookup lot by barcode
    lot_ids = get_model("stock.lot").search([
        ["number", "=", barcode]
    ])

    if not lot_ids:
        return {"error": "Barcode not found"}

    lot_id = lot_ids[0]
    lot = get_model("stock.lot").browse(lot_id)

    # Find line in count
    line_ids = get_model("stock.count.line").search([
        ["count_id", "=", count_id],
        ["lot_id", "=", lot_id]
    ])

    if not line_ids:
        return {"error": "Item not in this count"}

    # Return mobile-formatted line
    line_id = line_ids[0]
    return {
        "found": True,
        "line": get_mobile_line_data(line_id),
        "action": "show_detail"  # Mobile shows detail form
    }

# Mobile scan handler
result = mobile_find_line_by_barcode(count_id, "LOT-2024-001")

Use Case 5: Mobile Summary Stats

# Mobile count progress display

def get_mobile_count_stats(count_id):
    """Calculate mobile-friendly count statistics"""
    lines = get_model("stock.count.line").search_browse([
        ["count_id", "=", count_id]
    ])

    total_lines = len(lines)
    counted = sum(1 for l in lines if l.new_qty != 0)
    matches = sum(1 for l in lines if l.new_qty == l.prev_qty)
    variances = total_lines - matches

    total_qty = sum(l.new_qty or 0 for l in lines)
    expected_qty = sum(l.prev_qty or 0 for l in lines)

    return {
        "progress": {
            "total": total_lines,
            "counted": counted,
            "remaining": total_lines - counted,
            "percent": f"{counted/total_lines*100:.0f}%" if total_lines else "0%"
        },
        "accuracy": {
            "matches": matches,
            "variances": variances,
            "accuracy_pct": f"{matches/total_lines*100:.1f}%" if total_lines else "100%"
        },
        "quantities": {
            "counted": float(total_qty),
            "expected": float(expected_qty),
            "variance": float(total_qty - expected_qty)
        }
    }

# Mobile dashboard display
stats = get_mobile_count_stats(count_id)
# Shows: "Progress: 47/100 (47%)" 
# "Accuracy: 95.7%"
# "Qty Variance: -23"

Mobile UI Best Practices

1. Progressive Disclosure

# Don't show all fields at once on mobile

# Good: Show essential fields in list
mobile_list_fields = ["product_id", "new_qty", "prev_qty"]

# Show details only when tapped
mobile_detail_fields = [
    "product_id", "lot_id", "bin_location",
    "prev_qty", "new_qty", "uom_id",
    "prev_cost_price", "unit_price"
]

# Bad: Showing all fields in cramped mobile list

2. Touch-Friendly Input

# Large, easy-to-tap controls

# Good: Large numeric input for quantities
mobile_input_config = {
    "type": "number",
    "keyboard": "numeric",
    "size": "large",  # Bigger input field
    "buttons": ["+10", "+1", "-1", "-10"]  # Quick adjust
}

# Bad: Small text input requiring precise tapping

3. Visual Feedback

# Immediate visual confirmation

def mobile_update_with_feedback(line_id, new_qty):
    """Update with visual feedback"""
    line = get_model("stock.count.line").browse(line_id)
    line.write({"new_qty": new_qty})

    return {
        "success": True,
        "animation": "fade_green",  # Success animation
        "sound": "beep_success",
        "haptic": "light",  # Vibration feedback
        "message": f"✓ Updated to {new_qty}"
    }

4. Offline Capability

# Cache lines for offline editing

class MobileLineCache:
    """Offline cache for count lines"""

    def cache_count_lines(self, count_id):
        """Download lines to mobile device"""
        lines = get_model("stock.count.line").search_browse([
            ["count_id", "=", count_id]
        ])

        # Minimal data for offline storage
        cached = []
        for line in lines:
            cached.append({
                "id": line.id,
                "product_id": line.product_id.id,
                "product_code": line.product_id.code,
                "product_name": line.product_id.name,
                "lot_id": line.lot_id.id if line.lot_id else None,
                "prev_qty": float(line.prev_qty or 0),
                "new_qty": float(line.new_qty or 0),
                "uom": line.uom_id.name
            })

        return cached

    def sync_offline_changes(self, count_id, offline_updates):
        """Sync changes when back online"""
        for update in offline_updates:
            line = get_model("stock.count.line").browse(update["id"])
            line.write({"new_qty": update["new_qty"]})

Performance Optimization for Mobile

1. Pagination

# Load lines in pages for mobile scrolling

PAGE_SIZE = 50  # Mobile-friendly page size

def load_mobile_page(count_id, page=1):
    """Load one page of lines"""
    offset = (page - 1) * PAGE_SIZE

    lines = get_model("stock.count.line").search_browse([
        ["count_id", "=", count_id]
    ], limit=PAGE_SIZE, offset=offset)

    return [get_mobile_line_data(l.id) for l in lines]

2. Lazy Loading

# Load details only when needed

# Initial load: minimal data
initial_data = {
    "id": line.id,
    "product": line.product_id.code,
    "qty": line.new_qty
}

# Detail load: full data (when user taps)
detail_data = get_mobile_line_data(line.id)

3. Delta Sync

# Only sync changed lines

def get_changed_lines(count_id, since_timestamp):
    """Get lines modified since timestamp"""
    # Track which lines have been updated
    # Only sync those lines to save bandwidth
    pass

Troubleshooting

Lines Not Appearing on Mobile

Cause: Pagination or filter issue.
Solution: Verify query parameters:

lines = get_model("stock.count.line").search([
    ["count_id", "=", count_id]
])
print(f"Total lines: {len(lines)}")

Mobile Update Not Saving

Cause: Count not in draft state or validation error.
Solution: Check state before update:

line = get_model("stock.count.line").browse(line_id)
if line.count_id.state != "draft":
    return {"error": "Count is completed"}

Slow Mobile Performance

Cause: Loading too much data at once.
Solution: Implement pagination:

# Load 50 lines at a time
lines = get_mobile_count_lines(count_id, limit=50, offset=0)


Model Relationship Description
stock.count Many2One Parent count (desktop)
mobile.count Many2One Parent count (mobile)
product Many2One Product being counted
stock.lot Many2One Lot/serial number
uom Many2One Unit of measure
cycle.stock.count Many2One Cycle count program

Version History

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


Additional Resources

  • Mobile Count Documentation: mobile.count
  • Stock Count Line Documentation: stock.count.line
  • Mobile API Best Practices
  • Offline Data Synchronization Guide

This documentation is generated for developer onboarding and reference purposes.