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:
-
Category Multiplication:
-
Per-Category Distribution:
-
Sequential Distribution:
- Pops items from end of each array
- Distributes X, Y, Z items evenly across sessions
- 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}")
Related Models¶
| 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.