Product Code Documentation¶
Overview¶
The Product Code module (product.code) is a lightweight hierarchical classification system for organizing products using numeric codes. Despite the filename product_category.py, the actual model name is product.code. This module provides a simple tree structure for product categorization with UUID-based unique identification.
IMPORTANT: The filename is product_category.py but the model name is product.code. This is a naming convention quirk in the codebase.
Model Information¶
Model Name: product.code
Display Name: Derived from name field (numeric code_id)
File Location: product_category.py (NOTE: Filename differs from model name)
Name Field: name (Integer field containing code_id)
Ordering: sequence field
Features¶
- ❌ Audit logging enabled (
_audit_log) - ❌ Multi-company support (
company_id) - ❌ Full-text content search (
_content_search) - ✅ Hierarchical structure (parent-child relationships)
- ✅ UUID-based unique identification
- ✅ Sequence-based ordering
Understanding the Model Name vs Filename Discrepancy¶
Why the Mismatch?¶
# File: product_category.py
class ProductCode(Model):
_name = "product.code" # Model name is product.code, NOT product.category
This is likely due to:
- Legacy naming conventions from earlier system versions
- Avoiding conflict with existing product.categ model
- Historical evolution of the product classification system
Important: Always use product.code when referencing this model in code, despite the filename.
Key Fields Reference¶
Core Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
Integer | ✅ | Numeric code identifier (code_id) |
parent_id |
Many2One | ❌ | Parent product code for hierarchical structure |
description |
Text | ❌ | Detailed description of the product code |
uuid |
Char | Auto | Universally unique identifier, auto-generated |
Special Attributes¶
| Attribute | Value | Purpose |
|---|---|---|
_name_field |
"name" |
Defines which field serves as record name |
_order |
"sequence" |
Orders records by sequence field (not visible in fields) |
Hierarchical Structure¶
Parent-Child Relationships¶
The parent_id field enables building a tree structure:
Product Code Hierarchy Example:
└── 1000 (Electronics)
├── 1100 (Computers)
│ ├── 1110 (Desktops)
│ └── 1120 (Laptops)
└── 1200 (Mobile Devices)
├── 1210 (Smartphones)
└── 1220 (Tablets)
Tree Navigation¶
# Get root-level codes (no parent)
root_codes = get_model("product.code").search([
["parent_id", "=", None]
])
# Get children of a specific code
parent_code = get_model("product.code").browse(code_id)
children = get_model("product.code").search([
["parent_id", "=", parent_code.id]
])
# Get full hierarchy path
def get_code_path(code_id):
code = get_model("product.code").browse(code_id)
path = [code.name]
current = code
while current.parent_id:
current = current.parent_id
path.insert(0, current.name)
return path
# Returns: [1000, 1100, 1110] for Desktops
UUID System¶
Auto-Generated Unique Identifiers¶
Each product code automatically receives a UUID:
Example UUID: "550e8400-e29b-41d4-a716-446655440000"
UUID Use Cases¶
- External System Integration: Reference product codes across systems
- Data Synchronization: Match records during import/export
- API Endpoints: Stable identifier for RESTful APIs
- Distributed Systems: Conflict-free merging of code hierarchies
# Find by UUID (external system integration)
code = get_model("product.code").search([
["uuid", "=", "550e8400-e29b-41d4-a716-446655440000"]
])
# Export with UUID for reimport
export_data = {
"uuid": code.uuid,
"name": code.name,
"description": code.description
}
API Methods¶
1. Create Product Code¶
Method: create(vals, context)
Creates a new product code record.
Parameters:
vals = {
"name": int, # Required: Numeric code ID
"parent_id": int, # Optional: Parent code for hierarchy
"description": str, # Optional: Description
"uuid": str # Optional: Auto-generated if not provided
}
Returns: int - New product code record ID
Example:
# Create root category
electronics_id = get_model("product.code").create({
"name": 1000,
"description": "Electronics and Technology Products"
})
# Create child category
computers_id = get_model("product.code").create({
"name": 1100,
"parent_id": electronics_id,
"description": "Computer Systems and Peripherals"
})
# Create grandchild category
laptops_id = get_model("product.code").create({
"name": 1120,
"parent_id": computers_id,
"description": "Laptop Computers"
})
2. Search Product Codes¶
Method: search(condition, order, context)
Searches for product codes with optional ordering.
Example:
# Search by code number
codes = get_model("product.code").search([
["name", "=", 1100]
])
# Search by description
codes = get_model("product.code").search([
["description", "ilike", "computer"]
])
# Search by UUID
codes = get_model("product.code").search([
["uuid", "=", "550e8400-e29b-41d4-a716-446655440000"]
])
# Get all root codes (no parent)
root_codes = get_model("product.code").search([
["parent_id", "=", None]
], order="sequence,name")
3. Browse and Navigate¶
Method: browse(ids)
Retrieves full record object with relationship access.
Example:
# Get code with parent relationship
code = get_model("product.code").browse(code_id)
print(f"Code: {code.name}")
print(f"Description: {code.description}")
print(f"UUID: {code.uuid}")
if code.parent_id:
print(f"Parent Code: {code.parent_id.name}")
print(f"Parent Description: {code.parent_id.description}")
Integration Points¶
Product Model Integration¶
The product.code model is designed to be referenced by products:
# In product model (hypothetical integration)
class Product(Model):
_name = "product"
_fields = {
"code_id": fields.Many2One("product.code", "Product Code"),
# Other product fields...
}
# Assign product to code
product_id = get_model("product").create({
"name": "ThinkPad T14",
"code_id": laptops_code_id, # References product.code
# Other fields...
})
# Query products by code
products = get_model("product").search([
["code_id", "=", laptops_code_id]
])
Related Models¶
| Model | Relationship | Description |
|---|---|---|
product |
Referenced by | Products use product codes for classification |
product.categ |
Alternative | Main product category system (different from product.code) |
sale.order.line |
Indirect | Order lines reference products with codes |
stock.move |
Indirect | Inventory movements filtered by product codes |
Note: product.code and product.categ are separate classification systems. Organizations may use one or both depending on needs.
Common Use Cases¶
Use Case 1: Build Product Code Hierarchy¶
# Create comprehensive product classification system
# Level 1: Major Categories
electronics_id = get_model("product.code").create({
"name": 1000,
"description": "Electronics & Technology"
})
clothing_id = get_model("product.code").create({
"name": 2000,
"description": "Apparel & Clothing"
})
# Level 2: Sub-Categories under Electronics
computers_id = get_model("product.code").create({
"name": 1100,
"parent_id": electronics_id,
"description": "Computer Systems"
})
mobile_id = get_model("product.code").create({
"name": 1200,
"parent_id": electronics_id,
"description": "Mobile Devices"
})
# Level 3: Specific Product Types under Computers
desktop_id = get_model("product.code").create({
"name": 1110,
"parent_id": computers_id,
"description": "Desktop Computers"
})
laptop_id = get_model("product.code").create({
"name": 1120,
"parent_id": computers_id,
"description": "Laptop Computers"
})
# Result: Tree structure for easy navigation
# 1000 - Electronics & Technology
# 1100 - Computer Systems
# 1110 - Desktop Computers
# 1120 - Laptop Computers
# 1200 - Mobile Devices
# 2000 - Apparel & Clothing
Use Case 2: Query Products by Code Hierarchy¶
# Find all products under a specific code and its children
def get_products_by_code_tree(code_id):
"""Get all products under a code and all its descendants"""
# Get the code and all its descendants
all_codes = [code_id]
def get_children_recursive(parent_id):
children = get_model("product.code").search([
["parent_id", "=", parent_id]
])
for child_id in children:
all_codes.append(child_id)
get_children_recursive(child_id) # Recursively get grandchildren
get_children_recursive(code_id)
# Find all products with any of these codes
products = get_model("product").search([
["code_id", "in", all_codes]
])
return products
# Example: Get all electronics products
electronics_code = get_model("product.code").search([
["name", "=", 1000]
])[0]
all_electronics = get_products_by_code_tree(electronics_code)
print(f"Found {len(all_electronics)} electronics products")
Use Case 3: External System Integration with UUID¶
# Sync product codes with external ERP system
# Export product codes with UUIDs
def export_product_codes():
codes = get_model("product.code").search([])
export_data = []
for code_id in codes:
code = get_model("product.code").browse(code_id)
export_data.append({
"uuid": code.uuid,
"code": code.name,
"description": code.description,
"parent_uuid": code.parent_id.uuid if code.parent_id else None
})
return export_data
# Import product codes from external system
def import_product_codes(import_data):
for item in import_data:
# Check if code exists by UUID
existing = get_model("product.code").search([
["uuid", "=", item["uuid"]]
])
# Find parent by UUID if specified
parent_id = None
if item.get("parent_uuid"):
parent = get_model("product.code").search([
["uuid", "=", item["parent_uuid"]]
])
parent_id = parent[0] if parent else None
vals = {
"uuid": item["uuid"],
"name": item["code"],
"description": item["description"],
"parent_id": parent_id
}
if existing:
# Update existing code
get_model("product.code").write(existing, vals)
else:
# Create new code
get_model("product.code").create(vals)
# Use for bidirectional sync
exported = export_product_codes()
# ... send to external system ...
# ... receive from external system ...
import_product_codes(received_data)
Use Case 4: Generate Code Hierarchy Report¶
# Create hierarchical report of product codes
def generate_code_hierarchy_report():
"""Generate indented list of all product codes"""
def print_code_tree(code_id, level=0):
code = get_model("product.code").browse(code_id)
indent = " " * level
# Count products under this code
product_count = len(get_model("product").search([
["code_id", "=", code_id]
]))
print(f"{indent}{code.name} - {code.description} ({product_count} products)")
# Get and print children
children = get_model("product.code").search([
["parent_id", "=", code_id]
], order="name")
for child_id in children:
print_code_tree(child_id, level + 1)
# Start with root codes
root_codes = get_model("product.code").search([
["parent_id", "=", None]
], order="name")
print("Product Code Hierarchy Report")
print("=" * 50)
for root_id in root_codes:
print_code_tree(root_id)
# Example output:
# Product Code Hierarchy Report
# ==================================================
# 1000 - Electronics & Technology (0 products)
# 1100 - Computer Systems (5 products)
# 1110 - Desktop Computers (15 products)
# 1120 - Laptop Computers (23 products)
# 1200 - Mobile Devices (8 products)
# 2000 - Apparel & Clothing (42 products)
Use Case 5: Validate Code Hierarchy Integrity¶
# Check for orphaned codes and circular references
def validate_code_hierarchy():
"""Validate product code hierarchy for data integrity"""
issues = []
# Get all codes
all_codes = get_model("product.code").search([])
for code_id in all_codes:
code = get_model("product.code").browse(code_id)
# Check for circular references
visited = set()
current = code
while current.parent_id:
if current.id in visited:
issues.append({
"type": "circular_reference",
"code": code.name,
"message": f"Code {code.name} has circular parent reference"
})
break
visited.add(current.id)
current = current.parent_id
# Check for orphaned references (parent doesn't exist)
if code.parent_id:
parent_exists = get_model("product.code").search([
["id", "=", code.parent_id.id]
])
if not parent_exists:
issues.append({
"type": "orphaned_parent",
"code": code.name,
"message": f"Code {code.name} references non-existent parent"
})
# Report issues
if issues:
print(f"Found {len(issues)} hierarchy issues:")
for issue in issues:
print(f" {issue['type']}: {issue['message']}")
else:
print("Product code hierarchy is valid")
return issues
# Run validation
validate_code_hierarchy()
Best Practices¶
1. Use Consistent Numbering Schemes¶
# Bad: Random, inconsistent code numbers
codes = [
{"name": 5, "description": "Electronics"},
{"name": 42, "description": "Computers"},
{"name": 999, "description": "Laptops"}
]
# Good: Systematic, hierarchical numbering
codes = [
{"name": 1000, "description": "Electronics"}, # Level 1: x000
{"name": 1100, "description": "Computers"}, # Level 2: xx00
{"name": 1110, "description": "Desktop Computers"}, # Level 3: xxx0
{"name": 1111, "description": "Gaming Desktops"}, # Level 4: xxxx
{"name": 1120, "description": "Laptop Computers"}
]
Recommended Numbering Pattern: - Level 1: 1000, 2000, 3000... (Major categories) - Level 2: 1100, 1200, 1300... (Sub-categories) - Level 3: 1110, 1120, 1130... (Product types) - Level 4: 1111, 1112, 1113... (Specific variants)
Benefits: - Easy to understand hierarchy depth from code number - Room for expansion within each level - Alphabetical sorting maintains logical grouping - Code patterns are self-documenting
2. Always Preserve UUIDs During Import/Export¶
# Bad: Recreating codes without preserving UUID
for item in import_data:
get_model("product.code").create({
"name": item["code"],
"description": item["description"]
# UUID will be regenerated - loses reference!
})
# Good: Preserve UUID for stable references
for item in import_data:
# Check if exists by UUID
existing = get_model("product.code").search([
["uuid", "=", item["uuid"]]
])
vals = {
"uuid": item["uuid"], # Preserve original UUID
"name": item["code"],
"description": item["description"]
}
if existing:
get_model("product.code").write(existing, vals)
else:
get_model("product.code").create(vals)
Why UUIDs Matter: - External systems reference by UUID - Data migration maintains relationships - Merging databases avoids conflicts - Audit trails remain valid
3. Validate Parent-Child Relationships¶
# Bad: Creating child without verifying parent exists
child_id = get_model("product.code").create({
"name": 1120,
"parent_id": 999 # Might not exist!
})
# Good: Verify parent exists before creating child
parent_code = 1100
parent_ids = get_model("product.code").search([
["name", "=", parent_code]
])
if not parent_ids:
raise Exception(f"Parent code {parent_code} not found")
child_id = get_model("product.code").create({
"name": 1120,
"parent_id": parent_ids[0],
"description": "Laptop Computers"
})
Validation Checklist: - Parent code exists before creating children - No circular references (code can't be its own ancestor) - Depth limits if needed (e.g., max 4 levels) - No orphaned codes after deletions
4. Use Descriptive Documentation¶
# Bad: Minimal or cryptic descriptions
get_model("product.code").create({
"name": 1120,
"description": "Laptops" # Too brief
})
# Good: Clear, comprehensive descriptions
get_model("product.code").create({
"name": 1120,
"description": """
Laptop Computers - Portable computing devices
Includes: Notebooks, ultrabooks, mobile workstations
Excludes: Tablets, 2-in-1 devices (see 1130)
"""
})
Description Best Practices: - Explain what belongs in this category - List examples of included items - Clarify exclusions to avoid confusion - Reference related codes when relevant
5. Implement Soft Deletion Instead of Hard Deletion¶
# Bad: Hard delete can break product references
get_model("product.code").delete([code_id])
# Products with this code_id now have broken reference!
# Good: Add active flag and mark as inactive
# (Requires adding 'active' field to model)
# Mark as inactive instead
get_model("product.code").write([code_id], {
"description": code.description + " [DEPRECATED]"
})
# Search only active codes
active_codes = get_model("product.code").search([
["active", "=", True] # If active field exists
])
Alternative: Move to "Deprecated" parent:
# Create deprecated codes container
deprecated_id = get_model("product.code").create({
"name": 9999,
"description": "Deprecated Product Codes"
})
# Move instead of delete
get_model("product.code").write([code_id], {
"parent_id": deprecated_id,
"description": code.description + " [DEPRECATED]"
})
Database Structure¶
Table Schema¶
CREATE TABLE product_code (
id SERIAL PRIMARY KEY,
name INTEGER NOT NULL, -- Numeric code
parent_id INTEGER REFERENCES product_code(id),
description TEXT,
uuid VARCHAR(36) UNIQUE NOT NULL, -- UUID for external reference
sequence INTEGER, -- Ordering (implicit in _order)
-- Standard Netforce fields
create_time TIMESTAMP DEFAULT NOW(),
write_time TIMESTAMP DEFAULT NOW(),
create_uid INTEGER,
write_uid INTEGER
);
CREATE INDEX product_code_parent_idx ON product_code(parent_id);
CREATE INDEX product_code_uuid_idx ON product_code(uuid);
CREATE INDEX product_code_sequence_idx ON product_code(sequence);
Performance Tips¶
1. Cache Code Hierarchy for Frequent Lookups¶
# Bad: Querying hierarchy repeatedly
for product in products:
code = get_model("product.code").browse(product.code_id)
parent = code.parent_id
grandparent = parent.parent_id if parent else None
# Multiple database queries per product!
# Good: Build hierarchy cache once
code_cache = {}
all_codes = get_model("product.code").search([])
for code_id in all_codes:
code = get_model("product.code").browse(code_id)
code_cache[code_id] = {
"name": code.name,
"description": code.description,
"parent_id": code.parent_id.id if code.parent_id else None
}
# Use cached data
for product in products:
code_data = code_cache.get(product.code_id)
# No database queries needed
2. Use Recursive CTE for Tree Queries¶
# For complex hierarchy queries, use SQL directly
def get_code_descendants_sql(code_id):
"""Get all descendants using recursive query"""
from netforce import database
db = database.get_connection()
cursor = db.cursor()
query = """
WITH RECURSIVE code_tree AS (
-- Anchor: starting code
SELECT id, name, parent_id, 1 as level
FROM product_code
WHERE id = %s
UNION ALL
-- Recursive: children of previous level
SELECT c.id, c.name, c.parent_id, ct.level + 1
FROM product_code c
INNER JOIN code_tree ct ON c.parent_id = ct.id
)
SELECT id FROM code_tree ORDER BY level, name;
"""
cursor.execute(query, [code_id])
results = cursor.fetchall()
return [row[0] for row in results]
3. Limit Hierarchy Depth¶
# Prevent performance issues from deeply nested hierarchies
MAX_HIERARCHY_DEPTH = 5
def check_hierarchy_depth(parent_id):
"""Validate hierarchy depth before creating child"""
depth = 1
current_id = parent_id
while current_id:
code = get_model("product.code").browse(current_id)
current_id = code.parent_id.id if code.parent_id else None
depth += 1
if depth > MAX_HIERARCHY_DEPTH:
raise Exception(
f"Cannot exceed maximum hierarchy depth of {MAX_HIERARCHY_DEPTH}"
)
return depth
# Use before creating child codes
parent_id = 1120
depth = check_hierarchy_depth(parent_id)
print(f"Creating code at depth {depth}")
Troubleshooting¶
"Sequence field not found" Warning¶
Cause: _order = "sequence" references field not defined in _fields
Solution: This is informational only - the model works without the sequence field. To fix:
# Add sequence field to _fields if needed
_fields = {
# ... existing fields ...
"sequence": fields.Integer("Sequence")
}
_defaults = {
"uuid": lambda *a: str(uuid.uuid4()),
"sequence": lambda *a: 10 # Default sequence
}
Circular Reference in Hierarchy¶
Cause: Parent-child chain loops back to itself Solution: Run validation and fix circular references
# Detect circular references
def find_circular_references():
all_codes = get_model("product.code").search([])
circular = []
for code_id in all_codes:
visited = set()
current = get_model("product.code").browse(code_id)
while current.parent_id:
if current.id in visited:
circular.append(code_id)
break
visited.add(current.id)
current = current.parent_id
return circular
# Fix by removing invalid parent
circular_codes = find_circular_references()
for code_id in circular_codes:
get_model("product.code").write([code_id], {"parent_id": None})
print(f"Fixed circular reference in code {code_id}")
UUID Conflicts During Import¶
Cause: Attempting to import duplicate UUIDs Solution: Check for existing UUIDs before import
# Safe import handling UUID conflicts
def safe_import_code(data):
existing = get_model("product.code").search([
["uuid", "=", data["uuid"]]
])
if existing:
# UUID exists - update instead of create
get_model("product.code").write(existing, {
"name": data["name"],
"description": data["description"]
})
return existing[0]
else:
# New code - create with provided UUID
return get_model("product.code").create(data)
Security Considerations¶
Permission Model¶
product_code_read- View product codesproduct_code_write- Create and modify codesproduct_code_delete- Remove codes (use with caution)
Data Access¶
- Product codes are typically accessible organization-wide
- Hierarchy modifications should be restricted to administrators
- UUID values should not be user-editable after creation
- Deletion should require verification of no product references
Testing Examples¶
Unit Test: Create Hierarchy and Navigate¶
def test_product_code_hierarchy():
# Create parent
parent_id = get_model("product.code").create({
"name": 1000,
"description": "Test Parent"
})
# Create child
child_id = get_model("product.code").create({
"name": 1100,
"parent_id": parent_id,
"description": "Test Child"
})
# Verify relationship
child = get_model("product.code").browse(child_id)
assert child.parent_id.id == parent_id
assert child.parent_id.name == 1000
# Verify UUID was generated
assert child.uuid is not None
assert len(child.uuid) == 36 # UUID format
# Cleanup
get_model("product.code").delete([child_id, parent_id])
Version History¶
Last Updated: 2026-01-05 Model Version: product_category.py (contains product.code model) Framework: Netforce
Additional Resources¶
- Product Catalog Documentation:
product - Product Category Documentation:
product.categ(different system) - Sales Order Line Documentation:
sale.order.line - Inventory Management: Stock movement filtering by codes
Support & Feedback¶
For issues or questions about this module:
1. Remember the model name is product.code despite filename
2. Use UUIDs for external system integration
3. Validate hierarchy integrity regularly
4. Implement consistent numbering schemes
5. Test hierarchy queries with performance in mind
6. Consider soft deletion instead of hard deletion
This documentation is generated for developer onboarding and reference purposes.