545 lines
10 KiB
Markdown
545 lines
10 KiB
Markdown
# 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
|
|
```
|