task-d.5.1
This commit is contained in:
phaichayon
2026-06-24 12:28:04 +07:00
parent c1ecd5ea50
commit 9c75788ba7
10 changed files with 794 additions and 1 deletions

View File

@@ -0,0 +1,117 @@
# ADR 0018: Lead / Enquiry Domain Separation
## Status
Proposed
## Context
ADR-0011 froze the current CRM lifecycle around a single persisted entity:
- `crm_enquiries` stores both marketing-owned leads and sales-owned enquiries
- `pipeline_stage` acts as the discriminator
- navigation separation exists, but domain separation does not
That model was an intentional stabilization step for Tasks D.3 and D.3.1. It reduced UX ambiguity without forcing a schema split during the first production rollout.
Task D.5 introduces a stronger business requirement:
- Marketing owns `Lead` as a dedicated entity
- Sales owns `Enquiry` as a dedicated entity
- one lead may create many enquiries
- sales may also create enquiries directly without a lead
- marketing outcome must be derived from sales execution, not manually controlled
The current single-record model now creates structural ambiguity in at least these areas:
1. Assignment currently mutates one record from `lead -> enquiry`, which destroys the original lead as a stable marketing record.
2. A true `1 Lead -> N Enquiries` relationship cannot be represented cleanly while the lead itself is also the enquiry record.
3. Dashboard and report logic still infer lead analytics from `crm_enquiries.pipeline_stage`, which couples marketing and sales datasets.
4. Outcome synchronization for marketing is harder to reason about because the source marketing entity does not exist independently.
## Decision
We will replace the single-record lead/enquiry persistence model with a split domain model:
- `crm_leads` becomes the dedicated marketing-owned lead table
- `crm_enquiries` remains the dedicated sales-owned enquiry table
- `crm_enquiries.lead_id` becomes nullable to support:
- lead-origin enquiries
- direct sales enquiries with `lead_id = null`
### Lifecycle
The target business flow is:
```txt
Lead
-> Enquiry
-> Quotation
-> Won / Lost
```
### Relationship rules
- `crm_leads 1 -> N crm_enquiries`
- `crm_enquiries 1 -> N crm_quotations`
- `crm_quotations` remains the owning record for quotation revisions and child resources
### Ownership rules
- marketing owns lead creation and lead follow-up
- sales owns enquiry creation, enquiry follow-up, quotation generation, and won/lost execution
- managers and admins retain cross-workspace monitoring based on resolved CRM access
### Outcome rules
- lead outcome is derived, never manually updated
- when any linked enquiry becomes won, the lead outcome becomes `won`
- when all linked enquiries are lost, the lead outcome becomes `lost`
- otherwise the lead outcome remains `open`
### Sequence rules
- lead codes use a dedicated lead document sequence
- enquiry codes keep a separate enquiry document sequence
### Reporting and dashboard rules
- lead KPI and lead reports read from `crm_leads`
- enquiry KPI and enquiry reports read from `crm_enquiries`
- no report should continue to treat `pipeline_stage` as the marketing/sales entity boundary after migration
## Consequences
### Positive
- aligns persistence with the real marketing-to-sales handoff
- supports true `1 Lead -> N Enquiries`
- preserves lead identity after assignment
- simplifies marketing analytics and awareness-source reporting
- makes direct sales enquiries first-class without overloading lead semantics
### Tradeoffs
- supersedes a previously accepted ADR and requires broad migration across services, reports, dashboard, and permissions
- requires data migration from existing `crm_enquiries.pipeline_stage`
- increases temporary compatibility complexity during rollout
## Migration Strategy
The migration should be delivered in explicit phases:
1. Introduce ADR and governance freeze for split-domain direction.
2. Add schema and seed support for `crm_leads`, `lead_id`, and lead master options.
3. Introduce lead services and lead APIs without deleting enquiry flows.
4. Migrate dashboard and reports to separated datasets.
5. Remove old single-record assumptions from shared enquiry services and UI.
## Supersedes
This ADR is intended to supersede the following sections of ADR-0011 once implemented:
- `Single record model`
- `Pipeline stage model` as the lead/enquiry persistence boundary
- `Navigation Separation` as the primary separation mechanism
Until implementation is complete, ADR-0011 remains the description of current production behavior, while this ADR defines the approved target direction for Task D.5.

View File

