Sales Channel Documentation¶
Overview¶
The Sales Channel module (sale.channel) provides a classification system for tracking and managing different sales distribution channels. This master data model enables companies to organize sales by channel (retail, online, wholesale, etc.), assign channel-specific pricing, and analyze performance across different go-to-market strategies.
Model Information¶
Model Name: sale.channel
Display Name: Sales Channel
Key Fields: None (no unique constraints)
Features¶
- Simple channel classification
- Price list integration per channel
- Search-enabled fields
- Minimal master data structure
Common Sales Channel Types¶
| Channel Type | Example Code | Typical Use Case |
|---|---|---|
| Retail Store | RETAIL |
Physical storefront sales |
| E-commerce | ONLINE |
Website and mobile app sales |
| Wholesale | WHOLESALE |
B2B distributor sales |
| Direct Sales | DIRECT |
Field sales team |
| Partner/Reseller | PARTNER |
Channel partner sales |
| Marketplace | MARKETPLACE |
Third-party platform sales (Amazon, eBay) |
Field Reference¶
Basic Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
Char | Yes | Channel name (e.g., "E-commerce Website") |
code |
Char | No | Short code for identification (e.g., "ECOM") |
Configuration Fields¶
| Field | Type | Description |
|---|---|---|
pricelist_id |
Many2One | Default price list for this channel |
API Methods¶
1. Create Sales Channel¶
Method: create(vals, context)
Example:
# Create an e-commerce channel
channel_id = get_model("sale.channel").create({
"name": "E-commerce Website",
"code": "ECOM",
"pricelist_id": online_pricelist_id
})
2. Search Channels¶
# Find channel by code
channel_ids = get_model("sale.channel").search([["code", "=", "ECOM"]])
# Get channel with price list
channel = get_model("sale.channel").browse(channel_id)
pricelist = channel.pricelist_id
Related Models¶
| Model | Relationship | Description |
|---|---|---|
sale.order |
Referenced by | Orders reference sales channel via sale_channel_id |
price.list |
Many2One | Each channel can have default pricing |
Common Use Cases¶
Use Case 1: Initial Channel Setup¶
# Create standard sales channels
channels = [
{"name": "Retail Stores", "code": "RETAIL", "pricelist_id": retail_price_id},
{"name": "E-commerce", "code": "ECOM", "pricelist_id": online_price_id},
{"name": "Wholesale", "code": "WHOLESALE", "pricelist_id": wholesale_price_id},
{"name": "Direct Sales", "code": "DIRECT", "pricelist_id": standard_price_id}
]
for channel in channels:
get_model("sale.channel").create(channel)
Use Case 2: Channel-Based Pricing¶
# Apply channel-specific pricing when creating orders
def create_order_with_channel_pricing(customer_id, channel_code, products):
# Get channel
channel_ids = get_model("sale.channel").search([["code", "=", channel_code]])
if not channel_ids:
raise Exception(f"Channel {channel_code} not found")
channel = get_model("sale.channel").browse(channel_ids[0])
# Create order with channel's price list
order_id = get_model("sale.order").create({
"contact_id": customer_id,
"sale_channel_id": channel.id,
"price_list_id": channel.pricelist_id.id if channel.pricelist_id else None,
"lines": [("create", line) for line in products]
})
return order_id
Use Case 3: Channel Performance Reporting¶
# Sales by channel report
def get_channel_performance(date_from, date_to):
results = []
channel_ids = get_model("sale.channel").search([])
channels = get_model("sale.channel").browse(channel_ids)
for channel in channels:
# Find orders in this channel
order_ids = get_model("sale.order").search([
["sale_channel_id", "=", channel.id],
["date", ">=", date_from],
["date", "<=", date_to],
["state", "in", ["confirmed", "done"]]
])
orders = get_model("sale.order").browse(order_ids)
total_revenue = sum(o.amount_total for o in orders)
results.append({
"channel": channel.name,
"orders": len(orders),
"revenue": total_revenue
})
return results
Use Case 4: Multi-Channel Customer¶
# Track customer purchases across channels
def get_customer_channel_history(customer_id):
# Find all orders for customer
order_ids = get_model("sale.order").search([
["contact_id", "=", customer_id]
])
orders = get_model("sale.order").browse(order_ids)
# Group by channel
by_channel = {}
for order in orders:
channel_name = order.sale_channel_id.name if order.sale_channel_id else "No Channel"
if channel_name not in by_channel:
by_channel[channel_name] = {
"count": 0,
"total": 0
}
by_channel[channel_name]["count"] += 1
by_channel[channel_name]["total"] += order.amount_total
return by_channel
Use Case 5: Channel-Specific Promotions¶
# Apply channel-specific discounts
def apply_channel_discount(order_id):
order = get_model("sale.order").browse(order_id)
if not order.sale_channel_id:
return
# Different discounts per channel
channel_discounts = {
"ECOM": 0.05, # 5% online discount
"WHOLESALE": 0.15, # 15% wholesale discount
"PARTNER": 0.20 # 20% partner discount
}
channel_code = order.sale_channel_id.code
discount = channel_discounts.get(channel_code, 0)
if discount > 0:
# Apply discount to order lines
for line in order.lines:
line_discount = line.amount * discount
# Update line with discount
Best Practices¶
1. Naming Conventions¶
# Good: Clear, business-oriented names
{"name": "E-commerce Website", "code": "ECOM"}
{"name": "Retail Stores", "code": "RETAIL"}
{"name": "Partner Network", "code": "PARTNER"}
# Bad: Technical or vague names
{"name": "Channel 1", "code": "CH1"}
{"name": "Online", "code": "online"} # Inconsistent casing
Guidelines: - Use business-friendly channel names - Uppercase codes for consistency - Be specific (not just "Online" but "E-commerce Website")
2. Price List Assignment¶
# Good: Each channel has appropriate pricing
channels_pricing = [
{"name": "Retail", "pricelist": "Retail Price List"},
{"name": "E-commerce", "pricelist": "Online Promotional Prices"},
{"name": "Wholesale", "pricelist": "Bulk Pricing"}
]
# Each channel targets different customer segments with appropriate pricing
3. Channel Organization¶
Start with core channels: - Physical retail - Online/E-commerce - Wholesale/Distribution
Add specialized channels only when needed: - Marketplace platforms - Partner/reseller programs - Corporate direct sales
4. Data Governance¶
Channel ownership: - Sales operations manager owns channel master data - Marketing reviews channel definitions quarterly - Finance validates price list assignments
Regular review: - Analyze channel performance monthly - Retire underperforming channels - Update pricing strategies per channel
Performance Tips¶
1. Cache Channel Lookups¶
_channel_cache = {}
def get_channel_by_code(code):
if code not in _channel_cache:
ids = get_model("sale.channel").search([["code", "=", code]])
if ids:
_channel_cache[code] = ids[0]
return _channel_cache.get(code)
2. Use Codes for Integration¶
# API or external systems should use codes, not names
channel_code = "ECOM" # Stable identifier
# Not: channel_name = "E-commerce" # May change
Troubleshooting¶
"Orders missing channel information"¶
Cause: Channel not set on order creation. Solution:
# Set default channel based on order source
def set_default_channel(order_id, source):
channel_mapping = {
"website": "ECOM",
"pos": "RETAIL",
"api": "PARTNER"
}
channel_code = channel_mapping.get(source)
if channel_code:
channel_ids = get_model("sale.channel").search([["code", "=", channel_code]])
if channel_ids:
get_model("sale.order").write([order_id], {
"sale_channel_id": channel_ids[0]
})
Security Considerations¶
Permission Model¶
- All sales users need read access
- Sales managers can create/modify channels
- Finance can update price list assignments
Data Access¶
- Channels are company-wide master data
- No row-level security typically needed
Integration Points¶
Internal Modules¶
- sale.order: Orders track which channel they came from
- price.list: Each channel can have default pricing
- report: Channel analysis and performance metrics
Version History¶
Last Updated: 2026-01-05 Model Version: sale_channel.py Framework: Netforce
Additional Resources¶
- Sales Order Documentation:
sale.order - Price List Configuration:
price.list - Channel Analytics Guide
This documentation is generated for developer onboarding and reference purposes.