Opportunity to Quotation Wizard Documentation¶
Overview¶
The Opportunity to Quotation module (opport.to.quot) is a transient wizard model that facilitates converting sales opportunities into formal quotations. This wizard provides a user-friendly interface for selecting optional quotation templates and streamlines the opportunity-to-quotation conversion process.
Model Information¶
Model Name: opport.to.quot
Display Name: (Not specified - wizard model)
Model Type: Transient/Wizard (not persisted in database)
Features¶
- No audit logging (transient model)
- No multi-company support (inherits from opportunity)
- Wizard pattern for user-guided process
- Optional template selection for pre-configured quotations
Understanding Wizard Models¶
What is a Transient Model?¶
A transient model (wizard) is a temporary model used for user interactions that don't need permanent storage:
Characteristics: - Records exist only during user session - Used for multi-step processes or selections - Automatically cleaned up after completion - Provides guided user experience
Common Uses: - Data import wizards - Record conversion tools - Batch operation dialogs - Report parameter selection
The Conversion Pattern¶
This wizard implements a common pattern seen throughout Netforce:
Source Record (Opportunity)
↓
Wizard (opport.to.quot) - User selects options
↓
Target Record (Quotation) - Created with configuration
Similar wizards:
- convert.lead - Lead to Opportunity
- import.wizard - Data import
- report.wizard - Report generation
Key Fields Reference¶
Wizard Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
opport_id |
Many2One | No | Source opportunity to convert |
template_id |
Many2One | No | Optional quotation template to use |
Field Details¶
opport_id: - Link to the sales opportunity being converted - Defaults from context when wizard is opened from opportunity form - Provides source data for quotation creation
template_id:
- Reference to pre-configured quotation template
- Condition: [["is_template", "=", True]] - only shows template quotations
- When selected, copies template structure instead of creating basic quotation
- Optional - if not selected, creates simple quotation from opportunity data
API Methods¶
1. Create Wizard Instance¶
Method: create(vals, context)
Creates wizard instance (automatically called when opening wizard).
Default Behavior:
When wizard is opened from an opportunity form, the opportunity ID is automatically populated from the refer_id context variable.
Example:
# Wizard automatically created with opportunity context
# User opens wizard from opportunity form
# System creates wizard with opport_id pre-filled
2. Create Quotation¶
Method: create_quot(ids, context)
Executes the conversion from opportunity to quotation.
Parameters:
- ids (list): Wizard record ID (single ID)
- context (dict): Execution context
Behavior:
1. Retrieves wizard record
2. Gets associated opportunity
3. Calls opportunity's copy_to_quotation() method
4. Passes template_id if specified
5. Returns navigation to new quotation
Returns: dict - Navigation action to newly created quotation
{
"next": {
"name": "quot",
"mode": "form",
"active_id": quot_id
},
"flash": "Quotation QUOT-0123 created from opportunity"
}
Example:
# Called when user clicks "Create Quotation" button
wizard = get_model("opport.to.quot").browse(wizard_id)
result = wizard.create_quot([wizard_id])
# System navigates to new quotation form automatically
# Flash message confirms creation
Conversion Logic¶
Without Template¶
When no template is selected, the opportunity's copy_to_quotation() creates a basic quotation:
Field Mapping:
Quotation Fields ← Opportunity Fields
----------------- ------------------
opport_id ← opportunity.id
ref ← opportunity.number
contact_id ← opportunity.contact_id
user_id ← opportunity.user_id
# If product specified:
lines[0].product_id ← opportunity.product_id
lines[0].qty ← opportunity.qty (or 1)
lines[0].unit_price ← opportunity.amount or product.sale_price
lines[0].uom_id ← product.uom_id
lines[0].description ← product name
With Template¶
When template is selected, quotation is created by copying template:
Process:
1. Copy entire quotation template (preserving all lines, terms, etc.)
2. Update header fields with opportunity data:
- opport_id ← opportunity ID
- ref ← opportunity number
- contact_id ← opportunity contact
- user_id ← opportunity owner
Benefits: - Pre-configured product lines - Standard terms and conditions - Consistent pricing and discounts - Company-specific quotation formats - Faster quotation generation
Related Models¶
| Model | Relationship | Description |
|---|---|---|
sale.opportunity |
Many2One | Source opportunity being converted |
sale.quot |
Via method | Target quotation created |
sale.quot (template) |
Many2One | Optional template for structured quotations |
Common Use Cases¶
Use Case 1: Simple Opportunity Conversion¶
# User workflow:
# 1. Open opportunity form
# 2. Click "Convert to Quotation" button
# 3. Wizard opens with opportunity pre-selected
# 4. User leaves template blank (simple quotation)
# 5. User clicks "Create"
# Behind the scenes:
opport_id = 123
# System creates wizard with context
wizard_id = get_model("opport.to.quot").create(
{},
context={"refer_id": opport_id}
)
# Wizard has opport_id = 123 from default
# User clicks Create button
result = get_model("opport.to.quot").create_quot([wizard_id])
# Creates basic quotation:
# - Header from opportunity
# - Single line if product specified
# - Links back to opportunity
# User is navigated to quotation form
Use Case 2: Template-Based Quotation¶
# Scenario: Standard product quotation with terms
# Company has quotation templates for common offerings
# User workflow:
# 1. Open opportunity for "Enterprise License" deal
# 2. Click "Convert to Quotation"
# 3. Wizard opens
# 4. User selects "Enterprise License Template"
# 5. User clicks "Create"
# Template quotation contains:
# - Standard product bundle lines
# - Volume-based pricing tiers
# - Payment terms (30% upfront, 70% on delivery)
# - Warranty and support terms
# - Company-specific T&Cs
# Behind the scenes:
wizard_id = get_model("opport.to.quot").create({
"template_id": 15 # "Enterprise License Template"
}, context={"refer_id": opport_id})
result = get_model("opport.to.quot").create_quot([wizard_id])
# Creates quotation by:
# 1. Copying template quotation (all lines, terms)
# 2. Updating header with opportunity data
# 3. Linking to opportunity
# Result: Professional quotation ready to send
# Only needs customer-specific customization
Use Case 3: Multiple Quotations from One Opportunity¶
# Scenario: Provide different options to customer
# Create first quotation - Basic package
wizard_id = get_model("opport.to.quot").create({
"template_id": 10 # "Basic Package"
}, context={"refer_id": opport_id})
result1 = get_model("opport.to.quot").create_quot([wizard_id])
basic_quot_id = result1["next"]["active_id"]
# Create second quotation - Premium package
wizard_id = get_model("opport.to.quot").create({
"template_id": 11 # "Premium Package"
}, context={"refer_id": opport_id})
result2 = get_model("opport.to.quot").create_quot([wizard_id])
premium_quot_id = result2["next"]["active_id"]
# Create third quotation - Enterprise package
wizard_id = get_model("opport.to.quot").create({
"template_id": 12 # "Enterprise Package"
}, context={"refer_id": opport_id})
result3 = get_model("opport.to.quot").create_quot([wizard_id])
enterprise_quot_id = result3["next"]["active_id"]
# All three quotations linked to same opportunity
# Customer can compare options
# Track which quotation is accepted
Use Case 4: Programmatic Conversion¶
# Automated quotation generation (e.g., from workflow)
def auto_convert_high_value_opportunities():
"""
Automatically create quotations for opportunities
over $100K that reach "Proposal" stage
"""
# Find qualifying opportunities
opports = get_model("sale.opportunity").search_browse([
["stage_id.name", "=", "Proposal"],
["amount", ">", 100000],
["quotations", "=", []] # No quotations yet
])
for opport in opports:
# Determine appropriate template
if opport.amount > 500000:
template_name = "Enterprise Template"
elif opport.amount > 250000:
template_name = "Corporate Template"
else:
template_name = "Standard Template"
# Find template
templates = get_model("sale.quot").search([
["name", "=", template_name],
["is_template", "=", True]
])
if templates:
template_id = templates[0]
else:
template_id = None
# Create quotation via wizard
wizard_id = get_model("opport.to.quot").create({
"template_id": template_id
}, context={"refer_id": opport.id})
result = get_model("opport.to.quot").create_quot([wizard_id])
print(f"Created quotation for opportunity {opport.number}: "
f"{result['flash']}")
Best Practices¶
1. Use Templates for Standardization¶
# Bad: Always creating blank quotations
# Requires manual entry of standard terms, pricing, etc.
# Good: Create templates for common scenarios
templates_to_create = [
{
"name": "Software License - Small Business",
"is_template": True,
"lines": [
# Standard product bundle
# Pre-configured pricing
# Volume discounts
],
"payment_terms": "Net 30",
"notes": "Standard T&Cs for SMB customers"
},
{
"name": "Software License - Enterprise",
"is_template": True,
"lines": [
# Enterprise product bundle
# Enterprise pricing
# Custom integration hours
],
"payment_terms": "30% upfront, 70% on delivery",
"notes": "Enterprise T&Cs with SLA guarantees"
}
]
# Sales reps use appropriate template
# Ensures consistency and saves time
2. Name Templates Clearly¶
# Bad: Unclear template names
"Template 1"
"Template A"
"New Template"
# Good: Descriptive template names
"Enterprise Software License - Standard Terms"
"SMB Package - Monthly Subscription"
"Professional Services - Hourly Rate"
"Product Bundle - Annual Contract"
# Benefits:
# - Easy to find right template
# - Self-documenting
# - Reduces errors
3. Link Quotations Back to Opportunities¶
# The wizard automatically links quotation to opportunity
# This enables tracking:
opport = get_model("sale.opportunity").browse(opport_id)
# View all quotations for this opportunity
for quot in opport.quotations:
print(f"Quotation: {quot.number}")
print(f" Amount: ${quot.amount_total}")
print(f" State: {quot.state}")
print(f" Validity: {quot.expiration_date}")
# Track conversion rate
opports_with_quots = get_model("sale.opportunity").search_count([
["quotations", "!=", []]
])
total_opports = get_model("sale.opportunity").search_count([])
conversion_rate = opports_with_quots / total_opports * 100
print(f"Quotation conversion rate: {conversion_rate:.1f}%")
4. Update Opportunity After Quotation Sent¶
# After creating and sending quotation
result = get_model("opport.to.quot").create_quot([wizard_id])
quot_id = result["next"]["active_id"]
# Update opportunity to reflect progress
get_model("sale.opportunity").write([opport_id], {
"stage_id": next_stage_id, # Move to "Proposal Sent" stage
"probability": 70, # Increase probability
"next_step": "Follow up on quotation in 3 days"
})
# Log activity
get_model("sale.activ").create({
"type": "task",
"subject": "Follow up on quotation",
"date": (datetime.today() + timedelta(days=3)).strftime("%Y-%m-%d"),
"related_id": f"sale.opportunity,{opport_id}",
"user_id": user_id,
"description": f"Follow up on quotation {quot.number}"
})
Performance Tips¶
1. Template Caching¶
# Bad: Searching for template every time
wizard_id = get_model("opport.to.quot").create({
"template_id": get_model("sale.quot").search([
["name", "=", "Standard Template"],
["is_template", "=", True]
])[0]
})
# Good: Cache template IDs
class QuotationConverter:
_template_cache = {}
@classmethod
def get_template_id(cls, template_name):
if template_name not in cls._template_cache:
templates = get_model("sale.quot").search([
["name", "=", template_name],
["is_template", "=", True]
])
cls._template_cache[template_name] = templates[0] if templates else None
return cls._template_cache[template_name]
# Use cached lookup
template_id = QuotationConverter.get_template_id("Standard Template")
2. Batch Conversions¶
# When converting multiple opportunities
opport_ids = [123, 124, 125]
for opport_id in opport_ids:
wizard_id = get_model("opport.to.quot").create(
{"template_id": template_id},
context={"refer_id": opport_id}
)
get_model("opport.to.quot").create_quot([wizard_id])
# Wizard records automatically cleaned up
Troubleshooting¶
Wizard Not Pre-Filling Opportunity¶
Cause: Missing refer_id in context when opening wizard
Solution: Ensure wizard is called with proper context
# From opportunity form button:
{
"action": "opport_to_quot",
"refer_id": "active_id" # This passes opportunity ID
}
# Programmatically:
wizard_id = get_model("opport.to.quot").create(
{},
context={"refer_id": opport_id}
)
Template Not Available in Selection¶
Cause: Quotation not marked as template
Solution: Ensure quotation has is_template = True
# Verify template flag
quot = get_model("sale.quot").browse(quot_id)
if not quot.is_template:
quot.write({"is_template": True})
# Or create proper template
template_id = get_model("sale.quot").create({
"name": "Standard Template",
"is_template": True,
"contact_id": demo_contact_id, # Dummy contact
"lines": [
# Template lines
]
})
Quotation Not Linking to Opportunity¶
Cause: Error in conversion process or missing opportunity reference
Solution: Verify quotation has opport_id field set
# Check quotation link
quot = get_model("sale.quot").browse(quot_id)
if not quot.opport_id:
# Manually link if needed
quot.write({"opport_id": opport_id})
# Verify from opportunity side
opport = get_model("sale.opportunity").browse(opport_id)
print(f"Linked quotations: {len(opport.quotations)}")
Configuration Settings¶
Required Settings¶
| Setting | Location | Description |
|---|---|---|
| Quotation Templates | sale.quot |
Create template quotations with is_template = True |
Optional Settings¶
| Setting | Default | Description |
|---|---|---|
| Default Template | None | Can be configured per opportunity type/stage |
| Template Categories | None | Organize templates by product line or customer segment |
Integration Points¶
Internal Modules¶
- Opportunity Management: Source data from
sale.opportunity - Quotation Management: Creates records in
sale.quot - Template System: Uses quotation templates for standardization
- UI Framework: Wizard pattern for guided user experience
Version History¶
Last Updated: 2026-01-05 Model Version: opport_to_quot.py Framework: Netforce
Additional Resources¶
- Opportunity Documentation:
sale.opportunity - Quotation Documentation:
sale.quot - Quotation Template Configuration
- Lead Conversion Wizard:
convert.lead(similar pattern)
Support & Feedback¶
For issues or questions about this module: 1. Verify quotation templates are properly configured 2. Check that wizard is called with correct context 3. Ensure opportunity has required fields populated 4. Review quotation creation for proper field mapping 5. Test template-based and non-template conversions
This documentation is generated for developer onboarding and reference purposes.