@@ -0,0 +1,134 @@
# Task D.5: Lead / Enquiry Domain Separation
## Status
In progress
## Objective
Replace the current single-record lead/enquiry persistence model with a split domain model that preserves marketing leads independently from sales enquiries.
## Review Summary
Reviewed before implementation:
- `AGENTS.md`
- `docs/standards/task-contract-template.md`
- `docs/standards/task-review-checklist.md`
- `docs/standards/task-catalog.md`
- `docs/standards/project-foundations.md`
- `docs/adr/0011-lead-enquiry-ownership-model.md`
- `docs/adr/0016-won-lost-lifecycle-governance.md`
- `docs/implementation/task-d3-lead-enquiry-ownership-refinement.md`
- `docs/implementation/task-d31-leads-enquiries-navigation-separation.md`
- `docs/implementation/task-d4-won-lost-lifecycle-governance.md`
- `docs/implementation/task-k2-pipeline-reports.md`
- `docs/security/crm-authorization-boundaries.md`
- `src/db/schema.ts`
- `src/features/crm/enquiries/server/service.ts`
## Historical Findings
### Current production truth
The current production CRM still follows ADR-0011:
- `crm_enquiries` is the single source of truth
- `pipeline_stage` distinguishes `lead`, `enquiry`, `closed_won`, `closed_lost`
- assigning a lead mutates the same record into an enquiry
- lead workspace and enquiry workspace are only UX separation
### Why D.5 is different
Task D.5 explicitly requires:
- dedicated `crm_leads`
- `crm_enquiries.lead_id`
- `1 Lead -> N Enquiries`
- direct sales enquiries with nullable `lead_id`
- dashboard/report split by entity rather than `pipeline_stage`
This is not an incremental extension of ADR-0011. It is a domain replacement.
## Governance Decision
Because Task D.5 conflicts with an accepted ADR, the first implementation step is to add a replacement ADR:
- `docs/adr/0018-lead-enquiry-domain-separation.md`
This task should not continue with schema or API changes while ADR-0011 remains the only source of truth for this area.
## Reuse Constraints
The split-domain refactor must still reuse existing foundations rather than rebuilding CRM from scratch:
- Audit Foundation for migration and lifecycle actions
- Master Data Foundation for lead awareness/status/follow-up/lost-reason options
- Document Sequence Foundation for lead and enquiry code generation
- Authorization Foundation for visibility and scope rules
- Dashboard Foundation for KPI composition
- Reporting Foundation for dataset and export contracts
- Won / Lost Governance Foundation for enquiry outcome transitions
## Phase Plan
### Phase 0
- freeze ADR-0018
- document the conflict with ADR-0011
- define migration boundaries
### Phase 1
- add `crm_leads`
- add `crm_enquiries.lead_id`
- add lead master option categories
- add lead document sequence type
### Phase 2
- create dedicated lead services and `/api/crm/leads/**`
- keep enquiry services focused on sales execution
- migrate assignment flow from `pipeline_stage` mutation to `lead -> create enquiry`
### Phase 3
- update dashboard datasets to separate lead/enquiry sources
- update report datasets and exports to separate lead/enquiry sources
- preserve quotation, approval, PDF, and pricing foundations
### Phase 4
- migrate legacy rows from `crm_enquiries.pipeline_stage`
- preserve follow-ups, project-party relations, attachments, quotations, and audit history
- remove single-record assumptions from surviving UI and server code
## Initial Risks
- this task affects schema, services, APIs, dashboard, reports, permissions, and migration logic at once
- existing lead reports and dashboard KPIs currently depend on `pipeline_stage`
- current enquiry services bundle lead creation defaults, assignment conversion, and won/lost logic together
- authorization rules for marketing monitoring are currently described against the shared enquiry table
## Deliverables Started
- added `docs/adr/0018-lead-enquiry-domain-separation.md`
- added `docs/implementation/task-d5-lead-enquiry-domain-separation.md`
## Next Implementation Slice
The next safe code slice is:
1. add schema support for `crm_leads` and `crm_enquiries.lead_id`
2. seed new lead option categories and document sequence configuration
3. keep existing enquiry routes live while introducing lead-specific route contracts
## Verification Target
When code phases begin, verification must include:
- `npm exec tsc --noEmit`
- migration validation for existing `crm_enquiries`
- direct sales enquiry creation with `leadId = null`
- lead assignment creating enquiries without deleting the lead
- outcome synchronization from enquiry results back to lead outcome