# Task D.5.6 Opportunity Lifecycle / Won-Lost Alignment ## Summary - Added explicit opportunity outcome fields in `crm_opportunities`: - `outcomeStatus` - `closedAt` - `lostDetail` - `cancelReason` - `noQuotationReason` - Preserved legacy compatibility fields: - `pipelineStage` - `closedWonAt` - `closedLostAt` ## Lifecycle Model - Working stage continues to use `crm_opportunities.status` for now. - UI/API now expose `stage` as an alias of `status`. - Final result is tracked with `outcomeStatus`. ### Closed outcomes - `won` - `lost` - `cancelled` - `no_quotation` ### Reopen behavior - Reopen clears final outcome data. - Reopen returns the opportunity to: - `status = quotation_created` - `outcomeStatus = open` - `pipelineStage = opportunity` ## Master Options - Added/seeded: - `crm_opportunity_stage` - `crm_opportunity_outcome_status` - `crm_opportunity_lost_reason` - `crm_opportunity_cancel_reason` - `crm_opportunity_no_quotation_reason` - Kept legacy compatibility categories: - `crm_opportunity_status` - `crm_lost_reason` ## API 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` ## Quotation Alignment - Quotation creation is blocked when linked opportunity has a closed `outcomeStatus`. - Quotation creation keeps outcome as `open`. - Opportunity stage sync remains compatibility-first and moves to `quotation_created`. ## Audit Actions - `mark_opportunity_won` - `mark_opportunity_lost` - `mark_opportunity_cancelled` - `mark_opportunity_no_quotation` - `reopen_opportunity` ## Encoding Guard - Verified repository text encoding with `npm run verify:encoding`. - Repaired mojibake on CRM dashboard components in the sales ranking, follow-up, and hot-project sections.