Skip to content

Sales Category Documentation

Overview

The Sales Category module (sale.categ) provides a classification system for organizing sales orders into distinct business categories. This master data model enables companies to segment their sales operations, apply category-specific numbering sequences, and generate category-based reports and analytics.


Model Information

Model Name: sale.categ Display Name: Sales Category Key Fields: name (unique)

Features

  • Unique key constraint per category name
  • Custom sales order numbering sequences per category
  • Search-enabled fields for quick lookups
  • Simple master data structure for easy maintenance

Understanding Key Fields

What are Key Fields?

In Netforce models, Key Fields are a combination of fields that together create a composite unique identifier for a record. Think of them as a business key that ensures data integrity across the system.

For the sale.categ model, the key field is:

_key = ["name"]

This means the category name must be unique: - name - The unique category name (required field)

Why Key Fields Matter

Uniqueness Guarantee - Key fields prevent duplicate records by ensuring unique names:

# Examples of valid combinations:
{"name": "Retail Sales"}        # Valid
{"name": "Wholesale"}           # Valid
{"name": "B2B Sales"}           # Valid

# This would fail - duplicate key:
{"name": "Retail Sales"}        # ERROR: Category name already exists!

Database Implementation

The key fields are enforced at the database level using a unique constraint:

_sql_constraints = [
    ("sale_categ_name_unique",
     "unique (name)",
     "Sales category name already exists!")
]

This translates to:

CREATE UNIQUE INDEX sale_categ_name_unique
    ON sale_categ (name);

Common Sales Category Types

Category Type Example Code Typical Use Case
Retail RETAIL Direct-to-consumer sales through stores
Wholesale WHOLESALE Bulk sales to resellers
B2B B2B Business-to-business transactions
E-commerce ECOM Online sales channel
Export EXPORT International sales
Government GOV Government contracts and tenders

Field Reference

Basic Fields

Field Type Required Description
name Char Yes Unique category name (e.g., "Retail Sales")
code Char No Short code for reports and identification (e.g., "RET")
description Text No Detailed description of category purpose and usage

Configuration Fields

Field Type Description
sale_sequence_id Many2One Link to custom sales order number sequence for this category

The sale_sequence_id field allows each category to have its own numbering format: - Retail orders: SO-R-0001, SO-R-0002 - Wholesale orders: SO-W-0001, SO-W-0002 - Export orders: SO-E-0001, SO-E-0002


API Methods

1. Create Sales Category

Method: create(vals, context)

Creates a new sales category record.

Parameters:

vals = {
    "name": "Retail Sales",                    # Required: Category name
    "code": "RETAIL",                          # Optional: Short code
    "description": "Direct sales to consumers", # Optional: Description
    "sale_sequence_id": 15                     # Optional: Sequence ID
}

Returns: int - New category ID

Example:

# Create a retail sales category
category_id = get_model("sale.categ").create({
    "name": "Retail Sales",
    "code": "RETAIL",
    "description": "Point of sale and direct customer sales",
    "sale_sequence_id": get_model("sequence").search([["type","=","sale_order"],["code","=","RETAIL"]])[0]
})


2. Search Categories

Method: search(condition, context)

Find sales categories by various criteria.

Examples:

# Find category by name
category_ids = get_model("sale.categ").search([["name", "=", "Retail Sales"]])

# Find categories with codes starting with "W"
category_ids = get_model("sale.categ").search([["code", "ilike", "W%"]])

# Search in name or code
category_ids = get_model("sale.categ").search([["name", "ilike", "retail"]])

3. Update Category

Method: write(ids, vals, context)

Update existing category records.

Example:

# Update category description
get_model("sale.categ").write([category_id], {
    "description": "Updated description for retail category",
    "sale_sequence_id": new_sequence_id
})


Search Functions

Search by Name

# Exact match
condition = [["name", "=", "Retail Sales"]]

# Partial match (case-insensitive)
condition = [["name", "ilike", "retail"]]

Search by Code

# Find by code
condition = [["code", "=", "RETAIL"]]

# Find all codes starting with "W"
condition = [["code", "ilike", "W%"]]

Model Relationship Description
sale.order Referenced by Sales orders reference category via categ_id
sequence Many2One Custom numbering sequences for each category
sale.quot Referenced by Sales quotations may reference category

Common Use Cases

Use Case 1: Initial Category Setup

# 1. Create standard business categories
categories = [
    {"name": "Retail Sales", "code": "RETAIL"},
    {"name": "Wholesale", "code": "WHOLESALE"},
    {"name": "B2B Sales", "code": "B2B"},
    {"name": "E-commerce", "code": "ECOM"},
    {"name": "Export Sales", "code": "EXPORT"}
]

