9.9 KiB
Task D.5.6: Opportunity Lifecycle / Won-Lost Alignment
Status
Planned
Objective
Establish the official Opportunity lifecycle and business outcome rules after the Enquiry domain has been force-renamed to Opportunity.
This task aligns:
Lead → Opportunity → Quotation → PO / Lost / Cancelled / No Quotation
without changing dashboard/report datasets yet.
The goal is to make Opportunity outcome states consistent before KPI, pipeline reporting, and dashboard work begins.
Mandatory Review
Review before implementation:
AGENTS.mdplans/task-d.5.6.mddocs/standards/project-foundations.mddocs/standards/task-catalog.mddocs/adr/0018-lead-enquiry-domain-separation.mddocs/implementation/task-d55-opportunity-sales-workspace-refinement.mddocs/implementation/task-d551-force-rename-enquiry-domain-opportunity.mddocs/security/crm-authorization-boundaries.mdsrc/db/schema.tssrc/features/crm/opportunities/**src/features/crm/quotations/**src/features/foundation/master-options/**src/features/foundation/audit-log/**
Current Foundation
Already available:
crm_leads
crm_opportunities
crm_opportunity_followups
crm_opportunity_customers
/api/crm/leads
/api/crm/opportunities
/api/crm/quotations
Lead → Opportunity assignment
Opportunity UI
Quotation flow
Approval flow
Audit foundation
Master option foundation
Do not recreate:
lead domain
opportunity domain
quotation domain
approval domain
dashboard
reports
Business Definitions
Opportunity
A sales-owned execution record created from Lead assignment or direct sales capture.
Opportunity Stage
The current sales working phase.
Example:
new
qualification
requirement_gathering
site_survey
proposal_preparation
quotation_created
negotiation
closed
Opportunity Outcome
The final business result.
Example:
open
won
lost
cancelled
no_quotation
Stage answers:
Where is Sales working now?
Outcome answers:
What was the final result?
Scope D5.6.1 Lifecycle Model
Define official lifecycle model.
Recommended fields if already available:
status
processStatus
businessStatus
outcomeStatus
lostReason
closedAt
closedBy
If schema does not yet support outcome fields, add a migration.
Recommended DB additions to crm_opportunities:
stage text not null default 'new'
outcome_status text not null default 'open'
lost_reason text null
lost_detail text null
closed_at timestamptz null
closed_by text null
cancel_reason text null
no_quotation_reason text null
Rules:
stage= working lifecycleoutcomeStatus= final result- only one final outcome allowed
- final opportunities should not allow normal editing unless reopened by permitted user
Scope D5.6.2 Master Options
Add or verify master option categories:
crm_opportunity_stage
crm_opportunity_outcome_status
crm_opportunity_lost_reason
crm_opportunity_cancel_reason
crm_opportunity_no_quotation_reason
Recommended stages:
new
qualification
requirement_gathering
site_survey
proposal_preparation
quotation_created
negotiation
closed
Recommended outcome statuses:
open
won
lost
cancelled
no_quotation
Recommended lost reasons:
price_too_high
competitor_won
budget_cancelled
timeline_not_match
spec_not_match
customer_no_response
other
Rules:
- no hardcoded labels in UI
- use master-option foundation
- seed all required options
Scope D5.6.3 Opportunity Outcome Service
Add service operations:
markOpportunityWon()
markOpportunityLost()
markOpportunityCancelled()
markOpportunityNoQuotation()
reopenOpportunity()
Location:
src/features/crm/opportunities/server/lifecycle.service.ts
Rules:
- all operations must enforce CRM access
- all operations must audit
- closed opportunities cannot be closed again
- reopening requires explicit permission
- outcome updates must be transactional
Scope D5.6.4 Outcome API Routes
Add routes:
POST /api/crm/opportunities/[id]/mark-won
POST /api/crm/opportunities/[id]/mark-lost
POST /api/crm/opportunities/[id]/mark-cancelled
POST /api/crm/opportunities/[id]/mark-no-quotation
POST /api/crm/opportunities/[id]/reopen
Request examples:
// mark lost
{
lostReason: string;
lostDetail?: string;
}
// mark cancelled
{
cancelReason: string;
}
// mark no quotation
{
noQuotationReason: string;
}
// reopen
{
reason: string;
}
Response:
{
opportunityId: string;
outcomeStatus: string;
stage: string;
closedAt?: string | null;
}
Scope D5.6.5 Quotation Alignment
Align opportunity outcome with quotation state.
Rules:
-
When quotation is created from opportunity:
- opportunity stage may become
quotation_created - outcome remains
open
- opportunity stage may become
-
When quotation is accepted / PO received:
- opportunity can be marked
won
- opportunity can be marked
-
When quotation is rejected / lost:
- opportunity can be marked
lost
- opportunity can be marked
-
Multiple quotations may exist under one opportunity
-
A single won quotation is enough to mark opportunity as
won -
If opportunity is closed, prevent creating new quotation unless reopened
Do not rewrite quotation service heavily in this task.
Add minimal guard/hooks only.
Scope D5.6.6 UI Outcome Actions
Update Opportunity Detail.
Add outcome action area:
Mark Won
Mark Lost
Mark Cancelled
Mark No Quotation
Reopen
Rules:
- show actions based on permission and current state
- require reason for lost/cancelled/no quotation
- require reason for reopen
- closed opportunities show locked/closed state
- outcome actions should not appear on lead page
Scope D5.6.7 Opportunity List Outcome Display
Update Opportunity List.
Add columns or badges:
Stage
Outcome
Lost Reason
Closed Date
Filters:
stage
outcomeStatus
lostReason
closedAt range
Rules:
- open opportunities remain default view
- closed outcomes can be filtered
- do not change dashboard/report datasets yet
Scope D5.6.8 Audit Logging
Required audit actions:
mark_opportunity_won
mark_opportunity_lost
mark_opportunity_cancelled
mark_opportunity_no_quotation
reopen_opportunity
Audit should capture:
previousStage
previousOutcomeStatus
newStage
newOutcomeStatus
reason
actedBy
actedAt
Scope D5.6.9 Permissions
Add or verify permissions:
crm.opportunity.lifecycle.update
crm.opportunity.lifecycle.reopen
Optional granular permissions:
crm.opportunity.mark_won
crm.opportunity.mark_lost
crm.opportunity.mark_cancelled
crm.opportunity.mark_no_quotation
crm.opportunity.reopen
Recommendation:
Use granular permissions if current permission model supports it.
Rules:
- do not check role strings directly
- use resolved CRM access
- backend remains source of truth
Scope D5.6.10 Documentation
Create:
docs/implementation/task-d56-opportunity-lifecycle-won-lost-alignment.md
Document:
stage model
outcome model
state transition rules
permission rules
quotation alignment
known limitations
State Transition Rules
Recommended:
open → won
open → lost
open → cancelled
open → no_quotation
won → open only via reopen
lost → open only via reopen
cancelled → open only via reopen
no_quotation → open only via reopen
Closed states:
won
lost
cancelled
no_quotation
Closed opportunities:
cannot edit normal fields
cannot create quotation
cannot add sales follow-up
can view history
can reopen if permitted
Deliverables
New or updated:
src/features/crm/opportunities/server/lifecycle.service.ts
src/features/crm/opportunities/schemas/lifecycle.schema.ts
src/app/api/crm/opportunities/[id]/mark-won/route.ts
src/app/api/crm/opportunities/[id]/mark-lost/route.ts
src/app/api/crm/opportunities/[id]/mark-cancelled/route.ts
src/app/api/crm/opportunities/[id]/mark-no-quotation/route.ts
src/app/api/crm/opportunities/[id]/reopen/route.ts
Updated:
src/db/schema.ts
src/db/seeds/foundation.seed.ts
src/features/crm/opportunities/types.ts
src/features/crm/opportunities/server/service.ts
src/features/crm/opportunities/components/opportunity-detail.tsx
src/features/crm/opportunities/components/opportunities-table.tsx
src/features/crm/opportunities/components/opportunity-status-badge.tsx
src/features/crm/opportunities/api/service.ts
src/features/crm/opportunities/api/mutations.ts
src/features/crm/opportunities/api/queries.ts
Documentation:
docs/implementation/task-d56-opportunity-lifecycle-won-lost-alignment.md
Explicit Non-Scope
Do not:
- rename opportunity domain again
- change lead domain
- rewrite quotation approval
- create dashboard datasets
- create reports
- implement sales KPI
- implement PO module
- migrate historical production data
- create Sales Enquiry document
These belong to later phases.
Verification
Run:
npm exec tsc --noEmit
npm run build
Manual verification:
Create opportunity
Create quotation from opportunity
Mark opportunity won
Mark opportunity lost with reason
Mark opportunity cancelled with reason
Mark opportunity no quotation with reason
Reopen opportunity
Blocked quotation creation on closed opportunity
Opportunity list filters by outcome
Audit logs exist for lifecycle actions
Lead → Opportunity still works
Quotation flow still works
Approval flow still works
Definition of Done
Task is complete when:
- Opportunity has official stage and outcome model
- Won/Lost/Cancelled/No Quotation actions work
- Reopen flow works with permission
- Opportunity list/detail show lifecycle state
- Closed opportunities are guarded
- Quotation alignment is minimally enforced
- Audit logs capture lifecycle transitions
- Existing Lead, Quotation, and Approval flows remain operational
Result:
Opportunity Lifecycle = Established
Won/Lost Alignment = Established
Pipeline Outcome Foundation = Ready
Ready for Pipeline KPI / Dashboard Foundation