Developer Prompt: SAM AI Funnels - Phase 1 (Foundation)
Developer Prompt: SAM AI Funnels - Phase 1 (Foundation)
Date: 2025-12-31
Phase: 1 of 7
Scope: Module skeleton, core models, basic views, security
CONTEXT
You are implementing Phase 1 of the SAM AI Funnels module. This module adds a "FUNNELS" tab to Odoo 18's website builder, enabling drag-and-drop sales funnel creation with SAM AI integration.
Architecture Document: D:\SAMAI-18-SaaS\ai_sam\ai_sam_docs\plans\2025-12-31_sam-ai-funnels-architecture.md
Requirements Document: D:\SAMAI-18-SaaS\The SAM Sales System\Gold Star Direction\FUNNELS-TAB-REQUIREMENTS.md
GOAL
Create the foundational module structure with 5 core data models, basic views, menus, and security rules. No snippets or website builder integration yet - that's Phase 2.
MODULE LOCATION
D:\SAMAI-18-SaaS\ai_sam\sam_ai_funnels\
This is a NEW module in the SAM AI ecosystem, alongside:
- ai_brain (data layer)
- ai_sam (framework)
- ai_sam_workflows (workflow automation)
- etc.
DELIVERABLES
1. Module Structure
Create this file structure:
sam_ai_funnels/
├── __manifest__.py
├── __init__.py
├── models/
│ ├── __init__.py
│ ├── funnel_definition.py
│ ├── funnel_page.py
│ ├── funnel_template.py
│ ├── funnel_snippet.py
│ └── funnel_conversion.py
├── controllers/
│ ├── __init__.py
│ └── funnel_controller.py # Placeholder for now
├── views/
│ ├── funnel_definition_views.xml
│ ├── funnel_page_views.xml
│ ├── funnel_template_views.xml
│ ├── funnel_snippet_views.xml
│ └── funnel_menus.xml
├── data/
│ └── funnel_snippet_data.xml # Initial snippet registry
├── static/
│ └── description/
│ └── icon.png # Copy from ai_sam module
└── security/
└── ir.model.access.csv
2. manifest.py
{
'name': 'SAM AI Funnels',
'version': '18.0.1.0.0',
'category': 'Website',
'summary': 'Sales funnel builder with SAM AI integration',
'description': """
SAM AI Funnels - Sales Funnel Builder
=====================================
Adds a FUNNELS tab to the Odoo 18 website builder with:
- 46 drag-and-drop funnel snippets
- 6 complete funnel templates
- Native CRM and mailing integration
- SAM AI powered copy generation
Part of the SAM AI ecosystem.
""",
'author': 'Anthony Gardiner - Odoo Consulting & Claude AI',
'maintainer': 'Anthony Gardiner <[email protected]>',
'website': 'https://sme.ec',
'license': 'LGPL-3',
'depends': [
'website',
'crm',
'mass_mailing',
'ai_brain',
'ai_sam',
],
'data': [
# Security
'security/ir.model.access.csv',
# Views
'views/funnel_definition_views.xml',
'views/funnel_page_views.xml',
'views/funnel_template_views.xml',
'views/funnel_snippet_views.xml',
'views/funnel_menus.xml',
# Data
'data/funnel_snippet_data.xml',
],
'assets': {
# Will be added in Phase 2
},
'images': ['static/description/icon.png'],
'installable': True,
'auto_install': False,
'application': True,
}
3. Models
models/init.py
from . import funnel_definition
from . import funnel_page
from . import funnel_template
from . import funnel_snippet
from . import funnel_conversion
models/funnel_definition.py
from odoo import models, fields, api
class FunnelDefinition(models.Model):
_name = 'funnel.definition'
_description = 'Sales Funnel Definition'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'create_date desc'
# Core Identity
name = fields.Char('Funnel Name', required=True, tracking=True)
description = fields.Text('Description')
funnel_type = fields.Selection([
('opt_in', 'Simple Opt-in'),
('lead_magnet', 'Lead Magnet'),
('quiz', 'Quiz Funnel'),
('eoi', 'Expression of Interest'),
('product_launch', 'Product Launch'),
('webinar', 'Webinar'),
('custom', 'Custom'),
], string='Funnel Type', default='custom', tracking=True)
# Pages
page_ids = fields.One2many('funnel.page', 'funnel_id', string='Pages')
page_count = fields.Integer('Page Count', compute='_compute_page_count', store=True)
# Template Source
template_id = fields.Many2one('funnel.template', string='Based on Template')
# CRM Integration
crm_team_id = fields.Many2one('crm.team', string='Sales Team')
default_tag_ids = fields.Many2many('crm.tag', string='Lead Tags')
# Mailing Integration
mailing_list_id = fields.Many2one('mailing.list', string='Mailing List')
# Analytics (computed from conversions)
total_views = fields.Integer('Total Views', compute='_compute_analytics', store=True)
total_conversions = fields.Integer('Total Conversions', compute='_compute_analytics', store=True)
conversion_rate = fields.Float('Conversion Rate (%)', compute='_compute_analytics', store=True)
# State
state = fields.Selection([
('draft', 'Draft'),
('active', 'Active'),
('paused', 'Paused'),
('archived', 'Archived'),
], string='Status', default='draft', tracking=True)
# SAM AI Integration
canvas_id = fields.Many2one('canvas', string='Workflow Canvas')
ai_generated_copy = fields.Boolean('AI Generated Copy', default=False)
# Company
company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
@api.depends('page_ids')
def _compute_page_count(self):
for record in self:
record.page_count = len(record.page_ids)
@api.depends('page_ids.view_count', 'page_ids.form_submissions')
def _compute_analytics(self):
for record in self:
record.total_views = sum(record.page_ids.mapped('view_count'))
record.total_conversions = sum(record.page_ids.mapped('form_submissions'))
record.conversion_rate = (
(record.total_conversions / record.total_views * 100)
if record.total_views > 0 else 0.0
)
def action_activate(self):
self.write({'state': 'active'})
def action_pause(self):
self.write({'state': 'paused'})
def action_archive(self):
self.write({'state': 'archived'})
def action_draft(self):
self.write({'state': 'draft'})
models/funnel_page.py
from odoo import models, fields, api
class FunnelPage(models.Model):
_name = 'funnel.page'
_description = 'Funnel Page'
_order = 'sequence, id'
# Core Identity
name = fields.Char('Page Name', required=True)
funnel_id = fields.Many2one('funnel.definition', string='Funnel', required=True, ondelete='cascade')
sequence = fields.Integer('Sequence', default=10)
# Page Type
page_type = fields.Selection([
('squeeze', 'Squeeze Page'),
('lead_magnet', 'Lead Magnet Page'),
('sales', 'Sales Page'),
('order', 'Order/Checkout'),
('upsell', 'Upsell Page'),
('thank_you', 'Thank You Page'),
('quiz_intro', 'Quiz Introduction'),
('quiz_questions', 'Quiz Questions'),
('quiz_gate', 'Quiz Email Gate'),
('quiz_results', 'Quiz Results'),
('webinar_registration', 'Webinar Registration'),
('webinar_confirmation', 'Webinar Confirmation'),
('webinar_replay', 'Webinar Replay'),
('custom', 'Custom Page'),
], string='Page Type', default='custom')
# Website Integration
website_page_id = fields.Many2one('website.page', string='Website Page')
page_url = fields.Char('URL Slug')
full_url = fields.Char('Full URL', compute='_compute_full_url')
# Snippet Configuration (JSON)
snippet_config = fields.Text('Snippet Configuration', default='[]')
# Redirect After Action
next_page_id = fields.Many2one('funnel.page', string='Next Page', domain="[('funnel_id', '=', funnel_id)]")
redirect_url = fields.Char('External Redirect URL')
# Analytics
view_count = fields.Integer('Views', default=0)
unique_visitors = fields.Integer('Unique Visitors', default=0)
form_submissions = fields.Integer('Form Submissions', default=0)
conversion_rate = fields.Float('Conversion Rate (%)', compute='_compute_conversion_rate')
@api.depends('page_url')
def _compute_full_url(self):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url', '')
for record in self:
if record.page_url:
record.full_url = f"{base_url}/{record.page_url}"
elif record.website_page_id:
record.full_url = f"{base_url}{record.website_page_id.url}"
else:
record.full_url = False
@api.depends('view_count', 'form_submissions')
def _compute_conversion_rate(self):
for record in self:
record.conversion_rate = (
(record.form_submissions / record.view_count * 100)
if record.view_count > 0 else 0.0
)
models/funnel_template.py
from odoo import models, fields, api
import json
class FunnelTemplate(models.Model):
_name = 'funnel.template'
_description = 'Funnel Template'
_order = 'usage_count desc, name'
name = fields.Char('Template Name', required=True)
description = fields.Text('Description')
funnel_type = fields.Selection([
('opt_in', 'Simple Opt-in'),
('lead_magnet', 'Lead Magnet'),
('quiz', 'Quiz Funnel'),
('eoi', 'Expression of Interest'),
('product_launch', 'Product Launch'),
('webinar', 'Webinar'),
('custom', 'Custom'),
], string='Funnel Type', required=True)
# Template Structure (JSON)
template_structure = fields.Text('Template Structure', default='{"pages": []}')
# Preview
thumbnail = fields.Binary('Thumbnail')
preview_url = fields.Char('Preview URL')
# Usage Stats
usage_count = fields.Integer('Times Used', default=0)
# Visibility
visibility = fields.Selection([
('private', 'Private'),
('company', 'Company'),
('public', 'Public'),
], string='Visibility', default='company')
# Owner
author_id = fields.Many2one('res.users', string='Author', default=lambda self: self.env.user)
company_id = fields.Many2one('res.company', default=lambda self: self.env.company)
def action_create_funnel_from_template(self):
"""Create new funnel.definition from this template"""
self.ensure_one()
# Parse template structure
structure = json.loads(self.template_structure or '{"pages": []}')
# Create funnel
funnel = self.env['funnel.definition'].create({
'name': f"{self.name} - Copy",
'description': self.description,
'funnel_type': self.funnel_type,
'template_id': self.id,
})
# Create pages from template
for idx, page_spec in enumerate(structure.get('pages', [])):
self.env['funnel.page'].create({
'funnel_id': funnel.id,
'name': page_spec.get('name', f'Page {idx + 1}'),
'page_type': page_spec.get('page_type', 'custom'),
'sequence': (idx + 1) * 10,
'snippet_config': json.dumps(page_spec.get('snippets', [])),
})
# Increment usage count
self.usage_count += 1
# Return action to open the new funnel
return {
'type': 'ir.actions.act_window',
'res_model': 'funnel.definition',
'res_id': funnel.id,
'view_mode': 'form',
'target': 'current',
}
models/funnel_snippet.py
from odoo import models, fields
class FunnelSnippet(models.Model):
_name = 'funnel.snippet'
_description = 'Funnel Snippet Definition'
_order = 'category, sequence, name'
name = fields.Char('Snippet Name', required=True)
technical_name = fields.Char('Technical Name', required=True)
sequence = fields.Integer('Sequence', default=10)
category = fields.Selection([
('complete_funnel', 'Complete Funnels'),
('hero', 'Hero Sections'),
('problem_story', 'Problem & Story'),
('solution_benefits', 'Solution & Benefits'),
('social_proof', 'Social Proof'),
('offers_pricing', 'Offers & Pricing'),
('cta_forms', 'CTAs & Forms'),
('quiz', 'Quiz Elements'),
('urgency_trust', 'Urgency & Trust'),
('utility', 'Utility'),
], string='Category', required=True)
# XML Template Reference
template_xml_id = fields.Char('XML Template ID')
# Field Schema (JSON)
field_schema = fields.Text('Field Schema', default='{"fields": []}')
# Preview
thumbnail = fields.Binary('Thumbnail')
description = fields.Text('Description')
# SAM AI Knowledge
copywriting_hints = fields.Text('Copywriting Hints for SAM')
# Active
active = fields.Boolean('Active', default=True)
models/funnel_conversion.py
from odoo import models, fields
class FunnelConversion(models.Model):
_name = 'funnel.conversion'
_description = 'Funnel Conversion Event'
_order = 'timestamp desc'
# Context
funnel_id = fields.Many2one('funnel.definition', string='Funnel', required=True, ondelete='cascade')
page_id = fields.Many2one('funnel.page', string='Page', required=True, ondelete='cascade')
# Visitor
visitor_id = fields.Char('Visitor ID')
partner_id = fields.Many2one('res.partner', string='Contact')
# Event
event_type = fields.Selection([
('page_view', 'Page View'),
('form_submit', 'Form Submission'),
('cta_click', 'CTA Click'),
('quiz_start', 'Quiz Started'),
('quiz_complete', 'Quiz Completed'),
('purchase', 'Purchase'),
], string='Event Type', required=True)
# Metadata
timestamp = fields.Datetime('Timestamp', default=fields.Datetime.now)
session_data = fields.Text('Session Data')
user_agent = fields.Char('User Agent')
ip_address = fields.Char('IP Address')
# UTM Tracking
utm_source = fields.Char('UTM Source')
utm_medium = fields.Char('UTM Medium')
utm_campaign = fields.Char('UTM Campaign')
utm_term = fields.Char('UTM Term')
utm_content = fields.Char('UTM Content')
# CRM Link
lead_id = fields.Many2one('crm.lead', string='Created Lead')
4. Views
views/funnel_definition_views.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Tree View -->
<record id="funnel_definition_view_tree" model="ir.ui.view">
<field name="name">funnel.definition.tree</field>
<field name="model">funnel.definition</field>
<field name="arch" type="xml">
<tree string="Funnels">
<field name="name"/>
<field name="funnel_type"/>
<field name="page_count"/>
<field name="total_views"/>
<field name="conversion_rate" widget="progressbar"/>
<field name="state" widget="badge" decoration-success="state == 'active'" decoration-warning="state == 'paused'" decoration-muted="state == 'draft'"/>
</tree>
</field>
</record>
<!-- Form View -->
<record id="funnel_definition_view_form" model="ir.ui.view">
<field name="name">funnel.definition.form</field>
<field name="model">funnel.definition</field>
<field name="arch" type="xml">
<form string="Funnel">
<header>
<button name="action_activate" string="Activate" type="object" class="btn-primary" invisible="state != 'draft'"/>
<button name="action_pause" string="Pause" type="object" invisible="state != 'active'"/>
<button name="action_draft" string="Set to Draft" type="object" invisible="state not in ['paused', 'archived']"/>
<button name="action_archive" string="Archive" type="object" invisible="state == 'archived'"/>
<field name="state" widget="statusbar" statusbar_visible="draft,active,paused,archived"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="Funnel Name"/>
</h1>
</div>
<group>
<group>
<field name="funnel_type"/>
<field name="template_id"/>
<field name="ai_generated_copy"/>
</group>
<group>
<field name="crm_team_id"/>
<field name="default_tag_ids" widget="many2many_tags"/>
<field name="mailing_list_id"/>
</group>
</group>
<group string="Analytics" col="4">
<field name="total_views"/>
<field name="total_conversions"/>
<field name="conversion_rate" widget="progressbar"/>
<field name="page_count"/>
</group>
<notebook>
<page string="Pages" name="pages">
<field name="page_ids">
<tree editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="page_type"/>
<field name="page_url"/>
<field name="view_count"/>
<field name="form_submissions"/>
<field name="conversion_rate" widget="progressbar"/>
</tree>
</field>
</page>
<page string="Description" name="description">
<field name="description" placeholder="Describe this funnel..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Search View -->
<record id="funnel_definition_view_search" model="ir.ui.view">
<field name="name">funnel.definition.search</field>
<field name="model">funnel.definition</field>
<field name="arch" type="xml">
<search string="Search Funnels">
<field name="name"/>
<field name="funnel_type"/>
<filter string="Active" name="active" domain="[('state', '=', 'active')]"/>
<filter string="Draft" name="draft" domain="[('state', '=', 'draft')]"/>
<separator/>
<filter string="AI Generated" name="ai_generated" domain="[('ai_generated_copy', '=', True)]"/>
<group expand="0" string="Group By">
<filter string="Type" name="group_type" context="{'group_by': 'funnel_type'}"/>
<filter string="Status" name="group_state" context="{'group_by': 'state'}"/>
</group>
</search>
</field>
</record>
<!-- Action -->
<record id="funnel_definition_action" model="ir.actions.act_window">
<field name="name">Funnels</field>
<field name="res_model">funnel.definition</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_active': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create your first sales funnel
</p>
<p>
Build high-converting funnels with drag-and-drop snippets.
Let SAM AI help you write compelling copy.
</p>
</field>
</record>
</odoo>
views/funnel_template_views.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Tree View -->
<record id="funnel_template_view_tree" model="ir.ui.view">
<field name="name">funnel.template.tree</field>
<field name="model">funnel.template</field>
<field name="arch" type="xml">
<tree string="Funnel Templates">
<field name="name"/>
<field name="funnel_type"/>
<field name="usage_count"/>
<field name="visibility"/>
<field name="author_id"/>
</tree>
</field>
</record>
<!-- Form View -->
<record id="funnel_template_view_form" model="ir.ui.view">
<field name="name">funnel.template.form</field>
<field name="model">funnel.template</field>
<field name="arch" type="xml">
<form string="Funnel Template">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_create_funnel_from_template" type="object" class="oe_stat_button" icon="fa-copy">
<span>Use Template</span>
</button>
</div>
<field name="thumbnail" widget="image" class="oe_avatar"/>
<div class="oe_title">
<h1>
<field name="name" placeholder="Template Name"/>
</h1>
</div>
<group>
<group>
<field name="funnel_type"/>
<field name="visibility"/>
</group>
<group>
<field name="usage_count"/>
<field name="author_id"/>
</group>
</group>
<notebook>
<page string="Description" name="description">
<field name="description"/>
</page>
<page string="Template Structure" name="structure">
<field name="template_structure" widget="ace" options="{'mode': 'json'}"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Action -->
<record id="funnel_template_action" model="ir.actions.act_window">
<field name="name">Funnel Templates</field>
<field name="res_model">funnel.template</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>
views/funnel_snippet_views.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Tree View -->
<record id="funnel_snippet_view_tree" model="ir.ui.view">
<field name="name">funnel.snippet.tree</field>
<field name="model">funnel.snippet</field>
<field name="arch" type="xml">
<tree string="Funnel Snippets">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="technical_name"/>
<field name="category"/>
<field name="template_xml_id"/>
<field name="active"/>
</tree>
</field>
</record>
<!-- Form View -->
<record id="funnel_snippet_view_form" model="ir.ui.view">
<field name="name">funnel.snippet.form</field>
<field name="model">funnel.snippet</field>
<field name="arch" type="xml">
<form string="Funnel Snippet">
<sheet>
<field name="thumbnail" widget="image" class="oe_avatar"/>
<div class="oe_title">
<h1>
<field name="name" placeholder="Snippet Name"/>
</h1>
</div>
<group>
<group>
<field name="technical_name"/>
<field name="category"/>
<field name="sequence"/>
</group>
<group>
<field name="template_xml_id"/>
<field name="active"/>
</group>
</group>
<notebook>
<page string="Description" name="description">
<field name="description"/>
</page>
<page string="Field Schema" name="schema">
<field name="field_schema" widget="ace" options="{'mode': 'json'}"/>
</page>
<page string="SAM AI Hints" name="ai_hints">
<field name="copywriting_hints" placeholder="Enter copywriting hints for SAM AI..."/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Action -->
<record id="funnel_snippet_action" model="ir.actions.act_window">
<field name="name">Snippet Library</field>
<field name="res_model">funnel.snippet</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>
views/funnel_page_views.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Form View (for popup editing) -->
<record id="funnel_page_view_form" model="ir.ui.view">
<field name="name">funnel.page.form</field>
<field name="model">funnel.page</field>
<field name="arch" type="xml">
<form string="Funnel Page">
<sheet>
<group>
<group>
<field name="name"/>
<field name="page_type"/>
<field name="sequence"/>
</group>
<group>
<field name="page_url"/>
<field name="full_url" widget="url"/>
<field name="website_page_id"/>
</group>
</group>
<group>
<group string="Navigation">
<field name="next_page_id"/>
<field name="redirect_url"/>
</group>
<group string="Analytics">
<field name="view_count"/>
<field name="unique_visitors"/>
<field name="form_submissions"/>
<field name="conversion_rate" widget="progressbar"/>
</group>
</group>
<notebook>
<page string="Snippet Configuration" name="snippets">
<field name="snippet_config" widget="ace" options="{'mode': 'json'}"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>
views/funnel_menus.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Top-level menu under SAM AI -->
<menuitem id="funnel_menu_root"
name="Funnels"
parent="ai_sam.menu_sam_ai_root"
sequence="30"/>
<!-- Funnels submenu -->
<menuitem id="funnel_menu_funnels"
name="My Funnels"
parent="funnel_menu_root"
action="funnel_definition_action"
sequence="10"/>
<!-- Templates submenu -->
<menuitem id="funnel_menu_templates"
name="Templates"
parent="funnel_menu_root"
action="funnel_template_action"
sequence="20"/>
<!-- Configuration menu -->
<menuitem id="funnel_menu_config"
name="Configuration"
parent="funnel_menu_root"
sequence="90"/>
<!-- Snippet Library (under Configuration) -->
<menuitem id="funnel_menu_snippets"
name="Snippet Library"
parent="funnel_menu_config"
action="funnel_snippet_action"
sequence="10"/>
</odoo>
5. Security
security/ir.model.access.csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_funnel_definition_user,funnel.definition.user,model_funnel_definition,base.group_user,1,1,1,0
access_funnel_definition_manager,funnel.definition.manager,model_funnel_definition,base.group_system,1,1,1,1
access_funnel_page_user,funnel.page.user,model_funnel_page,base.group_user,1,1,1,0
access_funnel_page_manager,funnel.page.manager,model_funnel_page,base.group_system,1,1,1,1
access_funnel_template_user,funnel.template.user,model_funnel_template,base.group_user,1,1,1,0
access_funnel_template_manager,funnel.template.manager,model_funnel_template,base.group_system,1,1,1,1
access_funnel_snippet_user,funnel.snippet.user,model_funnel_snippet,base.group_user,1,0,0,0
access_funnel_snippet_manager,funnel.snippet.manager,model_funnel_snippet,base.group_system,1,1,1,1
access_funnel_conversion_user,funnel.conversion.user,model_funnel_conversion,base.group_user,1,0,0,0
access_funnel_conversion_manager,funnel.conversion.manager,model_funnel_conversion,base.group_system,1,1,1,1
6. Initial Data
data/funnel_snippet_data.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Hero Snippets -->
<record id="snippet_hero_minimal" model="funnel.snippet">
<field name="name">Minimal Hero</field>
<field name="technical_name">hero_minimal</field>
<field name="category">hero</field>
<field name="sequence">10</field>
<field name="description">Clean, focused opening with headline, subheadline, and single CTA</field>
<field name="copywriting_hints">Headline: ONE powerful promise, 5-10 words. Use power words: Free, New, Proven, Secret, Discover. Formula: "[Outcome] Without [Pain Point]"</field>
</record>
<record id="snippet_hero_full" model="funnel.snippet">
<field name="name">Full Hero</field>
<field name="technical_name">hero_full</field>
<field name="category">hero</field>
<field name="sequence">20</field>
<field name="description">Complete hero with headline, subheadline, video/image, and CTA</field>
</record>
<record id="snippet_hero_video" model="funnel.snippet">
<field name="name">Video Hero</field>
<field name="technical_name">hero_video</field>
<field name="category">hero</field>
<field name="sequence">30</field>
<field name="description">Video-focused hero with background video, overlay text, and CTA</field>
</record>
<!-- Problem & Story Snippets -->
<record id="snippet_problem_agitation" model="funnel.snippet">
<field name="name">Problem Agitation</field>
<field name="technical_name">problem_agitation</field>
<field name="category">problem_story</field>
<field name="sequence">10</field>
<field name="description">Paint the pain using the PAS (Problem-Agitate-Solution) framework</field>
<field name="copywriting_hints">1. PROBLEM: State pain clearly ("Tired of...", "Frustrated with..."). 2. AGITATE: Make it worse ("And the worst part?", "Every day it gets harder..."). 3. BRIDGE: Hint at hope ("What if there was a way...")</field>
</record>
<!-- CTA & Forms Snippets -->
<record id="snippet_opt_in_form" model="funnel.snippet">
<field name="name">Opt-in Form</field>
<field name="technical_name">opt_in_form</field>
<field name="category">cta_forms</field>
<field name="sequence">10</field>
<field name="description">Email capture form with CRM integration</field>
<field name="copywriting_hints">Button text: Action + Outcome. NOT: "Submit" or "Sign Up". YES: "Get My Free Guide", "Start My Trial", "Show Me How"</field>
</record>
<!-- Add more snippets as needed... -->
</data>
</odoo>
7. Controllers (Placeholder)
controllers/init.py
from . import funnel_controller
controllers/funnel_controller.py
from odoo import http
from odoo.http import request
import json
class FunnelController(http.Controller):
"""
Placeholder controller for Phase 1.
Form submission and API endpoints will be added in Phase 3.
"""
@http.route('/funnel/health', type='json', auth='public')
def health_check(self):
"""Simple health check endpoint"""
return {'status': 'ok', 'module': 'sam_ai_funnels', 'phase': 1}
VALIDATION CHECKLIST
After implementation, verify:
- [ ] Module installs without errors
- [ ] Can create a new funnel definition
- [ ] Can add pages to a funnel
- [ ] Can view funnel templates list
- [ ] Can view snippet library
- [ ] Menus appear under SAM AI > Funnels
- [ ] Security rules allow user CRUD operations
- [ ] Form fields render correctly
- [ ] Computed fields (page_count, conversion_rate) work
DO NOT IMPLEMENT (Deferred to Later Phases)
- Website builder snippets (Phase 2)
- Form submission handling (Phase 3)
- CRM/Mailing integration (Phase 3)
- Complete funnel generation (Phase 4)
- SAM AI integration (Phase 6)
- Quiz logic (Phase 7)
FILES TO CREATE (Summary)
__manifest__.py__init__.pymodels/__init__.pymodels/funnel_definition.pymodels/funnel_page.pymodels/funnel_template.pymodels/funnel_snippet.pymodels/funnel_conversion.pycontrollers/__init__.pycontrollers/funnel_controller.pyviews/funnel_definition_views.xmlviews/funnel_page_views.xmlviews/funnel_template_views.xmlviews/funnel_snippet_views.xmlviews/funnel_menus.xmldata/funnel_snippet_data.xmlsecurity/ir.model.access.csvstatic/description/icon.png(copy from ai_sam)
NEXT PHASE
After Phase 1 is validated, proceed to Phase 2: Core Snippets (15).
The Phase 2 prompt will cover:
- FUNNELS tab registration in website builder
- 15 MVP snippet XML templates
- Snippet SCSS styling
- Customize panel options
END OF PHASE 1 DEVELOPER PROMPT