Skip to content

Cycle Stock Count Edit Documentation

Overview

The Cycle Stock Count Edit module (cycle.stock.count.edit) is an improved version of the cycle stock count module with enhanced scheduling algorithms and distribution logic. This module implements the same ABC/XYZ classification system but features optimized count session distribution to ensure more balanced workloads throughout the counting period.


Model Information

Model Name: cycle.stock.count (same model, enhanced implementation)
Display Name: Cycle Stock Count
Key Fields: location_id, product_id

Key Improvements Over Base Version

  • Better Distribution Algorithm: More even spread of items across count sessions
  • Simplified Session Creation: Cleaner logic for distributing X/Y/Z items
  • Per-Category Session Counts: Calculates items per session for each category separately
  • Progress Tracking: Background job support with progress indicators
  • Sequence Integration: Better number generation for count sessions

Key Differences from Base Implementation

Scheduling Algorithm Comparison

Base Version (cycle_stock_count.py): - Creates a queue of all items (X×count, Y×count, Z×count) - Randomly samples from queue - Can result in uneven distribution

Edit Version (cycle_stock_count_edit.py): - Calculates items per session for each category - Distributes X, Y, Z items separately per session - More predictable and balanced workload

# Edit version algorithm (simplified):
total_z_per_session = ceil(total_z_count / sessions)
total_y_per_session = ceil(total_y_count / sessions)
total_x_per_session = ceil(total_x_count / sessions)

for each_session:
    add z_per_session Z items
    add y_per_session Y items
    add x_per_session X items

Enhanced Scheduling Logic

Distribution Calculation

The edit version uses a more deterministic approach:

# Calculate items per session for each category
total_z_count_per_session = math.ceil(total_z_count / total_count)
total_y_count_per_session = math.ceil(total_y_count / total_count)
total_x_count_per_session = math.ceil(total_x_count / total_count)

# Distribute evenly
for session in range(total_sessions):
    session_items = []

    # Add Z items (if any left)
    for _ in range(total_z_count_per_session):
        if Z_array:
            session_items.append(Z_array.pop())

    # Add Y items (if any left)
    for _ in range(total_y_count_per_session):
        if Y_array:
            session_items.append(Y_array.pop())

    # Add X items (if any left)
    for _ in range(total_x_count_per_session):
        if X_array:
            session_items.append(X_array.pop())

    create_stock_count_session(session_items)

API Methods

1. Schedule Cycle Count (Enhanced)

Method: schedule_cycle_count(location_id, context)

Enhanced version with better distribution logic.

Parameters: - location_id (int): Location to schedule counts for

Behavior Improvements:

  1. Category Multiplication:

    # Creates arrays with repeated items based on frequency
    X = [item for item in x_products] * total_x_count
    Y = [item for item in y_products] * total_y_count
    Z = [item for item in z_products] * total_z_count
    

  2. Per-Category Distribution:

    # Calculates exact items per session for each category
    z_per_session = ceil(z_count / sessions)
    y_per_session = ceil(y_count / sessions)
    x_per_session = ceil(x_count / sessions)
    

  3. Sequential Distribution:

  4. Pops items from end of each array
  5. Distributes X, Y, Z items evenly across sessions
  6. Handles remaining items gracefully

Example:

# Configure settings
settings = get_model("settings").browse(1)
settings.write({
    "cycle_stockcount_xyz": True,
    "total_stockcount_per_year": 52,    # Weekly counts
    "z_total_stockcount_per_year": 2,   # Z: 2x/year
    "y2z_ratio": 4,                      # Y: 8x/year
    "x2z_ratio": 26                      # X: 52x/year (weekly)
})

# Classify products first
get_model("cycle.stock.count").assign_sku_categ(warehouse_id)

# Schedule with enhanced algorithm
result = get_model("cycle.stock.count").schedule_cycle_count(warehouse_id)

# Result: 52 count sessions created
# Each session has balanced mix of X, Y, Z items


Comparison Examples

Example: Workload Distribution

Scenario: - 100 X items (counted 52 times/year) - 200 Y items (counted 12 times/year) - 300 Z items (counted 2 times/year) - 52 count sessions per year

Base Version Results:

Session 1:  105 items (98 X, 5 Y, 2 Z)
Session 2:  95 items (90 X, 4 Y, 1 Z)
Session 3:  115 items (108 X, 6 Y, 1 Z)
...
# Uneven distribution due to random sampling

Edit Version Results:

Session 1:  100 items (100 X, 0 Y, 0 Z)
Session 2:  100 items (100 X, 0 Y, 0 Z)
Session 3:  100 items (100 X, 0 Y, 0 Z)
Session 4:  100 items (100 X, 0 Y, 0 Z)
Session 5:  104 items (100 X, 2 Y, 2 Z)  # Y and Z distributed
...
# More predictable distribution

Calculation:

