# 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 ```