M03 Leads — sales pipeline
Lead capture through 7-stage pipeline to convert-to-client. Activity timeline, source tracking, weighted-revenue forecast, "stuck" detection, conversion to M04 + lineage stamping. Per-stage tiles + filterable index. The first touchpoint of the entire revenue cycle.
The 7-stage pipeline
| Stage | Meaning | Probability |
|---|---|---|
| new | Just captured · no contact yet | 10% |
| contacted | Initial call / email made | 25% |
| qualified | Confirmed budget + need + timeline + authority | 40% |
| proposal | Quote sent | 60% |
| negotiation | Active discussion on terms / price | 80% |
| won | Accepted · ready to convert | 100% |
| lost | Rejected · reason captured | 0% |
Lead row fields
Prospect's company. Used as lead title.
Primary contact. Becomes client_contacts primary on conversion.
Estimated annual fee. Drives weighted forecast.
referral · website · cold_call · whatsapp · OCCI · LinkedIn · networking · other.
Default new. Walk through stages. Each move is timestamped.
Manager who owns the lead. Drives "My leads" filter.
When you expect to win/lose. Drives forecast bucketing.
Discovery notes, independence-check conclusions, internal context.
price · timing · scope · competitor · independence · other.
Activity timeline
Each lead has a polymorphic activity log via lead_activities:
- activity_type — call / email / meeting / whatsapp / proposal_sent / stage_change / note
- direction — inbound / outbound / internal
- summary — short one-liner
- details — long-form notes
- occurred_at — when
- logged_by — who recorded
Renders as iconified vertical timeline on the lead show page. Becomes the audit-trail of the relationship.
Pipeline view (index)
The default Leads list renders as a 7-stage tile band:
- Per stage: count of leads + total expected revenue + weighted revenue
- Click a tile → list filtered to that stage
- Filters: source · assigned_to · stuck flag · search
- "My leads" toggle for the logged-in user
- "Stuck" filter: open leads with no activity 14d+
Stuck detection
Lead::listFiltered($filters) with stuck=1 applies:
WHERE stage NOT IN ('won', 'lost')
AND updated_at < CURDATE() - INTERVAL 14 DAY
AND deleted_at IS NULL
Surfaced as a Pipeline Conversion tile on the dashboard. Click → list filtered. Either re-engage or move to lost.
Step-by-step — capture + walk a lead
Capture
Sales → Leads → New lead. Fill company, contact, source, expected revenue, assign manager.
Stage = new
System auto-creates lead. Activity timeline shows "Lead captured by [you]".
First contact
Make the call. Stage move new → contacted via show page action button. Add an activity log: outbound call, summary, key takeaways.
Qualify (BANT check)
Confirm Budget · Authority · Need · Timeline. If yes, stage → qualified.
Run independence check
Document in notes. If conflict, stage → lost (reason: independence). Otherwise continue.
Proposal
From show page → Create quote. Quote auto-inherits lead contact + expected revenue. Stage moves proposal automatically.
Negotiation
Discussions happen. Stage → negotiation when actively negotiating terms.
Won
Client accepts. Mark stage = won. Convert to Client button enables.
Convert
Click Convert to Client. Auto-creates M04 client + primary contact. Stamps
leads.converted_client_id. Click-through to client detail.
Lost reason discipline
When marking a lead lost, the system enforces a reason from the lookup:
- price — too expensive · drives fee-bracket review
- timing — not now · re-engage in 6 months
- scope — needed something we don't offer
- competitor — picked another firm · drives competitive analysis
- independence — conflict identified · documented for OAAA records
- internal — firm declined · capacity / risk / strategy
- other — free text required
Tracked centrally so the firm can report on win/loss patterns + tune sales approach.
RBAC matrix
| Role | view | create | update | convert | delete | Scope |
|---|---|---|---|---|---|---|
| super_admin / partner | ✓ | ✓ | ✓ | ✓ | ✓ | all |
| admin_staff / accountant | ✓ | ✓ | ✓ | ✓ | — | all |
| manager | ✓ | ✓ | ✓ | — | — | department |
| senior / staff auditor | ✓ | — | — | — | — | view |
| read_only | ✓ | — | — | — | — | view |
Open Sales Pipeline → Leads. Toggle "My leads". Apply Stuck filter. The intersection is "leads I'm responsible for that haven't moved in 2 weeks". This is your weekly sales hygiene list.
Don't sit on lost reasons. If 60% of last quarter's losses were "price", you have a pricing/positioning issue. If 40% were "competitor", you have a positioning issue. Use Reports → Quote Conversion + lead lost-reason analysis monthly.
Leads marked lost with reason="timing" should auto-trigger a follow-up task 6 months later. Currently manual; add a "Reactivate" action button on lost leads that creates a calendar event for re-contact. Soft pipeline = future revenue.