CRM Module¶
Lead pipeline management with swipeable stage navigation.
Overview¶
The CRM module provides:
- Stage-based pipeline view with swipe navigation
- Lead list with search and filtering
- Lead detail with customer information
- Quote creation from leads
- Stage workflow management
- Activity logging and comments
- Mark as lost functionality
Routes¶
Pipeline View¶
GET /crm
Displays leads grouped by stage in a swipeable carousel interface.
| Parameter | Type | Description |
|---|---|---|
| stage | int | Initial stage to display (stage_id) |
Lead List¶
GET /crm/leads
Lists all active leads with search capability.
| Parameter | Type | Description |
|---|---|---|
| q | string | Search query (name, phone, email) |
Lead Detail¶
GET /crm/lead/{lead_id}
Displays full lead details including:
- Contact information
- Address
- Stage and priority
- Expected revenue
- Linked quotations
- Activity log
- Tags
Create Quote from Lead¶
GET /crm/lead/{lead_id}/quote/new
Creates a new draft quote linked to the lead and redirects to quote editor.
API Endpoints¶
Stage Management¶
Get Stages¶
GET /api/crm/stages
Get all CRM stages ordered by sequence.
Response:
{
"success": true,
"stages": [
{"id": 1, "name": "New", "sequence": 1, "is_won": false, "fold": false},
{"id": 2, "name": "Qualified", "sequence": 2, "is_won": false, "fold": false},
{"id": 3, "name": "Proposition", "sequence": 3, "is_won": false, "fold": false},
{"id": 4, "name": "Won", "sequence": 10, "is_won": true, "fold": false}
]
}
Get Leads by Stage¶
GET /api/crm/stages/{stage_id}/leads
Get leads for a specific stage.
Response:
{
"success": true,
"leads": [
{
"id": 123,
"name": "Window Blinds - John Doe",
"partner_id": [45, "John Doe"],
"contact_name": "John Doe",
"phone": "+1 555-1234",
"stage_id": [1, "New"],
"expected_revenue": 5000.00,
"quotation_count": 2
}
],
"count": 15
}
Change Stage¶
POST /api/crm/lead/{lead_id}/stage
Move lead to a different stage.
Response:
Lead Updates¶
Update Description¶
PUT /api/crm/lead/{lead_id}/description
Update Priority¶
POST /api/crm/lead/{lead_id}/priority
Priority values: 0-3 (0 = no stars, 3 = 3 stars)
Mark as Lost¶
POST /api/crm/lead/{lead_id}/lost
This archives the lead (active=False) and sets the lost reason.
Get Lost Reasons¶
GET /api/crm/lost-reasons
Response:
{
"success": true,
"reasons": [
{"id": 1, "name": "Too expensive"},
{"id": 2, "name": "Went with competitor"},
{"id": 3, "name": "Project cancelled"}
]
}
Comments¶
POST /api/crm/lead/{lead_id}/comment
Tags¶
GET /api/crm/tags
Get all available CRM tags.
Response:
{
"success": true,
"tags": [
{"id": 1, "name": "Hot", "color": 1},
{"id": 2, "name": "Referral", "color": 4}
]
}
Pipeline View¶
flowchart LR
subgraph "Swipeable Pipeline"
S1[New<br/>5 leads]
S2[Qualified<br/>3 leads]
S3[Proposition<br/>8 leads]
S4[Won<br/>12 leads]
end
S1 <--> S2 <--> S3 <--> S4
Stage Navigation¶
The pipeline view uses a swipeable carousel:
- Swipe left/right to navigate between stages
- Stage pills show current position and counts
- Tap a stage pill to jump to that stage
- Pull down to refresh leads
Lead Card Display¶
Each lead card shows:
- Lead name
- Customer name (if linked)
- Phone number (tap to call)
- Expected revenue
- Quote count badge
- Priority stars
- Tags
Quote Creation Flow¶
sequenceDiagram
participant User
participant PWA
participant Odoo
User->>PWA: Click "New Quote" on lead
PWA->>Odoo: Get lead details
Odoo-->>PWA: {partner_id, user_id, team_id}
PWA->>Odoo: Get partner pricelist
Odoo-->>PWA: {pricelist_id}
PWA->>Odoo: Create sale.order
Note over Odoo: partner_id from lead<br/>opportunity_id = lead_id
Odoo-->>PWA: {new_order_id}
PWA-->>User: Redirect to /quotes/{id}/edit
Stage Workflow¶
Default Stages¶
| Stage | Sequence | is_won | Description |
|---|---|---|---|
| New | 1 | false | Fresh leads |
| Qualified | 2 | false | Verified interest |
| Proposition | 3 | false | Quote sent |
| Won | 10 | true | Converted to sale |
Lost Leads¶
Marking a lead as lost:
- Opens modal with lost reasons
- User selects reason
- Lead is archived (active=False)
- Lead disappears from pipeline
Activity Attribution¶
CRM actions are properly attributed to users:
partner_id = session.get('partner_id')
result = odoo_api.post_crm_lead_comment(lead_id, comment, author_id=partner_id)
Lead Fields¶
| Field | Type | Description |
|---|---|---|
name |
Char | Lead/opportunity title |
partner_id |
Many2one | Linked customer |
contact_name |
Char | Contact person name |
phone |
Char | Phone number |
mobile |
Char | Mobile number |
email_from |
Char | Email address |
street, city, state_id, zip |
Address fields | |
stage_id |
Many2one | Pipeline stage |
user_id |
Many2one | Salesperson |
team_id |
Many2one | Sales team |
expected_revenue |
Float | Estimated value |
probability |
Float | Win probability (%) |
priority |
Selection | 0-3 stars |
description |
Text | Notes |
tag_ids |
Many2many | Tags |
order_ids |
One2many | Linked sales orders |
Search¶
The search function looks across multiple fields:
- Lead name
- Contact name
- Phone number
- Mobile number
def search_crm_leads(query: str, limit: int = 50):
query_lower = query.lower()
filtered = [
l for l in all_leads
if (query_lower in (l.get('name') or '').lower() or
query_lower in (l.get('contact_name') or '').lower() or
query_lower in (l.get('phone') or '').lower() or
query_lower in (l.get('mobile') or '').lower() or
query_lower in (l.get('email_from') or '').lower())
]
return filtered[:limit]
Linked Quotations¶
Quotations linked to a lead (via opportunity_id) are displayed: