Files
alla-allaos-fullstack/plans/task-d.5.6.md
phaichayon 65dd0e2e56 task-d.5.6
2026-06-25 13:09:21 +07:00

603 lines
9.9 KiB
Markdown

# 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:
```txt
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.md`
* `plans/task-d.5.6.md`
* `docs/standards/project-foundations.md`
* `docs/standards/task-catalog.md`
* `docs/adr/0018-lead-enquiry-domain-separation.md`
* `docs/implementation/task-d55-opportunity-sales-workspace-refinement.md`
* `docs/implementation/task-d551-force-rename-enquiry-domain-opportunity.md`
* `docs/security/crm-authorization-boundaries.md`
* `src/db/schema.ts`
* `src/features/crm/opportunities/**`
* `src/features/crm/quotations/**`
* `src/features/foundation/master-options/**`
* `src/features/foundation/audit-log/**`
---
## Current Foundation
Already available:
```txt
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:
```txt
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:
```txt
new
qualification
requirement_gathering
site_survey
proposal_preparation
quotation_created
negotiation
closed
```
### Opportunity Outcome
The final business result.
Example:
```txt
open
won
lost
cancelled
no_quotation
```
Stage answers:
```txt
Where is Sales working now?
```
Outcome answers:
```txt
What was the final result?
```
---
## Scope D5.6.1 Lifecycle Model
Define official lifecycle model.
Recommended fields if already available:
```txt
status
processStatus
businessStatus
outcomeStatus
lostReason
closedAt
closedBy
```
If schema does not yet support outcome fields, add a migration.
Recommended DB additions to `crm_opportunities`:
```txt
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 lifecycle
* `outcomeStatus` = 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:
```txt
crm_opportunity_stage
crm_opportunity_outcome_status
crm_opportunity_lost_reason
crm_opportunity_cancel_reason
crm_opportunity_no_quotation_reason
```
Recommended stages:
```txt
new
qualification
requirement_gathering
site_survey
proposal_preparation
quotation_created
negotiation
closed
```
Recommended outcome statuses:
```txt
open
won
lost
cancelled
no_quotation
```
Recommended lost reasons:
```txt
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:
```ts
markOpportunityWon()
markOpportunityLost()
markOpportunityCancelled()
markOpportunityNoQuotation()
reopenOpportunity()
```
Location:
```txt
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:
```txt
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:
```ts
// mark lost
{
lostReason: string;
lostDetail?: string;
}
// mark cancelled
{
cancelReason: string;
}
// mark no quotation
{
noQuotationReason: string;
}
// reopen
{
reason: string;
}
```
Response:
```ts
{
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`
* When quotation is accepted / PO received:
* opportunity can be marked `won`
* When quotation is rejected / lost:
* opportunity can be marked `lost`
* 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:
```txt
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:
```txt
Stage
Outcome
Lost Reason
Closed Date
```
Filters:
```txt
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:
```txt
mark_opportunity_won
mark_opportunity_lost
mark_opportunity_cancelled
mark_opportunity_no_quotation
reopen_opportunity
```
Audit should capture:
```txt
previousStage
previousOutcomeStatus
newStage
newOutcomeStatus
reason
actedBy
actedAt
```
---
## Scope D5.6.9 Permissions
Add or verify permissions:
```txt
crm.opportunity.lifecycle.update
crm.opportunity.lifecycle.reopen
```
Optional granular permissions:
```txt
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:
```txt
docs/implementation/task-d56-opportunity-lifecycle-won-lost-alignment.md
```
Document:
```txt
stage model
outcome model
state transition rules
permission rules
quotation alignment
known limitations
```
---
## State Transition Rules
Recommended:
```txt
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:
```txt
won
lost
cancelled
no_quotation
```
Closed opportunities:
```txt
cannot edit normal fields
cannot create quotation
cannot add sales follow-up
can view history
can reopen if permitted
```
---
## Deliverables
New or updated:
```txt
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:
```txt
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:
```txt
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:
```txt
npm exec tsc --noEmit
npm run build
```
Manual verification:
```txt
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:
```txt
Opportunity Lifecycle = Established
Won/Lost Alignment = Established
Pipeline Outcome Foundation = Ready
Ready for Pipeline KPI / Dashboard Foundation
```