task-d.5.7

This commit is contained in:
phaichayon
2026-06-25 15:41:59 +07:00
parent 65dd0e2e56
commit 69279fc341
9 changed files with 1111 additions and 29 deletions

544
plans/task-d.5.7.md Normal file
View File

@@ -0,0 +1,544 @@
# Task D.5.7: Opportunity ↔ Quotation Synchronization Foundation
## Status
Planned
## Objective
Establish synchronization rules between Opportunity and Quotation after the Opportunity lifecycle foundation is in place.
This task ensures that Opportunity stage and outcome are aligned with quotation creation, quotation approval, quotation acceptance, quotation rejection, revision, and future PO flow.
Business flow:
```txt
Lead → Opportunity → Quotation → Approval → Customer → PO / Lost
```
The objective is to make Opportunity the sales pipeline source of truth while keeping Quotation as the commercial document source of truth.
---
## Mandatory Review
Review before implementation:
* `AGENTS.md`
* `plans/task-d.5.7.md`
* `docs/standards/project-foundations.md`
* `docs/standards/task-catalog.md`
* `docs/adr/0018-lead-enquiry-domain-separation.md`
* `docs/implementation/task-d551-force-rename-enquiry-domain-opportunity.md`
* `docs/implementation/task-d56-opportunity-lifecycle-won-lost-alignment.md`
* `docs/security/crm-authorization-boundaries.md`
* `src/db/schema.ts`
* `src/features/crm/opportunities/**`
* `src/features/crm/quotations/**`
* `src/features/crm/approval/**`
* `src/features/foundation/audit-log/**`
---
## Current Foundation
Already available:
```txt
crm_leads
crm_opportunities
crm_opportunity_followups
crm_opportunity_customers
crm_quotations
crm_quotation_items
crm_quotation_followups
crm_approval_requests
crm_document_artifacts
Lead → Opportunity assignment
Opportunity lifecycle
Quotation creation
Quotation approval
Approved PDF artifact
Audit foundation
```
---
## Business Ownership Rule
Opportunity owns:
```txt
sales pipeline
sales stage
business outcome
sales owner
expected value
chance percent
expected close date
```
Quotation owns:
```txt
commercial document
quotation amount
revision
approval state
approved PDF
customer-facing proposal
```
---
## Scope D5.7.1 Relationship Naming Alignment
Ensure active code uses:
```txt
opportunityId
```
instead of:
```txt
enquiryId
```
for quotation relationship fields and DTOs.
If DB still contains legacy column name:
```txt
crm_quotations.enquiry_id
```
then rename to:
```txt
crm_quotations.opportunity_id
```
Because the project is still in dev/reset mode, prefer clean naming.
Rules:
* no active application code should expose `enquiryId`
* quotation APIs should accept `opportunityId`
* quotation detail should show linked Opportunity
* Opportunity detail should show linked Quotations
---
## Scope D5.7.2 Quotation Creation Sync
When creating a quotation from an Opportunity:
* require `opportunityId`
* verify Opportunity exists and is open
* prevent quotation creation from closed opportunities
* copy baseline context from Opportunity:
* customer
* contact
* project name
* project location
* product type
* salesman / owner
* chance percent
* hot project flag if available
After quotation creation:
```txt
opportunity.status = quotation_created
opportunity.outcomeStatus = open
```
Rules:
* do not overwrite closed outcome
* do not auto mark won
* audit both quotation creation and opportunity sync
---
## Scope D5.7.3 Quotation Revision Sync
When creating quotation revision:
* revision must remain linked to the same Opportunity
* parent and child quotation must share `opportunityId`
* Opportunity stays open unless already closed
* Opportunity stage remains `quotation_created` or moves to `negotiation` if supported
Rules:
* do not duplicate opportunity
* do not unlink old revisions
* detail UI should group revisions under the same opportunity
---
## Scope D5.7.4 Approval Sync
When quotation is submitted for approval:
```txt
Opportunity stage may remain quotation_created
```
When quotation is approved:
```txt
Opportunity stage may become negotiation
```
Recommended mapping:
```txt
quotation draft → opportunity quotation_created
quotation pending_approval → opportunity quotation_created
quotation approved → opportunity negotiation
quotation sent_to_customer → opportunity negotiation
```
Rules:
* approval sync must be non-destructive
* do not change opportunity outcome
* do not mark won on approval
* audit stage sync if changed
---
## Scope D5.7.5 Quotation Outcome Sync
Add explicit service operations or hooks for:
```txt
acceptQuotation()
rejectQuotation()
markQuotationLost()
```
If such operations already exist, wire them to Opportunity lifecycle.
When quotation is accepted or PO is confirmed:
```txt
opportunity.outcomeStatus = won
opportunity.closedAt = now
opportunity.closedWonAt = now
```
When quotation is rejected/lost and no other active quotation remains viable:
```txt
opportunity.outcomeStatus = lost
opportunity.closedAt = now
opportunity.closedLostAt = now
```
Rules:
* one won quotation marks opportunity won
* one lost quotation must not automatically mark opportunity lost if another active quotation/revision is still open
* latest active revision should drive outcome when applicable
* lost requires reason
* won should eventually be driven by PO, but manual won remains allowed until PO module exists
---
## Scope D5.7.6 Opportunity Detail Integration
Update Opportunity detail to show linked quotations.
Display:
```txt
Quotation Code
Revision
Status
Approval Status
Total Amount
Currency
Valid Until
Accepted / Rejected state
```
Actions:
```txt
Open Quotation
Create Quotation
Create Revision
Mark Won
Mark Lost
```
Rules:
* Create Quotation action should preselect current opportunity
* closed opportunities disable create quotation
* winning quotation should be visibly highlighted
---
## Scope D5.7.7 Quotation Detail Integration
Update Quotation detail to show linked Opportunity.
Display:
```txt
Opportunity Code
Opportunity Stage
Opportunity Outcome
Sales Owner
Expected Close Date
```
Actions:
```txt
Open Opportunity
```
Rules:
* no broader permission leak
* show only if user can access quotation and linked opportunity context is safe to display
---
## Scope D5.7.8 Data Consistency Guards
Add guards:
* quotation cannot link to opportunity from another organization
* quotation product type must match opportunity product type unless explicitly permitted
* quotation customer must match opportunity customer or opportunity project party unless explicitly permitted
* closed opportunity cannot create quotation
* quotation revision must keep the same opportunity
---
## Scope D5.7.9 Audit Logging
Required audit actions:
```txt
sync_opportunity_from_quotation_created
sync_opportunity_from_quotation_approved
sync_opportunity_from_quotation_sent
sync_opportunity_from_quotation_won
sync_opportunity_from_quotation_lost
sync_quotation_opportunity_link
```
Audit must capture:
```txt
opportunityId
quotationId
previousOpportunityStage
newOpportunityStage
previousOutcomeStatus
newOutcomeStatus
reason
actedBy
```
---
## Scope D5.7.10 Permissions
Use existing permissions:
```txt
crm.opportunity.read
crm.opportunity.update
crm.opportunity.lifecycle.update
crm.quotation.create
crm.quotation.update
crm.quotation.read
```
Rules:
* quotation creation requires quotation create permission
* opportunity sync should be service-side and not require separate user UI permission if triggered by valid quotation action
* manual outcome changes still require opportunity lifecycle permission
* do not check role strings directly
---
## Scope D5.7.11 Documentation
Create:
```txt
docs/implementation/task-d57-opportunity-quotation-synchronization-foundation.md
```
Document:
```txt
relationship model
sync trigger map
stage mapping
outcome mapping
revision rules
guard rules
known limitations
```
---
## Recommended Sync Map
```txt
Quotation Created
→ Opportunity stage = quotation_created
→ Outcome = open
Quotation Submitted
→ Opportunity stage = quotation_created
→ Outcome = open
Quotation Approved
→ Opportunity stage = negotiation
→ Outcome = open
Quotation Sent
→ Opportunity stage = negotiation
→ Outcome = open
Quotation Accepted / PO Confirmed
→ Opportunity stage = closed
→ Outcome = won
Quotation Rejected / Lost
→ Opportunity stage = closed only if no viable quotation remains
→ Outcome = lost
```
---
## Deliverables
Updated:
```txt
src/db/schema.ts
src/features/crm/opportunities/**
src/features/crm/quotations/**
src/features/crm/approval/**
```
New or updated:
```txt
src/features/crm/opportunities/server/quotation-sync.service.ts
src/features/crm/quotations/server/opportunity-link.service.ts
```
Updated APIs:
```txt
/api/crm/quotations
/api/crm/quotations/[id]
/api/crm/quotations/[id]/revisions
```
Updated UI:
```txt
Opportunity Detail linked quotations
Quotation Detail linked opportunity
Quotation Create form preselect opportunity
```
Documentation:
```txt
docs/implementation/task-d57-opportunity-quotation-synchronization-foundation.md
```
---
## Explicit Non-Scope
Do not:
* create PO module
* create dashboard datasets
* create KPI cards
* create reports
* rewrite approval matrix
* create Sales Enquiry document
* rewrite PDF template
* implement revenue recognition
* migrate production data
These belong to later phases.
---
## Verification
Run:
```txt
npm exec tsc --noEmit
npm run build
```
Manual verification:
```txt
Create quotation from opportunity
Opportunity stage becomes quotation_created
Submit quotation for approval
Approve quotation
Opportunity stage becomes negotiation if configured
Create quotation revision
Revision keeps same opportunityId
Mark quotation accepted / won
Opportunity outcome becomes won
Mark quotation lost
Opportunity becomes lost only when no viable quotation remains
Closed opportunity blocks quotation creation
Opportunity detail shows quotations
Quotation detail shows opportunity
Audit logs capture sync actions
Lead → Opportunity still works
Approval flow still works
PDF flow still works
```
---
## Definition of Done
Task is complete when:
* Quotation uses `opportunityId`
* Opportunity stage syncs from quotation lifecycle
* Opportunity outcome can sync from quotation won/lost events
* Closed opportunity blocks new quotations
* Opportunity detail shows linked quotations
* Quotation detail shows linked opportunity
* Revisions preserve opportunity linkage
* Audit logs capture synchronization
* Existing Lead, Opportunity, Quotation, Approval, and PDF flows remain operational
Result:
```txt
Opportunity ↔ Quotation Sync = Established
Pipeline Outcome Integrity = Strengthened
Ready for Pipeline KPI / Dashboard Foundation
```