# Edit version calculation
x_per_session = ceil(100 * 52 / 52) = 100 per session
y_per_session = ceil(200 * 12 / 52) = 47 per session (distributed)
z_per_session = ceil(300 * 2 / 52) = 12 per session (distributed)

# Total per session ~= 100 + 47 + 12 = 159 items
# Distributed over 52 sessions


Common Use Cases

Use Case 1: Balanced Workload Scheduling

# Using enhanced scheduling for balanced workload

# Step 1: Configure for balanced distribution
settings = get_model("settings").browse(1)
settings.write({
    "total_stockcount_per_year": 52,     # Weekly
    "z_total_stockcount_per_year": 4,    # Z: quarterly
    "y2z_ratio": 3,                       # Y: 12x/year (monthly)
    "x2z_ratio": 13                       # X: 52x/year (weekly)
})

# Step 2: Classify products
get_model("cycle.stock.count").assign_sku_categ(warehouse_id)

# Step 3: Schedule with enhanced algorithm
result = get_model("cycle.stock.count").schedule_cycle_count(warehouse_id)

# Step 4: Verify distribution
counts = get_model("stock.count").search_browse([
    ["location_id", "=", warehouse_id],
    ["state", "=", "draft"]
])

items_per_session = [len(c.lines) for c in counts]
avg_items = sum(items_per_session) / len(items_per_session)
max_items = max(items_per_session)
min_items = min(items_per_session)

print(f"Average items per session: {avg_items:.1f}")
print(f"Range: {min_items} to {max_items}")
print(f"Std deviation: {statistics.stdev(items_per_session):.1f}")

# Edit version should have lower std deviation (more consistent)

Use Case 2: Weekly High-Mover Counts

# Count X items (fast movers) every week

settings = get_model("settings").browse(1)
settings.write({
    "total_stockcount_per_year": 52,
    "z_total_stockcount_per_year": 1,    # Z: once per year
    "y2z_ratio": 4,                       # Y: 4x per year
    "x2z_ratio": 52                       # X: every week
})

# Classify and schedule
get_model("cycle.stock.count").assign_sku_categ(warehouse_id)
get_model("cycle.stock.count").schedule_cycle_count(warehouse_id)

# Result: X items appear in every session (or nearly every)
# Y items appear quarterly
# Z items appear once

Use Case 3: Compare Distribution Quality

# Compare base vs edit version distribution quality
import statistics

def analyze_distribution(counts):
    """Analyze count session distribution"""
    items_per_session = [len(c.lines) for c in counts]

    return {
        "total_sessions": len(counts),
        "total_items": sum(items_per_session),
        "avg_per_session": statistics.mean(items_per_session),
        "std_dev": statistics.stdev(items_per_session),
        "min": min(items_per_session),
        "max": max(items_per_session),
        "range": max(items_per_session) - min(items_per_session)
    }

# Run with base version
# result_base = analyze_distribution(base_counts)

# Run with edit version  
counts = get_model("stock.count").search_browse([
    ["location_id", "=", warehouse_id],
    ["state", "=", "draft"]
])
result_edit = analyze_distribution(counts)

print("Edit Version Distribution:")
print(f"  Sessions: {result_edit['total_sessions']}")
print(f"  Avg items: {result_edit['avg_per_session']:.1f}")
print(f"  Std dev: {result_edit['std_dev']:.1f}")
print(f"  Range: {result_edit['min']} - {result_edit['max']}")

# Lower std_dev = more consistent workload
# Lower range = better balance

Best Practices

1. Choose Appropriate Session Count

# Good: Match session count to team capacity

# Calculate expected workload
settings = get_model("settings").browse(1)

x_count = get_model("cycle.stock.count").search_count([
    ["location_id", "=", warehouse_id],
    ["xyz_categ", "=", "x_"]
])
y_count = get_model("cycle.stock.count").search_count([
    ["location_id", "=", warehouse_id],
    ["xyz_categ", "=", "y_"]
])
z_count = get_model("cycle.stock.count").search_count([
    ["location_id", "=", warehouse_id],
    ["xyz_categ", "=", "z_"]
])

# Calculate total counts needed
z_freq = settings.z_total_stockcount_per_year
y_freq = z_freq * settings.y2z_ratio
x_freq = z_freq * settings.x2z_ratio

total_counts = (x_count * x_freq + 
                y_count * y_freq + 
                z_count * z_freq)

# Team can count 50 items per session
items_per_session = 50
recommended_sessions = math.ceil(total_counts / items_per_session)

print(f"Recommended sessions per year: {recommended_sessions}")

# Adjust settings
settings.write({"total_stockcount_per_year": recommended_sessions})

2. Validate Schedule Before Execution

# Good: Review schedule before starting counts

counts = get_model("stock.count").search_browse([
    ["location_id", "=", warehouse_id],
    ["state", "=", "draft"]
], order="id")

print("=== Count Schedule Review ===\n")

