Skip to content

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:

_defaults = {
    "uuid": lambda *a: str(uuid.uuid4())
}

Example UUID: "550e8400-e29b-41d4-a716-446655440000"

UUID Use Cases

  1. External System Integration: Reference product codes across systems
  2. Data Synchronization: Match records during import/export
  3. API Endpoints: Stable identifier for RESTful APIs
  4. 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]
])

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 codes
  • product_code_write - Create and modify codes
  • product_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.