taks-d.2.1
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# Task D.2 Billing Customer + Project Parties
|
||||
|
||||
## Scope delivered
|
||||
|
||||
- Kept the main CRM field named `Billing Customer` on enquiry and quotation forms/detail pages.
|
||||
- Added a separate `Project Parties` model for enquiries and aligned quotation related customers to the same business concept.
|
||||
- Added `remark` support on quotation project parties and introduced enquiry project-party persistence.
|
||||
- Switched role sourcing to the shared master-option category `crm_project_party_role`.
|
||||
- Added compatibility mapping so legacy quotation roles such as `owner` and `billing` still render correctly.
|
||||
|
||||
## Data model
|
||||
|
||||
- Added `crm_enquiry_customers` for enquiry project parties.
|
||||
- Added `remark` column to `crm_quotation_customers`.
|
||||
- Generated migration `drizzle/0010_calm_wendell_rand.sql`.
|
||||
|
||||
## Server behavior
|
||||
|
||||
- Enquiry create/update now sync `Project Parties` transactionally.
|
||||
- Quotation create/update now sync `Project Parties` transactionally.
|
||||
- Billing customer is auto-added as a `billing_customer` project party during sync.
|
||||
- Duplicate `customerId + roleCode` combinations are de-duplicated during main form sync and rejected in quotation detail CRUD.
|
||||
- Quotation project-party reads normalize legacy role codes to current shared role options.
|
||||
|
||||
## UI changes
|
||||
|
||||
- Enquiry form now has `Billing Customer` plus a `Project Parties` editor.
|
||||
- Enquiry detail now shows `Billing Customer` and `Project Parties` separately.
|
||||
- Quotation form now has `Billing Customer` plus a `Project Parties` editor.
|
||||
- Quotation detail overview separates `Billing Customer` and `Project Parties`.
|
||||
- Quotation customer tab is relabeled to `Project Parties`, shows role clearly, and supports remark editing.
|
||||
|
||||
## Verification
|
||||
|
||||
- `npx tsc --noEmit` passes.
|
||||
- `npm run lint` still fails because of pre-existing repo issues outside this task, including:
|
||||
- `src/components/ui/input-group.tsx`
|
||||
- `src/features/chat/components/message-composer.tsx`
|
||||
- `src/components/forms/demo-form.tsx`
|
||||
@@ -0,0 +1,76 @@
|
||||
# Task D.2.1: Revenue Attribution and Party Governance
|
||||
|
||||
## Scope delivered
|
||||
|
||||
- Froze the revenue attribution rules for CRM reporting.
|
||||
- Froze project-party reporting authority so dashboard and reports use the same source of truth.
|
||||
- Added reusable server-side helpers for grouped revenue analytics by party role.
|
||||
- Added ADR governance so future reporting changes require an explicit decision trail.
|
||||
|
||||
## Frozen rules
|
||||
|
||||
### Revenue owner
|
||||
|
||||
- `Revenue Owner = End Customer`
|
||||
- Authority: project-party role code `end_customer`
|
||||
- Fallback: if no `end_customer` exists, use `billing_customer`
|
||||
|
||||
### Relationship revenue dimensions
|
||||
|
||||
- `Billing Revenue` = quotation revenue grouped by project-party role `billing_customer`
|
||||
- `Contractor Revenue` = quotation revenue grouped by project-party role `contractor`
|
||||
- `Consultant Revenue` = quotation revenue grouped by project-party role `consultant`
|
||||
|
||||
### Multiple party behavior
|
||||
|
||||
- When multiple parties share the same role on a quotation, each party receives full quotation attribution.
|
||||
- This is intentional CRM relationship analytics behavior.
|
||||
- No proration is applied.
|
||||
|
||||
### Reporting authority
|
||||
|
||||
- Reporting must use project-party relationships, not raw customer references by themselves.
|
||||
- Service logic prefers `crm_quotation_customers`.
|
||||
- If quotation parties are missing, service logic falls back to `crm_enquiry_customers` on the linked enquiry.
|
||||
- Cancelled quotations are excluded by default from revenue attribution.
|
||||
|
||||
## Service layer added
|
||||
|
||||
Added server-only reporting helpers under `src/features/crm/reporting/server/`:
|
||||
|
||||
- `getRevenueByEndCustomer()`
|
||||
- `getRevenueByBillingCustomer()`
|
||||
- `getRevenueByContractor()`
|
||||
- `getRevenueByConsultant()`
|
||||
|
||||
Shared behavior:
|
||||
|
||||
- organization scoped
|
||||
- supports active quotation/report filters
|
||||
- groups revenue by customer
|
||||
- counts quotation coverage per attributed customer
|
||||
- respects end-customer fallback to billing-customer for revenue-owner attribution
|
||||
|
||||
## Governance added
|
||||
|
||||
- Added ADR [0010-revenue-attribution-governance.md](/c:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0010-revenue-attribution-governance.md)
|
||||
- This ADR freezes:
|
||||
- revenue owner rule
|
||||
- project-party reporting rule
|
||||
- fallback rule
|
||||
- multiple-party rule
|
||||
|
||||
## Remaining risks
|
||||
|
||||
- quotations and enquiries still use separate party tables, so analytics currently depends on a precedence rule rather than a unified reporting projection
|
||||
- legacy records without synchronized project-party rows may not attribute revenue until backfill or edit/save normalization happens
|
||||
- historical analytics still reflects current project-party state, not an effective-dated party snapshot
|
||||
|
||||
## Task J readiness
|
||||
|
||||
- revenue attribution rules frozen
|
||||
- revenue owner frozen
|
||||
- project-party reporting rules frozen
|
||||
- KPI dimensions frozen for reporting by end customer, billing customer, contractor, and consultant
|
||||
- reusable service layer added for dashboard/report reuse
|
||||
- ADR added
|
||||
139
docs/implementation/task-j0-kpi-definition-freeze.md
Normal file
139
docs/implementation/task-j0-kpi-definition-freeze.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Task J.0: KPI Definition Freeze
|
||||
|
||||
## Scope delivered
|
||||
|
||||
- Froze the CRM sales pipeline definitions that Dashboard KPI must use.
|
||||
- Froze customer party terminology so enquiry, quotation, and dashboard reporting use the same business language.
|
||||
- Froze KPI and revenue formulas before Task J dashboard work begins.
|
||||
- Declared that KPI definition changes after this point require a new ADR or equivalent decision record.
|
||||
|
||||
## Governance rule
|
||||
|
||||
- After Task J.0, KPI definitions, pipeline meanings, and dashboard counting rules must not change silently.
|
||||
- Any future change to these definitions requires a new ADR so reporting behavior stays auditable.
|
||||
|
||||
## Frozen sales pipeline
|
||||
|
||||
### Lead
|
||||
|
||||
- Definition: enquiry is not assigned to sales yet.
|
||||
- Rule: `assignedToUserId = null`
|
||||
- Rule: `pipelineStage = lead`
|
||||
- KPI impact: `Lead Count`, `Lead Aging`, `Lead Source`, `New Leads`, `Unassigned Leads`
|
||||
|
||||
### Opportunity
|
||||
|
||||
- Definition: enquiry has already been assigned to sales.
|
||||
- Rule: `assignedToUserId != null`
|
||||
- Rule: `pipelineStage = opportunity`
|
||||
- Auto transition: assigning sales moves the record from `Lead` to `Opportunity`
|
||||
- KPI impact: `Opportunity Count`, `Opportunity Value`, `Hot Opportunities`, `Opportunity Aging`, `Open Opportunities`
|
||||
|
||||
### Closed Won
|
||||
|
||||
- Definition: customer confirms the award, or the project is officially awarded.
|
||||
- Rule: `pipelineStage = closed_won`
|
||||
|
||||
### Closed Lost
|
||||
|
||||
- Definition: competitor wins, budget is not approved, customer cancels, or project does not proceed.
|
||||
- Rule: `pipelineStage = closed_lost`
|
||||
|
||||
## Frozen opportunity lifecycle
|
||||
|
||||
```txt
|
||||
Lead
|
||||
-> Assign Sales
|
||||
Opportunity
|
||||
-> Create Quotation
|
||||
Opportunity
|
||||
-> Approved Quotation
|
||||
Opportunity
|
||||
-> Award
|
||||
Closed Won
|
||||
```
|
||||
|
||||
## Frozen customer party definitions
|
||||
|
||||
- `Billing Customer`: the company used for quotation issue and billing.
|
||||
- `End Customer`: the real project owner.
|
||||
- `Contractor`: the construction contractor or main contractor.
|
||||
- `Consultant`: the project consultant.
|
||||
- `Customer`: a general commercial counterparty or related party when a more specific role is not applicable.
|
||||
|
||||
These terms are frozen as the shared language for CRM forms, detail views, and future dashboard/reporting logic.
|
||||
|
||||
## Frozen KPI definitions
|
||||
|
||||
### Lead metrics
|
||||
|
||||
- `Lead Count` = enquiries where `pipelineStage = lead`
|
||||
- `New Leads` = lead-stage enquiries created within the selected reporting period
|
||||
- `Unassigned Leads` = lead-stage enquiries where `assignedToUserId = null`
|
||||
- `Lead Aging` = age of lead-stage enquiries based on creation date until current/report date
|
||||
|
||||
### Opportunity metrics
|
||||
|
||||
- `Opportunity Count` = enquiries where `pipelineStage = opportunity`
|
||||
- `Open Opportunities` = opportunities still active and not moved to `closed_won` or `closed_lost`
|
||||
- `Hot Opportunities` = opportunity-stage enquiries flagged by the future dashboard/business rule layer
|
||||
- `Opportunity Aging` = age of opportunity-stage enquiries based on entry into active opportunity lifecycle until current/report date
|
||||
|
||||
### Conversion metrics
|
||||
|
||||
- `Lead -> Opportunity` = `Opportunity Created / Total Leads`
|
||||
- `Opportunity -> Quotation` = `Opportunity With Quotation / Total Opportunities`
|
||||
- `Quotation -> Won` = `Closed Won / Total Opportunities With Quotation`
|
||||
|
||||
## Frozen revenue rules
|
||||
|
||||
### Opportunity value
|
||||
|
||||
- Source: `crm_enquiries.estimatedValue`
|
||||
- Condition: only count rows where `pipelineStage = opportunity`
|
||||
|
||||
### Quotation value
|
||||
|
||||
- Source: `crm_quotations.totalAmount`
|
||||
- Condition: count quotations where `status != cancelled`
|
||||
|
||||
### Won value
|
||||
|
||||
- Source: `crm_quotations.totalAmount`
|
||||
- Condition: count quotation-linked business only when parent enquiry `pipelineStage = closed_won`
|
||||
|
||||
### Lost value
|
||||
|
||||
- Source: `crm_quotations.totalAmount`
|
||||
- Condition: count quotation-linked business only when parent enquiry `pipelineStage = closed_lost`
|
||||
|
||||
## Frozen ownership rules for reporting
|
||||
|
||||
- Dashboard ownership for `Lead` and `Opportunity` must use `assignedToUserId`.
|
||||
- Dashboard ownership for `Quotation` must use `salesmanId`.
|
||||
|
||||
This prevents mixing enquiry ownership with quotation ownership in the same KPI bucket.
|
||||
|
||||
## Explicit non-scope items
|
||||
|
||||
Task J.0 intentionally does not add lifecycle timestamp fields or new master-data defaults.
|
||||
|
||||
Deferred fields:
|
||||
|
||||
- `convertedToOpportunityAt`
|
||||
- `closedWonAt`
|
||||
- `closedLostAt`
|
||||
- `lostReason`
|
||||
- `defaultPartyRole`
|
||||
|
||||
These remain technical debt and should be added only when downstream reporting or workflow requirements justify schema changes.
|
||||
|
||||
## Dashboard readiness
|
||||
|
||||
Task J can now build CRM dashboard KPIs on top of these frozen definitions:
|
||||
|
||||
- KPI definitions frozen
|
||||
- sales pipeline frozen
|
||||
- customer party definitions frozen
|
||||
- revenue rules frozen
|
||||
- dashboard semantics ready for implementation
|
||||
@@ -242,3 +242,58 @@ Future:
|
||||
- decide when direct signed URLs are acceptable versus mandatory server streaming
|
||||
- add response caching strategy for large artifact downloads
|
||||
- evaluate optional download-audit sampling if volume grows
|
||||
|
||||
## After Task J.0
|
||||
|
||||
### Missing lifecycle timestamps for KPI precision
|
||||
|
||||
Task J.0 freezes KPI rules around `pipelineStage`, but the schema still lacks explicit lifecycle timestamps for stage conversion and closure events.
|
||||
|
||||
Future:
|
||||
|
||||
- add `convertedToOpportunityAt` for precise lead-to-opportunity timing
|
||||
- add `closedWonAt` for won-date reporting
|
||||
- add `closedLostAt` for lost-date reporting
|
||||
- backfill reporting logic to prefer explicit lifecycle timestamps over inference when available
|
||||
|
||||
### Lost reason analytics
|
||||
|
||||
Closed-lost semantics are frozen, but there is still no structured `lostReason` field for dashboard grouping and sales analysis.
|
||||
|
||||
Future:
|
||||
|
||||
- add `lostReason` to the CRM enquiry lifecycle model
|
||||
- define a controlled vocabulary or master-option category for loss analysis
|
||||
- expose lost-reason reporting only after the field is reliably captured
|
||||
|
||||
### Default customer party role behavior
|
||||
|
||||
Customer party definitions are frozen, but there is still no `defaultPartyRole` behavior to help normalize customer selection and reduce operator ambiguity.
|
||||
|
||||
Future:
|
||||
|
||||
- decide whether customers should carry a suggested default project-party role
|
||||
- add `defaultPartyRole` only if it improves data-entry quality without creating hidden automation
|
||||
- keep dashboard logic independent from this field until the model is formally introduced
|
||||
|
||||
## After Task D.2.1
|
||||
|
||||
### Unified project-party reporting projection
|
||||
|
||||
Revenue attribution is now frozen, but the schema still stores enquiry and quotation parties in separate tables rather than a dedicated reporting projection.
|
||||
|
||||
Future:
|
||||
|
||||
- add a unified reporting view or materialized projection if dashboard/report queries become heavy
|
||||
- keep quotation-party precedence and enquiry fallback centralized in one service until that projection exists
|
||||
- avoid duplicating attribution logic in dashboard-specific code
|
||||
|
||||
### Historical party snapshot fidelity
|
||||
|
||||
Current revenue attribution uses live project-party relationships with quotation-first fallback behavior, but it does not yet provide effective-dated historical party snapshots for reporting.
|
||||
|
||||
Future:
|
||||
|
||||
- decide whether approved quotation snapshots or another immutable reporting snapshot should become the historical source of truth
|
||||
- define how reporting should behave if project parties change after quotation approval or after a won/lost outcome
|
||||
- add backfill or reconciliation tooling if historical party drift becomes a business issue
|
||||
|
||||
Reference in New Issue
Block a user