lead menu
nquiry menu
This commit is contained in:
131
docs/adr/0011-lead-enquiry-ownership-model.md
Normal file
131
docs/adr/0011-lead-enquiry-ownership-model.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# ADR 0011: Lead and Enquiry Ownership Model
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
The CRM module originally used a mixed terminology model where:
|
||||
|
||||
- `Lead` meant an unassigned enquiry
|
||||
- `Opportunity` meant an assigned enquiry
|
||||
|
||||
That model no longer matched the business process. The business now distinguishes ownership by function:
|
||||
|
||||
- `Lead` is marketing-owned
|
||||
- `Enquiry` is sales-owned
|
||||
|
||||
The system must preserve the single-source-of-truth table `crm_enquiries` while refining lifecycle, permissions, monitoring visibility, and KPI semantics.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. Single record model
|
||||
|
||||
- `crm_enquiries` remains the single source of truth
|
||||
- no `crm_leads` table is introduced
|
||||
- no split lead form or split enquiry API is introduced
|
||||
|
||||
### 2. Pipeline stage model
|
||||
|
||||
`crm_enquiries.pipeline_stage` is the lifecycle discriminator with allowed values:
|
||||
|
||||
- `lead`
|
||||
- `enquiry`
|
||||
- `closed_won`
|
||||
- `closed_lost`
|
||||
|
||||
### 3. Ownership model
|
||||
|
||||
- `lead` is marketing-owned
|
||||
- `enquiry` is sales-owned
|
||||
- assignment converts a lead into an enquiry
|
||||
|
||||
### 4. Assignment behavior
|
||||
|
||||
On assign:
|
||||
|
||||
- `assignedToUserId` is set
|
||||
- `assignedAt` is set
|
||||
- `assignedBy` is set
|
||||
- `pipelineStage` becomes `enquiry`
|
||||
|
||||
On reassign:
|
||||
|
||||
- assignee metadata is updated
|
||||
- `pipelineStage` remains `enquiry`
|
||||
|
||||
### 5. Create behavior
|
||||
|
||||
- marketing-created records default to `pipelineStage = lead`
|
||||
- sales-created records default to `pipelineStage = enquiry`
|
||||
|
||||
### 6. Closure behavior
|
||||
|
||||
- lost or cancelled enquiry status resolves to `pipelineStage = closed_lost`
|
||||
- accepted or lost/rejected quotation outcomes may synchronize the linked enquiry to:
|
||||
- `closed_won`
|
||||
- `closed_lost`
|
||||
|
||||
### 7. Visibility policy
|
||||
|
||||
Marketing can monitor:
|
||||
|
||||
- leads
|
||||
- enquiries
|
||||
- follow-ups
|
||||
- status
|
||||
- pipeline stage
|
||||
- enquiry owner
|
||||
- won/lost result
|
||||
|
||||
Marketing cannot access:
|
||||
|
||||
- quotation pricing
|
||||
- quotation items
|
||||
- cost, discount, or margin views
|
||||
- approval analytics or approval workflow data
|
||||
|
||||
Sales scope remains ownership-based:
|
||||
|
||||
- `sales` and `sales_support` are limited server-side to enquiries they created or are assigned to
|
||||
- managers and admins retain wider organization visibility
|
||||
|
||||
### 8. KPI terminology
|
||||
|
||||
User-facing dashboard terminology replaces `Opportunity` with `Enquiry`.
|
||||
|
||||
Count definitions:
|
||||
|
||||
- `Lead Count` = `pipelineStage = lead`
|
||||
- `Enquiry Count` = `pipelineStage = enquiry`
|
||||
- `Won Count` = `pipelineStage = closed_won`
|
||||
- `Lost Count` = `pipelineStage = closed_lost`
|
||||
|
||||
### 9. Navigation Separation
|
||||
|
||||
- `Lead` and `Enquiry` are separate UX workspaces
|
||||
- `Lead` and `Enquiry` are not separate domain entities
|
||||
- both workspaces continue to use the same `crm_enquiries` records
|
||||
- `pipelineStage` determines whether a record appears in the Leads or Enquiries workspace
|
||||
- the navigation split reduces user confusion without duplicating schema, API, or service layers
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- matches the real business handoff from marketing to sales
|
||||
- keeps backward-compatible storage in `crm_enquiries`
|
||||
- removes ambiguous `opportunity` wording from user-facing CRM surfaces
|
||||
- gives marketing monitoring visibility without exposing commercial data
|
||||
- enforces own-or-assigned visibility server-side for sales roles
|
||||
|
||||
### Tradeoffs
|
||||
|
||||
- lifecycle still depends partly on status mapping and quotation outcome synchronization
|
||||
- the model does not yet include dedicated timestamps such as `closedWonAt` or `closedLostAt`
|
||||
- reporting still relies on current-state lifecycle fields rather than effective-dated snapshots
|
||||
|
||||
## Supersedes
|
||||
|
||||
This ADR supersedes the opportunity terminology frozen in `docs/implementation/task-j0-kpi-definition-freeze.md` for user-facing CRM ownership and KPI language.
|
||||
109
docs/implementation/task-d3-lead-enquiry-ownership-refinement.md
Normal file
109
docs/implementation/task-d3-lead-enquiry-ownership-refinement.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Task D.3: Lead / Enquiry Ownership Refinement
|
||||
|
||||
## 1. Files Added
|
||||
|
||||
- `drizzle/0012_odd_fat_cobra.sql`
|
||||
- `docs/adr/0011-lead-enquiry-ownership-model.md`
|
||||
- `docs/implementation/task-d3-lead-enquiry-ownership-refinement.md`
|
||||
|
||||
## 2. Files Modified
|
||||
|
||||
- `src/db/schema.ts`
|
||||
- `src/lib/auth/rbac.ts`
|
||||
- `src/features/users/components/user-form-sheet.tsx`
|
||||
- `src/features/crm/enquiries/api/types.ts`
|
||||
- `src/features/crm/enquiries/server/service.ts`
|
||||
- `src/features/crm/quotations/server/service.ts`
|
||||
- `src/app/api/crm/enquiries/route.ts`
|
||||
- `src/app/api/crm/enquiries/[id]/route.ts`
|
||||
- `src/app/api/crm/enquiries/[id]/assign/route.ts`
|
||||
- `src/app/api/crm/enquiries/[id]/reassign/route.ts`
|
||||
- `src/app/api/crm/enquiries/[id]/customers/route.ts`
|
||||
- `src/app/api/crm/enquiries/[id]/followups/route.ts`
|
||||
- `src/app/api/crm/enquiries/[id]/followups/[followupId]/route.ts`
|
||||
- `src/app/api/crm/dashboard/route.ts`
|
||||
- `src/app/api/crm/dashboard/export/route.ts`
|
||||
- `src/app/dashboard/crm/enquiries/page.tsx`
|
||||
- `src/app/dashboard/crm/enquiries/[id]/page.tsx`
|
||||
- `src/config/nav-config.ts`
|
||||
- `src/features/crm/enquiries/components/enquiry-form-sheet.tsx`
|
||||
- `src/features/crm/enquiries/components/enquiry-assignment-dialog.tsx`
|
||||
- `src/features/crm/enquiries/components/enquiry-columns.tsx`
|
||||
- `src/features/crm/enquiries/components/enquiry-detail.tsx`
|
||||
- `src/features/crm/enquiries/components/enquiry-cell-action.tsx`
|
||||
- `src/features/crm/dashboard/api/types.ts`
|
||||
- `src/features/crm/dashboard/server/service.ts`
|
||||
- `src/features/crm/dashboard/components/crm-dashboard.tsx`
|
||||
- `src/features/crm/dashboard/components/dashboard-summary-cards.tsx`
|
||||
- `src/features/crm/dashboard/components/dashboard-funnel.tsx`
|
||||
- `src/features/crm/dashboard/components/dashboard-sales-ranking.tsx`
|
||||
- `src/features/crm/dashboard/components/dashboard-followups.tsx`
|
||||
- `src/features/crm/dashboard/components/dashboard-hot-projects.tsx`
|
||||
|
||||
## 3. Pipeline Changes
|
||||
|
||||
- Added `crm_enquiries.pipeline_stage`
|
||||
- Allowed values:
|
||||
- `lead`
|
||||
- `enquiry`
|
||||
- `closed_won`
|
||||
- `closed_lost`
|
||||
- Backfilled legacy enquiry rows:
|
||||
- assigned rows -> `enquiry`
|
||||
- unassigned rows -> `lead`
|
||||
- closed-lost/cancelled status rows -> `closed_lost`
|
||||
- Assign flow now converts `lead -> enquiry`
|
||||
- Quotation accepted/lost/rejected outcomes can synchronize linked enquiry lifecycle to won/lost stages
|
||||
|
||||
## 4. Ownership Changes
|
||||
|
||||
- Added business role `marketing`
|
||||
- Marketing-created records default to `lead`
|
||||
- Sales-created records default to `enquiry`
|
||||
- `sales` and `sales_support` now use server-enforced own-or-assigned enquiry visibility
|
||||
- managers, admins, and marketing retain broader monitoring visibility
|
||||
|
||||
## 5. Visibility Rules Added
|
||||
|
||||
- Marketing can read CRM lead/enquiry monitoring surfaces
|
||||
- Marketing cannot see quotation pricing or approval analytics
|
||||
- Enquiry detail hides related quotation links when the user lacks quotation read access
|
||||
- CRM dashboard hides quotation, revenue, hot-enquiry, sales-ranking, and approval sections when the user lacks those permissions
|
||||
|
||||
## 6. KPI Changes
|
||||
|
||||
- Dashboard summary now uses `pipelineStage` for:
|
||||
- lead count
|
||||
- enquiry count
|
||||
- won count
|
||||
- lost count
|
||||
- User-facing KPI language changed from `Opportunity` to `Enquiry`
|
||||
- Funnel wording now follows lead -> enquiry -> quotation flow
|
||||
- Follow-up and ownership labels now use `Enquiry Owner`
|
||||
|
||||
## 7. ADR Added
|
||||
|
||||
- Added `docs/adr/0011-lead-enquiry-ownership-model.md`
|
||||
- This freezes:
|
||||
- lead ownership
|
||||
- enquiry ownership
|
||||
- assignment conversion behavior
|
||||
- marketing visibility restriction boundaries
|
||||
- won/lost lifecycle naming
|
||||
|
||||
## 8. Migration Notes
|
||||
|
||||
- Apply `drizzle/0012_odd_fat_cobra.sql`
|
||||
- Existing `crm_enquiries` rows are backfilled in-place; no new lead table is introduced
|
||||
- Existing forms and APIs remain shared across lead and enquiry flows
|
||||
- Existing quotation routes remain intact; only linked enquiry lifecycle sync was added
|
||||
|
||||
## 9. Remaining Risks
|
||||
|
||||
- `closed_won` still has no dedicated timestamp field; lifecycle timing remains coarse
|
||||
- dashboard/export visibility now follows permission boundaries, but any future custom report endpoints must preserve the same commercial-data guardrails
|
||||
- quotation-to-enquiry lifecycle sync currently depends on quotation status transitions and does not yet model reopen scenarios explicitly
|
||||
|
||||
## Verification
|
||||
|
||||
- `npx tsc --noEmit`
|
||||
@@ -0,0 +1,72 @@
|
||||
# Task D3.1: Leads / Enquiries Navigation Separation
|
||||
|
||||
## Files Added
|
||||
|
||||
- `src/app/dashboard/crm/leads/page.tsx`
|
||||
- `src/app/dashboard/crm/leads/[id]/page.tsx`
|
||||
- `docs/implementation/task-d31-leads-enquiries-navigation-separation.md`
|
||||
|
||||
## Files Modified
|
||||
|
||||
- `src/config/nav-config.ts`
|
||||
- `src/lib/auth/rbac.ts`
|
||||
- `src/app/dashboard/crm/enquiries/page.tsx`
|
||||
- `src/app/dashboard/crm/enquiries/[id]/page.tsx`
|
||||
- `src/app/api/crm/enquiries/route.ts`
|
||||
- `src/features/crm/dashboard/components/dashboard-summary-cards.tsx`
|
||||
- `src/features/crm/enquiries/api/types.ts`
|
||||
- `src/features/crm/enquiries/api/service.ts`
|
||||
- `src/features/crm/enquiries/api/mutations.ts`
|
||||
- `src/features/crm/enquiries/components/enquiry-listing.tsx`
|
||||
- `src/features/crm/enquiries/components/enquiries-table.tsx`
|
||||
- `src/features/crm/enquiries/components/enquiry-columns.tsx`
|
||||
- `src/features/crm/enquiries/components/enquiry-cell-action.tsx`
|
||||
- `src/features/crm/enquiries/components/enquiry-form-sheet.tsx`
|
||||
- `src/features/crm/enquiries/components/enquiry-detail.tsx`
|
||||
- `src/features/crm/enquiries/server/service.ts`
|
||||
- `docs/adr/0011-lead-enquiry-ownership-model.md`
|
||||
|
||||
## Navigation Changes
|
||||
|
||||
- Split the combined CRM workspace into `Leads` and `Enquiries`.
|
||||
- Kept a single shared feature module by reusing `src/features/crm/enquiries/**`.
|
||||
|
||||
## Route Changes
|
||||
|
||||
- Added `/dashboard/crm/leads`
|
||||
- Added `/dashboard/crm/leads/[id]`
|
||||
- Reused the same list/detail backend and scoped each page with `pipelineStage`
|
||||
- Redirected lead detail to enquiry detail when the record has already moved past the `lead` stage
|
||||
|
||||
## Permission Changes
|
||||
|
||||
- Added:
|
||||
- `crm.lead.read`
|
||||
- `crm.lead.create`
|
||||
- `crm.lead.update`
|
||||
- `crm.lead.assign`
|
||||
- `crm.lead.delete`
|
||||
- Mapped `marketing` to lead-centric permissions
|
||||
- Kept `sales` on enquiry-centric permissions
|
||||
- Gave manager roles both lead and enquiry capabilities
|
||||
|
||||
## Query Invalidation Added
|
||||
|
||||
- CRM enquiry mutations now invalidate lead/enquiry list queries, detail queries, follow-up queries, and CRM dashboard KPI queries.
|
||||
- Assigned leads now disappear from the Leads workspace and appear in the Enquiries workspace without manual refresh.
|
||||
|
||||
## Dashboard Changes
|
||||
|
||||
- Lead KPI card links to `/dashboard/crm/leads`
|
||||
- Enquiry KPI card links to `/dashboard/crm/enquiries`
|
||||
- List filtering now accepts `pipelineStage` for workspace drill-down
|
||||
|
||||
## ADR Updates
|
||||
|
||||
- Added a `Navigation Separation` section to ADR 0011
|
||||
- Clarified that lead/enquiry are UX workspaces, not separate persisted entities
|
||||
|
||||
## Remaining Risks
|
||||
|
||||
- Shared route handlers still live under the enquiry API namespace by design, so the lead workspace depends on the shared `crm_enquiries` backend contract.
|
||||
- Manager permission breadth may need later refinement if `department_manager` and `top_manager` should diverge.
|
||||
Reference in New Issue
Block a user