for categ in categories:
    get_model("sale.categ").create(categ)

# 2. Verify created categories
all_categs = get_model("sale.categ").search([])
print(f"Created {len(all_categs)} sales categories")

Use Case 2: Category with Custom Numbering

# 1. Create a sequence for retail sales
seq_id = get_model("sequence").create({
    "name": "Retail Sales Orders",
    "code": "SO-RETAIL",
    "type": "sale_order",
    "prefix": "SO-R-",
    "padding": 4,
    "number_next": 1
})

# 2. Create category with sequence
categ_id = get_model("sale.categ").create({
    "name": "Retail Sales",
    "code": "RETAIL",
    "description": "Direct-to-consumer sales",
    "sale_sequence_id": seq_id
})

# 3. When creating sales orders with this category,
#    they will automatically use SO-R-0001, SO-R-0002, etc.

Use Case 3: Category-Based Reporting

# Generate sales report by category
def get_sales_by_category(date_from, date_to):
    results = []

    # Get all categories
    category_ids = get_model("sale.categ").search([])
    categories = get_model("sale.categ").browse(category_ids)

    for categ in categories:
        # Find sales orders in this category
        order_ids = get_model("sale.order").search([
            ["categ_id", "=", categ.id],
            ["date", ">=", date_from],
            ["date", "<=", date_to],
            ["state", "in", ["confirmed", "done"]]
        ])

        orders = get_model("sale.order").browse(order_ids)
        total_amount = sum(o.amount_total for o in orders)

        results.append({
            "category": categ.name,
            "code": categ.code,
            "order_count": len(orders),
            "total_amount": total_amount
        })

    return results

Use Case 4: Filtering Orders by Category

# Find all retail sales orders this month
import datetime

start_of_month = datetime.date.today().replace(day=1)

# Get retail category
retail_categ = get_model("sale.categ").search([["code", "=", "RETAIL"]])

# Find orders in this category
order_ids = get_model("sale.order").search([
    ["categ_id", "in", retail_categ],
    ["date", ">=", start_of_month.strftime("%Y-%m-%d")]
])

print(f"Found {len(order_ids)} retail orders this month")

Use Case 5: Category Migration

# Migrate old category to new category
def migrate_category(old_categ_id, new_categ_id):
    # Find all orders with old category
    order_ids = get_model("sale.order").search([
        ["categ_id", "=", old_categ_id]
    ])

    # Update to new category
    if order_ids:
        get_model("sale.order").write(order_ids, {
            "categ_id": new_categ_id
        })

    print(f"Migrated {len(order_ids)} orders to new category")

    # Optionally delete old category
    # get_model("sale.categ").delete([old_categ_id])

Best Practices

1. Naming Conventions

# Good: Clear, descriptive names
{"name": "Retail Sales", "code": "RETAIL"}
{"name": "Wholesale Distribution", "code": "WHOLESALE"}
{"name": "B2B Corporate Sales", "code": "B2B"}

# Bad: Vague or inconsistent names
{"name": "Cat1", "code": "C1"}           # Not descriptive
{"name": "retail", "code": "retail"}     # Inconsistent casing
{"name": "Sales Type A", "code": "A"}    # Unclear meaning

Guidelines: - Use title case for category names - Use uppercase for codes - Keep codes short but meaningful (3-8 characters) - Be consistent across all categories


2. Category Organization

Start with core categories:

# Basic set for most businesses
core_categories = [
    "Retail Sales",      # Direct consumer sales
    "Wholesale",         # Bulk/reseller sales
    "Online Sales"       # E-commerce channel
]

Expand as needed:

# Add specialized categories only when necessary
specialized = [
    "Government Contracts",
    "Export Sales",
    "Tender Projects",
    "Consignment Sales"
]

Avoid over-categorization: - Don't create too many categories (aim for 5-10 maximum) - Each category should have a clear business purpose - Categories should be mutually exclusive


3. Sequence Assignment

# Good: Each major category has its own sequence
retail_seq = create_sequence("SO-R-", "Retail Sales")
wholesale_seq = create_sequence("SO-W-", "Wholesale")
online_seq = create_sequence("SO-O-", "Online Sales")

# This makes it easy to identify order type from number:
# SO-R-0001 = Retail order
# SO-W-0001 = Wholesale order
# SO-O-0001 = Online order

# Bad: No sequences or shared sequences
# All orders use same numbering (harder to track by category)

4. Data Governance

Establish clear ownership:

# Define who can create/modify categories
# Typically: Sales Manager, System Administrator