for i, count in enumerate(counts, 1):
    # Analyze session composition
    x_items = sum(1 for l in count.lines 
                  if l.cyclecount_id and l.cyclecount_id.xyz_categ == "x_")
    y_items = sum(1 for l in count.lines 
                  if l.cyclecount_id and l.cyclecount_id.xyz_categ == "y_")
    z_items = sum(1 for l in count.lines 
                  if l.cyclecount_id and l.cyclecount_id.xyz_categ == "z_")

    print(f"Session {i}: {len(count.lines)} items")
    print(f"  X: {x_items}, Y: {y_items}, Z: {z_items}")

    # Flag unusual distributions
    if x_items == 0:
        print(f"  ⚠️ Warning: No X items in this session")
    if len(count.lines) > 100:
        print(f"  ⚠️ Warning: High item count")
    print()

3. Monitor Execution Progress

# Track which sessions have been completed
warehouse_id = 5

total_sessions = get_model("stock.count").search_count([
    ["location_id", "=", warehouse_id]
])

completed = get_model("stock.count").search_count([
    ["location_id", "=", warehouse_id],
    ["state", "=", "done"]
])

in_progress = get_model("stock.count").search_count([
    ["location_id", "=", warehouse_id],
    ["state", "=", "draft"]
])

print(f"Cycle Count Progress:")
print(f"  Total sessions: {total_sessions}")
print(f"  Completed: {completed} ({completed/total_sessions*100:.1f}%)")
print(f"  Remaining: {in_progress}")

# Get next session to count
next_count = get_model("stock.count").search_browse([
    ["location_id", "=", warehouse_id],
    ["state", "=", "draft"]
], order="id", limit=1)

if next_count:
    print(f"\nNext count: {next_count[0].number}")
    print(f"Items to count: {len(next_count[0].lines)}")

Performance Considerations

Memory Usage

The edit version creates full arrays of items before distribution:

# Creates large arrays in memory
X = product_list * 52  # If 100 products × 52 = 5,200 items
Y = product_list * 12
Z = product_list * 2

For large inventories: - Consider increasing available memory - Or reduce count frequencies - Or process in batches

Calculation Efficiency

# Edit version is more efficient for calculation
# O(n × m) where n=sessions, m=categories

# Base version uses random sampling
# O(n × k) where k=total items, with potential clustering

Troubleshooting

Uneven Distribution Despite Using Edit Version

Cause: Integer rounding when items don't divide evenly across sessions.
Solution: This is expected behavior. Adjust session count:

# If 100 items, 52 sessions: 100/52 = 1.92 → 2 per session
# Some sessions will have 2, others 1
# Total still equals target

Empty Sessions Created

Cause: More sessions than total item-counts needed.
Solution: Reduce total_stockcount_per_year:

# Calculate minimum sessions needed
min_sessions = max(
    x_count * settings.x2z_ratio / settings.z_total_stockcount_per_year,
    y_count * settings.y2z_ratio / settings.z_total_stockcount_per_year,
    z_count
)
print(f"Minimum sessions needed: {math.ceil(min_sessions)}")

All Items in First Few Sessions

Cause: Settings misconfigured or count frequencies too low.
Solution: Verify frequency ratios:

settings = get_model("settings").browse(1)
print(f"Z counts per year: {settings.z_total_stockcount_per_year}")
print(f"Y multiplier: {settings.y2z_ratio}")
print(f"X multiplier: {settings.x2z_ratio}")

# Ensure: x_ratio > y_ratio > 1
# And total sessions sufficient for distribution


Migration from Base Version

Switching from Base to Edit Version

# 1. Delete existing scheduled counts
draft_counts = get_model("stock.count").search([
    ["location_id", "=", warehouse_id],
    ["state", "=", "draft"]
])
get_model("stock.count").delete(draft_counts)

# 2. Keep cycle count records (classifications)
# No need to reclassify

# 3. Reschedule with edit version
result = get_model("cycle.stock.count").schedule_cycle_count(warehouse_id)

# 4. Verify improved distribution
counts = get_model("stock.count").search_browse([
    ["location_id", "=", warehouse_id],
    ["state", "=", "draft"]
])

items_per_session = [len(c.lines) for c in counts]
print(f"New distribution std dev: {statistics.stdev(items_per_session):.2f}")

Model Relationship Description
cycle.stock.count Self Base classification records
stock.count Created Generated count sessions
stock.count.line Created Individual items to count
product Many2One Products being classified
stock.location Many2One Warehouse location
settings Referenced Configuration parameters

Version History

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

Improvements over base: - Better distribution algorithm - More predictable workload - Cleaner scheduling logic - Enhanced per-category handling


Additional Resources

  • Base Cycle Count Documentation: cycle.stock.count
  • Stock Count Documentation: stock.count
  • Stock Balance Documentation: stock.balance
  • Settings Configuration: settings

Support & Feedback

For issues or questions about this module: 1. Compare distribution with base version 2. Review session count settings 3. Verify category frequencies are appropriate 4. Test with smaller dataset first 5. Check memory availability for large inventories


This documentation is generated for developer onboarding and reference purposes.