Skip to content

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:

active: True  ←→  active: False
   (visible)       (hidden)
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:

context = {
    "trigger_ids": [customer_id1, customer_id2],  # Customer IDs
}

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]
])

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.