# Document category purposes
category_docs = {
    "RETAIL": {
        "purpose": "Direct customer sales through physical stores",
        "owner": "Retail Sales Manager",
        "sequence_format": "SO-R-XXXX"
    },
    "WHOLESALE": {
        "purpose": "Bulk sales to authorized resellers",
        "owner": "Wholesale Manager",
        "sequence_format": "SO-W-XXXX"
    }
}

Review periodically: - Quarterly review of category usage - Remove unused categories - Merge similar categories - Update descriptions as business evolves


Database Constraints

Unique Name Constraint

CREATE UNIQUE INDEX sale_categ_name_unique
    ON sale_categ (name);

This ensures no two categories can have the same name, preventing confusion and data integrity issues.


Performance Tips

1. Use Codes for Queries

# Good: Search by code (faster, indexed)
categ_ids = get_model("sale.categ").search([["code", "=", "RETAIL"]])

# Less optimal: Search by description
categ_ids = get_model("sale.categ").search([["description", "ilike", "%retail%"]])

2. Cache Category Lookups

# Cache frequently used categories
_category_cache = {}

def get_category_by_code(code):
    if code not in _category_cache:
        categ_ids = get_model("sale.categ").search([["code", "=", code]])
        if categ_ids:
            _category_cache[code] = categ_ids[0]
    return _category_cache.get(code)

3. Limit Category Count

  • Keep total categories under 20 for optimal performance
  • More categories = more complex reporting and slower queries
  • Use other fields (tags, customer groups) for finer segmentation

Troubleshooting

"Sales category name already exists!"

Cause: Attempting to create a category with a name that already exists in the system. Solution: - Check existing categories: get_model("sale.categ").search([["name", "ilike", "retail"]]) - Use a different name or update the existing category - Ensure no leading/trailing spaces in category name

"Cannot delete category - referenced by sales orders"

Cause: Category is being used by existing sales orders and cannot be deleted. Solution: - Migrate orders to a different category first - Or mark the category as inactive (add custom field) instead of deleting - Use category migration script (see Use Case 5)

"Missing sequence - orders not numbered correctly"

Cause: Category's sale_sequence_id is not set or references deleted sequence. Solution:

# Create new sequence for category
seq_id = get_model("sequence").create({
    "name": "Sales Orders - Retail",
    "type": "sale_order",
    "prefix": "SO-R-",
    "padding": 4
})

# Update category
get_model("sale.categ").write([categ_id], {
    "sale_sequence_id": seq_id
})


Testing Examples

Unit Test: Create Category

def test_create_sales_category():
    # Create category
    categ_id = get_model("sale.categ").create({
        "name": "Test Retail",
        "code": "TEST_RET"
    })

    # Verification
    assert categ_id > 0

    # Read back
    categ = get_model("sale.categ").browse(categ_id)
    assert categ.name == "Test Retail"
    assert categ.code == "TEST_RET"

    # Cleanup
    get_model("sale.categ").delete([categ_id])

Unit Test: Unique Name Constraint

def test_unique_category_name():
    # Create first category
    categ1_id = get_model("sale.categ").create({
        "name": "Unique Category Test"
    })

    # Try to create duplicate
    try:
        categ2_id = get_model("sale.categ").create({
            "name": "Unique Category Test"
        })
        assert False, "Should have raised error for duplicate name"
    except Exception as e:
        assert "already exists" in str(e).lower()

    # Cleanup
    get_model("sale.categ").delete([categ1_id])

Security Considerations

Permission Model

  • sale_categ_create - Create new sales categories
  • sale_categ_write - Modify existing categories
  • sale_categ_delete - Delete categories (requires no order references)
  • sale_categ_read - View category information

Data Access

  • Categories are typically company-wide master data
  • No row-level security needed (all users see all categories)
  • Restrict create/modify/delete to sales managers and administrators
  • All sales users should have read access for order creation

Integration Points

Internal Modules

  • sale.order: References categories for order classification
  • sale.quot: May reference categories for quotation tracking
  • sequence: Provides custom numbering sequences per category
  • report: Categories used for sales analytics and reports

Reporting Integration

# Categories commonly used in:
# - Sales by Category Report
# - Category Performance Dashboard
# - Order Volume by Category
# - Revenue Analysis by Sales Channel

Version History

Last Updated: 2026-01-05 Model Version: sale_categ.py Framework: Netforce


Additional Resources

  • Sales Order Documentation: sale.order
  • Sequence Configuration: sequence
  • Sales Quotation: sale.quot
  • Sales Reporting Guide

Support & Feedback

For issues or questions about this module: 1. Check existing categories before creating new ones 2. Review sequence configuration for numbering issues 3. Verify category codes are unique and meaningful 4. Test category changes in development environment first


This documentation is generated for developer onboarding and reference purposes.