This commit is contained in:
phaichayon
2026-06-22 16:57:02 +07:00
parent 3f28fde39f
commit ffa5de8311
33 changed files with 6706 additions and 101 deletions

View File

@@ -0,0 +1,58 @@
# ADR 0016: Won / Lost Lifecycle Governance
## Status
Accepted
## Context
CRM dashboard and reporting previously treated quotation acceptance and rejection as proxies for opportunity outcomes. That made business outcome data unstable because:
- quotation approval/acceptance does not equal PO received
- lost opportunities could be inferred without a required lost reason
- reopen rules were undefined
- revenue and win/loss KPI logic depended on quotation status instead of the enquiry lifecycle
Business Discovery 3.0 froze the production rule set:
- `closed_won` means PO received
- `closed_lost` requires a lost reason
- won cannot reopen
- lost can reopen with audit
- won revenue prefers PO amount and falls back to quotation total
- lost revenue uses quotation total
## Decision
We use `crm_enquiries` as the official business outcome record.
We add outcome fields on `crm_enquiries`:
- `closed_won_at`
- `closed_lost_at`
- `closed_by_user_id`
- `po_number`
- `po_date`
- `po_amount`
- `po_currency`
- `lost_reason`
- `lost_competitor`
- `lost_remark`
We add `crm_enquiry_attachments` for purchase order files with category `purchase_order`.
All lifecycle transitions must go through the enquiry outcome service layer:
- `markEnquiryAsWon()`
- `markEnquiryAsLost()`
- `reopenLostEnquiry()`
Quotation status is no longer allowed to mutate enquiry outcome automatically.
## Consequences
- dashboard won/lost KPI must read enquiry `pipeline_stage`
- marketing can see outcome state but PO amount remains server-side restricted
- PO attachment access follows commercial-data visibility rules
- audit coverage expands to `mark_won`, `mark_lost`, `reopen`, and `upload_po`
- downstream reporting can reuse a stable outcome model for Task K

View File

@@ -0,0 +1,41 @@
# Task D.4 Implementation Summary
## Completed
- extended `crm_enquiries` with official won/lost lifecycle fields
- added `crm_enquiry_attachments` for purchase order files
- seeded `crm_lost_reason` master data
- added permissions:
- `crm.enquiry.mark_won`
- `crm.enquiry.mark_lost`
- `crm.enquiry.reopen`
- added outcome service operations for:
- mark won
- mark lost
- reopen lost opportunity
- added API routes for outcome transitions and PO attachment upload/view/download
- added enquiry detail outcome card with:
- open / won / lost state
- PO data
- lost data
- closed by / closed date
- mark won / mark lost / reopen actions
- PO attachment upload and retrieval
- removed quotation-status-driven mutation of enquiry pipeline outcome
- migrated dashboard summary/funnel/sales won revenue to enquiry outcome helpers
- added reporting helpers:
- `getWonRevenue()`
- `getLostRevenue()`
- `getLostByReason()`
- `getLostByCompetitor()`
- `getWinRate()`
## Verification
- `npm exec tsc --noEmit`
## Notes
- marketing users still receive outcome state, but PO amount is redacted server-side unless commercial pricing visibility exists
- PO attachment endpoints are also restricted to commercial-data viewers
- revenue analytics cards still use existing quotation-attribution views for top-party ranking, but won/lost headline KPI and sales won revenue now follow the D.4 lifecycle rule