Sale Coupon Master Documentation¶
Overview¶
The Sale Coupon Master module (sale.coupon.master) serves as a template system for generating individual customer coupons. It defines the coupon campaign parameters, visual assets, eligibility criteria, and automated generation rules. Each master can generate multiple individual sale.coupon instances distributed to qualified customers.
Model Information¶
Model Name: sale.coupon.master
Display Name: Coupon Master
Key Fields: None (standard ID-based identification)
Features¶
- Template-based coupon generation
- Customer segmentation (categories and groups)
- Visual asset management (banners and images)
- Time-based expiration and hiding
- Usage duration control
- Bulk coupon creation for eligible customers
- Integration with promotion system
Coupon Master vs Individual Coupon¶
Understanding the relationship between these two models:
sale.coupon.master (Template)
|
├─> sale.coupon (Instance 1 - Customer A)
├─> sale.coupon (Instance 2 - Customer B)
├─> sale.coupon (Instance 3 - Customer C)
└─> ... (Many individual coupons)
Coupon Master: - Defines the coupon campaign - Stores visual assets and instructions - Determines who is eligible - Can generate many individual coupons
Individual Coupon (sale.coupon): - Unique code for one customer - Can be redeemed once - Tracks usage state
State Workflow¶
While there's no explicit state field, the coupon master uses active flag:
| Status | Description |
|---|---|
active: True |
Coupon master and generated coupons are visible to customers |
active: False |
Hidden from customers (expired or deactivated) |
Key Fields Reference¶
Basic Information Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
Char | Yes | Coupon campaign name |
description |
Text | No | Detailed description of the coupon offer |
instructions |
Text | No | How to use the coupon instructions |
notes |
Text | No | Internal notes about the campaign |
active |
Boolean | Yes | Whether coupon is active/visible |
Visual Asset Fields¶
| Field | Type | Description |
|---|---|---|
banner_image |
File | Banner image for coupon display (e.g., website header) |
image |
File | Main coupon image (e.g., coupon card visual) |
Time Management Fields¶
| Field | Type | Description |
|---|---|---|
expiry_date |
DateTime | When generated coupons expire |
hide_date |
DateTime | When to hide coupons from customer view |
use_duration |
Integer | Minutes customer has to use coupon after activation |
Customer Targeting Fields¶
| Field | Type | Description |
|---|---|---|
contact_categs |
Many2Many | Customer categories eligible for coupons |
contact_groups |
Many2Many | Customer groups eligible for coupons |
Relationship Fields¶
| Field | Type | Description |
|---|---|---|
coupons |
One2Many | Individual coupons generated from this master |
promotions |
One2Many | Promotions that require coupons from this master |
API Methods¶
1. Create Coupon Master¶
Method: create(vals, context)
Creates a new coupon master template.
Parameters:
vals = {
"name": "Holiday Special", # Required: campaign name
"description": "20% off your next order", # Optional: offer description
"instructions": "Enter code at checkout", # Optional: usage instructions
"active": True, # Required: visibility status
"expiry_date": "2026-12-31 23:59:59", # Optional: expiration
"use_duration": 60, # Optional: 60 minutes to use
"hide_date": "2027-01-01 00:00:00", # Optional: when to hide
"contact_categs": [ # Optional: eligible categories
("set", [vip_categ_id, premium_categ_id])
],
"contact_groups": [ # Optional: eligible groups
("set", [loyalty_group_id])
]
}
Returns: int - New coupon master ID
Example:
# Create a VIP customer coupon campaign
master_id = get_model("sale.coupon.master").create({
"name": "VIP Exclusive Coupon - Q1 2026",
"description": "Thank you for being a valued customer! Enjoy 25% off your next purchase.",
"instructions": "Show this coupon code at checkout. One-time use only.",
"active": True,
"expiry_date": "2026-03-31 23:59:59",
"hide_date": "2026-04-01 00:00:00",
"use_duration": 120, # 2 hours after activation
"contact_categs": [("set", [vip_category_id])]
})
2. Generate Coupons for Customers¶
Method: create_coupons(ids, context)
Generates individual coupon instances for all customers matching the eligibility criteria.
Parameters:
- ids (list): Coupon master IDs to generate coupons from
Behavior:
- Searches for all contacts matching contact_categs criteria
- Searches for all contacts in contact_groups
- Creates one sale.coupon instance per eligible customer
- Assigns unique coupon codes automatically
- Copies expiry_date, use_duration, and hide_date from master
- Returns success message with count
Returns: dict - Flash message with count of coupons created
Example:
# Generate coupons for all VIP customers
result = get_model("sale.coupon.master").create_coupons([master_id])
# Returns: {"flash": "150 coupons created"}
# This creates 150 individual sale.coupon records, one per VIP customer
Error Handling:
# If no criteria specified
try:
result = get_model("sale.coupon.master").create_coupons([master_id])
except Exception as e:
# Raises: "Missing customer category or groups"
pass
# If no customers match criteria
try:
result = get_model("sale.coupon.master").create_coupons([master_id])
except Exception as e:
# Raises: "No contact matches criteria"
pass
3. Create Individual Coupon¶
Method (on sale.coupon model): create_individual_coupon(master_ids, context)
Creates coupons for specific customers (typically triggered by workflow/automation).
Context Options:
Behavior: - Accepts list of master IDs and customer IDs - Creates one coupon per master per customer - Useful for event-driven coupon creation (e.g., new customer signup)
Example:
# When a new customer signs up, give them a welcome coupon
new_customer_id = 456
get_model("sale.coupon").create_individual_coupon(
[welcome_coupon_master_id],
context={"trigger_ids": [new_customer_id]}
)
# Creates one welcome coupon for the new customer
Search Functions¶
Find Active Coupon Masters¶
# Get all active coupon campaigns
active_masters = get_model("sale.coupon.master").search([
["active", "=", True]
])
Find Coupon Masters by Customer Category¶
# Find coupons available to VIP customers
vip_masters = get_model("sale.coupon.master").search([
["contact_categs.id", "in", [vip_categ_id]],
["active", "=", True]
])
Find Unexpired Coupon Masters¶
# Find coupon masters not yet expired
from datetime import datetime
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
valid_masters = get_model("sale.coupon.master").search([
["active", "=", True],
["expiry_date", ">=", now]
])
Related Models¶
| Model | Relationship | Description |
|---|---|---|
sale.coupon |
One2Many | Individual coupon instances generated from this master |
sale.promotion |
One2Many | Promotions that require coupons from this master |
contact.categ |
Many2Many | Customer categories eligible for coupons |
contact.group |
Many2Many | Customer groups eligible for coupons |
contact |
Indirect (via sale.coupon) | Customers who receive individual coupons |
Common Use Cases¶
Use Case 1: VIP Customer Exclusive Coupon Campaign¶
# Create a premium coupon campaign for top-tier customers
# 1. Get VIP customer category
vip_categ_id = get_model("contact.categ").search([["name", "=", "VIP"]])[0]
# 2. Create coupon master
master_id = get_model("sale.coupon.master").create({
"name": "VIP Platinum Card - 30% Off",
"description": "Exclusive offer for our VIP members: 30% off any purchase",
"instructions": "Present this coupon code at checkout. Valid for 30 days from receipt.",
"notes": "Q1 2026 VIP retention campaign",
"active": True,
"expiry_date": "2026-03-31 23:59:59",
"hide_date": "2026-04-01 00:00:00",
"contact_categs": [("set", [vip_categ_id])]
})
# 3. Generate individual coupons for all VIP customers
result = get_model("sale.coupon.master").create_coupons([master_id])
print(result) # {"flash": "250 coupons created"}
# 4. Each VIP customer now has a unique coupon code in their account
Use Case 2: Time-Limited Flash Coupon¶
# Create a flash coupon valid for only 2 hours after customer activates it
master_id = get_model("sale.coupon.master").create({
"name": "Flash Sale - 2 Hour Coupon",
"description": "Activate this coupon and you have 2 hours to complete your purchase!",
"instructions": "Click 'Use Coupon' to activate. Must complete purchase within 2 hours.",
"active": True,
"use_duration": 120, # 2 hours (120 minutes)
"expiry_date": "2026-06-15 23:59:59", # Campaign ends June 15
"contact_groups": [("set", [newsletter_subscriber_group_id])]
})
# Generate coupons
get_model("sale.coupon.master").create_coupons([master_id])
# When customer activates their coupon:
# - Coupon state changes to "in_use"
# - expiry_date is set to current_time + 120 minutes
# - Customer has 2 hours to complete purchase
Use Case 3: New Customer Welcome Coupon (Automated)¶
# Setup: Create welcome coupon master (done once)
welcome_master_id = get_model("sale.coupon.master").create({
"name": "Welcome Gift - 15% Off First Order",
"description": "Welcome to our store! Enjoy 15% off your first purchase.",
"instructions": "Use this code at checkout on your first order.",
"active": True,
"expiry_date": "2026-12-31 23:59:59", # Valid all year
"use_duration": None # No time limit after activation
})
# In your customer registration workflow:
def on_customer_registered(customer_id):
# Automatically create a welcome coupon for the new customer
get_model("sale.coupon").create_individual_coupon(
[welcome_master_id],
context={"trigger_ids": [customer_id]}
)
# Customer immediately receives their welcome coupon
Use Case 4: Birthday Coupon Campaign¶
# Monthly task: Generate birthday coupons for customers with birthdays this month
# 1. Create birthday coupon master (done once)
birthday_master_id = get_model("sale.coupon.master").create({
"name": "Happy Birthday - Special Gift",
"description": "Happy Birthday! Enjoy a special 20% discount on us!",
"instructions": "Valid for 30 days from your birthday.",
"active": True,
"use_duration": None
})
# 2. Monthly job: Find customers with birthdays this month
import datetime
today = datetime.date.today()
month_start = today.replace(day=1)
month_end = (month_start + datetime.timedelta(days=32)).replace(day=1) - datetime.timedelta(days=1)
birthday_customers = get_model("contact").search([
# Assuming birthdate field exists
["birthdate", ">=", month_start.strftime("%Y-%m-%d")],
["birthdate", "<=", month_end.strftime("%Y-%m-%d")]
])
# 3. Generate individual coupons for birthday customers
for customer_id in birthday_customers:
# Set expiry to 30 days from their birthday
customer = get_model("contact").browse(customer_id)
expiry = customer.birthdate + datetime.timedelta(days=30)
get_model("sale.coupon").create({
"master_id": birthday_master_id,
"contact_id": customer_id,
"expiry_date": expiry.strftime("%Y-%m-%d 23:59:59")
})
Use Case 5: Segmented Coupon Campaign¶
# Different coupon campaigns for different customer segments
# High-value customers: 25% off
high_value_categ = get_model("contact.categ").search([["name", "=", "High Value"]])[0]
high_value_master = get_model("sale.coupon.master").create({
"name": "Premium Customer Appreciation - 25% Off",
"description": "Thank you for your continued support! 25% off your next order.",
"active": True,
"expiry_date": "2026-06-30 23:59:59",
"contact_categs": [("set", [high_value_categ])]
})
# Regular customers: 15% off
regular_categ = get_model("contact.categ").search([["name", "=", "Regular"]])[0]
regular_master = get_model("sale.coupon.master").create({
"name": "Customer Loyalty - 15% Off",
"description": "We value your business! Enjoy 15% off your next purchase.",
"active": True,
"expiry_date": "2026-06-30 23:59:59",
"contact_categs": [("set", [regular_categ])]
})
# Generate coupons for both segments
get_model("sale.coupon.master").create_coupons([high_value_master]) # 25% for high-value
get_model("sale.coupon.master").create_coupons([regular_master]) # 15% for regular
Use Case 6: Coupon with Visual Assets for Mobile App¶
# Create coupon with banner and card images for mobile app display
import base64
# Load images (example - in practice, use proper file handling)
with open("/path/to/banner.png", "rb") as f:
banner_data = base64.b64encode(f.read()).decode()
with open("/path/to/coupon_card.png", "rb") as f:
card_data = base64.b64encode(f.read()).decode()
master_id = get_model("sale.coupon.master").create({
"name": "Spring Sale 2026",
"description": "Spring into savings with 20% off!",
"instructions": "Show this coupon in-store or enter code online",
"banner_image": banner_data, # Displayed in app header
"image": card_data, # Displayed as coupon card
"active": True,
"expiry_date": "2026-05-31 23:59:59",
"contact_groups": [("set", [mobile_app_users_group_id])]
})
# Generate coupons - customers see branded images in mobile app
get_model("sale.coupon.master").create_coupons([master_id])
Best Practices¶
1. Clear Campaign Naming¶
# Good: Descriptive names with date/purpose
get_model("sale.coupon.master").create({
"name": "Q1 2026 VIP Retention - 30% Off",
"description": "Quarterly loyalty reward for top-tier customers"
})
# Bad: Vague names
get_model("sale.coupon.master").create({
"name": "Coupon 1", # Not helpful
"description": "Discount"
})
2. Set Expiration Dates¶
# Good: Always set expiry dates to prevent indefinite coupons
get_model("sale.coupon.master").create({
"name": "Summer Sale Coupon",
"expiry_date": "2026-08-31 23:59:59", # Clear end date
"hide_date": "2026-09-01 00:00:00" # Hide after expiry
})
# Risky: No expiry date
get_model("sale.coupon.master").create({
"name": "Summer Sale Coupon",
# No expiry - coupon valid forever!
})
3. Use Customer Segmentation¶
# Good: Target specific customer segments
get_model("sale.coupon.master").create({
"name": "Inactive Customer Win-Back",
"contact_groups": [("set", [inactive_customers_group_id])],
"description": "We miss you! Come back with 30% off"
})
# Less effective: Blanket coupons to everyone
get_model("sale.coupon.master").create({
"name": "Generic Coupon",
# No targeting - everyone gets it, less special
})
4. Provide Clear Instructions¶
# Good: Detailed instructions
get_model("sale.coupon.master").create({
"name": "Mobile App Exclusive",
"description": "20% off your next in-app purchase",
"instructions": "1. Open mobile app\n"
"2. Add items to cart\n"
"3. Go to checkout\n"
"4. Tap 'Apply Coupon'\n"
"5. Select this coupon\n"
"6. Complete purchase within 24 hours of activation",
"use_duration": 1440 # 24 hours
})
5. Use Notes for Internal Tracking¶
# Good: Document campaign purpose and tracking
get_model("sale.coupon.master").create({
"name": "Black Friday 2026",
"notes": "Campaign ID: BF2026-001\n"
"Budget: $50,000\n"
"Expected redemption rate: 25%\n"
"Marketing channel: Email + SMS\n"
"Owner: Marketing Team - Alice Smith",
"description": "Black Friday exclusive: 40% off everything!"
})
Performance Tips¶
1. Batch Coupon Generation¶
# The create_coupons method handles batching automatically
# It creates all coupons in one operation
# Don't do this:
customers = get_model("contact").search([["categ_id", "=", vip_categ]])
for customer_id in customers:
get_model("sale.coupon").create({
"master_id": master_id,
"contact_id": customer_id
}) # Many database calls
# Do this instead:
get_model("sale.coupon.master").create_coupons([master_id])
# Single operation, much faster
2. Index Customer Segments¶
Ensure contact.categ and contact.group are properly indexed for fast customer searches during coupon generation.
3. Cleanup Expired Coupons¶
# Scheduled task: Hide expired coupons
from datetime import datetime
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Find masters that should be hidden
expired_masters = get_model("sale.coupon.master").search([
["active", "=", True],
["hide_date", "<=", now]
])
# Deactivate them
if expired_masters:
get_model("sale.coupon.master").write(expired_masters, {"active": False})
Troubleshooting¶
"No coupons created when calling create_coupons"¶
Cause: No customers match the eligibility criteria
Solution:
- Check that contact_categs or contact_groups has values
- Verify customers exist in those categories/groups
- Examine error message: "Missing customer category or groups" or "No contact matches criteria"
"Coupons expiring too quickly"¶
Cause: use_duration set too short
Solution:
- use_duration is in minutes
- 60 = 1 hour, 1440 = 24 hours, 10080 = 1 week
- Set appropriately for your campaign
"Customers can't see their coupons"¶
Cause: Master or individual coupons are inactive or hidden
Solution:
- Check active field is True on coupon master
- Check individual coupon active field is True
- Verify hide_date hasn't passed
- Check customer UI is querying active coupons only
Security Considerations¶
Permission Model¶
- Create coupon masters: Marketing/sales management only
- Generate coupons: Authorized staff only (can create many coupons)
- View masters: Internal staff
Data Access¶
- Customers can only see their own individual coupons
- Master templates are internal only
- Coupon codes are unique and hard to guess
Integration Points¶
Internal Modules¶
- sale.coupon: Individual coupon instances generated from masters
- sale.promotion: Promotions that require coupons from specific masters
- contact: Customer database for eligibility and coupon assignment
- contact.categ: Customer category segmentation
- contact.group: Customer group segmentation
External Integration¶
- Mobile Apps: Visual assets (banner_image, image) for app display
- Email Marketing: Send coupon codes via email campaigns
- CRM Systems: Trigger coupon creation on customer lifecycle events
Version History¶
Last Updated: 2026-01-05 Model Version: sale_coupon_master.py (64 lines) Framework: Netforce
Additional Resources¶
- Sale Coupon Documentation:
sale.coupon - Sale Promotion Documentation:
sale.promotion - Contact Category Documentation:
contact.categ - Contact Group Documentation:
contact.group
Support & Feedback¶
For issues or questions about this module: 1. Check sale.coupon documentation for individual coupon behavior 2. Verify customer segmentation (categs and groups) 3. Review expiration and hiding date logic 4. Test coupon generation with small customer segment first
This documentation is generated for developer onboarding and reference purposes.