19 Commits

Author SHA1 Message Date
phaichayon
69ae3d42c4 commit 2026-07-15 15:43:28 +07:00
phaichayon
df1821982d commit 2026-07-15 05:46:59 +07:00
phaichayon
b51bf3e02a preview pdf 2026-07-14 18:37:09 +07:00
phaichayon
a9dcc11ea9 commit show calenda 2026-07-14 16:15:30 +07:00
phaichayon
a16dc8e548 task approve-csv file 2026-07-14 08:33:07 +07:00
phaichayon
bb5a910b88 commit task-ep.1.6.2-r1 2026-07-13 22:29:11 +07:00
phaichayon
7d7548ebd4 task ep1.6.1 2026-07-13 17:01:54 +07:00
phaichayon
e0fcb3992b task-ep.1.7 2026-07-13 16:55:55 +07:00
phaichayon
ab56852c47 task-ep.1.6 2026-07-13 16:29:40 +07:00
phaichayon
2bc8dd184c task-ep.1.5 2026-07-13 15:03:17 +07:00
phaichayon
bba4c8d97d task ep.1.4.1 2026-07-13 13:08:00 +07:00
phaichayon
06af36156f task ep.1.4 2026-07-13 12:48:33 +07:00
phaichayon
d78c56148e task ep.1.3.md 2026-07-13 11:00:49 +07:00
phaichayon
e96320a24d task-ep.1.2 2026-07-13 09:49:45 +07:00
phaichayon
9ae4f31f28 task-ep.1.1 2026-07-10 10:33:45 +07:00
phaichayon
f47fea5d6f commit eng0 2026-07-07 19:46:31 +07:00
phaichayon
919d9e4f8a ar2 2026-07-07 16:32:26 +07:00
phaichayon
62762fc70c bu1 ad email config 2026-07-07 14:37:17 +07:00
phaichayon
3b5fede134 r0 2026-07-07 10:02:28 +07:00
234 changed files with 71831 additions and 2613 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,791 @@
# Task AR.1 Architecture Transition Plan - 2026-07-07
## Scope
- complete AR.1 as an architecture-only transition-planning phase
- map the current ALLA OS production architecture to the frozen Relationship-Driven Sales Workspace blueprint
- define the target architecture, transition rules, ownership boundaries, projection strategy, workspace strategy, business-event strategy, and epic order
- keep the phase documentation-only with no schema, API, UI, service, permission, or business-logic runtime changes
## Review Summary
Reviewed before writing this architecture baseline:
- `AGENTS.md`
- `plans/task-ar.1.md`
- `docs/standards/task-contract-template.md`
- `docs/standards/task-catalog.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/task-review-checklist.md`
- `docs/business/relationship-sales-workspace-blueprint-v1.md`
- `docs/implementation/task-bu-r.0-business-blueprint-freeze-2026-07-07.md`
- `docs/implementation/task-bu-r.0.1-workspace-activity-business-blueprint-2026-07-07.md`
- `docs/implementation/task-bu-r.1-business-capability-audit-2026-07-07.md`
- `docs/security/crm-authorization-boundaries.md`
- `docs/adr/0014-crm-multi-role-user-assignment.md`
- `docs/adr/0015-customer-ownership-contact-sharing.md`
- `docs/adr/0016-won-lost-lifecycle-governance.md`
- `docs/adr/0017-report-foundation.md`
- `docs/adr/0018-lead-enquiry-domain-separation.md`
- `docs/implementation/task-f4-notification-framework-foundation.md`
- `docs/implementation/task-f5-approval-automation-foundation.md`
- `docs/implementation/task-d.6-crm-activity-foundation-audit-log-separation-2026-07-01.md`
- `docs/implementation/task-d54-lead-enquiry-workspace-separation-ui-foundation.md`
- `docs/implementation/task-d55-opportunity-sales-workspace-refinement.md`
- `docs/implementation/task-d551-force-rename-enquiry-domain-opportunity.md`
- `src/db/schema.ts`
- `src/lib/auth/crm-access.ts`
- `src/features/crm/security/server/service.ts`
- `src/features/foundation/audit-log/service.ts`
- `src/features/foundation/approval/server/service.ts`
- `src/features/foundation/notifications/server/event-service.ts`
- `src/features/crm/customers/server/service.ts`
- `src/features/crm/opportunities/server/service.ts`
- `src/features/crm/quotations/server/service.ts`
- `src/features/crm/dashboard/server/service.ts`
- `src/features/crm/reports/server/service.ts`
- `src/features/crm/activity/types.ts`
- `src/config/nav-config.ts`
- `src/app/dashboard/workspaces/page.tsx`
## Executive Summary
1. The current production backbone is already strong and should be preserved: customer/contact governance, lead/opportunity split, quotation lifecycle, approval runtime, notification foundation, dashboard KPI, report foundation, PDF/artifact, document sequencing, and CRM resolved-access security.
2. The main architecture gap is not core CRUD. The gap is the operating layer above it: shared Activity, generated Timeline, Calendar projection, My Day, Manager Workspace, Executive Workspace, and broader business-event fan-out.
3. The transition strategy should preserve existing source-of-truth domains and introduce new capabilities as extension and projection layers, not replacements.
4. Timeline, Calendar, Dashboard, Notification, and Workspace surfaces must consume derived views over governed source domains instead of storing duplicate business lifecycle state.
5. Existing admin `/dashboard/workspaces` routes already mean organization administration. Future business workspaces must not silently reuse that path or overload its meaning.
## 1. Current Architecture Diagram
```mermaid
flowchart TD
Auth[Auth.js + Session Helpers]
Access[Resolved CRM Access<br/>organization + role assignments + scope]
Routes[Route Handlers<br/>/api/crm/**]
Relationship[Relationship Layer<br/>Customer + Contact]
Lead[Lead Layer]
Opportunity[Opportunity / Project Layer]
Quotation[Quotation / Commercial Layer]
Approval[Approval Layer]
Dashboard[Dashboard Layer]
Reports[Report Layer]
Notifications[Notification Foundation]
Audit[Audit Foundation]
Foundations[Foundations<br/>master options, document sequence, storage, artifact, PDF, role management]
DB[(PostgreSQL + Drizzle)]
Auth --> Access
Access --> Routes
Routes --> Relationship
Routes --> Lead
Routes --> Opportunity
Routes --> Quotation
Routes --> Approval
Routes --> Dashboard
Routes --> Reports
Relationship --> Foundations
Lead --> Audit
Opportunity --> Audit
Quotation --> Audit
Approval --> Audit
Approval --> Notifications
Dashboard --> Opportunity
Dashboard --> Quotation
Dashboard --> Approval
Reports --> Opportunity
Reports --> Quotation
Reports --> Relationship
Relationship --> DB
Lead --> DB
Opportunity --> DB
Quotation --> DB
Approval --> DB
Notifications --> DB
Audit --> DB
Foundations --> DB
```
### Current-state interpretation
- `Customer` and `Contact` are the production relationship anchor, with owner history and contact sharing already governed.
- `Lead` is already a distinct domain, but its follow-up seam remains lighter than the target shared activity model.
- `Opportunity` is already the active sales-owned project execution domain.
- `Quotation` is already the active commercial document lifecycle owner.
- `Approval` already publishes notification events and controls document workflow.
- `Dashboard` and `Reports` already aggregate opportunity, quotation, follow-up, and approval data.
- `Timeline`, `Calendar Workspace`, `My Day`, `Manager Workspace`, and `Executive Workspace` do not yet exist as production-grade architecture layers.
## 2. Current Architecture Responsibility Matrix
| Layer | Current Owner | Current Modules | Current Responsibility | Current Dependency | Extension Point |
| --- | --- | --- | --- | --- | --- |
| Security Layer | Auth + CRM access foundations | `src/lib/auth/crm-access.ts`, `src/features/crm/security/**` | resolve permission, branch, product, ownership, pricing, approval authority | memberships, CRM role assignments, route handlers | reuse for Activity, Timeline, Calendar, Workspace queries |
| Relationship Layer | Customer domain | `src/features/crm/customers/**` | customer master, ownership, contacts, contact sharing, related commercial visibility | master options, document sequence, audit | extend into Customer Workspace projections |
| Lead Layer | Lead domain | `src/features/crm/leads/**` | marketing signal capture, assignment, audit-backed follow-up seam | customer/contact foundation, access, audit | adapt lead follow-up into Activity later |
| Project Layer | Opportunity domain | `src/features/crm/opportunities/**` | sales execution, assignment, forecast dates, Hot Project, project parties, won/lost outcome | customer, lead, storage, audit | primary consumer and publisher for Activity, Timeline, Calendar |
| Commercial Layer | Quotation domain | `src/features/crm/quotations/**` | pricing, revision, approval entry, customer package, quotation follow-up, linked documents | opportunity, customer, approval, PDF, artifact | extend into richer timeline/calendar/event projections |
| Approval Layer | Approval foundation | `src/features/foundation/approval/**` | workflow runtime, steps, request history, current actor resolution | quotations, notifications, audit, role assignments | preserve as workflow owner and publish more event consumers |
| Notification Layer | Notification foundation | `src/features/foundation/notifications/**` | event persistence, recipient resolution, inbox delivery | approval events, templates, users | extend beyond approvals into operational activity events |
| Reporting Layer | Report foundation | `src/features/crm/reports/**` | registry, filters, datasets, export, audit | resolved access, opportunity, quotation, ownership | reuse for manager/executive read models |
| Dashboard Layer | Dashboard feature | `src/features/crm/dashboard/**` | KPI summary, follow-up analytics, approval widgets, hot projects | opportunity, quotation, approval, reporting helpers | split into manager/executive workspace modules later |
| Foundation Layer | Shared foundations | audit, master options, document sequence, storage, PDF, artifact, role management | shared infrastructure and governed cross-cutting behavior | DB, auth context | preserve and compose rather than replace |
## 3. Target Architecture Diagram
```mermaid
flowchart TD
Security[Security Layer<br/>resolved CRM access + pricing + scope]
Foundation[Foundation Layer<br/>audit, master options, sequence, storage, PDF, artifact, notification infra]
Relationship[Relationship Layer<br/>Customer + Contact + Relationship Health]
Lead[Lead Layer]
Project[Project Layer<br/>Opportunity + project parties + forecast + hot project]
Commercial[Commercial Layer<br/>Quotation + approval workflow + PO linkage]
Activity[Activity Layer<br/>shared operational work model]
Events[Business Event Layer]
Timeline[Timeline Projection]
Calendar[Calendar Projection]
Workspace[Workspace Layer<br/>Customer, Sales, My Day, Manager, Executive, Calendar]
Analytics[Analytics Layer<br/>Dashboard + Reports + Forecast Views]
Security --> Relationship
Security --> Lead
Security --> Project
Security --> Commercial
Security --> Activity
Security --> Timeline
Security --> Calendar
Security --> Workspace
Security --> Analytics
Foundation --> Relationship
Foundation --> Lead
Foundation --> Project
Foundation --> Commercial
Foundation --> Activity
Foundation --> Events
Relationship --> Activity
Lead --> Activity
Project --> Activity
Commercial --> Activity
Relationship --> Events
Lead --> Events
Project --> Events
Commercial --> Events
Activity --> Events
Events --> Timeline
Events --> Calendar
Events --> Analytics
Events --> Foundation
Activity --> Calendar
Activity --> Workspace
Timeline --> Workspace
Calendar --> Workspace
Analytics --> Workspace
```
### Target principles
- `Activity` becomes the shared operational domain.
- `Timeline` and `Calendar` are projections, not primary-write domains.
- `Dashboard`, `Manager Workspace`, `Executive Workspace`, and `My Day` consume a combination of Activity, event, approval, and commercial projections.
- `Approval` remains within the commercial/document control boundary, not inside Activity.
- `Customer Workspace` extends the current customer detail foundation rather than creating a parallel relationship module.
## 4. Domain Ownership Matrix
| Domain | Primary Owner | Owns | Does Not Own |
| --- | --- | --- | --- |
| Organization | Foundation | tenant boundary, active workspace, plan | CRM lifecycle |
| RBAC / CRM Access | Security foundation | permission union, scope union, pricing visibility, approval authority | business workflow state |
| Customer | Relationship layer | account master, owner, relationship anchor | quotation status, opportunity outcome |
| Contact | Relationship layer | person record, sharing | customer ownership, quotation pricing |
| Lead | Marketing layer | early demand signal, handoff readiness | quotation approval, won/lost |
| Opportunity | Project layer | project pursuit, forecast, hot project, business outcome | quotation revision history |
| Quotation | Commercial layer | pricing, revision, approval lifecycle, approved document | won/lost outcome |
| Approval | Approval layer | workflow execution and decision history | customer relationship state |
| Activity | Activity layer | planned/completed work, owner/assignee, outcome summary | immutable audit, business result lifecycle |
| Timeline | Projection layer | generated chronology view | manual source-of-truth editing |
| Calendar | Projection layer | scheduled projection view | underlying work ownership |
| Notification | Foundation delivery layer | delivery and inbox state | business lifecycle ownership |
| Dashboard | Analytics layer | KPI aggregation and decision views | source business state |
| Reports | Analytics layer | report datasets, exports, filters | live source mutations |
## 5. Layer Responsibility Matrix
| Layer | Responsibilities | Allowed Inputs | Outputs |
| --- | --- | --- | --- |
| Foundation | audit, storage, document generation, sequence, notification infra, master options | service-layer calls | shared services, artifacts, events, logs |
| Security | auth, scope resolution, pricing visibility, approval visibility | session + memberships + role assignments | scoped context for services and read models |
| Relationship | customer/contact data and ownership | route handlers, master options, security context | customer workspace source data, relationship event payloads |
| Lead | marketing pipeline state and handoff | route handlers, customer/contact refs, security context | lead events, lead follow-up adapters |
| Project | opportunity pursuit and outcome | route handlers, lead/customer refs, security context | forecast signals, hot project signals, activity adapters, project events |
| Commercial | quotation, approval entry, approved package | route handlers, opportunity/customer refs, security context | document events, approval events, milestone signals |
| Activity | shared work record lifecycle | route handlers, source-entity links, security context | activity events, timeline items, calendar items, My Day records |
| Projection | timeline and calendar assembly | activities + business events + source-domain milestone fields | read models only |
| Analytics | dashboard, reports, forecast | governed datasets, events, follow-up/activity views | KPI views, exports, manager/executive signals |
| Workspace | role-oriented composition | projections + source detail + analytics summaries | navigable work surfaces |
## 6. Transition Architecture Diagram
```mermaid
flowchart LR
Current[Current Production Domains<br/>Customer, Contact, Lead, Opportunity, Quotation, Approval]
Adapters[Adapter Layer<br/>follow-up adapters, event publishers, workspace composition]
Activity[New Activity Domain]
Projections[New Projection Layer<br/>Timeline + Calendar + My Day]
Workspaces[New Workspace Layer]
Analytics[Extended Dashboard / Reports]
Current --> Adapters
Adapters --> Activity
Current --> Projections
Activity --> Projections
Projections --> Workspaces
Current --> Workspaces
Current --> Analytics
Activity --> Analytics
```
### Transition rules
1. Preserve existing source domains as write owners.
2. Introduce adapters before moving any follow-up semantics.
3. Introduce projections before changing navigation meaning.
4. Extend dashboard and report datasets before creating manager and executive workspaces.
5. Keep current APIs compatible while new read models are added beside them.
## 7. Preserve / Extend / Refactor / Replace Matrix
| Module | Strategy | Notes |
| --- | --- | --- |
| Organization | Preserve | existing tenant foundation remains unchanged |
| RBAC / Resolved CRM Access | Preserve | mandatory security boundary for all future workspaces and projections |
| Customer | Preserve + Extend | extend current detail into Customer Workspace sections |
| Contact | Preserve | existing sharing rules stay authoritative |
| Lead | Preserve + Minor Enhancement | preserve split lead domain and add activity adapter / richer handoff semantics |
| Opportunity | Preserve + Extend | forecast, hot project, event publishing, activity adapter, workspace composition |
| Quotation | Preserve | document lifecycle owner remains unchanged |
| Approval | Preserve | existing approval engine remains the only workflow runtime |
| PDF / Artifact | Preserve | no duplicate document pipeline |
| Dashboard | Preserve + Extend | expand role-specific views from current KPI base |
| Reports | Preserve + Extend | reuse report foundation for manager/executive read models |
| Notification | Preserve + Extend | extend event coverage beyond approval |
| Activity | New Capability | shared operational work model |
| Timeline | New Projection | generated view only |
| Calendar | New Projection | generated view only |
| My Day | New Workspace | personalized action workspace |
| Manager Workspace | New Workspace | control-tower composition |
| Executive Workspace | New Workspace | strategic composition |
| Existing admin `/dashboard/workspaces` | Preserve | reserved for organization administration, not business-role workspaces |
| Replace | None approved | no production module currently justifies full replacement |
## 8. Capability Transition Matrix
| Capability | Current State | Transition Strategy | Migration Required | Backward Compatible | Risk |
| --- | --- | --- | --- | --- | --- |
| Customer Workspace | partial | extend current customer detail tabs and summary sections | yes | yes | medium |
| Shared Activity | missing / contract-only | create new domain and adapt existing follow-up seams | yes | yes | high |
| Timeline | missing | build generated read model over events and activities | yes | yes | medium |
| Calendar Workspace | missing | build projection query + workspace UI over activity and milestones | yes | yes | medium |
| My Day | missing | compose activity, approval, hot project, reminder signals | yes | yes | medium |
| Manager Workspace | missing | extend dashboard + team calendar + overdue/idle datasets | yes | yes | medium |
| Executive Workspace | missing | extend reports + dashboard + strategic customer signals | yes | yes | medium |
| Notification Expansion | approval-centric | add activity/opportunity/quotation event publishers | yes | yes | medium |
| Forecast Semantics | partial | preserve fields, freeze business meaning, expose via projections | no schema | yes | low |
| Relationship Health | missing | new derived model in customer workspace | yes | yes | medium |
## 9. Activity Platform Architecture
### 9.1 Domain role
`Activity` becomes the shared operational work record for customer-, lead-, opportunity-, quotation-, and internal-only execution.
### 9.2 Ownership model
- one activity has one accountable owner
- one activity may also have one assignee
- one activity has one primary reference
- one activity may include many related references
- visibility inherits from the primary record plus assignment rules
### 9.3 Service architecture
| Component | Responsibility |
| --- | --- |
| Activity Route Handlers | validate payload, require permissions, call activity service, map response |
| Activity Service | lifecycle rules, ownership rules, next-action suggestions, event publication |
| Activity Persistence Adapter | Drizzle-backed persistence module behind the service |
| Activity Projection Adapter | map activity changes into timeline and calendar projections |
| Activity Security Adapter | reuse resolved CRM access for scope validation |
### 9.4 Repository rule
This repo should not introduce a heavyweight new repository abstraction layer. `Activity Repository` in AR.1 means the internal Drizzle-backed persistence adapter owned by the Activity server service, consistent with existing repo service-layer patterns.
### 9.5 API boundary
Recommended future routes:
- `GET /api/crm/activities`
- `POST /api/crm/activities`
- `GET /api/crm/activities/[id]`
- `PATCH /api/crm/activities/[id]`
- `POST /api/crm/activities/[id]/complete`
- `POST /api/crm/activities/[id]/cancel`
- `POST /api/crm/activities/[id]/reassign`
### 9.6 Lifecycle
```text
Planned -> In Progress -> Completed
Planned -> Cancelled
In Progress -> Cancelled
Overdue = derived operational view only
Rescheduled = event, not standalone lifecycle owner
```
### 9.7 Integration points
| Source / Consumer | Integration Rule |
| --- | --- |
| Customer | activities may attach to customer/contact relationship work |
| Lead | existing lead follow-up becomes an adapter candidate, not a second source of truth |
| Opportunity | opportunity follow-up is the first high-value migration seam |
| Quotation | quotation chase and commercial follow-up adapt into Activity |
| Notification | assignment, due soon, overdue, escalation, reminder |
| Calendar | scheduled activities and reminders project into calendar |
| Timeline | activity create/assign/reschedule/complete/cancel publish generated timeline items |
| Dashboard / My Day | read operational workload and overdue signals from activity views |
### 9.8 Transition rule
Existing lead/opportunity/quotation follow-up APIs remain production-compatible during migration. Activity arrives beside them first, then adapters and dual-read views are introduced before any storage consolidation is attempted.
## 10. Timeline Projection Architecture
### 10.1 Source model
Timeline is generated from:
- activity lifecycle events
- lead creation / assignment / handoff events
- opportunity assignment, forecast, hot-project, and outcome events
- quotation creation, revision, send, approval, acceptance, rejection, and expiry-adjacent events
- approval actions
- attachment / approved-artifact events
- customer owner and contact-sharing governance events where relationship context matters
### 10.2 Generator design
| Component | Responsibility |
| --- | --- |
| Timeline Source Adapters | map source-domain rows and events into a common timeline item contract |
| Timeline Generator | merge, normalize, sort, and tie-break timeline items |
| Timeline Query Service | apply security, filters, paging, and caching |
| Timeline Serializer | shape business-readable chronology for Customer/Opportunity/Quotation workspaces |
### 10.3 Frozen rules
- timeline is generated, never manually edited
- timeline does not own workflow state
- timeline visibility follows originating record access
- pricing-sensitive timeline items must respect quotation pricing visibility
### 10.4 Query strategy
Phase 1:
- query-time generation from source domains and activity/event adapters
- no new timeline source-of-truth table required
Phase 2, only if needed for scale:
- optional cached projection table or materialized read store
- cache remains derivative, rebuildable, and non-authoritative
### 10.5 Security strategy
- use resolved CRM access before source data is merged
- pricing-sensitive quotation items are redacted for unauthorized readers
- approval timeline visibility inherits approval boundary plus quotation access rules
### 10.6 Caching strategy
- cache by `organizationId + workspace + source record`
- invalidate from source-domain event publication
- never allow cache to become the only persisted business history
### 10.7 Event mapping
Minimum timeline events:
- `activity.created`
- `activity.reassigned`
- `activity.rescheduled`
- `activity.completed`
- `activity.cancelled`
- `lead.created`
- `lead.assigned`
- `lead.handoff_ready`
- `opportunity.created`
- `opportunity.reassigned`
- `opportunity.expected_award_changed`
- `opportunity.hot_project_flagged`
- `opportunity.won`
- `opportunity.lost`
- `quotation.created`
- `quotation.revised`
- `quotation.sent`
- `quotation.pending_approval`
- `quotation.approved`
- `quotation.rejected`
- `quotation.returned`
- `approval.completed`
- `po.received`
## 11. Calendar Projection Architecture
### 11.1 Source model
Calendar aggregates:
- scheduled activities
- reminder prompts
- opportunity milestone dates
- quotation milestone dates
- approval due and escalation events
- hot-project-sensitive near-term commitments
### 11.2 Projection model
| Projection Type | Owner | Notes |
| --- | --- | --- |
| Activity Projection | Activity | owned work, meetings, visits, tasks |
| Reminder Projection | Activity / notification timing | visible prompt, not new work owner |
| Milestone Projection | opportunity / quotation | expected-award, expected-delivery, validity, PO milestone |
| Approval Projection | approval runtime | due, escalated, timeout-sensitive pending step |
| Forecast Projection | opportunity | management awareness, not task ownership |
### 11.3 Aggregation rules
- activity events are first-class actionable rows
- milestone events are visually distinct and not editable as activities
- reminder prompts may render separately from the activity row when configured
- manager and executive lenses are filtered calendar views, not separate source stores
### 11.4 Filter strategy
Required filters:
- user
- team
- branch
- customer
- lead
- opportunity
- quotation
- activity type
- priority
- status
- Hot Project only
### 11.5 Permission strategy
- personal calendar shows owned/assigned activity and permitted milestones
- team/manager calendar reuses resolved CRM access and future team graph rules
- executive calendar is read-oriented and should not widen source-record access
### 11.6 Query strategy
Phase 1:
- build a calendar aggregation service over Activity plus source-domain milestone fields
- no separate scheduling domain table
Phase 2, optional:
- add optimized read-model caching when activity volume requires it
## 12. Business Event Architecture
### 12.1 Event philosophy
Business events are immutable facts emitted after successful state change. One event may fan out to timeline, calendar, dashboard, notification, reminder, and future automation.
### 12.2 Publisher ownership
| Event Family | Publisher Owner |
| --- | --- |
| relationship.* | customer/contact service |
| lead.* | lead service |
| opportunity.* | opportunity service |
| quotation.* | quotation service |
| approval.* | approval service |
| activity.* | activity service |
### 12.3 Subscriber ownership
| Consumer | Responsibility |
| --- | --- |
| Timeline | chronology projection |
| Calendar | schedule and milestone projection |
| Notification | inbox delivery and reminder prompts |
| Dashboard / Workspace | aggregated operational signals |
| Audit | mutation truth remains mandatory even when event also exists |
| Future Automation | SLA, escalation, next-step suggestion, health monitoring |
### 12.4 Contract envelope
Recommended event envelope:
```ts
type BusinessEvent = {
eventType: string;
schemaVersion: 1;
organizationId: string;
entityType: string;
entityId: string;
occurredAt: string;
actorUserId: string | null;
primaryRecord: { type: string; id: string };
relatedRecords: Array<{ type: string; id: string }>;
visibility: {
branchId?: string | null;
productType?: string | null;
pricingSensitive?: boolean;
};
attributes: Record<string, unknown>;
};
```
### 12.5 Naming strategy
- lowercase, dot-separated event names
- stable family prefixes: `activity.*`, `lead.*`, `opportunity.*`, `quotation.*`, `approval.*`
- preserve existing approval notification event names for backward compatibility
### 12.6 Versioning strategy
- start with `schemaVersion: 1` in the envelope
- only introduce a new version when payload meaning changes incompatibly
- avoid renaming existing approval events that already drive the notification foundation
### 12.7 Payload strategy
- include business-readable identifiers only when safe
- keep pricing-bearing attributes behind source authorization
- include enough context for projections without forcing projection services to re-query everything
## 13. Workspace Architecture
### 13.1 Route strategy
Do not reuse `/dashboard/workspaces` for business-role workspaces because production already reserves that path for organization administration.
Recommended business workspace routes:
- `/dashboard/crm/my-day`
- `/dashboard/crm/calendar`
- `/dashboard/crm/manager`
- `/dashboard/crm/executive`
- `/dashboard/crm/customers/[id]` extended as Customer Workspace
- `/dashboard/crm/opportunities` and `/dashboard/crm/quotations` remain the sales execution backbone
### 13.2 Workspace composition
| Workspace | Primary Composition | Source Domains |
| --- | --- | --- |
| Customer Workspace | customer profile, contacts, upcoming work, activities, timeline, leads, opportunities, quotations, files | customer, contact, lead, opportunity, quotation, activity, timeline |
| Sales Workspace | my-day signals, opportunity queue, quotation blockers, customer context | activity, opportunity, quotation, approval, customer |
| Manager Workspace | overdue work, idle opportunities, team calendar, approvals, forecast, hot projects | activity, calendar, dashboard, reports, approval |
| Executive Workspace | revenue outlook, strategic accounts, pipeline health, exception review | dashboard, reports, relationship health, timeline summaries |
| Calendar Workspace | personal, team, manager, executive time lenses | activity, reminders, milestone projections, approval due events |
| My Day | today's activities, overdue items, meetings, urgent quotations, approvals, relationship alerts | activity, approval, quotation, hot project, relationship signals |
### 13.3 Navigation boundary
- preserve existing entity routes
- add workspace routes as composition surfaces, not replacements for source modules
- keep Customer Workspace anchored on the existing customer detail route
## 14. Integration Strategy
| Concern | Strategy |
| --- | --- |
| Existing module reuse | extend customers, leads, opportunities, quotations, dashboard, reports, notifications; do not fork them |
| Adapter layer | build follow-up adapters before any shared activity migration |
| Projection layer | implement timeline/calendar as read-only aggregation services first |
| Service composition | projections call owning services or datasets instead of bypassing them |
| Shared components | reuse `PageContainer`, existing table/filter shells, and CRM detail patterns |
| API compatibility | add new read routes; keep current lead/opportunity/quotation APIs stable |
| Feature flags | optional for workspace rollout and projection swaps, especially timeline/calendar |
| Migration strategy | dual-read/adapter-first, then storage convergence only when safe |
## 15. Domain Dependency Graph
```mermaid
flowchart TD
Customer[Customer]
Contact[Contact]
Lead[Lead]
Activity[Activity]
Opportunity[Opportunity]
Quotation[Quotation]
Approval[Approval]
Timeline[Timeline]
Calendar[Calendar]
Dashboard[Dashboard]
Reports[Reports]
Notification[Notification]
MyDay[My Day]
Manager[Manager Workspace]
Executive[Executive Workspace]
Customer --> Contact
Customer --> Lead
Customer --> Opportunity
Customer --> Quotation
Contact --> Lead
Contact --> Activity
Lead --> Opportunity
Opportunity --> Quotation
Quotation --> Approval
Activity --> Timeline
Activity --> Calendar
Opportunity --> Dashboard
Quotation --> Dashboard
Approval --> Dashboard
Opportunity --> Reports
Quotation --> Reports
Approval --> Notification
Activity --> Notification
Timeline --> MyDay
Calendar --> MyDay
Dashboard --> Manager
Calendar --> Manager
Reports --> Executive
Dashboard --> Executive
```
## 16. Domain Dependency Matrix
| Domain | Depends On | Used By | Shared Services | Projection Consumers | Potential Breaking Change Impact |
| --- | --- | --- | --- | --- | --- |
| Customer | foundation, security | contact, lead, opportunity, quotation, customer workspace | ownership, sharing, option lookup | timeline, dashboard, manager, executive | high |
| Contact | customer | lead, activity, quotation follow-up | contact sharing | customer workspace, timeline | medium |
| Activity | customer, contact, lead, opportunity, quotation, security | calendar, timeline, My Day, notifications, manager workspace | notification, audit, security | calendar, timeline, My Day, dashboard | very high |
| Lead | customer, contact, security | opportunity, customer workspace, reports | assignment, audit | timeline, customer workspace | high |
| Opportunity | customer, lead, security | quotation, dashboard, reports, manager workspace | hot project, outcome, follow-up | calendar, timeline, dashboard | very high |
| Quotation | opportunity, customer, security | approval, PDF, dashboard, reports | document, revision, customer package | timeline, calendar, My Day | very high |
| Approval | quotation, role assignments, security | notification, dashboard, manager workspace | reminder/escalation, audit | timeline, calendar, manager workspace | high |
| Timeline | business events, activities | customer workspace, My Day, manager/executive drill-down | projection generator | customer, sales, manager, executive | medium |
| Calendar | activity + milestones + approvals | My Day, Manager Workspace | projection query, reminder rules | personal/team/manager/executive | medium |
| Dashboard | opportunity + quotation + approval + follow-up/activity | manager, executive | reporting helpers, security | manager, executive | high |
### Coupling analysis
1. `Opportunity -> Quotation -> Approval` is the strongest existing operational chain and must remain stable.
2. `Customer` is the relationship anchor and should stay the main shared-reference root, not be duplicated in workspaces.
3. `Activity` will become the main cross-cutting coupling point, so it must remain reference-based and projection-friendly instead of embedding duplicate lifecycle fields.
4. `Dashboard` and `Reports` already depend on follow-up-like signals. Activity migration must preserve those datasets without KPI regressions.
### High-risk dependency list
- opportunity follow-up analytics currently drive dashboard behavior
- quotation follow-up analytics currently drive dashboard behavior
- approval notification events already exist and can be double-published if event layering is careless
- current admin `workspaces` route meaning can be broken by navigation overloading
- pricing-sensitive quotation data can leak if timeline/calendar/event payloads ignore security boundaries
### Recommended decoupling strategy
- introduce Activity adapters instead of immediate follow-up replacement
- keep event fan-out behind source-service publishers
- centralize timeline/calendar security in shared projection query services
- preserve dashboard/report dataset contracts during activity transition
## 17. Breaking Change Assessment
| Area | Risk | Why | Mitigation |
| --- | --- | --- | --- |
| Follow-up migration | high | dashboard/report/current tabs already depend on existing follow-up storage | adapter-first, dual-read, explicit dataset regression checks |
| Workspace navigation | medium | `/dashboard/workspaces` already has admin meaning | use `/dashboard/crm/*` for business workspaces |
| Notification fan-out | medium | approval event pipeline already exists | preserve approval events, add new event families incrementally |
| Timeline security | high | cross-entity chronology may surface pricing or restricted records | centralize source-security checks and pricing redaction |
| Calendar milestones | medium | milestone dates come from several owners | keep milestone projections read-only and source-owned |
| Customer Workspace expansion | low | current customer detail already provides a strong base | extend existing route and detail composition gradually |
## 18. Migration Strategy
### Preserve
- organization and workspace-admin foundation
- resolved CRM authorization
- customer ownership and contact sharing
- lead domain split
- opportunity outcome governance
- quotation lifecycle, approval runtime, PDF/artifact, reports, dashboard
### Extend
- customer detail into Customer Workspace
- dashboard into manager/executive decision views
- notifications into broader operational event coverage
- forecast and Hot Project semantics into calendar/workspace projections
### Controlled Refactor
- follow-up semantics toward shared Activity
- event publication as a reusable source-service concern
- route/nav wording so business workspaces do not collide with admin workspaces
### Replace
- none approved in AR.1
## 19. Epic Roadmap
| Order | Epic | Why First / Dependency |
| --- | --- | --- |
| 1 | Activity Platform Foundation | unlocks timeline, calendar, My Day, manager signals |
| 2 | Business Event Publishing Expansion | required for projections and notification breadth |
| 3 | Customer Workspace | strongest existing base and relationship anchor |
| 4 | Timeline Projection | consumes activity + event contracts |
| 5 | Calendar Projection and Workspace | depends on activity and milestone mapping |
| 6 | My Day Workspace | depends on activity, approvals, reminders, hot projects |
| 7 | Manager Workspace | depends on calendar, dashboard extension, approvals, forecast |
| 8 | Notification Expansion | grows with activity and manager workflows |
| 9 | Executive Workspace | depends on dashboard/report/relationship health layering |
| 10 | Relationship Health and Forecast Refinements | safer after core projections and workspaces exist |
## 20. Official Architecture Governance Outcome
AR.1 freezes these transition rules:
1. Preserve current production CRM source domains.
2. Add Activity as a new shared operational domain, not a shortcut rewrite of existing modules.
3. Treat Timeline and Calendar as generated projections only.
4. Keep Approval as the quotation/document workflow owner.
5. Keep Opportunity as the business outcome owner.
6. Keep business-role workspaces under CRM routes and preserve the existing admin workspaces route meaning.
7. Require future implementation epics to reference this AR.1 baseline before modifying preserved modules.
## Verification
- Documentation-only change.
- No schema, migration, API, service, UI, permission, or runtime business-logic files were changed for production behavior.
- No build or test command was required for this architecture pass.
## Outcome
AR.1 now provides the transition baseline between the current production ALLA OS CRM foundation and the target Relationship-Driven Sales Workspace architecture. The baseline is implementation-ready for AR.2 because it defines the current architecture, target layering, transition strategy, ownership rules, event strategy, projection strategy, workspace routing strategy, coupling risks, and epic order without forcing unnecessary replacement of mature production modules.

View File

@@ -0,0 +1,34 @@
# Task AR.1 Implementation Report - 2026-07-07
## Scope
- start and complete AR.1 as a documentation-only architecture transition phase
- review the frozen BU-R business baseline, current production CRM implementation, reusable foundations, ADRs, security boundaries, and transition seams
- record the official transition architecture baseline for later AR.2 epics and technical design
## What Was Produced
- created the main AR.1 transition architecture artifact:
[task-ar.1-architecture-transition-plan-2026-07-07.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-ar.1-architecture-transition-plan-2026-07-07.md)
## Key Decisions Captured
1. Current production CRM domains remain the write owners and must be preserved.
2. New capability should arrive as:
- shared `Activity` domain
- generated `Timeline` projection
- generated `Calendar` projection
- role-oriented CRM workspaces
3. Existing admin `/dashboard/workspaces` routes are preserved for organization administration and must not be silently reused for business-role workspace navigation.
4. `Approval` stays the document workflow owner, `Opportunity` stays the business outcome owner, and `Quotation` stays the document lifecycle owner.
5. Notification expansion, My Day, Manager Workspace, and Executive Workspace must build on event/projection layers rather than creating duplicate lifecycle stores.
## Verification
- Documentation-only change.
- No schema, API, UI, service, permission, or business-logic runtime change was made.
- No build or test command was required for this phase.
## Outcome
AR.1 is now recorded as the architecture transition baseline that future epics can reference for preserve/extend/refactor decisions, event design, projection strategy, workspace routing, dependency analysis, and implementation ordering.

View File

@@ -0,0 +1,607 @@
# Task AR.2 Epic & Technical Design Baseline - 2026-07-07
## Scope
- convert the AR.1 transition architecture into an implementation-ready epic and technical-design baseline
- define epic boundaries, dependency order, delivery slices, technical contracts, and reuse rules for each AR.1 capability
- include workspace-design prerequisites and design-governance checkpoints before code implementation begins
- keep this phase documentation-only with no schema, API, UI, service, permission, or runtime business-logic changes
## Review Summary
Reviewed before drafting AR.2:
- `AGENTS.md`
- `plans/task-ar.2.md`
- `docs/standards/task-contract-template.md`
- `docs/standards/task-catalog.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/task-review-checklist.md`
- `layout.md`
- `docs/business/relationship-sales-workspace-blueprint-v1.md`
- `docs/implementation/task-bu-r.1-business-capability-audit-2026-07-07.md`
- `docs/implementation/task-ar.1-architecture-transition-plan-2026-07-07.md`
- `docs/implementation/task-ar.1-implementation-report-2026-07-07.md`
- `docs/security/crm-authorization-boundaries.md`
- `docs/adr/0014-crm-multi-role-user-assignment.md`
- `docs/adr/0015-customer-ownership-contact-sharing.md`
- `docs/adr/0016-won-lost-lifecycle-governance.md`
- `docs/adr/0017-report-foundation.md`
- `docs/adr/0018-lead-enquiry-domain-separation.md`
- `plans/task-j.md`
- `plans/task-d.md`
- `src/components/layout/page-container.tsx`
- `src/features/crm/dashboard/components/crm-dashboard.tsx`
- `src/features/crm/customers/components/customer-detail.tsx`
- `src/features/crm/reports/components/pipeline-report-view.tsx`
UI/UX design process inputs:
- `.agents/skills/ui-ux-pro-max/SKILL.md`
- `python .agents/skills/ui-ux-pro-max/scripts/search.py "relationship driven sales workspace enterprise crm operational dashboard" --design-system -f markdown -p "ALLA OS CRM Workspaces"`
- `python .agents/skills/ui-ux-pro-max/scripts/search.py "dashboard information hierarchy filters empty loading" --domain ux -n 12`
- `python .agents/skills/ui-ux-pro-max/scripts/search.py "minimalism enterprise dashboard" --domain style -n 8`
- `python .agents/skills/ui-ux-pro-max/scripts/search.py "analytics dashboard operational crm" --domain chart -n 10`
## AR.2 Objective
AR.2 turns AR.1 from architecture baseline into delivery baseline. The result of AR.2 is not code. The result is a frozen set of implementation epics and technical-design contracts that future work must follow.
AR.2 therefore answers:
1. Which epic comes first and why.
2. Which existing modules and foundations each epic must reuse.
3. Which new routes, services, query layers, and UI shells are expected.
4. Which schema/API risks exist before implementation starts.
5. Which workspace surfaces require dedicated UI/UX notes before coding.
## Implementation Principles Carried Forward
1. Preserve current production write owners: Customer, Contact, Lead, Opportunity, Quotation, Approval.
2. Introduce Activity, Timeline, Calendar, and role workspaces as extensions and projections.
3. Keep route handlers thin and business logic in feature/foundation server services.
4. Reuse resolved CRM access, approval, report, notification, audit, PDF, and artifact foundations.
5. Preserve current admin `/dashboard/workspaces` meaning; business workspaces live under `/dashboard/crm/*`.
6. Do not start any major workspace UI implementation without an approved UI/UX design note.
## Delivery Model
AR.2 groups future work into three layers of implementation:
| Layer | Purpose | Epics |
| --- | --- | --- |
| Foundational Runtime | create shared operational primitives | Activity Platform, Business Event Expansion |
| Projection Layer | create generated read models | Timeline Projection, Calendar Projection |
| Workspace Layer | create user-facing operational surfaces | Customer Workspace, My Day, Manager Workspace, Executive Workspace |
Supporting alignment epics:
- Lead Handoff & Outcome Alignment
- Relationship Health & Forecast Semantics
- Notification Expansion
- Terminology & Governance Reconciliation
## Epic Sequencing
| Order | Epic | Reason |
| --- | --- | --- |
| 1 | Activity Platform Foundation | unblocks Timeline, Calendar, My Day, Manager signals, and notification expansion |
| 2 | Business Event Publishing Expansion | creates shared event contracts and projection fan-out |
| 3 | Customer Workspace Enhancement | strongest partial base and least risky visible value |
| 4 | Timeline Projection | depends on Activity + event publishing |
| 5 | Calendar Projection & Workspace | depends on Activity + milestone mapping + event contracts |
| 6 | My Day Workspace | depends on Activity + Calendar + Approval + Hot Project signals |
| 7 | Notification Expansion | safer after event taxonomy and activity lifecycle exist |
| 8 | Manager Workspace | depends on Calendar, Dashboard, Timeline, My Day patterns |
| 9 | Executive Workspace | depends on Dashboard + Reports + Relationship Health summaries |
| 10 | Lead Handoff & Outcome Alignment | can proceed alongside workspace work, but should preserve current Lead foundation |
| 11 | Relationship Health & Forecast Semantics | finalizes derived scoring and naming after projections exist |
| 12 | Terminology & Governance Reconciliation | ongoing cross-epic cleanup with low runtime dependency |
## Epic Overview Matrix
| Epic | Type | Main Output | Depends On | Reuses | Risk |
| --- | --- | --- | --- | --- | --- |
| Activity Platform Foundation | new capability | shared activity domain | none | security, audit, notifications, customers, leads, opportunities, quotations | high |
| Business Event Publishing Expansion | extension | event envelope + publisher rules | none | approval notifications, audit, source services | medium |
| Customer Workspace Enhancement | preserve + extend | relationship execution workspace | AR.1 only | customer detail, related opportunity/quotation links, audit tab | medium |
| Timeline Projection | new projection | generated chronology read model | Activity + Events | audit naming, source services, CRM security | high |
| Calendar Projection & Workspace | new projection + workspace | cross-role time view | Activity + Events | approval automation, dashboard filter patterns, PageContainer | high |
| My Day Workspace | new workspace | personalized action home | Activity + Calendar + Approval | dashboard cards, report filters, notification signals | medium |
| Notification Expansion | preserve + extend | broader operational event coverage | Events + Activity | notification foundation, templates, recipient resolver | medium |
| Manager Workspace | new workspace | team control tower | Calendar + My Day + Dashboard | dashboard foundation, reports, approvals | medium |
| Executive Workspace | new workspace | strategic monitoring surface | Manager dependencies partly complete | dashboard, reports, relationship summaries | medium |
| Lead Handoff & Outcome Alignment | enhancement | richer lead-to-opportunity flow | Activity optional | lead foundation, opportunity links, CRM access | medium |
| Relationship Health & Forecast Semantics | enhancement | derived health/forecast views | Timeline + Activity + Customer Workspace | customer, opportunity, quotation, dashboard | medium |
| Terminology & Governance Reconciliation | controlled refactor | docs and naming cleanup | none | blueprint, ADRs, current routes | low |
## Technical Design Standards for Every Epic
Each future implementation epic under AR.2 must include:
1. objective and explicit non-goals
2. historical review section
3. foundations reused
4. API boundary
5. service-layer contract
6. query/mutation design
7. permission and pricing boundary
8. audit events
9. UI/UX design note reference if the epic includes user-facing workspace surfaces
10. verification plan
## EPIC 1: Activity Platform Foundation
### Objective
Introduce the first production-grade shared Activity domain without breaking current lead/opportunity/quotation follow-up behavior.
### Non-goals
- no replacement of current follow-up APIs on day one
- no silent migration of dashboard/report datasets
- no automatic removal of audit-backed lead follow-up seam
### Existing foundations to reuse
- `src/lib/auth/crm-access.ts`
- `src/features/crm/security/server/service.ts`
- `src/features/foundation/audit-log/service.ts`
- `src/features/foundation/notifications/**`
- `src/features/crm/customers/**`
- `src/features/crm/leads/**`
- `src/features/crm/opportunities/**`
- `src/features/crm/quotations/**`
- `src/features/crm/activity/types.ts`
### Recommended implementation structure
```text
src/features/crm/activities/
api/
types.ts
service.ts
queries.ts
mutations.ts
components/
schemas/
server/
service.ts
adapters/
lead-followup.adapter.ts
opportunity-followup.adapter.ts
quotation-followup.adapter.ts
```
### Expected route surface
- `GET /api/crm/activities`
- `POST /api/crm/activities`
- `GET /api/crm/activities/[id]`
- `PATCH /api/crm/activities/[id]`
- `POST /api/crm/activities/[id]/complete`
- `POST /api/crm/activities/[id]/cancel`
- `POST /api/crm/activities/[id]/reassign`
### Minimum technical design decisions
- one primary record reference plus optional related references
- owner and assignee fields
- lifecycle states: planned, in_progress, completed, cancelled
- overdue derived in query/view layer, not lifecycle storage
- activity-level security resolves from primary record plus assignment
- pricing-sensitive notes must reuse quotation pricing visibility checks
### Required permission set
- `crm.activity.read`
- `crm.activity.create`
- `crm.activity.update`
- `crm.activity.complete`
- `crm.activity.cancel`
- `crm.activity.delete`
- `crm.activity.reassign`
### Query strategy
- define centralized query keys in `api/queries.ts`
- use list/detail/related-entity keys
- keep existing follow-up queries intact during transition
### Migration rule
Phase 1 must support coexistence:
- existing lead follow-up
- existing opportunity follow-up
- existing quotation follow-up
- new Activity records
No destructive cutover is allowed until dashboard/report continuity is proven.
### Validation and testing expectations
- permission tests
- ownership and visibility tests
- primary/related reference validation tests
- mutation audit tests
- notification publication smoke tests
## EPIC 2: Business Event Publishing Expansion
### Objective
Standardize operational business events so source services can publish shared events consumed by Timeline, Calendar, Notification, Dashboard, My Day, and future automation.
### Foundations to reuse
- approval notification event pipeline
- audit foundation
- source-domain service layers
### Technical design
- keep source-of-truth writes inside existing services
- publish event only after successful mutation
- preserve current approval event names for compatibility
- add shared envelope with `schemaVersion`
### Event families
- `relationship.*`
- `lead.*`
- `opportunity.*`
- `quotation.*`
- `approval.*`
- `activity.*`
### Implementation note
This epic should not replace the notification foundation with a new event bus. It should formalize a shared publisher contract and allow the existing notification event service to consume it.
## EPIC 3: Customer Workspace Enhancement
### Objective
Extend current customer detail into the first production Customer Workspace aligned to the business blueprint.
### Starting point
- `src/features/crm/customers/components/customer-detail.tsx`
- existing related opportunities and quotations
- owner card
- contacts tab
- audit log tab
### Required new sections
- Relationship Health
- Upcoming Activities
- Activities
- Timeline
- PO / Won History
- Files / Documents
- Relationship Summary / Notes
### Technical design
- preserve current `/dashboard/crm/customers/[id]` route
- extend the existing detail shell rather than replacing it
- add new tabs/cards fed by Activity and Timeline projections when available
- relationship health remains derived, not manually editable
### Dependency
Can start before Activity/Timeline are complete by using placeholders or staged sections, but final implementation depends on those epics.
## EPIC 4: Timeline Projection
### Objective
Create a generated chronology layer for Customer, Opportunity, Quotation, and later Lead contexts.
### Technical design
- query-time read model first
- source adapters per domain
- deterministic ordering and tie-break rules
- no manual timeline CRUD
- no timeline table as source-of-truth in phase 1
### Recommended server structure
```text
src/features/crm/timeline/
api/
types.ts
service.ts
queries.ts
server/
service.ts
adapters/
activity.adapter.ts
lead.adapter.ts
opportunity.adapter.ts
quotation.adapter.ts
approval.adapter.ts
customer.adapter.ts
```
### Security rule
Projection service must apply source-record access and pricing redaction before returning rows.
## EPIC 5: Calendar Projection & Workspace
### Objective
Create a time-based operational workspace over activities, reminders, milestones, and approval due signals.
### Technical design
- new route: `/dashboard/crm/calendar`
- day/week/month/agenda views
- agenda is manager-friendly default view
- milestone events are visually distinct from owned work
- route uses server prefetch and client `useSuspenseQuery()`
### Sources
- activities
- reminder signals
- opportunity expected-award / expected-delivery milestones
- quotation validity or milestone signals
- approval due and escalation signals
### UI dependency
Must not start code before a workspace UI/UX note is approved.
## EPIC 6: My Day Workspace
### Objective
Create the personalized operational home screen for action-taking users.
### Technical design
- new route: `/dashboard/crm/my-day`
- modules:
- Today's Activities
- Overdue Activities
- Upcoming Meetings
- Pending Approvals
- Hot Projects Requiring Action
- Urgent Quotations
- Relationship Alerts
### Composition rule
My Day does not own data. It composes:
- Activity views
- Calendar windows
- Approval pending data
- Hot Project signals
- relationship health or follow-up gap signals
### UX rule
My Day must prioritize next action, not passive analytics.
## EPIC 7: Notification Expansion
### Objective
Extend the notification foundation from approval-centric delivery into broader operational activity and manager workflow coverage.
### New event coverage candidates
- activity assigned
- activity due soon
- activity overdue
- manager escalation
- quotation expiring soon
- hot project flagged
- expected award date changed
### Foundations to reuse
- `src/features/foundation/notifications/**`
- existing recipient resolver
- existing template system
- existing inbox UI and React Query hooks
### Constraint
Do not widen notification writes in a way that duplicates Activity or Reminder as source-of-truth records.
## EPIC 8: Manager Workspace
### Objective
Create the team execution control tower for managers.
### Technical design
- new route: `/dashboard/crm/manager`
- core modules:
- Team Calendar
- Overdue Activities
- Idle Opportunities
- Forecast Review
- Hot Projects
- Approval Queue
- Workload Balance
- Relationship Risk Alerts
### Reuse expectations
- reuse dashboard KPI service
- reuse report datasets where possible
- reuse approval runtime and visibility rules
- consume calendar and timeline projections instead of building ad hoc tables
### Risk
Team visibility is still partially approximate until a first-class team graph exists. AR.2 must document that manager workspace should remain branch/product/access aware and avoid inventing a parallel team permission system.
## EPIC 9: Executive Workspace
### Objective
Create a high-level strategic workspace for pipeline health, revenue outlook, and strategic account monitoring.
### Technical design
- new route: `/dashboard/crm/executive`
- emphasize summary + exception drilling, not operational task lists
- reuse dashboard/report infrastructure first
- use compact, high-value KPI modules and limited chart count
### Recommended data blocks
- Pipeline Health
- Revenue Outlook
- Strategic Accounts at Risk
- Win/Loss Trend
- Forecast Confidence
- Key Relationship Movement
## EPIC 10: Lead Handoff & Outcome Alignment
### Objective
Strengthen lead-to-opportunity handoff and downstream lead-outcome derivation without destabilizing the current lead foundation.
### Scope ideas
- explicit handoff-ready flow
- clearer lead-to-opportunity conversion markers
- derived lead outcome rules based on linked opportunities
- activity suggestions around handoff readiness
### Reuse
- current lead domain
- opportunity links
- customer ownership
- CRM access
## EPIC 11: Relationship Health & Forecast Semantics
### Objective
Freeze the implementation rules for relationship health, follow-up health, expected award date, expected delivery date, and hot-project-driven urgency.
### Technical design
- derived scoring/service only
- no user-maintained health override in phase 1 unless separately approved
- consume activity recency, overdue count, open opportunity gaps, and strategic flags
- align dashboard, customer workspace, my day, and manager workspace to one shared semantics layer
## EPIC 12: Terminology & Governance Reconciliation
### Objective
Clean up remaining historical `enquiry` wording and reconcile governance docs with the active `opportunity` production model.
### Constraints
- do not rename stable production routes or schema without explicit migration approval
- prefer documentation, labels, and implementation-note reconciliation first
## UI/UX Design Gate
The following epics require a mandatory approved UI/UX design note before code starts:
- Customer Workspace Enhancement
- Calendar Projection & Workspace
- My Day Workspace
- Manager Workspace
- Executive Workspace
- any Activity list/detail page that becomes a new major workspace surface
The UI/UX note must reference:
- Business Constitution / BU-R.0 / BU-R.0.1
- BU-R.1
- AR.1
- this AR.2 baseline
- `layout.md`
- `docs/standards/ui-ux-rules.md`
- `ui-ux-pro-max`
## Shared API and Service Conventions
Every new AR.2 feature should follow:
```text
src/features/<feature>/
api/
types.ts
service.ts
queries.ts
mutations.ts
components/
schemas/
server/
```
Route handlers remain under `src/app/api/**`.
## Shared Permission and Security Requirements
Every AR.2 epic touching CRM data must:
- require organization access
- resolve CRM access server-side
- enforce branch/product/ownership scope in service layer
- enforce pricing visibility for quotation-derived commercial data
- audit security denials where applicable
No epic may authorize from raw role strings alone.
## Shared Verification Standard
Implementation tasks spawned from AR.2 should define:
- `tsc --noEmit`
- targeted lint, if touched area already has lint expectations
- feature service tests where practical
- route handler and permission scenarios
- manual workspace checks for loading, empty, error, responsive, and permission-aware states
## Technical Risk Register
| Risk | Affected Epics | Why | Mitigation |
| --- | --- | --- | --- |
| Follow-up data fragmentation | Activity, Timeline, Calendar, Dashboard consumers | existing lead/opportunity/quotation follow-up storage differs | adapter-first rollout and dataset continuity tests |
| Approval notification duplication | Events, Notification Expansion | approval flow already publishes notifications | keep approval event compatibility and dedupe strategy |
| Workspace route meaning collision | Calendar, My Day, Manager, Executive | `/dashboard/workspaces` already used for admin workspaces | keep business workspaces under `/dashboard/crm/*` |
| Pricing leakage | Timeline, Calendar, Executive, My Day | quotation-derived data may appear in new views | centralize pricing checks in projection/query services |
| Team visibility ambiguity | Manager, Calendar, Executive | team graph not yet first-class | reuse current resolved access and document known limitation |
| Over-engineered repository abstraction | Activity, Timeline | repo prefers service-layer-first pattern | keep internal adapters lightweight and feature-owned |
## AR.2 Freeze Outcome
AR.2 freezes:
1. the epic order for transition delivery
2. the minimum technical design boundaries for each epic
3. the no-code-before-design rule for major workspace surfaces
4. the reuse-first rule for security, notification, dashboard, report, and document foundations
5. the implementation expectation that Activity and projections arrive incrementally beside current production modules
## Verification
- Documentation-only change.
- No schema, migration, API, service, UI, permission, or runtime business-logic file changed.
- No build or test command was required for this phase.
## Outcome
AR.2 now provides the implementation-ready epic and technical design baseline for the Relationship-Driven Sales Workspace transition. Future implementation tasks can use this document to choose sequence, define scope, reuse correct foundations, prepare workspace UI/UX notes, and avoid breaking the mature production CRM backbone.

View File

@@ -0,0 +1,32 @@
# Task AR.2 Implementation Report - 2026-07-07
## Scope
- start and complete AR.2 as a documentation-only epic and technical-design phase
- review AR.1, BU-R blueprint/audit, current CRM foundations, current UI patterns, and workspace UI governance
- produce an implementation-ready epic baseline and a required workspace UI/UX design note
## What Was Produced
- epic and technical design baseline:
[task-ar.2-epic-technical-design-2026-07-07.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-ar.2-epic-technical-design-2026-07-07.md)
- workspace UI/UX design note:
[task-ar.2-workspace-ui-ux-design-note-2026-07-07.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-ar.2-workspace-ui-ux-design-note-2026-07-07.md)
## Key Decisions Captured
1. AR.2 freezes the implementation sequence from foundational runtime to projection layer to workspace layer.
2. Activity Platform and Business Event Expansion are the first delivery epics because they unblock most downstream work.
3. Customer Workspace is the first major visible workspace because it extends the strongest existing production base.
4. Calendar, My Day, Manager, Executive, and major Activity surfaces require an approved UI/UX design note before code starts.
5. Business-role workspaces must stay under `/dashboard/crm/*` and must not reuse the existing admin `workspaces` route.
## Verification
- Documentation-only change.
- No schema, API, UI, service, permission, or runtime business-logic files were changed.
- No build or test command was required for this phase.
## Outcome
AR.2 now records both the implementation-ready epic/technical-design baseline and the approved workspace UI/UX baseline needed before future implementation work begins.

View File

@@ -0,0 +1,398 @@
# Task AR.2 Workspace UI/UX Design Note - 2026-07-07
## Purpose
This note satisfies the AR.2 governance requirement that major workspace surfaces must have a UI/UX design note before implementation begins.
This is not final visual design. It is the approved interaction and composition baseline for future workspace implementation tasks.
## Inputs Reviewed
- `docs/business/relationship-sales-workspace-blueprint-v1.md`
- `docs/implementation/task-bu-r.1-business-capability-audit-2026-07-07.md`
- `docs/implementation/task-ar.1-architecture-transition-plan-2026-07-07.md`
- `docs/implementation/task-ar.2-epic-technical-design-2026-07-07.md`
- `layout.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/architecture-rules.md`
- existing shadcn/ui patterns in CRM detail/report/dashboard pages
- `src/features/crm/dashboard/components/crm-dashboard.tsx`
- `src/features/crm/customers/components/customer-detail.tsx`
- `src/features/crm/reports/components/pipeline-report-view.tsx`
- `ui-ux-pro-max` design-system and UX searches
## UI/UX Direction
### Visual direction
- preserve the existing dashboard shell and `PageContainer`
- extend the current CRM card, tab, filter, and table language
- use a restrained enterprise visual system
- prefer Minimalism + Swiss-style structure over decorative redesign
- keep CRM operational surfaces dense enough for work, but not noisy
### Style summary from `ui-ux-pro-max`
- enterprise / operational dashboard fit
- Minimalism & Swiss Style is the best match
- use clear type hierarchy, subtle hover, minimal shadow, clean borders
- keep high contrast and strong layout rhythm
### Layout behavior
- summary and action modules first
- active work queue second
- contextual history and related-record panels next
- keep page composition modular and card-based
- use bento-style composition only where it improves scanning, not as decoration
### Chart guidance
- use bullet charts for compact performance-vs-target manager/executive KPIs
- use line highlights for anomaly or risk spikes
- use gauge only for isolated single target KPIs, not crowded summary rows
## Shared Design Rules Across All New Workspaces
1. Each workspace must make the next decision obvious within the first screenful.
2. Filters should stay compact, reusable, and URL-aware where sharing matters.
3. Empty states must explain the missing condition and the next useful action.
4. Loading states should preserve layout using skeletons.
5. Use text and icons in addition to color for status or urgency.
6. Preserve current CRM terminology helpers and do not create a second terminology registry.
7. New workspace routes must feel connected to the existing CRM nav, not like a parallel product.
## Surface 1: Customer Workspace
### Page purpose
Relationship execution hub for sales and managers.
### Primary user role
Sales first, managers second.
### Layout structure
- top header with customer code, status, owner, and quick actions
- main left column:
- overview summary
- relationship health
- upcoming work
- activities
- timeline
- related leads/opportunities/quotations
- right column:
- owner card
- quick metrics
- recent meetings / visits
- system metadata
### Key actions
- edit customer
- manage owner
- add contact
- add activity
- jump to open opportunity or quotation
### Empty state
- if no open work: explain that the relationship exists but has no upcoming action
- primary CTA should be create activity or open opportunity depending on access
### Loading state
- keep the existing detail-shell rhythm with card skeletons
### Permission-aware visibility
- contacts and sharing controls remain permission-gated
- quotation-related commercial values must obey pricing visibility
### Responsive behavior
- mobile stacks left and right columns
- tabs remain usable without horizontal clipping
- quick actions wrap into compact rows
### Relation to current nav
- stays on `/dashboard/crm/customers/[id]`
- this is an extension of current customer detail, not a new disconnected route
## Surface 2: Calendar Workspace
### Page purpose
Time-based operational workspace for activities, reminders, milestones, and approval due signals.
### Primary user role
All operational roles, with manager emphasis in agenda view.
### Layout structure
- page header and lens switcher
- compact filter bar
- calendar body with:
- day/week/month/agenda tabs
- side summary rail for selected date or selected item
### Key actions
- change lens
- filter by user/team/branch/entity/activity type
- open linked record
- create activity from selected slot, when permitted
### Empty state
- no scheduled work in period
- explain whether filters or true no-work condition caused it
### Loading state
- skeleton for toolbar and calendar grid/list shell
### Permission-aware visibility
- personal lens defaults to owned and assigned work
- manager lens shows only permitted team work
- milestone rows do not leak unauthorized quotation data
### Responsive behavior
- agenda becomes the mobile-friendly default
- month view should degrade carefully on small widths
### Relation to current nav
- new route under `/dashboard/crm/calendar`
## Surface 3: My Day
### Page purpose
Personal action home screen for the logged-in user.
### Primary user role
Sales and marketing; managers can still use it as a personal operating view.
### Layout structure
- high-priority queue at the top:
- overdue
- due today
- urgent meetings
- pending approvals
- lower sections:
- hot projects requiring action
- urgent quotations
- relationship alerts
- upcoming schedule snapshot
### Key actions
- mark activity complete
- reschedule
- open source record
- open today's calendar
### Empty state
- celebrate cleared queue but provide a useful next action, such as reviewing upcoming work
### Loading state
- summary and queue skeletons
### Permission-aware visibility
- approvals and quotation urgency must obey approval/pricing boundaries
### Responsive behavior
- priority queue stays first on mobile
- cards collapse into a single-column task feed
### Relation to current nav
- new route under `/dashboard/crm/my-day`
## Surface 4: Manager Workspace
### Page purpose
Operational control tower for team execution quality.
### Primary user role
Manager.
### Layout structure
- summary band:
- team overdue
- idle opportunities
- pending approvals
- forecast at risk
- main body:
- team calendar / agenda
- hot projects
- approval queue
- workload balance
- relationship risk alerts
### Key actions
- drill into overdue item
- reassign or escalate
- jump to approval blocker
- review tomorrow risk
### Empty state
- if no risks or overdue work, show healthy-state copy plus forecast review CTA
### Loading state
- KPI band skeleton
- agenda/table skeleton
### Permission-aware visibility
- use team/branch/product scope rules
- no UI should imply broader access than current security model grants
### Responsive behavior
- mobile prioritizes risk list and agenda over wider comparison panels
- workload balance and comparison charts move below action queues
### Relation to current nav
- new route under `/dashboard/crm/manager`
## Surface 5: Executive Workspace
### Page purpose
Strategic monitoring workspace for executives.
### Primary user role
Executive or high-level decision maker.
### Layout structure
- top KPI row with limited count
- trend and exception cards next
- strategic accounts and pipeline/risk summaries below
### Key actions
- drill into strategic account
- open executive report slice
- inspect risk trend
### Empty state
- not likely full empty; if limited access, explain scope instead of showing blank business panels
### Loading state
- stable KPI and trend skeletons
### Permission-aware visibility
- commercial values and strategic summaries follow pricing visibility and report context rules
### Responsive behavior
- mobile reduces chart count and shows stacked summary cards
- avoid forcing dense wide tables on first view
### Relation to current nav
- new route under `/dashboard/crm/executive`
## Surface 6: Activity Workspace or Major Activity Views
### Page purpose
Shared operational work management across customers, leads, opportunities, and quotations.
### Primary user role
All operational roles.
### Layout structure
- filter bar
- queue/list view first
- optional board or grouped sections later if justified
- detail drawer or detail route for context
### Key actions
- create activity
- complete
- reschedule
- reassign
- open linked record
### Empty state
- distinguish between no activities and filters producing no result
### Loading state
- data-table skeleton or list skeleton, not blank feed
### Permission-aware visibility
- show source record badges, but hide or redact unauthorized commercial details
### Responsive behavior
- mobile uses stacked activity cards rather than wide data tables where needed
## Recommended Visual Patterns by Surface
| Surface | Best Current Pattern to Extend |
| --- | --- |
| Customer Workspace | customer detail tabs + side cards |
| Calendar Workspace | dashboard filter bar + new calendar composition |
| My Day | dashboard summary + operational queue cards |
| Manager Workspace | dashboard bento rhythm + report table density |
| Executive Workspace | dashboard summary + report cards with fewer but stronger blocks |
| Activity Views | report/list filter pattern + CRM detail drawer pattern |
## Anti-patterns to Avoid
- giant marketing-style hero sections inside dashboard workspaces
- visual redesign that breaks nearby CRM pages
- overusing charts when a ranked list or action queue is clearer
- blank states without next actions
- status communicated by color alone
- new workspace nav under `/dashboard/workspaces`
- layout shift on loading or button pending states
## Handoff Requirement
Any future implementation task for these surfaces must cite this design note and explicitly state:
- which surface it implements
- whether it preserves this design note or intentionally deviates
- why any deviation is needed
## Verification
- Documentation-only design note.
- No runtime code, route, schema, API, or permission behavior changed.
## Outcome
AR.2 now has an approved UI/UX baseline for Customer Workspace, Calendar Workspace, My Day, Manager Workspace, Executive Workspace, and major Activity views, aligned with current CRM patterns and the required `ui-ux-pro-max` design process.

View File

@@ -0,0 +1,74 @@
# Task BU-R.0 Implementation Report - 2026-07-07
## Scope
- start Phase R.0 as a documentation-only business architecture task
- review current governance, CRM business rules, ADRs, security boundaries, and current schema direction
- produce the first frozen business blueprint artifact and record the baseline findings
## Review Summary
Reviewed before writing the blueprint:
- [AGENTS.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/AGENTS.md)
- [plans/task-bu-r.0.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/plans/task-bu-r.0.md)
- [docs/standards/project-foundations.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/project-foundations.md)
- [docs/standards/architecture-rules.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/architecture-rules.md)
- [docs/standards/ui-ux-rules.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/ui-ux-rules.md)
- [docs/standards/task-review-checklist.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/task-review-checklist.md)
- [docs/standards/task-catalog.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/task-catalog.md)
- [docs/business/crm-terminology.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/business/crm-terminology.md)
- [docs/security/crm-authorization-boundaries.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/security/crm-authorization-boundaries.md)
- [docs/adr/0011-lead-enquiry-ownership-model.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0011-lead-enquiry-ownership-model.md)
- [docs/adr/0015-customer-ownership-contact-sharing.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0015-customer-ownership-contact-sharing.md)
- [docs/adr/0016-won-lost-lifecycle-governance.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0016-won-lost-lifecycle-governance.md)
- [docs/adr/0018-lead-enquiry-domain-separation.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0018-lead-enquiry-domain-separation.md)
- [plans/task-d.5.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/plans/task-d.5.md)
- [plans/task-d.6.2.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/plans/task-d.6.2.md)
- [task-d55-opportunity-sales-workspace-refinement.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-d55-opportunity-sales-workspace-refinement.md)
- [schema.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/db/schema.ts)
- [crm-access.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/lib/auth/crm-access.ts)
## Baseline Findings
1. The codebase has already moved materially toward a split `lead + opportunity + quotation` model in schema and UI direction, even though older ADR history still contains `enquiry`-centric language.
2. `crm_leads` and `crm_opportunities` already exist in [schema.ts](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/src/db/schema.ts), which makes the unresolved terminology conflict a governance problem more than an implementation absence.
3. Hot-project and delivery timeline behavior already exist technically in opportunity and quotation fields, but the business definitions for `Hot Project`, `Expected Award Date`, and `Expected Delivery Date` were not frozen in one business document.
4. The repo has strong foundations for ownership, authorization, approval, PDF, reporting, and audit, so Phase R.0 should not redesign foundations. It should clarify business meaning that later phases must map onto those foundations.
5. Calendar and timeline concepts appear in plans and feature discussions, but there was no single approved statement that timeline is a generated view and calendar is a role-based workspace.
## Artifacts Created
- Created the official blueprint draft:
[relationship-sales-workspace-blueprint-v1.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/business/relationship-sales-workspace-blueprint-v1.md)
## Freeze Decisions Captured in This Pass
- ALLA OS is frozen as a `Relationship-Driven Sales Workspace`.
- `Opportunity` is frozen as the official sales-owned project lifecycle term.
- `Quotation` is frozen as the document lifecycle owner.
- `Hot Project` is frozen as a governed priority signal, not a lifecycle status.
- `Timeline` is frozen as a generated business view.
- `Calendar` is frozen as an operational workspace, not a standalone isolated module.
- `Expected Award Date` and `Expected Delivery Date` are frozen as governed business forecast concepts.
- `Activity` is frozen as the shared operational model that should unify follow-up-oriented work over time.
## Remaining Governance Gaps
1. ADR reconciliation is still needed where accepted historical ADRs use `enquiry` while current schema and blueprint use `opportunity`.
2. The blueprint freezes the target activity model, but implementation still contains domain-specific follow-up structures that Phase R.1 should audit for consolidation strategy.
3. Calendar Workspace and generated Timeline are frozen conceptually here, but not yet decomposed into implementation contracts, datasets, or APIs.
## Verification
- No schema, API, UI, service, or permission code was changed.
- No build or test command was needed because this pass only added documentation.
## Next Recommended Step
Use this blueprint as the required input for Phase R.1 Business Capability Audit, with special focus on:
- terminology conflicts
- activity model duplication
- timeline / calendar capability mapping
- dashboard and forecast metric alignment

View File

@@ -0,0 +1,71 @@
# Task BU-R.0.1 Implementation Report - 2026-07-07
## Scope
- continue BU-R.0 by freezing the remaining business architecture for Activity, Workspace Interaction, Customer Workspace, Calendar Workspace, My Day, Manager Workspace, and Business Event fan-out
- update the main relationship and sales business blueprint without changing previously frozen BU-R.0 principles
- keep the task documentation-only with no schema, API, UI, service, permission, or ADR changes
## Review Summary
Reviewed before drafting:
- [AGENTS.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/AGENTS.md)
- [plans/task-bu-r.0.1.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/plans/task-bu-r.0.1.md)
- [plans/task-bu-r.0.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/plans/task-bu-r.0.md)
- [docs/standards/task-contract-template.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/task-contract-template.md)
- [docs/standards/task-catalog.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/task-catalog.md)
- [docs/standards/project-foundations.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/project-foundations.md)
- [docs/standards/architecture-rules.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/architecture-rules.md)
- [docs/standards/ui-ux-rules.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/ui-ux-rules.md)
- [docs/standards/task-review-checklist.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/task-review-checklist.md)
- [docs/business/crm-terminology.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/business/crm-terminology.md)
- [docs/business/relationship-sales-workspace-blueprint-v1.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/business/relationship-sales-workspace-blueprint-v1.md)
- [docs/security/crm-authorization-boundaries.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/security/crm-authorization-boundaries.md)
- [docs/adr/0015-customer-ownership-contact-sharing.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0015-customer-ownership-contact-sharing.md)
- [docs/adr/0016-won-lost-lifecycle-governance.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0016-won-lost-lifecycle-governance.md)
- [docs/adr/0017-report-foundation.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0017-report-foundation.md)
- [docs/adr/0018-lead-enquiry-domain-separation.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/adr/0018-lead-enquiry-domain-separation.md)
- [docs/implementation/task-bu-r.0-business-blueprint-freeze-2026-07-07.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-bu-r.0-business-blueprint-freeze-2026-07-07.md)
- [docs/implementation/task-d.6-crm-activity-foundation-audit-log-separation-2026-07-01.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-d.6-crm-activity-foundation-audit-log-separation-2026-07-01.md)
- current CRM customer, opportunity, quotation, dashboard, notification, and workspace implementations under `src/features/**`, `src/app/dashboard/**`, and `src/db/schema.ts`
## Current-State Findings Used For The Freeze
1. Customer detail already behaves as a relationship context surface with contacts plus linked opportunities and quotations, but it is not yet governed as the official Customer Workspace.
2. Opportunity and quotation features already store follow-up rows, Hot Project flags, and forecast-related dates, which makes them valid current seams for the future shared activity and calendar model.
3. Dashboard already aggregates follow-up and Hot Project signals, but those signals are still oriented around dashboards rather than a broader My Day, Calendar Workspace, or Manager Workspace philosophy.
4. The notification foundation is production-ready, but current event publishing is mainly approval-driven, so activity-driven notification fan-out still needed business governance before implementation.
5. Existing `/dashboard/workspaces` navigation currently means organization/team administration, not the future business-role workspace model. That distinction had to be frozen explicitly to avoid naming drift.
## Documentation Changes
- Expanded [relationship-sales-workspace-blueprint-v1.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/business/relationship-sales-workspace-blueprint-v1.md) with:
- Activity Blueprint Freeze
- Workspace Interaction Blueprint Freeze
- Customer Workspace Blueprint Freeze
- Calendar Workspace Blueprint Freeze
- My Day Blueprint Freeze
- Manager Workspace Blueprint Freeze
- Business Event Matrix
- Activity State Machine
- Workspace Navigation Diagram
- Customer 360 Diagram
- Daily User Journey for Marketing, Sales, Manager, and Executive
- Current-system alignment notes for Phase R.1 audit readiness
## Freeze Decisions Added In This Pass
- Activity is now the officially governed shared work model and remains distinct from Audit Log.
- Calendar is frozen as a workspace projection over activities and milestone events, not a detached scheduling module.
- My Day is frozen as the personalized action home screen prioritized by urgency and commitment risk.
- Manager Workspace is frozen as an execution-control workspace, not only a KPI dashboard.
- Business events are frozen as the common fan-out source for timeline, notification, reminder, dashboard, audit, and future automation behavior.
- Customer Workspace is frozen as the main relationship execution hub and Customer 360 surface.
## Verification
- Documentation-only change.
- No schema, API, UI, service, permission, ADR, or migration files changed.
- No build or test command was required for this pass.
## Outcome
BU-R.0.1 now closes the major business architecture gaps left outside BU-R.0 and leaves Phase R.1 with a clearer audit target for:
- follow-up-to-activity unification
- workspace and navigation semantics
- calendar and reminder projections
- activity-driven notification design
- manager and My Day operational surfaces

View File

@@ -0,0 +1,218 @@
# Task BU-R.1 Implementation Report - 2026-07-07
## Scope
- audit the current ALLA OS implementation against the frozen BU-R.0 and BU-R.0.1 business blueprint
- classify capabilities as `Existing`, `Partial`, `Missing`, `Legacy`, or `Refactor Candidate`
- document preservation, dependency, priority, and readiness guidance for later implementation phases
- keep the phase documentation-only with no schema, API, UI, service, permission, or business-logic changes
## Review Summary
Reviewed before auditing:
- [AGENTS.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/AGENTS.md)
- [plans/task-bu-r.1.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/plans/task-bu-r.1.md)
- [docs/business/relationship-sales-workspace-blueprint-v1.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/business/relationship-sales-workspace-blueprint-v1.md)
- [docs/implementation/task-bu-r.0-business-blueprint-freeze-2026-07-07.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-bu-r.0-business-blueprint-freeze-2026-07-07.md)
- [docs/implementation/task-bu-r.0.1-workspace-activity-business-blueprint-2026-07-07.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-bu-r.0.1-workspace-activity-business-blueprint-2026-07-07.md)
- [docs/standards/project-foundations.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/project-foundations.md)
- [docs/standards/architecture-rules.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/architecture-rules.md)
- [docs/standards/task-review-checklist.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/standards/task-review-checklist.md)
- [docs/security/crm-authorization-boundaries.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/security/crm-authorization-boundaries.md)
- relevant CRM/foundation source under `src/features/crm/**`, `src/features/foundation/**`, `src/app/dashboard/crm/**`, `src/app/api/**`, `src/config/nav-config.ts`, and `src/db/schema.ts`
## Executive Summary
1. Customer, Contact, Opportunity, Quotation, Approval, Dashboard, security, and PDF/document foundations are already mature and should be preserved.
2. Lead capability exists as a real separated workspace and schema, but its follow-up implementation remains lighter and more audit-log-backed than the blueprint target.
3. Follow-up capability is real across lead, opportunity, quotation, and dashboard analytics, but a shared production Activity platform does not exist yet.
4. Timeline, Calendar Workspace, My Day, Manager Workspace, and role-based business workspaces are still mostly target-state architecture, not production capability.
5. Notification foundation is reusable and production-ready, but current live business-event coverage is approval-centric rather than activity-centric.
## 1. Business Capability Matrix
| Domain | Capability | Status | Notes |
| --- | --- | --- | --- |
| Relationship | Customer master, profile, related opportunity/quotation linkage | Existing | Customer detail already acts as a relationship context surface with related commercial records. |
| Relationship | Customer ownership | Existing | Persisted owner assignment plus owner history exists and is governed by ADR-0015. |
| Relationship | Contact sharing and visibility | Existing | Persisted `crm_contact_shares` plus share-aware access enforcement exists. |
| Relationship | Customer account type / prospect classification | Partial | Customer type and status options exist, but relationship-health semantics are not governed in product behavior yet. |
| Relationship | Customer relationship health | Missing | No governed health model, scoring logic, or dedicated UI module yet. |
| Relationship | Customer timeline | Partial | Audit Log and related-record links exist, but no generated cross-entity customer timeline exists. |
| Activity | Lead follow-up | Existing | Lead follow-ups exist, but are stored via audit payloads instead of a dedicated follow-up table. |
| Activity | Opportunity follow-up | Existing | Persisted child resource with CRUD, UI tab, dashboard aggregation, and report reuse exists. |
| Activity | Quotation follow-up | Existing | Persisted child resource with CRUD, UI tab, dashboard aggregation, and report reuse exists. |
| Activity | Shared Activity entity/platform | Partial | Contract-first activity types and permissions exist under `src/features/crm/activity/types.ts`, but there is no production storage/service/UI yet. |
| Activity | Meeting / visit / phone / reminder / internal task | Missing | Only follow-up-oriented capability is implemented today. |
| Activity | Shared activity lifecycle and ownership rules | Partial | Blueprint-ready contract exists; production behavior still fragmented by feature. |
| Lead | Separate lead workspace | Existing | Dedicated pages, service layer, schema, assignment, follow-up, and linked opportunities are live. |
| Lead | Marketing handoff into opportunity | Partial | Linked opportunity visibility exists, but explicit governed conversion workflow is still limited. |
| Lead | Lead outcome derivation from downstream sales | Missing | Lead outcome behavior remains lighter than the blueprint target and ADR-0018 direction. |
| Opportunity | Opportunity ownership and assignment | Existing | Sales assignment, reassignment, branch/product scope, and linked lead provenance are implemented. |
| Opportunity | Forecast fields and probability | Existing | `expectedCloseDate`, `projectCloseDate`, `deliveryDate`, `chancePercent`, and `isHotProject` are live. |
| Opportunity | Expected Award Date / Expected Delivery Date semantics | Partial | Underlying dates exist, but exact blueprint naming/behavior still needs semantic reconciliation. |
| Opportunity | Outcome governance won/lost/reopen/PO | Existing | ADR-0016 behavior is implemented with mark-won, mark-lost, reopen, and PO attachment flows. |
| Commercial | Quotation CRUD, revision, approval, pricing | Existing | Mature quotation feature with revision chain, approval, preview, PDF, and customer package support exists. |
| Commercial | Customer package | Existing | Implemented as approved-PDF plus document-library assembly with permission checks. |
| Commercial | PO integration | Partial | PO capture is implemented on opportunity outcome, but customer-workspace and timeline integration are not yet complete. |
| Approval | Workflow runtime | Existing | Shared approval runtime, steps, actor resolution, notification publishing, and dashboard analytics exist. |
| Approval | Approval automation reminders/escalation | Existing | Reminder and escalation policy foundations are present. |
| Workspace | Customer workspace | Partial | Current customer detail is a strong base, but blueprint sections like Activities, Timeline, Health, Upcoming Work, and Notes are incomplete. |
| Workspace | Sales workspace | Partial | Lead, opportunity, quotation pages exist, but they are entity pages more than one unified role workspace. |
| Workspace | Manager workspace | Missing | No first-class manager control-tower page exists yet. |
| Workspace | Executive workspace | Missing | No dedicated executive workspace exists yet. |
| Workspace | My Day | Missing | No personalized action-home surface exists yet. |
| Workspace | Calendar workspace | Missing | No dedicated calendar route, projection layer, or calendar UI exists yet. |
| Timeline | Generated business timeline | Missing | Audit Log exists, but there is no platform-generated cross-entity timeline model. |
| Dashboard | CRM dashboard KPI foundation | Existing | Summary, funnel, follow-up, approval, revenue, ranking, and hot projects are implemented. |
| Dashboard | Manager / executive-specific dashboard layers | Partial | Current dashboard supports broad KPI visibility, but not fully distinct role workspaces. |
| Notification | Inbox foundation and read/archive flows | Existing | Generic app notifications and inbox UI are production-ready. |
| Notification | Approval notifications | Existing | Approval runtime publishes notification events and inbox surfaces them. |
| Notification | Activity / follow-up / assignment notifications | Missing | Business-event coverage for operational activity is not implemented yet. |
| Event Model | Audit events | Existing | Core audit foundation is widespread and mature. |
| Event Model | Approval events | Existing | Approval event publishing is live. |
| Event Model | Activity-driven business events | Partial | Opportunity and quotation mutations exist, but shared fan-out governance is not yet implemented as one event model. |
## 2. Capability Coverage Report
Coverage values below are qualitative readiness estimates based on audited business scope, not literal line-of-code metrics.
| Domain | Coverage | Summary |
| --- | --- | --- |
| Customer / Relationship | 70% | Strong ownership, sharing, profile, and related-record foundation; missing relationship health and generated timeline. |
| Activity | 35% | Follow-up is real, but shared activity platform, meeting/task/reminder families, and unified lifecycle are not implemented. |
| Lead | 70% | Dedicated domain and workspace exist, but downstream derived outcome and richer handoff workflow remain incomplete. |
| Opportunity | 85% | Assignment, forecast fields, follow-up, hot project, and outcome governance are mature. |
| Quotation | 90% | CRUD, revision, approval, document, package, and pricing foundations are strong. |
| Approval | 90% | Runtime, settings, automation, and dashboard visibility are well-established. |
| Dashboard | 80% | KPI dashboard is live and substantial, but role-specialized workspace behavior is still incomplete. |
| Calendar | 10% | Date fields and reminder policies exist, but no true calendar workspace capability exists. |
| Timeline | 20% | Audit Log exists, but blueprint-grade generated business timeline does not. |
| Notification | 50% | Good reusable foundation, but limited current business-event breadth. |
| Workspace Model | 40% | Entity routes exist, but My Day, Manager, Executive, and Calendar workspaces are still future capability. |
## 3. Gap Analysis: Current System vs Business Blueprint
### No Change
- Customer ownership and contact sharing foundation
- Opportunity outcome governance
- Quotation approval, document, PDF, artifact, and customer package foundations
- CRM dashboard KPI foundation
- CRM authorization and pricing-visibility enforcement
### Enhancement
- Customer detail evolving into Customer Workspace
- Opportunity/quotation date semantics aligning to expected-award and expected-delivery definitions
- Dashboard role layering for manager and executive needs
- Notification foundation expanded from approval-centric events into broader CRM business events
- Lead handoff and downstream outcome synchronization
### Refactor
- Fragmented follow-up behavior into a shared Activity platform over time
- Historical `enquiry` terminology remnants in docs and older lineage
- `workspaces` navigation semantics, which currently mean organization/team administration rather than business-role workspaces
### New Capability
- Generated Timeline
- Calendar Workspace
- My Day
- Manager Workspace
- Executive Workspace
- Activity platform with shared families beyond follow-up
## 4. Architecture Preservation Report
### Keep Unchanged
- `src/features/crm/customers/**`
- `src/features/crm/opportunities/**`
- `src/features/crm/quotations/**`
- `src/features/foundation/approval/**`
- `src/features/foundation/approval-automation/**`
- `src/features/foundation/notifications/**`
- `src/features/foundation/pdf-generator/**`
- `src/features/foundation/document-artifact/**`
- `src/features/crm/dashboard/**`
- `src/lib/auth/crm-access.ts` and CRM security enforcement
### Extend Only
- customer detail and related tabs into Customer Workspace sections
- lead/opportunity/quotation follow-up flows into future Activity adapters
- dashboard into manager and executive role surfaces
- notification event publishing into broader business-event coverage
- nav and workspace routing into clearer role-oriented destinations
### Controlled Refactor Required
- shared activity storage/service/UI
- timeline projection layer
- calendar projection and filtering layer
- terminology cleanup across historical docs and edge code paths
### Backward Compatibility Concerns
- lead follow-up currently depends on audit-log payloads rather than a dedicated persisted follow-up table
- approval and notification fan-out already exist for approvals, so later activity events must avoid double-publishing behavior
- dashboard/service/reporting logic already depends on current follow-up storage, so activity migration must preserve analytics continuity
- current `workspaces` routes are already used for organization/team administration and should not be silently repurposed
## 5. Business Capability Dependency Map
```text
Customer / Contact
|- Lead
| `- Opportunity
| |- Follow-up / Future Activity Platform
| |- Quotation
| | |- Approval
| | |- PDF / Artifact / Customer Package
| | `- Commercial Notifications
| |- Dashboard / Reports
| `- Won / Lost / PO
|
`- Future Customer Workspace
|- Timeline
|- Calendar Projection
|- My Day
`- Manager / Executive Workspace
```
## 6. Capability Priority Matrix
| Epic / Capability | Priority | Why |
| --- | --- | --- |
| Activity Platform | Critical | It is the main missing shared business model and blocks Calendar, Timeline, My Day, and richer notifications. |
| Customer Workspace | Critical | Customer is the relationship anchor and already has a strong partial base worth extending. |
| Timeline | High | Required for blueprint-aligned relationship history and event visibility. |
| Calendar Workspace | High | Strong business need, but depends on activity/event projection clarity. |
| My Day | High | Key daily operating surface and natural consumer of activity, reminders, and approvals. |
| Manager Workspace | High | Needed for operational control, workload balancing, and hot-project governance. |
| Notification Expansion | Medium | Foundation exists; main work is business-event coverage and orchestration. |
| Executive Workspace | Medium | Valuable, but depends on manager/dashboard/timeline foundations first. |
| Terminology / legacy cleanup | Medium | Important for governance clarity, but not as blocking as Activity or Workspace delivery. |
## 7. Implementation Readiness Report
| Area | Readiness | Notes |
| --- | --- | --- |
| Customer Workspace | Requires UI enhancement | Data foundation is strong; sections like health, upcoming work, and relationship summary need expansion. |
| Activity Platform | Requires schema + service + UI enhancement | Only contract-first types exist today. |
| Lead lifecycle enhancement | Requires service + UI enhancement | Existing lead feature is usable, but derived outcome/handoff depth is incomplete. |
| Opportunity forecast semantics | Requires service + UI enhancement | Core fields exist already. |
| Quotation commercial execution | Ready immediately | Mature enough to preserve and extend. |
| Approval runtime | Ready immediately | Mature enough to preserve and extend. |
| Dashboard role layering | Requires UI + service enhancement | Foundation exists and is reusable. |
| Timeline | Requires schema/service/UI future phase | No true generated timeline capability yet. |
| Calendar Workspace | Requires schema/service/UI future phase | No production projection layer or UI route exists yet. |
| My Day | Requires service/UI future phase | Depends on activity, reminder, approval, and hot-project orchestration. |
| Notification expansion | Requires service enhancement | Foundation exists; business-event coverage does not. |
## 8. Candidate Epics After BU-R.1
- Epic: Customer Workspace
- Epic: Activity Platform
- Epic: Timeline Projection
- Epic: Calendar Workspace
- Epic: My Day
- Epic: Manager Workspace
- Epic: Executive Workspace
- Epic: Notification Event Expansion
- Epic: Lead Handoff and Outcome Alignment
- Epic: Terminology and Governance Reconciliation
## Verification
- Documentation-only change.
- No schema, API, UI, service, permission, or business-logic files were modified for runtime behavior.
- No build or test command was required for this audit pass.
## Outcome
BU-R.1 confirms that the repo already has a strong production backbone for relationship, opportunity, quotation, approval, document, dashboard, and security work. The biggest blueprint gaps are no longer hidden inside core CRUD; they are concentrated in the cross-cutting operating model: shared Activity, Timeline, Calendar, My Day, Manager Workspace, Executive Workspace, and business-event-driven notifications.

View File

@@ -0,0 +1,70 @@
# Task ENG.0 Implementation Report - 2026-07-07
## Scope
- start and complete the documentation-only Engineering Governance phase
- review business, architecture, UI, standards, security, and current implementation patterns already frozen by BU-R and AR work
- produce the official engineering governance baseline for future implementation tasks
## Review Summary
Reviewed before drafting:
- `AGENTS.md`
- `plans/task-eng.0.md`
- `docs/standards/task-contract-template.md`
- `docs/standards/task-catalog.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/task-review-checklist.md`
- `layout.md`
- `docs/security/crm-authorization-boundaries.md`
- `docs/business/relationship-sales-workspace-blueprint-v1.md`
- `docs/implementation/task-bu-r.0-business-blueprint-freeze-2026-07-07.md`
- `docs/implementation/task-bu-r.0.1-workspace-activity-business-blueprint-2026-07-07.md`
- `docs/implementation/task-bu-r.1-business-capability-audit-2026-07-07.md`
- `docs/implementation/task-ar.1-architecture-transition-plan-2026-07-07.md`
- `docs/implementation/task-ar.2-epic-technical-design-2026-07-07.md`
- `docs/implementation/task-ar.2-workspace-ui-ux-design-note-2026-07-07.md`
- `src/lib/auth/session.ts`
- `src/lib/auth/crm-access.ts`
- representative route/service/foundation patterns under:
- `src/app/api/crm/**`
- `src/features/crm/customers/**`
- `src/features/products/**`
- `src/features/users/**`
- `src/features/foundation/audit-log/**`
- `src/features/foundation/approval/**`
- `src/features/foundation/storage/**`
## What Was Produced
- official engineering governance baseline:
`docs/standards/engineering-constitution.md`
## Key Decisions Captured
1. ENG.0 freezes review-first and reuse-first implementation as mandatory, not optional advice.
2. Feature ownership, service boundaries, Route Handler patterns, and projection rules now follow one documented model aligned with current repo structure.
3. Business logic remains service-owned; projections remain read-only; route handlers remain thin.
4. Security enforcement is frozen around resolved CRM access, server-side scope enforcement, and pricing visibility checks.
5. UI implementation is frozen to existing dashboard shell, `PageContainer`, shadcn/ui reuse, and no-code-before-design for major new workspace surfaces.
6. Database, testing, delivery, AI implementation, and code review expectations are now documented in one standards file future tasks can cite directly.
7. Preserve -> Extend -> Controlled Refactor -> Replace is frozen as the engineering decision matrix for change strategy.
## Verification
- Documentation-only change.
- No schema, migration, API, service, UI, permission, or runtime business-logic files changed production behavior.
- No build or test command was required for this phase.
## Outcome
ENG.0 now gives ALLA OS an explicit Engineering Constitution aligned with:
- Business Constitution (`BU-R.0`, `BU-R.0.1`, `BU-R.1`)
- Architecture Constitution (`AR.1`, `AR.2`)
- Workspace UI Constitution
- current foundations and production code patterns already present in the repository

View File

@@ -0,0 +1,70 @@
# Task EP.1.1 Activity Domain Foundation - 2026-07-10
## Scope
- introduce the first additive `crm_activities` write model
- add activity API, service, lightweight repository, and read-model shaping
- add foundational UI building blocks for sheet, form, detail, and badges
- preserve legacy lead, opportunity, and quotation follow-up implementations
## Review Summary
Reviewed before implementation:
- `AGENTS.md`
- `plans/task-ep1.1.md`
- `docs/standards/task-contract-template.md`
- `docs/standards/task-catalog.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/task-review-checklist.md`
- `docs/security/crm-authorization-boundaries.md`
- `docs/business/relationship-sales-workspace-blueprint-v1.md`
- `docs/implementation/task-bu-r.0.1-workspace-activity-business-blueprint-2026-07-07.md`
- `docs/implementation/task-ar.1-architecture-transition-plan-2026-07-07.md`
- `docs/implementation/task-ar.2-epic-technical-design-2026-07-07.md`
- `docs/implementation/task-ar.2-workspace-ui-ux-design-note-2026-07-07.md`
- `plans/task-eng.0.md`
- existing follow-up services and route handlers under `src/features/crm/leads/**`, `src/features/crm/opportunities/**`, and `src/features/crm/quotations/**`
- existing CRM security, audit, and customer detail foundations
## Foundations Reused
- `src/lib/auth/session.ts`
- `src/features/crm/security/server/service.ts`
- `src/features/foundation/audit-log/service.ts`
- `src/features/foundation/display/server/display-resolver.ts`
- existing customer, lead, opportunity, and quotation detail services for primary-record access validation
## Implementation Notes
- `crm_activities` is additive and does not replace existing follow-up tables or audit-backed lead follow-up behavior.
- activity visibility reuses resolved CRM scope plus owner/assignee visibility and an internal-only rule for manager/admin style access.
- pricing-sensitive activity content is redacted when tied to quotation or PO context without quotation pricing visibility.
- follow-up adapters are documented as candidate contracts only in this phase. No data migration or dual-write was introduced.
## Follow-up Gap Analysis
1. Lead follow-up is still audit-log-backed rather than row-backed, so migration needs an adapter that can translate immutable audit entries into activity candidates without rewriting history.
2. Opportunity follow-up is row-backed and is the cleanest EP.1.2 consolidation seam.
3. Quotation follow-up is row-backed and can follow the same adapter path as opportunity follow-up, but pricing visibility rules must stay active when notes expose commercial values.
4. Dashboard and report consumers still read legacy follow-up data today, so EP.1.2 needs continuity checks before any shared activity projection becomes source input.
## Migration Preparation Report
- prepared:
- shared activity write model
- route-handler boundary
- service-owned lifecycle validation
- repository/read-model split
- projection contract interfaces
- basic activity UI foundation
- deferred intentionally:
- timeline projection
- calendar projection
- notification fan-out
- legacy follow-up storage migration
- dashboard/report dataset swap
## Verification Plan
- run `npm run typecheck`
- run `npm run db:generate`
- run targeted manual API smoke checks for create, update, assign, complete, cancel, reschedule, delete
## Residual Risks
- current activity creation UI uses manual primary record id entry because source-record pickers are intentionally deferred to later workspace integration work
- contact primary-record validation remains lightweight and should be revisited when customer workspace activity embedding begins

View File

@@ -0,0 +1,151 @@
# Task EP.1.2 Activity Integration & Legacy Follow-up Adapter - 2026-07-13
## Scope
- introduced adapter-based Activity timeline integration for legacy lead, opportunity, and quotation follow-ups
- added additive `recentActivities` detail API data for customer, lead, opportunity, and quotation workspaces
- kept existing follow-up APIs, dashboard consumers, report datasets, and audit history unchanged
- added Activity numbering through the Document Sequence Foundation using `documentType = activity`
- added Activity attachment foundation service on top of Storage and Document Artifact foundations
- documented migration readiness without executing migration, dual-write, projections, calendar, timeline, notifications, or dashboard swaps
## Review Summary
Reviewed before and during implementation:
- `AGENTS.md`
- `plans/task-ep.1.2.md`
- `docs/standards/task-catalog.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/task-review-checklist.md`
- `docs/security/crm-authorization-boundaries.md`
- `docs/implementation/task-ep1.1-activity-domain-foundation-2026-07-10.md`
- `docs/implementation/task-ar.1-architecture-transition-plan-2026-07-07.md`
- `docs/implementation/task-ar.2-epic-technical-design-2026-07-07.md`
- `docs/implementation/task-ar.2-workspace-ui-ux-design-note-2026-07-07.md`
- existing lead, opportunity, quotation follow-up services and APIs
- existing dashboard and report follow-up consumers
- existing audit, storage, document artifact, and document sequence foundations
## Implementation Summary
### Legacy Follow-up Adapters
Created Activity-compatible timeline adapters:
- `src/features/crm/activities/server/adapters/lead-followup.adapter.ts`
- `src/features/crm/activities/server/adapters/opportunity-followup.adapter.ts`
- `src/features/crm/activities/server/adapters/quotation-followup.adapter.ts`
The adapters only read and translate legacy records. They do not migrate data, dual-write, or remove existing follow-up behavior.
### Compatibility Layer
Added `src/features/crm/activities/server/timeline-service.ts` as the single Activity compatibility read layer for detail-page recent activity views.
Current composition:
- new `crm_activities` rows queried by context columns such as `customerId`, `leadId`, `opportunityId`, and `quotationId`
- legacy lead follow-ups adapted from audit-log-backed records
- legacy opportunity follow-ups adapted from row-backed records
- legacy quotation follow-ups adapted from row-backed records
Dashboard and report datasets remain on their existing legacy sources for EP.1.2.
### Activity Context Integration
Integrated recent activity reads into:
- customer detail API and UI
- lead detail API and UI
- opportunity detail API and UI
- quotation detail API and UI
The UI uses `RecentActivitiesPanel` and keeps existing follow-up tabs and audit log tabs in place.
### Activity Type Governance
Expanded the official fallback Activity catalog to cover the EP.1.2 minimum types and kept backward-compatible aliases.
Reference data now prefers `crm_activity_type` master options when configured, with static catalog fallback for environments that have not seeded the new category yet.
### Activity Numbering Strategy
Activity creation now calls `generateNextDocumentCode()` from the Document Sequence Foundation using:
- `documentType = activity`
- branch scope from the resolved Activity context
- product type scope from the resolved Activity context
- default prefix `ACT`
- period-based sequence from the existing foundation
The generated code is stored in `crm_activities.metadata.activityCode` to avoid a schema migration in EP.1.2.
### Activity Attachment Foundation
Added `src/features/crm/activities/server/attachment-service.ts`.
The service supports Activity attachment metadata and binary storage through:
- Storage Foundation object writes
- Document Artifact Foundation metadata records
- `entityType = crm_activity`
- `artifactType = attachment`
Supported attachment kinds are `image`, `pdf`, `office_document`, `voice_recording`, `drawing`, and `other`.
No separate storage engine or attachment table was introduced.
## Adapter Validation
| Source | Legacy Storage | Adapter Output | Validation Status |
| --- | --- | --- | --- |
| Lead follow-up | audit log payload | `ActivityTimelineItem` | implemented |
| Opportunity follow-up | `crm_opportunity_followups` | `ActivityTimelineItem` | implemented |
| Quotation follow-up | `crm_quotation_followups` | `ActivityTimelineItem` | implemented |
| New Activity | `crm_activities` | `ActivityTimelineItem` | implemented |
## Legacy Usage Inventory
Existing consumers intentionally remain unchanged:
- lead follow-up API and panel
- opportunity follow-up API and tab
- quotation follow-up API and tab
- CRM dashboard service follow-up queries
- pipeline/report datasets reading legacy follow-up state
- audit log views for customer, lead, opportunity, and quotation details
## Projection Readiness
EP.1.3 can consume the following stable contracts:
- `ActivityTimelineItem`
- legacy follow-up adapters
- `listRecentActivitiesForContext()`
- Activity numbering metadata under `metadata.activityCode`
- Activity attachment artifacts under `entityType = crm_activity`
Projection consumers should still wait for EP.1.3 event and projection contracts before replacing dashboard, report, calendar, or timeline behavior.
## Migration Checklist
- Keep legacy follow-up writes active until adapter parity is validated with production-like data.
- Backfill or project legacy follow-ups only after a separate migration task approves the source-of-truth switch.
- Decide whether `metadata.activityCode` should become a first-class column in a future schema task.
- Add upload/download route handlers for Activity attachments when the UI workflow is scheduled.
- Add seeded `crm_activity_type` master options before disabling fallback catalog behavior.
- Run dataset regression checks before dashboard/report consumers switch from legacy follow-ups to Activity projections.
## Verification
- `npm run typecheck`
- targeted `npx oxlint` for Activity adapter, timeline, route, and detail UI files
## Residual Risks
- Activity attachment upload/download routes are intentionally deferred; the foundation service is ready for those routes.
- Activity numbering is metadata-backed in EP.1.2 to honor the no-migration constraint.
- Detail pages show recent activity read integration, but create-activity shortcuts from each detail page are still deferred to the next UI workflow task.

View File

@@ -0,0 +1,211 @@
# Task EP.1.3 Business Event Foundation - 2026-07-13
## Scope
- introduced canonical Business Event contract
- added machine-readable registry, alias resolution, ownership metadata, naming rules, and payload validation
- added transport-independent publisher abstraction and in-process dispatcher
- added idempotent subscriber handling and unsupported-version detection
- published initial Activity lifecycle business events from Activity service layer
- documented projection consumer contracts, event sequence matrix, replay readiness, compatibility mapping, and versioning strategy
- preserved existing notification, approval, follow-up, dashboard, report, and audit behavior
## Review Summary
Reviewed before and during implementation:
- `AGENTS.md`
- `plans/task-ep.1.3.md`
- `docs/standards/task-catalog.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/task-review-checklist.md`
- `docs/security/crm-authorization-boundaries.md`
- `docs/implementation/task-ep1.1-activity-domain-foundation-2026-07-10.md`
- `docs/implementation/task-ep1.2-activity-integration-legacy-followup-adapter-2026-07-13.md`
- existing Activity lifecycle service operations
- existing notification event service
- existing approval notification publication behavior
- existing audit-log foundation
## Implementation Summary
### Canonical Contract
Added `BusinessEvent<TPayload>` in `src/features/foundation/business-events/types.ts`.
The envelope includes:
- globally unique `eventId`
- registered `eventType`
- explicit `schemaVersion`
- organization, branch, entity, primary record, and related records
- actor, `occurredAt`, `correlationId`, and `causationId`
- visibility metadata for product type, pricing sensitivity, and internal-only events
- payload and metadata
### Registry And Naming
Added `src/features/foundation/business-events/registry.ts`.
The registry freezes canonical names and ownership for Activity, Lead, Opportunity, Quotation, Approval, Customer, and PO families. Naming follows lowercase `<entity>.<past-tense-action>` where practical, with compatibility aliases for existing approval notification names such as `approval.step.approved`.
Unknown event types are rejected by publisher validation.
### Publisher And Dispatcher
Added:
- `src/features/foundation/business-events/publisher.ts`
- `src/features/foundation/business-events/dispatcher.ts`
The initial dispatcher is in-process and transport-independent. It supports independent subscriber registration, no-subscriber dispatch, duplicate detection by `consumerName:eventId`, subscriber isolation, unsupported-version reporting, and publish-many ordering.
No Kafka, RabbitMQ, Redis Streams, persistent event store, replay table, or projection table was introduced.
### Initial Activity Event Publishing
Activity lifecycle events now publish from `src/features/crm/activities/server/service.ts`, after successful service-layer mutations:
- `activity.created`
- `activity.assigned`
- `activity.reassigned`
- `activity.started`
- `activity.rescheduled`
- `activity.completed`
- `activity.cancelled`
- `activity.deleted`
Route handlers do not publish events directly.
Activity payloads redact narrative fields when an Activity is pricing-sensitive or internal-only. Visibility metadata remains available so future consumers can enforce CRM authorization before re-querying source details.
Legacy follow-up adapters do not publish events in EP.1.3.
### Event Sequence Matrix
Added `BUSINESS_EVENT_SEQUENCE_MATRIX` in `src/features/foundation/business-events/matrices.ts`.
It freezes expected order for key operations:
- Create Activity
- Assign Existing Activity
- Start Activity
- Reschedule Activity
- Complete Activity
- Cancel Activity
- Convert Lead
- Submit Approval
- Receive PO
### Event Consumer Matrix
Added `BUSINESS_EVENT_CONSUMER_MATRIX` in `src/features/foundation/business-events/matrices.ts`.
Consumers are explicitly non-owning:
- Timeline
- Calendar
- Dashboard
- Notification
- My Day
- Relationship Health
- Forecast
All listed projection consumers are forbidden from mutating source-domain lifecycle state.
### Replay Contracts
Added `src/features/foundation/business-events/replay.ts`.
Replay is contract-only in EP.1.3:
- request must be organization-scoped
- dry-run defaults to true
- ordering is `occurred_at_then_event_id`
- idempotency is required
- no replay route or persistent event store was added
### Existing Event Compatibility
Added `src/features/foundation/business-events/compatibility.ts`.
Existing approval notification event behavior remains untouched. Compatibility aliases map current names to canonical business event names where needed:
- `approval.step.approved` -> `approval.step-approved`
- `approval.step.rejected` -> `approval.step-rejected`
## Versioning Strategy
- Every event includes `schemaVersion`.
- Additive optional payload fields can remain on the same event type and schema version when existing consumers are unaffected.
- Incompatible semantic changes require a new schema version.
- Consumers can declare supported versions per event type.
- Unsupported versions are reported by the dispatcher and do not silently process.
- Deprecated event definitions must specify `replacementEventType`.
## Ordering And Idempotency Rules
- Global ordering across all domains is not guaranteed.
- Related events in one operation share `correlationId`.
- Child events use `causationId` when one event directly triggers another.
- Consumers must not rely on undocumented timing.
- Dispatcher duplicate handling is in-memory for EP.1.3 tests and contracts only.
- Persistent checkpointing remains a future projection-foundation concern.
## Observability Hooks
The dispatch result captures:
- event id and type
- dispatch status
- dispatch duration
- subscriber name
- subscriber status
- subscriber duration
- subscriber error message
- duplicate and unsupported-version outcomes
Structured persistence of these observations is intentionally deferred until persistent event/projection infrastructure is approved.
## Projection Readiness
Ready for EP.1.4 to define projection storage and consumers using:
- `BusinessEvent`
- `BusinessEventConsumer`
- `BusinessEventPublisher`
- `BusinessEventDispatcher`
- event registry definitions
- sequence matrix
- consumer matrix
- replay contract
Not implemented in EP.1.3:
- Timeline projection
- Calendar projection
- Dashboard projection replacement
- Notification expansion
- My Day
- Manager Workspace
- Executive Workspace
- Relationship Health
- Forecast projection
- Automation engine
- external broker
- persistent replay engine
- projection storage tables
## Verification
- `npm run typecheck`
- `node --disable-warning=ExperimentalWarning --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --experimental-strip-types --test src/features/foundation/business-events/*.test.ts src/features/crm/activities/server/business-events.test.ts`
## Residual Risks
- Business events are currently in-process only; no cross-process delivery guarantee exists.
- No persistent event store exists, so replay remains a contract and testing surface only.
- Subscriber failures will fail publisher dispatch; no production subscriber is registered in EP.1.3.
- Existing approval notifications still use the current notification event service and are mapped for compatibility rather than migrated.

View File

@@ -0,0 +1,154 @@
# Task EP.1.4 Projection Foundation & Delivery Reliability - 2026-07-13
## Scope
- introduced persistent Business Event outbox schema
- introduced persistent projection consumer checkpoint, dead-letter, rebuild-run, and health snapshot schema
- added shared projection runtime contracts and processing runtime
- added outbox publisher contract that persists events without running projection consumers synchronously
- added governed retry policy and sanitized failure model
- added machine-readable Projection Registry for Timeline, Calendar, Dashboard, Notification, My Day, Manager Workspace, Executive Workspace, Relationship Health, and Forecast
- added no-op skeleton consumers for Timeline, Calendar, Dashboard, and Notification
- added hybrid rebuild contract and projection delivery, failure, and security matrices
- preserved current Dashboard, Report, Notification, Approval, Follow-up, Activity, and recent-activity behavior
## Review Summary
Reviewed before and during implementation:
- `AGENTS.md`
- `plans/task-ep.1.4.md`
- `docs/standards/engineering-constitution.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/task-review-checklist.md`
- `docs/standards/task-catalog.md`
- `docs/security/crm-authorization-boundaries.md`
- `docs/business/relationship-sales-workspace-blueprint-v1.md`
- `docs/adr/0014-crm-multi-role-user-assignment.md`
- `docs/adr/0017-report-foundation.md`
- `docs/implementation/task-ep1.2-activity-integration-legacy-followup-adapter-2026-07-13.md`
- `docs/implementation/task-ep1.3-business-event-foundation-2026-07-13.md`
- existing `src/features/foundation/business-events/**`
- existing Activity business event adapter
- existing notification event and delivery schema
## Delivery Semantics Decision
Selected strategy: Transactional Outbox foundation.
Source-domain services should persist the source mutation and Business Event outbox record in the same database transaction. Projection consumers process only committed outbox events. Projection failures are recorded in checkpoints, retries, and dead letters; they must not cause a successful source mutation to be reported as failed.
Event persistence failure inside the source transaction remains mutation-failing and rollback-worthy. Consumer failure after commit is projection technical state, not source-domain lifecycle state.
## Implementation Summary
### Persistent Delivery Model
Added Drizzle schema and migration for:
- `business_event_outbox`
- `projection_consumer_checkpoints`
- `projection_dead_letters`
- `projection_rebuild_runs`
- `projection_health_snapshots`
Generated migration:
- `drizzle/0003_green_squadron_supreme.sql`
### Projection Runtime
Added `src/features/foundation/projections/**`.
Runtime behavior:
- persists Business Events as delivery records
- supports outbox publisher source path where publish means durable enqueue, not synchronous projection execution
- resolves registered consumers by event type
- validates supported schema versions
- records persistent checkpoint state by `consumerName + eventId`
- skips duplicate completed checkpoints
- isolates consumer failures
- schedules retry for retryable failures
- creates dead letters for non-retryable or max-attempt failures
- records projection health snapshots without storing unrestricted business payloads
### Retry Policy
Frozen default policy:
- attempt 1: immediate
- attempt 2: +1 minute
- attempt 3: +5 minutes
- attempt 4: +30 minutes
- attempt 5: dead letter
Failures are sanitized to `code`, bounded `message`, and `retryable`.
### Projection Registry
Machine-readable registry added for:
- Timeline
- Calendar
- Dashboard
- Notification
- My Day
- Manager Workspace
- Executive Workspace
- Relationship Health
- Forecast
Timeline, Calendar, Dashboard, and Notification have no-op skeleton consumers for runtime validation only. No final business-facing projection tables or UI were introduced.
### Rebuild Contract
Added `ProjectionRebuildRequest` and plan creation helper.
Frozen hybrid strategy:
- Initial build: governed source queries plus legacy adapters
- Incremental updates: Business Events through projection runtime checkpoints
- Recovery: source rebuild plus event checkpoint reconciliation
- Reset: explicit `resetExisting` only and organization-scoped
### Matrices
Added code-owned matrices:
- `PROJECTION_DELIVERY_MATRIX`
- `PROJECTION_FAILURE_MATRIX`
- `PROJECTION_SECURITY_MATRIX`
These freeze delivery reliability, failure behavior, and security expectations for future projection tasks.
## Compatibility Notes
- Existing in-process Business Event publisher remains available.
- Existing notification inbox and approval notification behavior remain unchanged.
- Existing Dashboard and Report datasets remain unchanged.
- Existing follow-up APIs and Activity APIs remain unchanged.
- No final Timeline, Calendar, My Day, Manager Workspace, Executive Workspace, Relationship Health, Forecast, or dashboard replacement was introduced.
## Security Notes
- Projection persistence is organization-scoped.
- Dead-letter records store sanitized failure metadata only.
- Projection consumers must re-query source services or security-aware modules when full detail is needed.
- Internal-only and pricing-sensitive payload handling remains governed by source event payload minimization plus future projection-specific security enforcement.
- Manager, Executive, Forecast, Dashboard, and Relationship Health projections must preserve resolved CRM access, branch/product scope, ownership, and pricing visibility boundaries.
## Verification
- `npm run typecheck`
- `node --disable-warning=ExperimentalWarning --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --experimental-strip-types --test src/features/foundation/projections/*.test.ts`
- `npm run db:generate`
- `npx oxlint src/features/foundation/projections src/db/schema.ts`
## Residual Risks / Follow-up
- Source services are not fully refactored to call the outbox publisher inside source transactions in this slice. Future source-domain cutovers must use the outbox transaction pattern before relying on production replay guarantees.
- Drizzle store is foundation-ready, but no polling worker or admin retry route is exposed yet.
- Projection health snapshots are written as append-only snapshots in this slice; operational dashboards can later compact or upsert by consumer.
- EP.1.5 Timeline Projection Foundation can now focus on timeline read-model rules rather than re-solving delivery reliability.

View File

@@ -0,0 +1,172 @@
# Task EP.1.4.1 Transactional Outbox Activation & Worker Runtime - 2026-07-13
## Scope
- activated Activity Business Event publication through transaction-aware outbox enqueue
- added lease-based outbox claiming fields to `business_event_outbox`
- added projection outbox worker runtime
- added manual projection operations service contracts
- added PostgreSQL `FOR UPDATE SKIP LOCKED` claim strategy in Drizzle store
- preserved existing in-process Business Event publisher for compatibility and tests
- preserved current Activity API response contracts, follow-up APIs, dashboard/report datasets, notifications, approvals, and recent-activity behavior
## Review Summary
Reviewed before and during implementation:
- `AGENTS.md`
- `plans/task-ep.1.4.1.md`
- `docs/implementation/task-ep1.3-business-event-foundation-2026-07-13.md`
- `docs/implementation/task-ep1.4-projection-foundation-delivery-reliability-2026-07-13.md`
- `docs/standards/engineering-constitution.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/task-review-checklist.md`
- `docs/security/crm-authorization-boundaries.md`
- existing Activity service mutation paths
- existing Business Event publisher and projection runtime
## Implementation Summary
### Activity Transactional Outbox Adoption
Activity mutation paths now enqueue Business Events to the outbox inside the same `db.transaction()` as the Activity row mutation:
- create
- update
- assign/reassign
- reschedule
- complete
- cancel
- delete
The Activity API still returns hydrated Activity records after commit. Projection consumers are not run synchronously from Activity source mutations.
### Transaction-Aware Publisher
Added:
- `src/features/foundation/projections/server/transactional-publisher.ts`
The publisher validates registry and payload contracts, then inserts into `business_event_outbox` using the supplied transaction/client. This prevents accidental double-publication through the in-process dispatcher.
### Worker Runtime
Added:
- `src/features/foundation/projections/worker.ts`
Worker behavior:
- claims available events in batches
- applies a lease with `claimedBy`, `claimedAt`, and `claimExpiresAt`
- dispatches claimed committed events through `ProjectionConsumerRuntime`
- does not auto-start unless `PROJECTION_WORKER_ENABLED=true`
- supports graceful stop semantics for the in-process loop
Default operational config:
- polling interval: `3000ms`
- batch size: `25`
- max concurrent events: `1`
- max concurrent consumers: `4`
- claim lease: `60000ms`
- stale claim: `90000ms`
- graceful shutdown: `30000ms`
### Multi-Instance Claiming
Added schema fields:
- `business_event_outbox.claimed_by`
- `business_event_outbox.claimed_at`
- `business_event_outbox.claim_expires_at`
Generated migration:
- `drizzle/0004_sharp_mercury.sql`
Database store uses PostgreSQL-safe claiming with `FOR UPDATE SKIP LOCKED`. Expired `claimed` or `processing` leases are recoverable.
### Manual Operations Contracts
Added:
- `src/features/foundation/projections/operations.ts`
Contracts:
- `drainOutbox`
- `retryEvent`
- `retryConsumer`
- `retryDeadLetter`
- `resolveDeadLetter`
Manual operations require `systemRole: "super_admin"` at the service-contract boundary. No public UI/API route was added in this task.
### Delivery State Machine
Outbox lifecycle:
```text
pending -> claimed -> processing -> completed
pending -> claimed -> processing -> retry_scheduled
pending -> claimed -> processing -> dead_letter
claimed / processing -> lease expired -> claimable again
```
Checkpoint lifecycle remains:
```text
pending -> processing -> completed
pending -> processing -> retry_scheduled
pending -> processing -> skipped_unsupported_version
pending -> processing -> dead_letter
```
Completed checkpoints are not rerun when another consumer fails.
### Worker Startup Strategy
Current strategy:
- no automatic worker startup in Next.js request/build/test paths
- dedicated process/service should instantiate `ProjectionOutboxWorker` and call `start()`
- scheduled CLI drain can call `drainOnce()` for UAT or operational repair
This avoids duplicate unmanaged loops during hot reload, build, migrations, tests, or serverless request execution.
### SLA Baseline
Initial non-binding targets:
- event available after commit: immediate
- polling interval: 1-5 seconds, default 3 seconds
- normal Activity projection delivery: under 10 seconds
- retry scheduling accuracy: within one polling interval
- stale claim recovery: lease duration plus one polling interval
- worker heartbeat/health integration: future operational surface
## Compatibility Notes
- Activity events are not emitted through both outbox and in-process publisher in production service paths.
- Existing in-process publisher remains available for tests and explicitly approved compatibility paths.
- Existing Dashboard, Report, Notification, Approval, Follow-up, and recent-activity behavior remains unchanged.
- No final Timeline, Calendar, Notification expansion, My Day, Manager Workspace, Executive Workspace, Forecast, or Relationship Health feature was implemented.
## Verification
- `node --disable-warning=ExperimentalWarning --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --experimental-strip-types --test src/features/foundation/business-events/*.test.ts src/features/foundation/projections/*.test.ts src/features/crm/activities/server/business-events.test.ts`
- `npx oxlint src/features/foundation/projections src/features/crm/activities/server/business-events.ts src/features/crm/activities/server/repository.ts src/features/crm/activities/server/service.ts src/db/schema.ts`
- `npm run db:generate`
## Typecheck Note
`npm run typecheck` is currently blocked by generated `.next/dev/types/routes.d.ts` syntax errors unrelated to the EP.1.4.1 source changes. Targeted runtime tests and scoped lint passed for the changed files.
## Residual Risks / Follow-up
- Worker process entrypoint/CLI command is not yet wired into `package.json`; runtime modules are ready for a dedicated process or scheduled drain task.
- Manual retry/dead-letter APIs are intentionally not exposed publicly yet.
- Health snapshots are recorded by runtime; a user-facing operational health page remains future work.
- Future source domains must adopt the same transaction-aware publisher through dedicated cutover tasks.

View File

@@ -0,0 +1,155 @@
# Task EP.1.5 Timeline Projection Foundation - 2026-07-13
## Scope
- introduced first production CRM Business Projection: Timeline
- added `crm_timeline_projection` materialized read model schema
- added Timeline event builder and Activity event mapping
- added Timeline projection consumer contract and active CRM registration
- added Timeline list/query storage service
- added Timeline rebuild and legacy adapter contracts
- added reusable Timeline UI components
- added optional Activity detail Timeline integration seam
- preserved Activity, Audit Log, Approval History, follow-up APIs, Dashboard, Reports, Notifications, and current detail-page behavior
## Review Summary
Reviewed before and during implementation:
- `AGENTS.md`
- `plans/task-ep.1.5.md`
- `LAYOUT.md`
- `docs/standards/engineering-constitution.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/ui-ux-rules.md`
- `docs/business/relationship-sales-workspace-blueprint-v1.md`
- `docs/security/crm-authorization-boundaries.md`
- `docs/implementation/task-ep1.4-projection-foundation-delivery-reliability-2026-07-13.md`
- `docs/implementation/task-ep1.4.1-transactional-outbox-activation-worker-runtime-2026-07-13.md`
- existing Activity business event adapter
- existing Projection Runtime and Worker
- existing CRM detail/audit UI patterns
## Implementation Summary
### Timeline Schema
Added table:
- `crm_timeline_projection`
Generated migration:
- `drizzle/0005_freezing_wendigo.sql`
The table stores organization-scoped, rebuildable read-model rows with entity references, event provenance, visibility metadata, display fields, and projection version. It has indexes for event id/version idempotency, organization chronology, customer chronology, and activity/event lookup.
### Timeline Builder
Added:
- `src/features/crm/timeline/server/builder.ts`
Builder maps Business Events into business-readable Timeline items. Initial active mappings cover Activity lifecycle events:
- `activity.created`
- `activity.assigned`
- `activity.reassigned`
- `activity.started`
- `activity.rescheduled`
- `activity.completed`
- `activity.cancelled`
- `activity.deleted`
Generic mapping is available for registered future CRM events until their business copy is specialized.
### Timeline Consumer
Added:
- `src/features/crm/timeline/server/consumer.ts`
- `src/features/crm/timeline/server/registration.ts`
The consumer builds a Timeline item and persists it with idempotent `eventId + sourceProjectionVersion` storage. Foundation default consumers remain safe skeletons; CRM worker startup can opt into `CRM_TIMELINE_PROJECTION_CONSUMERS` for the active Timeline projection set.
### Timeline Storage And Query Contract
Added:
- `src/features/crm/timeline/api/types.ts`
- `src/features/crm/timeline/server/service.ts`
Supported filters include organization, customer, lead, opportunity, quotation, activity, event, category, actor, search, date range, and cursor/limit contract. Timeline remains read-only.
### Legacy Adapter And Rebuild Contracts
Added:
- `src/features/crm/timeline/server/legacy-adapters.ts`
- `src/features/crm/timeline/server/rebuild.ts`
Legacy adapters are explicitly read-only rebuild inputs for lead follow-up, opportunity follow-up, quotation follow-up, activity, approval, and audit history. No data migration was introduced.
### Timeline UI
Added:
- `src/features/crm/timeline/components/timeline-list.tsx`
Components:
- `TimelineList`
- `TimelineCard`
- `TimelineEmpty`
- `TimelineSkeleton`
The UI follows the existing dashboard card language, uses deterministic shared date formatting, shadcn primitives, and app icon registry. It preserves a calm enterprise density while adding a clear vertical chronology.
### Workspace Integration
Added an optional `timelineItems` prop to Activity detail. Existing callers are unaffected; when supplied, Activity detail renders an additive Timeline card below the existing Activity detail card.
## Security Notes
- Timeline rows are organization-scoped.
- Timeline visibility stores product type, pricing sensitivity, and internal-only flags.
- Pricing-sensitive/internal-only event payloads continue to rely on source event minimization.
- Timeline query service is storage-ready; route/API exposure must still apply CRM resolved access, branch/product scope, ownership, and pricing visibility before becoming public.
- Timeline never mutates source domains and does not replace Audit Log.
## Delivery Matrix
Initial production path:
| Business Event | Timeline Item | Workspace | Consumer | Projection |
| --- | --- | --- | --- | --- |
| `activity.created` | Activity created | Customer / Lead / Opportunity / Quotation / Activity | `timeline.projection.consumer` | `crm_timeline_projection` |
| `activity.completed` | Activity completed | Customer / Lead / Opportunity / Quotation / Activity | `timeline.projection.consumer` | `crm_timeline_projection` |
| `activity.cancelled` | Activity cancelled | Customer / Lead / Opportunity / Quotation / Activity | `timeline.projection.consumer` | `crm_timeline_projection` |
Additional Activity events are supported in code and documented in the builder mapping.
## UX Review
- Layout: follows `LAYOUT.md` detail-card and timeline entry patterns.
- Density: Timeline cards stay compact, scannable, and fit existing CRM dashboard surfaces.
- Accessibility: items use semantic `article`, `time`, non-color badges, and text labels.
- Loading/empty states: `TimelineSkeleton` and `TimelineEmpty` included.
- Dark mode: tone classes include dark-mode variants.
- Date rendering: uses `src/lib/date-format.ts`.
## Verification
- `npm run typecheck`
- `node --disable-warning=ExperimentalWarning --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --experimental-strip-types --test src/features/foundation/business-events/*.test.ts src/features/foundation/projections/*.test.ts src/features/crm/activities/server/business-events.test.ts src/features/crm/timeline/server/*.test.ts`
- `npx oxlint src/features/crm/timeline src/features/foundation/projections/registry.ts src/features/foundation/projections/consumers.ts src/features/foundation/projections/server/store.ts src/features/crm/activities/components/activity-detail.tsx src/db/schema.ts`
- `npm run db:generate`
## Residual Risks / Follow-up
- Public Timeline route handlers and server-side CRM resolved-access filtering remain future work before exposing Timeline broadly.
- Customer, Lead, Opportunity, and Quotation detail pages are not yet wired to fetch Timeline data; Activity detail has a backward-compatible render seam.
- Approval, quotation, opportunity, customer, and PO event mappings have generic fallback support but need specialized business copy when those domains publish durable events.
- Legacy adapters are contract-only in this slice; source-query rebuild implementation should be expanded as each workspace adopts Timeline.
- EP.1.6 Calendar can reuse the same projection runtime and Activity event delivery path.

View File

@@ -0,0 +1,153 @@
# Task EP.1.6 Calendar Projection, Workspace & Big Calendar Foundation - 2026-07-13
## Scope Delivered
- introduced `crm_calendar_projection` materialized read model schema and migration `drizzle/0006_massive_scalphunter.sql`
- added Calendar projection contracts under `src/features/crm/calendar/**`
- added Activity business-event mapping for scheduled Activity calendar rows
- added terminal Activity event handling that removes completed/cancelled/deleted Activity rows from Calendar projection
- added Calendar projection consumer, registration seam, rebuild plan contract, query service, API route, React Query service, and dashboard workspace route
- added `CalendarProjectionItem -> BigCalendarEvent` adapter so UI never consumes database rows directly
- added Calendar Workspace UI at `/dashboard/crm/calendar` with Agenda, Day, Week, Month tabs, summary cards, search, empty/loading states, and read-only/editable visual distinction
- added Calendar navigation item under CRM
- preserved Activity, Timeline, Dashboard, Reports, Notifications, Approval, Follow-up APIs, and source-domain ownership
## Review Summary
Reviewed before/during implementation:
- `AGENTS.md`
- `plans/task-ep.1.6.md`
- `docs/standards/engineering-constitution.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/task-review-checklist.md`
- `docs/security/crm-authorization-boundaries.md`
- `docs/business/relationship-sales-workspace-blueprint-v1.md`
- `docs/implementation/task-ar.2-epic-technical-design-2026-07-07.md`
- `docs/implementation/task-ar.2-workspace-ui-ux-design-note-2026-07-07.md`
- `docs/implementation/task-bu-r.0.1-workspace-activity-business-blueprint-2026-07-07.md`
- `docs/implementation/task-ep1.4-projection-foundation-delivery-reliability-2026-07-13.md`
- `docs/implementation/task-ep1.4.1-transactional-outbox-activation-worker-runtime-2026-07-13.md`
- `docs/implementation/task-ep1.5-timeline-projection-foundation-2026-07-13.md`
- existing Projection Runtime, Activity Business Events, Timeline Projection, CRM security, and date formatting utilities
## Big Calendar Source Audit
Upstream: `https://github.com/lramos33/big-calendar`
Findings:
- Compatibility: upstream is a Next.js, TypeScript, Tailwind, shadcn-oriented calendar app, but targets Next.js 14 and Tailwind v3 while this repo is Next.js 16 and Tailwind v4.
- React/Next: source is useful as UI composition reference, but the upstream app shell, provider model, demo data, and direct event mutation flow are not compatible with ALLA OS projection governance.
- shadcn/Tailwind: visual approach is compatible, but code must be adapted to existing dashboard shell and token usage.
- Accessibility/dark mode/responsive: upstream explicitly supports responsive and dark-mode calendar behavior; ALLA OS implementation keeps shadcn primitives, semantic cards, badges, buttons, and deterministic date rendering.
- Timezone: upstream uses general date-fns/date string assumptions; ALLA OS calendar rendering uses `src/lib/date-format.ts` and stores projection timezone as `Asia/Bangkok`.
- Drag/drop: upstream supports direct drag/drop rescheduling. ALLA OS does not persist Calendar mutations directly; drag/resize must call Activity APIs, publish Business Events, and refresh the projection.
- License: upstream repository is MIT licensed.
Adoption decision:
- Adopted conceptually: Agenda/Day/Week/Month views, toolbar/search pattern, event rendering, editable vs read-only event behavior, user/lens filter seam, working-hours/timezone constraints.
- Rejected: upstream app shell, backend assumptions, mock event store, direct event CRUD, global Calendar context, uncontrolled user lists, direct drag/drop persistence.
- Modification strategy: implement native ALLA OS components under `src/features/crm/calendar/`, keep projection adapter as boundary, and wire future drag/resize only through Activity services.
## Implementation Summary
### Calendar Schema
Added `crm_calendar_projection` with organization, branch, product, source refs, activity/customer/lead/opportunity/quotation/approval refs, owner/assignee, event provenance, title/summary/location, start/end/timezone, priority/category, editable/milestone/overdue/hot-project/all-day flags, visibility metadata, source event, projection version, timestamps.
Indexes cover organization/date range, owner/assignee/date range, branch/date range, category/date range, customer/date range, activity/event lookup, and event/version idempotency.
### Calendar Builder and Consumer
Activity events supported:
- `activity.created`
- `activity.assigned`
- `activity.reassigned`
- `activity.started`
- `activity.rescheduled`
- `activity.completed`
- `activity.cancelled`
- `activity.deleted`
Non-terminal scheduled Activity events upsert one current Calendar row per Activity. Terminal events delete the Activity calendar row so Calendar remains a forward-looking operational workspace, not a history timeline.
### Query and Security
Calendar query service supports:
- date range
- user/team user IDs
- branch/product
- customer/lead/opportunity/quotation
- activity type/category/priority
- overdue/milestone/hot-project flags
- search
- Personal/Team/Manager/Executive lens contract
The service applies bounded SQL first, then server-side CRM scope checks through `CrmSecurityContext` and `canAccessScopedCrmRecord()`. Owner/assignee direct visibility is preserved. Internal-only rows remain restricted unless directly assigned or organization-wide scope applies.
### Workspace
Added `/dashboard/crm/calendar` using `PageContainer`, React Query prefetch/hydration, and existing shadcn UI primitives.
Workspace includes:
- summary cards: Due Today, Overdue, Meetings, Hot Projects
- Agenda default view
- Day/Week/Month tabs
- search
- empty/loading states
- event cards with read-only milestone vs editable activity distinction
### Rebuild and Health
Calendar rebuild contract added as a plan/result helper for hybrid rebuild:
- sources: Activity, Opportunity, Quotation, Approval, Business Event replay
- supports organization/date range/dry-run/reset/restart contract
Runtime checkpoint/retry/dead-letter behavior remains owned by Projection Runtime. Calendar consumer can be registered with runtime consumers without changing source-domain writes.
## Delivery Matrix
| Source | Business Event / Source Signal | Projection | Calendar Item | Workspace |
| --- | --- | --- | --- | --- |
| Activity | `activity.created`, assigned, reassigned, started, rescheduled | `crm_calendar_projection` | editable Activity event | Calendar Agenda/Day/Week/Month |
| Activity | `activity.completed`, cancelled, deleted | delete projection row | removed from forward schedule | Calendar refresh |
| Opportunity | Expected Award / Expected Delivery / Hot Project | rebuild contract seam | read-only milestone | future source adapter |
| Quotation | Valid Until / Delivery milestone | rebuild contract seam | read-only milestone | future source adapter |
| Approval | Due / Escalation | rebuild contract seam | read-only milestone | future source adapter |
## UI/UX Review
- Layout follows AR.2 Calendar Workspace note: PageContainer, header, compact filter/search, calendar body, summary cards.
- Agenda is default manager/mobile-friendly dense view.
- Day/Week/Month views are available as foundation tabs.
- State coverage includes loading skeleton and empty state.
- Status is not color-only: badges show overdue/hot-project/read-only/category text.
- Date/time rendering uses `src/lib/date-format.ts`.
- UI avoids importing upstream shell, backend, provider, or mock data.
## Known Follow-ups
- Opportunity, Quotation, and Approval milestone projection adapters are contract-ready but not populated until those domains publish durable milestone events or rebuild source adapters are expanded.
- Drag/drop and resize UI actions are represented as Activity-only affordances, but direct mutation wiring is intentionally deferred until Activity reschedule API integration is added to Calendar UI.
- User/team filter contract exists; UI currently exposes lens/search foundation only. Manager team hierarchy should be wired after team graph/scope UX is formalized.
- Working hours and conflict detection are schema/service-ready concepts but need fuller UI and source preference integration in a follow-up slice.
- `INITIAL_PROJECTION_CONSUMERS` remains skeleton-safe for foundation runtime tests; CRM runtime composition should opt into `CRM_CALENDAR_PROJECTION_CONSUMERS` or combined CRM registration when running production projection workers.
## Verification
- `npm run typecheck`
- `node --disable-warning=ExperimentalWarning --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --experimental-strip-types --test src/features/crm/calendar/server/builder.test.ts src/features/crm/timeline/server/builder.test.ts src/features/foundation/projections/runtime.test.ts`
- `npx oxlint src/features/crm/calendar src/app/api/crm/calendar src/app/dashboard/crm/calendar src/features/foundation/projections/consumers.ts src/features/foundation/projections/registry.ts src/features/crm/timeline/server/registration.ts src/db/schema.ts`
- `npm run db:generate`
## Outcome
EP.1.6 now has a governed Calendar Projection and first Calendar Workspace foundation. Calendar remains additive, rebuildable, and non-authoritative. Activity remains the only editable operational work source; Timeline remains unchanged as historical chronology.

View File

@@ -0,0 +1,39 @@
# Task EP.1.6.1 Calendar Empty State Rendering Improvement - 2026-07-13
## Scope Delivered
- updated Calendar Day, Week, and Month views to always render calendar grid cells even when there are zero events
- kept Agenda view list-oriented empty state behavior unchanged
- added inline empty overlay for Day/Week/Month instead of replacing the calendar surface
- added `+ Add Activity` entry point from empty calendar overlay using existing Activity workspace route
- kept date/time rendering through `src/lib/date-format.ts`
- preserved Calendar projection, Activity ownership, and Calendar adapter architecture
## Review Summary
Reviewed before implementation:
- `AGENTS.md`
- `plans/task-ep.1.6.1.md`
- `docs/implementation/task-ep1.6-calendar-projection-workspace-big-calendar-foundation-2026-07-13.md`
- `docs/implementation/task-ep1.7-my-day-workspace-foundation-2026-07-13.md`
- `src/features/crm/calendar/components/calendar-workspace.tsx`
- Calendar API contracts under `src/features/crm/calendar/api/types.ts`
## Implementation Notes
- Day view now renders 12 working-hour slots.
- Week view now renders seven day cells.
- Month view now renders a 42-cell month grid.
- Empty Day/Week/Month views show a lightweight non-blocking overlay with Add Activity action.
- Calendar cells remain rendered and selectable via button semantics.
- Agenda retains `CalendarEmptyState` because a list view should show a list empty state.
## Verification
- `npm run typecheck`
- `npx oxlint src/features/crm/calendar/components/calendar-workspace.tsx`
## Outcome
Empty graphical Calendar views now feel like available scheduling capacity rather than a missing screen.

View File

@@ -0,0 +1,65 @@
# Task EP.1.6.2 - Direct Big Calendar UI Adoption
Date: 2026-07-13
Status: Foundation Implemented
Upstream reviewed: `lramos33/big-calendar`
Upstream commit: `fbb8485e3d54b267261056c784632229ba6f7bc3`
License: MIT, upstream concepts retained with local ALLA OS adaptation.
## Summary
EP.1.6.2 replaces the CRM calendar workspace's custom tab/grid renderer with a local Big Calendar layer adapted from the upstream component architecture. The implementation preserves ALLA OS backend ownership: Activity, Business Events, Calendar Projection, Projection Runtime, permissions, and API contracts remain unchanged.
The UI now flows through:
```text
CalendarProjectionItem
-> mapCalendarProjectionToBigCalendarEvent()
-> BigCalendarProvider
-> BigCalendarHeader
-> Day / Week / Month / Agenda views
-> Activity reschedule command
```
## Adopted Components And Concepts
- Added `src/features/crm/calendar/big-calendar/provider.tsx` for local calendar state, selected date, selected view, user filter, drag state, working hours, and Activity command dispatch.
- Added `src/features/crm/calendar/big-calendar/header.tsx` for upstream-style Today, previous, next, range display, user filter, and view switching.
- Added `src/features/crm/calendar/big-calendar/views.tsx` for Day, Week, Month, and Agenda rendering.
- Added `src/features/crm/calendar/big-calendar/calendar-slot.tsx` for calendar slots, empty overlays, and HTML5 drop handling.
- Added `src/features/crm/calendar/big-calendar/event-card.tsx` for draggable event cards and resize command affordance.
- Added `src/features/crm/calendar/big-calendar/helpers.ts` and `types.ts` for local view, slot, date range, and command contracts.
- Updated `src/features/crm/calendar/components/calendar-workspace.tsx` to use the Big Calendar provider/header/views instead of local custom renderers.
- Updated `src/features/crm/calendar/components/calendar-adapter.ts` with `mapBigCalendarActionToActivityCommand()`.
- Extended `BigCalendarEvent.source` with `ownerId` and `assigneeId` so the provider can support user filtering without reading database rows.
## Rejected Upstream Demo Code
- Did not import the upstream app shell, demo stores, or mock data ownership.
- Did not add React Hook Form; this repository forbids new RHF usage.
- Did not add upstream persistence or direct database reads.
- Did not mutate `crm_calendar_projection` rows from UI interactions.
- Did not replace the Activity API or Business Event flow.
## Interaction Behavior
- Editable Activity events can emit reschedule commands through `rescheduleActivity()`.
- Drag/drop maps a dropped editable Activity to an Activity reschedule request.
- Resize currently uses a command-based duration extension affordance rather than full upstream edge-resize parity.
- Read-only milestones do not emit Activity commands.
- After successful reschedule, Calendar, Activity, and My Day query groups are invalidated.
- Day, Week, and Month retain EP.1.6.1 behavior by always rendering grid slots, including empty states.
- Agenda keeps list-style empty behavior.
## Upgrade Strategy
Future upstream sync should compare against commit `fbb8485e3d54b267261056c784632229ba6f7bc3`, then port only rendering and interaction improvements into `src/features/crm/calendar/big-calendar/**`. Keep ALLA OS adapter and command boundaries stable.
Full upstream parity still requires a deliberate dependency review for `react-dnd`, upstream resize behavior, and any accessibility changes before adding them to this Next.js 16 / React 19 / shadcn / TanStack stack.
## Verification
- Passed: `npm run typecheck`
- Passed: `npx oxlint src/features/crm/calendar`
- Not run: `npm run db:generate` because EP.1.6.2 made no schema changes.
- Not automated: browser drag/drop, resize, responsive, and screen-reader regression checks.

View File

@@ -0,0 +1,89 @@
# Task EP.1.6.2-R1 - Upstream Big Calendar Source Adoption
Date: 2026-07-13
Status: Implemented
Upstream: `https://github.com/lramos33/big-calendar`
Pinned commit: `fbb8485e3d54b267261056c784632229ba6f7bc3`
## Outcome
R1 replaces the locally reimplemented EP.1.6.2 calendar renderer with copied upstream Big Calendar source under:
```text
src/features/crm/calendar/upstream-big-calendar/**
```
The active `/dashboard/crm/calendar` workspace now renders:
```text
CalendarProvider -> ClientContainer -> CalendarHeader -> Day / Week / Month / Year / Agenda
```
ALLA OS still owns Calendar Projection, Activity lifecycle, Business Events, Projection Runtime, CRM authorization, and React Query invalidation.
## Removal / Replacement Matrix
| Previous EP.1.6.2 file | R1 action | Replacement |
| --- | --- | --- |
| `big-calendar/provider.tsx` | removed | upstream `contexts/calendar-context.tsx` |
| `big-calendar/header.tsx` | removed | upstream `components/header/**` |
| `big-calendar/views.tsx` | removed | upstream `month-view`, `week-and-day-view`, `agenda-view`, `year-view` |
| `big-calendar/calendar-slot.tsx` | removed | upstream day/week/month cells and DnD droppables |
| `big-calendar/event-card.tsx` | removed | upstream event block, month badge, agenda card |
| `big-calendar/helpers.ts` | removed | upstream `helpers.ts` |
| `big-calendar/types.ts` | removed | upstream `interfaces.ts` and `types.ts` |
The previous custom folder was removed from the active source tree.
## Local Adapter Boundary
- `mapCalendarProjectionToUpstreamEvent()` maps `CalendarProjectionItem` into upstream `IEvent`.
- `mapUpstreamEventToActivityCommand()` maps upstream moved events into Activity reschedule commands.
- Upstream `IEvent` carries an `alla` metadata object for source references, editability, milestone/read-only flags, and visibility metadata.
- Read-only milestones are marked non-editable and prevented from dragging through upstream DnD wrappers.
## Upstream Patches
- Import paths were rewritten from `@/calendar/*` to the isolated local upstream directory.
- `lucide-react` imports were mapped to the project icon registry via `upstream-big-calendar/icons.tsx`.
- Upstream demo add/edit dialogs were replaced with ALLA-safe integration shells; React Hook Form was not introduced.
- Upstream mock data, request helpers, and demo schemas were removed.
- `CalendarProvider` was extended with `initialDate`, `initialView`, and `onEventChange`.
- `ClientContainer` now reads selected view from upstream context so the toolbar can switch views inside the ALLA route.
- Header view buttons preserve upstream toolbar composition but no longer navigate to upstream demo routes.
- Upstream `react-dnd` / `react-dnd-html5-backend` behavior is used for drag/drop.
## Dependency Review
Added:
- `react-dnd@16.0.1`
- `react-dnd-html5-backend@16.0.1`
Not added:
- `react-hook-form`
- `@hookform/resolvers`
- `react-aria-components`
- `lucide-react`
Reason: those were only required by upstream demo forms/config controls or were replaced by existing ALLA UI/icon primitives.
`npm install` reported existing audit findings after dependency update: 9 moderate and 3 high. No automatic `audit fix --force` was run because it may introduce unrelated breaking changes.
## Parity Notes
- Adopted upstream Day, Week, Month, Agenda, Year, Header, DnD provider, drag layer, droppable cells, event positioning helpers, multi-day rows, and month event badges.
- Agenda empty state comes from upstream agenda view.
- Day/Week/Month still render actual upstream grids when empty.
- Year view is adopted.
- Drag/drop uses upstream `react-dnd` mechanics and forwards changed editable Activity events to the ALLA Activity reschedule API.
- Resize edge-handle parity remains limited by the reviewed upstream commit; no separate local resize approximation was retained.
- Full browser visual parity, dark-mode parity, and interaction parity still require manual browser verification.
## Verification
- Passed: `npm run typecheck`
- Passed: `npx oxlint src/features/crm/calendar`
- Not run: `npm run db:generate`; no schema changes.
- Not automated: browser drag/drop regression, responsive screenshots, screen-reader pass, dark-mode visual parity.

View File

@@ -0,0 +1,103 @@
# Task EP.1.7 My Day Workspace Foundation - 2026-07-13
## Scope Delivered
- added My Day feature module under `src/features/crm/my-day/**`
- added `/api/crm/my-day` route handler with organization access and CRM Activity read permission
- added `/dashboard/crm/my-day` workspace route with server prefetch, React Query hydration, loading states, and widget layout
- added My Day Widget Registry with widget id, title, priority, icon, permission, visibility, refresh strategy, and empty state
- added My Day service composition over existing foundations:
- Calendar Projection for today's schedule and due-today work
- Activity service for overdue Activity queue
- CRM Dashboard service for Hot Projects and pending approvals
- Timeline Projection for recent user-authored updates
- added priority queue ordering for overdue, due today, approvals, hot projects, and schedule items
- added CRM nav entry for My Day before CRM Dashboard
- preserved Activity, Calendar, Timeline, Opportunity, Quotation, Approval, Dashboard, Report, Notification behavior
## Review Summary
Reviewed before/during implementation:
- `AGENTS.md`
- `plans/task-ep.1.7.md`
- `LAYOUT.md`
- `docs/standards/engineering-constitution.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/task-review-checklist.md`
- `docs/security/crm-authorization-boundaries.md`
- `docs/business/relationship-sales-workspace-blueprint-v1.md`
- `docs/implementation/task-ar.2-workspace-ui-ux-design-note-2026-07-07.md`
- `docs/implementation/task-ep1.5-timeline-projection-foundation-2026-07-13.md`
- `docs/implementation/task-ep1.6-calendar-projection-workspace-big-calendar-foundation-2026-07-13.md`
- existing Activity, Calendar, Timeline, Dashboard, CRM security, and dashboard UI patterns
## Architecture Notes
My Day is read-oriented and does not own business data. It composes existing source-domain and projection services instead of introducing duplicate Activity, Calendar, Timeline, Approval, Opportunity, or Quotation logic.
Primary service:
- `src/features/crm/my-day/server/service.ts`
Public contracts:
- `src/features/crm/my-day/api/types.ts`
- `src/features/crm/my-day/api/service.ts`
- `src/features/crm/my-day/api/queries.ts`
Widget registry:
- `src/features/crm/my-day/server/widget-registry.ts`
## Widget Delivery
Implemented foundation widgets:
- Overdue Activities
- Today's Schedule
- Due Today priority items
- Hot Projects
- Pending Approvals
- Recently Updated
- My Statistics
Quotation Attention is represented in the registry and response contract, but no bespoke quotation-risk query was added in this slice to avoid duplicating Quotation/Dashboard logic before a governed urgency adapter exists.
## Security Notes
- Route requires active organization access and `crm.activity.read`.
- Activity visibility is enforced by existing Activity service.
- Calendar visibility is enforced by existing Calendar projection query service.
- Hot Projects and approvals reuse Dashboard service visibility rules.
- Recent Timeline is limited to the current actor to avoid organization-wide timeline leakage until team-scoped Timeline access is formalized.
- Manager mode is contract-ready but intentionally conservative while team graph scope remains a known governance limitation.
## UI/UX Review
- Follows AR.2 My Day design note: high-priority queue first, summary stats, active schedule, hot projects, approvals, recent updates.
- Uses `PageContainer`, shadcn cards, badges, buttons, and skeletons.
- Adds Add Activity entry point via existing Activity workspace instead of creating a duplicate form.
- Uses deterministic date/time display through `src/lib/date-format.ts`.
- Empty states exist per rendered widget.
- Status is not color-only: priority, source type, refresh strategy, and action labels are visible text.
## Known Follow-ups
- Wire Add Activity to an Activity form sheet with context-aware prefills once reusable Activity form entry is exposed outside the Activity listing.
- Implement source-owned Quotation Attention adapter for expiring soon, pending approval, and returned revision.
- Add manager team filter UI after team hierarchy/scope behavior is formalized.
- Add independent per-widget query endpoints if operational telemetry shows full workspace refetch is too coarse.
- Add workspace analytics for widget usage, quick action usage, Add Activity usage, and completion rate with sanitized payloads.
- Decide rollout for making My Day the post-login/default CRM landing without disrupting the existing dashboard route.
## Verification
- `npm run typecheck`
- `npx oxlint src/features/crm/my-day src/app/api/crm/my-day src/app/dashboard/crm/my-day src/config/nav-config.ts`
## Outcome
EP.1.7 now has a governed My Day Workspace foundation that lets users start from a prioritized operational queue while preserving source-domain ownership and existing CRM workflows.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,701 @@
# Engineering Constitution
This document freezes the engineering governance for ALLA OS after the Business Constitution (`BU-R.0`, `BU-R.0.1`, `BU-R.1`), Architecture Constitution (`AR.1`, `AR.2`), and Workspace UI Constitution are in place.
It defines how future implementation work must be designed, coded, reviewed, tested, and delivered.
---
## 1. Purpose and Precedence
### Purpose
- keep implementation aligned with the Relationship-Driven Sales Workspace blueprint
- preserve current production foundations while enabling controlled extension
- prevent duplicate services, APIs, projections, UI systems, and business logic
- give engineers and AI agents one shared delivery model
### Governance precedence
Future work must follow this order:
1. business constitution and business blueprint documents
2. architecture transition and epic technical design documents
3. this Engineering Constitution
4. workspace UI/UX governance
5. existing approved foundations and feature implementations
If two sources conflict, the higher item in this list wins unless a newer accepted ADR explicitly replaces it.
### Mandatory inputs before implementation
Every implementation task must review:
- `AGENTS.md`
- `docs/standards/project-foundations.md`
- `docs/standards/architecture-rules.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/task-review-checklist.md`
- `docs/standards/task-catalog.md`
- relevant `docs/adr/**`
- relevant `docs/business/**`
- relevant `docs/security/**`
- relevant `docs/implementation/**`
- relevant existing foundations under `src/features/foundation/**`
- relevant existing feature implementations under `src/features/**`
No implementation may bypass this review-first rule.
---
## 2. Feature Architecture Rules
### Feature ownership model
- each domain or shared capability must have one owning feature or foundation
- route handlers are HTTP boundaries, not business owners
- shared cross-cutting behavior must live in a foundation before a second feature reimplements it
- new CRM work must extend `src/features/crm/**` or `src/features/foundation/**` instead of creating parallel top-level domains
### Approved feature structure
Default structure for app-owned features:
```text
src/features/<feature>/
api/
types.ts
service.ts
queries.ts
mutations.ts
components/
schemas/
server/
```
Allowed additions when justified:
- `adapters/` for migration seams or projection adapters
- `lib/` for feature-local pure helpers
- `constants/` for feature-local stable constants
- `hooks/` for feature-local client hooks
### Layer responsibilities
- `api/types.ts`: public TypeScript contracts for UI and service consumers
- `api/service.ts`: client-facing API client wrappers only
- `api/queries.ts`: React Query key factories and query options
- `api/mutations.ts`: centralized mutation configs and invalidation behavior
- `components/`: UI composition only
- `schemas/`: request payload validation and form schemas
- `server/`: business logic, persistence orchestration, scope enforcement, data shaping
### Public and internal modules
- only `api/**`, `components/**`, and explicitly shared helpers are public to outside consumers
- `server/**` is internal to the owning feature unless consciously reused by another server-side feature
- client components must not import database or server-only modules
- cross-feature imports should target stable public contracts, not incidental internals
### Shared module rules
- shared UI belongs in `src/components/**` or an approved foundation/shared area
- shared business logic belongs in the owning foundation or feature server layer
- do not create shared modules only to avoid choosing ownership
### Cross-feature dependency rules
Allowed direction:
- app routes -> feature API/services/components
- route handlers -> auth/session/validation -> owning feature server services
- feature server services -> foundations and lower-level shared security/utilities
- projections/read models -> source services or approved dataset layers
Forbidden direction:
- source domains depending on projection-only domains for write behavior
- features reaching directly into another feature's UI internals
- client code importing Drizzle, server-only services, or auth enforcement modules
- projection code becoming the write owner of business lifecycle state
### Feature registration rule
Before creating a new feature, confirm that the need cannot be satisfied by extending:
- an existing CRM feature
- an existing foundation
- an existing report, approval, PDF, storage, audit, or security layer
If a new feature is still required, document why reuse was insufficient.
---
## 3. Service Layer Standards
### Service composition
The service layer owns business rules. It may be split into:
- business services
- query services
- projection services
- application/orchestration services
- domain-specific helper adapters
### Frozen service rules
- route handlers stay thin
- services own business lifecycle rules
- services enforce server-side scope, pricing visibility, and permission-sensitive behavior
- services may call foundations and approved sibling services when reuse is explicit
- services must not contain UI concerns
- services must not authorize with raw role strings alone
### Business ownership boundaries
- `Customer` owns relationship anchor behavior
- `Lead` owns early demand signal behavior
- `Opportunity` owns project pursuit and won/lost outcome behavior
- `Quotation` owns commercial document lifecycle
- `Approval` owns approval workflow execution
- future `Activity` owns shared operational work records
- `Timeline`, `Calendar`, `My Day`, `Manager`, and `Executive` are read-model or workspace consumers, not primary write owners
### Transaction boundary rules
- one service owns the transaction for one business mutation
- downstream side effects such as audit logging, notifications, event publishing, or artifact updates must happen from the owning service flow
- avoid multi-route, UI-driven orchestration for business-critical mutations
### Query and projection service rules
- query services return read-ready data and may combine multiple foundations
- projection services are read-only and rebuildable
- projection services must not become hidden write paths
- report services must reuse the report foundation dataset and builder layers
### Internal repository rule
This repo does not use a heavyweight repository architecture.
Allowed pattern:
- lightweight persistence helpers or adapters inside `server/`
- Drizzle queries close to the owning service
Forbidden pattern:
- introducing a parallel abstract repository layer across the app without a project-level decision
---
## 4. Repository and Persistence Standards
### Ownership
- each table or schema area must have a clear owning feature or foundation
- only the owning server service should perform write orchestration for its tables
- cross-feature readers should prefer owning services or approved read models before querying tables directly
### Persistence rules
- use Drizzle ORM for database access
- keep SQL and query composition inside server services or approved dataset modules
- reuse existing dataset and export services for reporting work
- do not move domain logic into route handlers just because Drizzle is easy to call there
### Backward compatibility
- preserve existing production contracts while extending them
- use additive migration patterns first
- dual-read or adapter-first strategies are preferred for high-risk transitions such as Activity and projection work
---
## 5. API Standards
### Boundary
- Route Handlers under `src/app/api/**` are the default HTTP boundary
- new CRM APIs belong under `/api/crm/**`
- settings or foundation APIs belong under their existing route families
### Route handler contract
Every route handler should:
1. require session or organization access
2. validate permissions
3. parse and validate request input
4. build access/security context when needed
5. call the owning server service
6. map response
7. log audit or security events where applicable
### Naming and path rules
- use plural resource names for collection routes
- use nested child routes only when the child lifecycle is truly parent-scoped
- use action routes only for explicit domain transitions such as `mark-won`, `submit-approval`, `complete`, `cancel`
- avoid generic verb-heavy route families when a resource-oriented route can express the behavior
### Validation
- use Zod for request validation at the boundary
- reuse feature schemas where possible
- validation error messages should be business-readable and not leak backend detail
### Response rules
- preserve current stable contract shapes inside an existing route family
- new routes should prefer predictable JSON responses with clear success, data, and meta sections when not constrained by an existing family
- list routes should return pagination metadata when pagination exists
- API responses must remain ISO-8601 UTC for datetime fields
### Authorization
- use `requireSession()`, `requireSystemRole()`, or `requireOrganizationAccess()`
- CRM routes must apply resolved CRM access and scope enforcement
- UI visibility never replaces server authorization
### Filtering, sorting, and search
- filters should be explicit, typed, and documented in feature contracts
- use URL-driven filter state for shareable list screens
- keep filtering semantics aligned between on-screen data and exports
### Versioning strategy
- preserve compatible route contracts whenever possible
- prefer additive fields and child routes over breaking replacements
- introduce a new route shape only when compatibility cannot be preserved cleanly
---
## 6. Business Event, Projection, and Timeline Rules
### Event philosophy
- business events are immutable facts emitted after successful state changes
- events are not a replacement for audit logs
- events may fan out to timeline, calendar, dashboard, notification, reminder, or future automation
### Publisher ownership
- source-domain services publish their own events
- do not centralize event publishing in UI or ad hoc route helpers
- preserve current approval notification compatibility while expanding the event model
### Event contract rules
- use lowercase dot-separated event names
- include `schemaVersion`
- include organization, entity, actor, primary record, related records, and visibility context
- event payloads must avoid leaking pricing-sensitive or unauthorized commercial data
### Projection rules
- `Timeline` and `Calendar` are generated projections only
- projections are read-only and rebuildable
- projections never own lifecycle state
- phase 1 projection services should favor query-time generation or cacheable read models before new authoritative tables
### Projection ownership
- activity projections belong to the Activity and projection layers
- milestone projections remain owned by their source domain semantics
- dashboard and reports consume projections or source datasets but do not become source-of-truth mutations
### Idempotency and dedupe
- event publication and subscribers must support safe retry behavior
- notification or projection consumers must guard against duplicate fan-out
- approval events require extra care because notification publishing already exists
---
## 7. UI Engineering Rules
### Required UI inputs
All user-facing work must review:
- `layout.md`
- `docs/standards/ui-ux-rules.md`
- relevant business terminology docs
- nearby production screens in the same route family
Major workspace surfaces must also reference:
- `BU-R.0`
- `BU-R.0.1`
- `BU-R.1`
- `AR.1`
- `AR.2`
- the latest workspace UI/UX design note
### Frozen UI rules
- preserve the existing dashboard shell
- use `PageContainer` for dashboard page framing
- reuse shadcn/ui primitives and app wrappers
- reuse existing table, form, filter, dialog, sheet, badge, and status patterns before creating new ones
- new business-role workspaces live under `/dashboard/crm/*`, not `/dashboard/workspaces`
- no code-first implementation for major new workspace surfaces without an approved design note
### Data and state rules
- server components first
- use React Query for server-state fetching
- use server prefetch plus `HydrationBoundary` for data-heavy app pages
- use `nuqs` for shareable URL state
- use TanStack Form plus Zod for new forms
### UX state rules
Every surface must define:
- loading states
- empty states
- error states
- disabled and pending action states
- permission-aware visibility behavior
- responsive behavior
### Accessibility rules
- semantic controls and accessible names are required
- keyboard navigation must remain usable for tables, dialogs, sheets, tabs, and menus
- focus behavior must be predictable
- status cannot be conveyed by color alone
---
## 8. Code Quality Standards
### Naming
- use business terms frozen by the business and architecture constitutions
- keep active production terminology aligned with `opportunity`, not historical `enquiry`, unless touching historical-only documentation
- use clear, explicit function and file names over vague generic helpers
### File and folder naming
- keep file names lowercase kebab-case unless a framework convention requires otherwise
- use stable feature-local names such as `service.ts`, `queries.ts`, `mutations.ts`, `types.ts`
- avoid creating near-duplicate files with ambiguous suffixes like `service2`, `new`, or `final`
### TypeScript rules
- strict typing is required
- avoid `any`
- define public contracts explicitly in `api/types.ts` or equivalent shared contracts
- keep server-only and client-safe types clearly separated when necessary
### Error handling
- expected business or authorization failures should produce explicit typed or status-aware errors
- do not leak SQL or infrastructure internals to business-facing surfaces
- centralize reusable failure behavior where patterns already exist
### Logging and audit
- use the audit foundation for business mutations, exports, and sensitive denials
- use ordinary console logging only for operational debugging or unexpected failures
- logs must not become a substitute for governed audit events
### Validation
- validate at the boundary
- revalidate critical business invariants inside services when necessary
- do not trust client-side validation alone
### Comments
- comments should explain non-obvious business or security rules
- avoid comments that restate the code
### Technical debt policy
- prefer preserve -> extend -> controlled refactor -> replace
- small cleanup is encouraged when it improves safety or clarity
- unrelated rewrites are prohibited
---
## 9. Database and Migration Standards
### Migration strategy
- additive, backward-compatible migrations are preferred
- schema changes must preserve current production behavior until replacement paths are proven
- high-risk data model changes must document migration and coexistence strategy first
### Schema ownership
- schema changes must be owned by the relevant feature or foundation
- projection schemas, if introduced later, must remain clearly separate from source-of-truth tables
### Rollback strategy
- every risky migration must define rollback or safe-disable behavior
- do not require emergency manual data surgery as the normal rollback path
### Seed strategy
- foundation seeds remain the source for governed options and bootstrap records where already used
- new seed data must be deterministic and documented
### Explicit non-changes rule
Documentation-only tasks must not silently modify schema, migrations, or runtime persistence behavior.
---
## 10. Testing Standards
### Minimum testing lens
Each implementation must define the right mix of:
- unit tests
- service tests
- route handler or API tests
- permission and scope tests
- projection and event tests where applicable
- regression tests for preserved behavior
- manual verification scenarios
### Required test themes
- happy path behavior
- permission denial behavior
- scope enforcement behavior
- pricing visibility behavior for quotation-derived outputs
- audit/event side effects when applicable
- cache freshness after CRUD mutations
### Projection and event testing
- timeline and calendar work must verify ordering, filtering, visibility, and dedupe behavior
- event-driven work must verify idempotency and compatibility with existing notification flows
### UAT checklist baseline
- loading state visible
- empty state helpful
- error state actionable
- successful mutation refreshes the affected UI without manual browser refresh
- mobile and desktop layouts remain usable
---
## 11. Delivery Rules
### Epic lifecycle
Each epic must define:
- objective
- non-goals
- reviewed history and foundations
- API and service boundaries
- security and pricing rules
- audit and event behavior
- verification plan
- rollout and rollback notes when risk is non-trivial
### Feature lifecycle
For feature implementation:
1. review requirements and historical context
2. identify reuse targets
3. confirm architecture and security boundaries
4. define smallest viable extension
5. implement in the owning layer
6. self-review architecture, UI, data freshness, and security
7. validate
8. document results
### Review gates
- architecture review
- business rule review
- security and permission review
- UI consistency review for user-facing work
- regression and backward-compatibility review
### Release and rollback baseline
- document migrations and toggles when present
- document impacted routes or surfaces
- document known compatibility seams
- document rollback or safe-disable path for non-trivial changes
---
## 12. Definition of Done
### Feature
- required review completed
- foundations reused or non-reuse rationale documented
- implementation stays within approved architecture
- permissions, scope, and pricing visibility enforced server-side where applicable
- audit and event behavior handled where required
- loading, empty, error, disabled, and success states covered for user-facing work
- validation completed and recorded
- documentation updated if governance or reusable patterns changed
### Epic
- feature-level done conditions met for each delivered slice
- sequence and dependency assumptions remain compatible with `AR.2`
- cross-feature duplication was not introduced
- migration or rollout notes recorded for future slices
### Bug Fix
- root cause identified
- fix scoped to the owning layer
- regression risk checked
- validation performed against the failing scenario
### Refactor
- ownership and behavior preserved unless explicitly approved otherwise
- backward compatibility risks documented
- no unrelated behavior churn introduced
---
## 13. AI Engineering Rules
AI contributors must follow the same governance as human contributors.
### Mandatory AI rules
- review before implementation
- reuse before creation
- preserve before refactor
- never duplicate business logic, lifecycle logic, projection logic, or permission logic
- identify existing services, routes, and components before adding new ones
- keep route handlers thin
- keep business logic in services
- keep projections read-only
- record assumptions and validation results
### AI implementation checklist
Before writing code, confirm:
- business constitution reviewed
- architecture constitution reviewed
- engineering constitution reviewed
- workspace UI constitution reviewed when UI is involved
- relevant ADRs reviewed
- related implementation history reviewed
- relevant foundations inspected
- existing pattern search completed
- reuse plan chosen
- validation plan defined
---
## 14. Code Review Checklist
### Architecture
- does the change extend the right owning feature or foundation?
- does it avoid parallel services, APIs, or UI systems?
- does it preserve route-handler-thin and service-owned business logic?
### Business
- does it follow frozen domain ownership and terminology?
- does it avoid moving responsibility into the wrong layer?
### Security
- are permission, scope, and pricing rules enforced server-side?
- are raw role-string checks avoided?
- are denials audited when needed?
### Performance and data freshness
- are query keys and invalidation complete?
- are projections or datasets using the approved read paths?
- does the UI refresh correctly after mutation?
### Accessibility and UI consistency
- does the surface reuse approved page, table, form, dialog, and state patterns?
- are loading, empty, error, and responsive states covered?
### Maintainability and backward compatibility
- are contracts explicit?
- does the change avoid hidden coupling and duplicate logic?
- are compatibility seams, rollout notes, or follow-up risks documented?
---
## 15. Engineering Decision Matrix
Use this matrix before choosing implementation strategy.
### Preserve
Use when:
- the existing module already owns the behavior
- the change is additive or corrective
- replacement would create unnecessary migration risk
### Extend
Use when:
- the existing module is the correct owner
- new capability fits the current architecture
- a controlled addition avoids parallel systems
### Controlled Refactor
Use when:
- current ownership is correct but the structure blocks safe extension
- refactor reduces duplication or clarifies a reusable seam
- compatibility can be preserved during the change
### Replace
Use only when:
- preserve, extend, and controlled refactor are insufficient
- replacement rationale is explicit
- migration and rollback strategy are documented
- governance review accepts the risk
### Explicit rule
If a task cannot explain why it is preserving, extending, refactoring, or replacing, it is not ready for implementation.
---
## 16. Official ENG.0 Outcome
ENG.0 freezes one engineering model for future ALLA OS implementation:
- review-first
- reuse-first
- service-owned business logic
- route-handler-thin API boundaries
- resolved-access security enforcement
- projection-as-read-model, not source-of-truth
- shared UI shell preservation
- explicit validation, audit, and delivery discipline
No future implementation epic may bypass this constitution.

View File

@@ -0,0 +1,41 @@
CREATE TABLE "crm_activities" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"primary_entity_type" text NOT NULL,
"primary_entity_id" text,
"primary_record_label" text,
"related_records" jsonb DEFAULT '[]'::jsonb NOT NULL,
"customer_id" text,
"contact_id" text,
"lead_id" text,
"opportunity_id" text,
"quotation_id" text,
"activity_type" text NOT NULL,
"subject" text NOT NULL,
"description" text,
"owner_id" text NOT NULL,
"assigned_to_id" text,
"scheduled_start_at" timestamp with time zone,
"scheduled_end_at" timestamp with time zone,
"due_at" timestamp with time zone,
"completed_at" timestamp with time zone,
"cancelled_at" timestamp with time zone,
"skipped_at" timestamp with time zone,
"status" text NOT NULL,
"priority" text NOT NULL,
"outcome_summary" text,
"cancellation_reason" text,
"skip_reason" text,
"next_action" text,
"branch_id" text,
"product_type" text,
"is_internal_only" boolean DEFAULT false NOT NULL,
"is_pricing_sensitive" boolean DEFAULT false NOT NULL,
"parent_activity_id" text,
"metadata" jsonb,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
"created_by" text NOT NULL,
"updated_by" text NOT NULL
);

View File

@@ -0,0 +1,117 @@
CREATE TABLE "business_event_outbox" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"event_type" text NOT NULL,
"schema_version" integer NOT NULL,
"entity_type" text NOT NULL,
"entity_id" text NOT NULL,
"correlation_id" text NOT NULL,
"causation_id" text,
"event_envelope" jsonb NOT NULL,
"delivery_status" text DEFAULT 'pending' NOT NULL,
"attempt_count" integer DEFAULT 0 NOT NULL,
"available_at" timestamp with time zone DEFAULT now() NOT NULL,
"processing_started_at" timestamp with time zone,
"dispatched_at" timestamp with time zone,
"failed_at" timestamp with time zone,
"dead_lettered_at" timestamp with time zone,
"last_error_code" text,
"last_error_message" text,
"occurred_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "projection_consumer_checkpoints" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"consumer_name" text NOT NULL,
"projection_name" text NOT NULL,
"event_id" text NOT NULL,
"event_type" text NOT NULL,
"schema_version" integer NOT NULL,
"processing_status" text DEFAULT 'pending' NOT NULL,
"attempt_count" integer DEFAULT 0 NOT NULL,
"first_attempted_at" timestamp with time zone,
"last_attempted_at" timestamp with time zone,
"completed_at" timestamp with time zone,
"next_retry_at" timestamp with time zone,
"error_code" text,
"error_message" text,
"processing_duration_ms" integer,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "projection_dead_letters" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"consumer_name" text NOT NULL,
"projection_name" text NOT NULL,
"event_id" text NOT NULL,
"event_type" text NOT NULL,
"schema_version" integer NOT NULL,
"attempt_count" integer NOT NULL,
"error_code" text NOT NULL,
"error_message" text NOT NULL,
"failure_metadata" jsonb,
"status" text DEFAULT 'open' NOT NULL,
"resolved_at" timestamp with time zone,
"resolved_by" text,
"resolution_reason" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "projection_health_snapshots" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"projection_name" text NOT NULL,
"consumer_name" text NOT NULL,
"status" text DEFAULT 'healthy' NOT NULL,
"last_processed_event_id" text,
"last_successful_processing_at" timestamp with time zone,
"pending_count" integer DEFAULT 0 NOT NULL,
"retry_count" integer DEFAULT 0 NOT NULL,
"dead_letter_count" integer DEFAULT 0 NOT NULL,
"unsupported_version_count" integer DEFAULT 0 NOT NULL,
"processing_lag_ms" integer DEFAULT 0 NOT NULL,
"average_processing_duration_ms" integer DEFAULT 0 NOT NULL,
"last_rebuild_at" timestamp with time zone,
"rebuild_source" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "projection_rebuild_runs" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"projection_name" text NOT NULL,
"entity_type" text,
"entity_id" text,
"occurred_from" timestamp with time zone,
"occurred_to" timestamp with time zone,
"dry_run" boolean DEFAULT true NOT NULL,
"reset_existing" boolean DEFAULT false NOT NULL,
"source_strategy" text NOT NULL,
"status" text DEFAULT 'pending' NOT NULL,
"processed_count" integer DEFAULT 0 NOT NULL,
"skipped_count" integer DEFAULT 0 NOT NULL,
"failed_count" integer DEFAULT 0 NOT NULL,
"error_code" text,
"error_message" text,
"requested_by" text NOT NULL,
"started_at" timestamp with time zone,
"completed_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE INDEX "business_event_outbox_status_available_idx" ON "business_event_outbox" USING btree ("delivery_status","available_at");--> statement-breakpoint
CREATE INDEX "business_event_outbox_org_event_idx" ON "business_event_outbox" USING btree ("organization_id","event_type");--> statement-breakpoint
CREATE UNIQUE INDEX "projection_checkpoints_consumer_event_idx" ON "projection_consumer_checkpoints" USING btree ("consumer_name","event_id");--> statement-breakpoint
CREATE INDEX "projection_checkpoints_status_retry_idx" ON "projection_consumer_checkpoints" USING btree ("processing_status","next_retry_at");--> statement-breakpoint
CREATE UNIQUE INDEX "projection_dead_letters_consumer_event_idx" ON "projection_dead_letters" USING btree ("consumer_name","event_id");--> statement-breakpoint
CREATE INDEX "projection_dead_letters_org_status_idx" ON "projection_dead_letters" USING btree ("organization_id","status");--> statement-breakpoint
CREATE UNIQUE INDEX "projection_health_org_consumer_idx" ON "projection_health_snapshots" USING btree ("organization_id","consumer_name");--> statement-breakpoint
CREATE INDEX "projection_rebuild_runs_org_projection_idx" ON "projection_rebuild_runs" USING btree ("organization_id","projection_name");

View File

@@ -0,0 +1,4 @@
ALTER TABLE "business_event_outbox" ADD COLUMN "claimed_by" text;--> statement-breakpoint
ALTER TABLE "business_event_outbox" ADD COLUMN "claimed_at" timestamp with time zone;--> statement-breakpoint
ALTER TABLE "business_event_outbox" ADD COLUMN "claim_expires_at" timestamp with time zone;--> statement-breakpoint
CREATE INDEX "business_event_outbox_claim_lease_idx" ON "business_event_outbox" USING btree ("delivery_status","claim_expires_at");

View File

@@ -0,0 +1,33 @@
CREATE TABLE "crm_timeline_projection" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"branch_id" text,
"customer_id" text,
"lead_id" text,
"opportunity_id" text,
"quotation_id" text,
"activity_id" text,
"event_id" text NOT NULL,
"entity_type" text NOT NULL,
"entity_id" text NOT NULL,
"timeline_type" text NOT NULL,
"timeline_category" text NOT NULL,
"occurred_at" timestamp with time zone NOT NULL,
"actor_id" text,
"actor_display" text,
"title" text NOT NULL,
"summary" text,
"icon" text NOT NULL,
"color" text NOT NULL,
"priority" text DEFAULT 'normal' NOT NULL,
"visibility" jsonb NOT NULL,
"metadata" jsonb,
"source_event" jsonb NOT NULL,
"source_projection_version" integer DEFAULT 1 NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "crm_timeline_projection_event_version_idx" ON "crm_timeline_projection" USING btree ("event_id","source_projection_version");--> statement-breakpoint
CREATE INDEX "crm_timeline_projection_org_occurred_idx" ON "crm_timeline_projection" USING btree ("organization_id","occurred_at");--> statement-breakpoint
CREATE INDEX "crm_timeline_projection_customer_occurred_idx" ON "crm_timeline_projection" USING btree ("organization_id","customer_id","occurred_at");--> statement-breakpoint
CREATE INDEX "crm_timeline_projection_activity_event_idx" ON "crm_timeline_projection" USING btree ("activity_id","event_id");

View File

@@ -0,0 +1,46 @@
CREATE TABLE "crm_calendar_projection" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"branch_id" text,
"product_type" text,
"customer_id" text,
"lead_id" text,
"opportunity_id" text,
"quotation_id" text,
"approval_id" text,
"activity_id" text,
"owner_id" text,
"assignee_id" text,
"event_id" text NOT NULL,
"entity_type" text NOT NULL,
"entity_id" text NOT NULL,
"title" text NOT NULL,
"summary" text,
"location" text,
"start_at" timestamp with time zone NOT NULL,
"end_at" timestamp with time zone NOT NULL,
"timezone" text DEFAULT 'Asia/Bangkok' NOT NULL,
"priority" text DEFAULT 'normal' NOT NULL,
"category" text NOT NULL,
"activity_type" text,
"editable" boolean DEFAULT false NOT NULL,
"milestone" boolean DEFAULT false NOT NULL,
"overdue" boolean DEFAULT false NOT NULL,
"hot_project" boolean DEFAULT false NOT NULL,
"all_day" boolean DEFAULT false NOT NULL,
"visibility" jsonb NOT NULL,
"metadata" jsonb,
"source_event" jsonb NOT NULL,
"source_projection_version" integer DEFAULT 1 NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "crm_calendar_projection_event_version_idx" ON "crm_calendar_projection" USING btree ("event_id","source_projection_version");--> statement-breakpoint
CREATE INDEX "crm_calendar_projection_org_start_idx" ON "crm_calendar_projection" USING btree ("organization_id","start_at");--> statement-breakpoint
CREATE INDEX "crm_calendar_projection_org_assignee_start_idx" ON "crm_calendar_projection" USING btree ("organization_id","assignee_id","start_at");--> statement-breakpoint
CREATE INDEX "crm_calendar_projection_org_owner_start_idx" ON "crm_calendar_projection" USING btree ("organization_id","owner_id","start_at");--> statement-breakpoint
CREATE INDEX "crm_calendar_projection_org_branch_start_idx" ON "crm_calendar_projection" USING btree ("organization_id","branch_id","start_at");--> statement-breakpoint
CREATE INDEX "crm_calendar_projection_org_category_start_idx" ON "crm_calendar_projection" USING btree ("organization_id","category","start_at");--> statement-breakpoint
CREATE INDEX "crm_calendar_projection_customer_start_idx" ON "crm_calendar_projection" USING btree ("organization_id","customer_id","start_at");--> statement-breakpoint
CREATE INDEX "crm_calendar_projection_activity_event_idx" ON "crm_calendar_projection" USING btree ("organization_id","activity_id","event_id");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,41 @@
"when": 1783350585715,
"tag": "0001_yellow_wasp",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1783647090217,
"tag": "0002_empty_jean_grey",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1783917011479,
"tag": "0003_green_squadron_supreme",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1783922653023,
"tag": "0004_sharp_mercury",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1783925810194,
"tag": "0005_freezing_wendigo",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1783931878587,
"tag": "0006_massive_scalphunter",
"breakpoints": true
}
]
}

88
package-lock.json generated
View File

@@ -74,6 +74,8 @@
"radix-ui": "^1.4.3",
"react": "19.2.4",
"react-day-picker": "^9.14.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "19.2.4",
"react-dropzone": "^14.4.1",
"react-resizable-panels": "^2.1.9",
@@ -5240,6 +5242,24 @@
"integrity": "sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==",
"license": "MIT"
},
"node_modules/@react-dnd/asap": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
"integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==",
"license": "MIT"
},
"node_modules/@react-dnd/invariant": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
"integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==",
"license": "MIT"
},
"node_modules/@react-dnd/shallowequal": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
"integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==",
"license": "MIT"
},
"node_modules/@rollup/plugin-commonjs": {
"version": "28.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz",
@@ -7460,6 +7480,17 @@
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
"license": "MIT"
},
"node_modules/dnd-core": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
"integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
"license": "MIT",
"dependencies": {
"@react-dnd/asap": "^5.0.1",
"@react-dnd/invariant": "^4.0.1",
"redux": "^4.2.0"
}
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
@@ -8042,6 +8073,15 @@
}
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"license": "BSD-3-Clause",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/html-entities": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
@@ -10107,6 +10147,45 @@
"react": ">=16.8.0"
}
},
"node_modules/react-dnd": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
"integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
"license": "MIT",
"dependencies": {
"@react-dnd/invariant": "^4.0.1",
"@react-dnd/shallowequal": "^4.0.1",
"dnd-core": "^16.0.1",
"fast-deep-equal": "^3.1.3",
"hoist-non-react-statics": "^3.3.2"
},
"peerDependencies": {
"@types/hoist-non-react-statics": ">= 3.3.1",
"@types/node": ">= 12",
"@types/react": ">= 16",
"react": ">= 16.14"
},
"peerDependenciesMeta": {
"@types/hoist-non-react-statics": {
"optional": true
},
"@types/node": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/react-dnd-html5-backend": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
"integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
"license": "MIT",
"dependencies": {
"dnd-core": "^16.0.1"
}
},
"node_modules/react-dom": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
@@ -10339,6 +10418,15 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/remove-accents": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",

View File

@@ -49,6 +49,7 @@
"audit:pdf:visual": "node --experimental-strip-types scripts/audit-pdf-visual-regression.ts",
"audit:pdf:visual:baseline": "node --experimental-strip-types scripts/audit-pdf-visual-regression.ts --refresh-baseline",
"audit:pdf": "npm run audit:pdf:inventory && npm run audit:pdf:coverage && npm run audit:pdf:runtime && npm run audit:pdf:versions && npm run audit:pdf:placeholders && npm run audit:pdf:parity && npm run audit:pdf:snapshot && npm run audit:pdf:report && npm run audit:pdf:integrity",
"pdf:seed:product": "node --disable-warning=ExperimentalWarning --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --experimental-strip-types scripts/reseed-pdf-template-version.ts --template-variant=product-v1",
"pdf:activate:product": "node --experimental-strip-types scripts/reseed-pdf-template-version.ts --template-variant=product-v1 --activate"
},
"dependencies": {
@@ -118,6 +119,8 @@
"radix-ui": "^1.4.3",
"react": "19.2.4",
"react-day-picker": "^9.14.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "19.2.4",
"react-dropzone": "^14.4.1",
"react-resizable-panels": "^2.1.9",

698
plans/task-ar.1.md Normal file
View File

@@ -0,0 +1,698 @@
# Task AR.1 Architecture Transition Plan
Status: Completed
Priority: Critical
Type: Solution Architecture / Transition Planning
Depends On
- BU-R.0 Business Blueprint Freeze
- BU-R.0.1 Workspace & Activity Blueprint
- BU-R.1 Business Capability Audit
---
# Objective
Produce the complete transition architecture from the current ALLA OS implementation to the approved Relationship-Driven Sales Operating Platform architecture.
This phase defines **how** the future architecture will be introduced while preserving the existing production foundation.
The primary goal is:
> Preserve the current production CRM foundation and extend it through new architectural layers rather than replacing existing domains.
This phase is architecture-only.
No production implementation is allowed.
---
# Background
Business Discovery has completed.
Business Blueprint has been frozen.
Business Capability Audit confirms that:
- Customer
- Contact
- Lead
- Opportunity
- Quotation
- Approval
- Organization
- RBAC
- PDF
- Dashboard
already provide a mature production foundation.
The remaining gaps are primarily cross-cutting capabilities:
- Activity Platform
- Timeline
- Calendar Workspace
- My Day
- Manager Workspace
- Executive Workspace
- Business Event Architecture
AR.1 defines how these capabilities are introduced without breaking the existing architecture.
---
# Architecture Principles
Every proposal must follow:
## Preserve Before Replace
Reuse existing production domains whenever possible.
---
## Extension Before Refactor
Prefer adding new architectural layers.
Avoid modifying existing domains unless unavoidable.
---
## Single Source of Truth
Every business concept must have one owner.
No duplicated lifecycle.
No duplicated status.
No duplicated business meaning.
---
## Projection over Duplication
Timeline
Calendar
Dashboard
Notification
must consume projections instead of storing duplicated business data.
---
## Backward Compatibility First
Existing APIs
Existing UI
Existing Schema
must remain compatible unless a controlled migration is explicitly approved.
---
# Review Required
Review
- Business Constitution
- BU-R.1 Capability Audit
- Current Schema
- Current CRM Modules
- Current Foundation Modules
- Existing API Contracts
- Existing Permission Model
- Existing Notification Foundation
- Existing Approval Runtime
- Existing Dashboard
- Existing PDF Engine
---
# Scope
## Part 1 — Current Architecture Map
Document the current architecture.
Identify
Relationship Layer
CRM Layer
Foundation Layer
Reporting Layer
Notification Layer
Approval Layer
Storage Layer
Security Layer
Document
Responsibilities
Ownership
Dependencies
Extension points
---
## Part 2 — Target Architecture
Produce the target architecture.
Include
Relationship Layer
Activity Layer
Project Layer
Commercial Layer
Workspace Layer
Analytics Layer
Foundation Layer
Security Layer
Document
Responsibilities
Boundaries
Ownership
Interaction
---
## Part 3 — Transition Architecture
Design the transition path.
For every capability determine
Preserve
Extend
Refactor
Replace
Migration Required
Backward Compatible
Breaking Change Risk
---
## Part 4 — Activity Platform Architecture
Define
Activity Domain
Activity Service
Activity Repository
Activity API
Activity Projection
Activity Ownership
Activity Lifecycle
Integration with
Customer
Lead
Opportunity
Quotation
Notification
Calendar
Timeline
Dashboard
---
## Part 5 — Timeline Projection Architecture
Define
Timeline Source
Timeline Generator
Timeline Projection
Timeline Query
Timeline Security
Timeline Caching
Timeline Event Mapping
Confirm
Timeline is generated.
Timeline is never manually edited.
---
## Part 6 — Calendar Projection Architecture
Define
Calendar Source
Activity Projection
Milestone Projection
Reminder Projection
Approval Projection
Forecast Projection
Calendar Aggregation
Calendar Filters
Calendar Permissions
Calendar Query Strategy
---
## Part 7 — Business Event Architecture
Freeze
Domain Event Ownership
Business Event Types
Publisher
Subscriber
Timeline Consumer
Dashboard Consumer
Notification Consumer
Automation Consumer
Determine
Event Contracts
Event Naming
Event Versioning
Event Payload Strategy
---
## Part 8 — Workspace Architecture
Design
Customer Workspace
Sales Workspace
Manager Workspace
Executive Workspace
Calendar Workspace
My Day
Navigation
Permission Boundary
Workspace Composition
---
## Part 9 — Integration Strategy
Determine
Existing Module Reuse
Adapter Layer
Projection Layer
Service Composition
Shared Components
API Compatibility
Feature Flags
Migration Strategy
---
## Part 10 — Implementation Strategy
Group implementation into Epics.
Example
Epic
Activity Platform
Epic
Timeline Projection
Epic
Calendar Workspace
Epic
Customer Workspace
Epic
Manager Workspace
Epic
My Day
Epic
Business Events
Epic
Notification Expansion
Epic
Forecast Enhancement
---
# Deliverables
Produce
## 1
Current Architecture Diagram
---
## 2
Target Architecture Diagram
---
## 3
Transition Architecture Diagram
---
## 4
Domain Ownership Matrix
---
## 5
Layer Responsibility Matrix
---
## 6
Business Event Architecture
---
## 7
Activity Architecture
---
## 8
Timeline Projection Architecture
---
## 9
Calendar Projection Architecture
---
## 10
Workspace Architecture
---
## 11
Dependency Graph
---
## 12
Migration Strategy
Categorize
Preserve
Extend
Refactor
Replace
---
## 13
Breaking Change Assessment
Risk
Mitigation
Compatibility
---
## 14
Epic Roadmap
Priority
Dependency
Estimated Order
---
## 15. Domain Dependency Matrix
Produce a dependency matrix describing how each business domain depends on and is consumed by other domains.
The objective is to visualize architectural coupling before implementation begins.
For every major domain identify:
- Domain Owner
- Depends On
- Used By
- Shared Services
- Projection Consumers
- Potential Breaking Change Impact
Minimum domains:
| Domain | Depends On | Used By |
|----------|------------|---------|
| Customer | Foundation | Contact, Activity, Lead, Opportunity |
| Contact | Customer | Activity, Lead |
| Activity | Customer, Contact, Lead, Opportunity, Quotation | Calendar, Timeline, My Day, Notification |
| Lead | Customer | Opportunity |
| Opportunity | Customer, Lead | Quotation, Dashboard, Forecast |
| Quotation | Opportunity | Approval, PDF, Timeline |
| Approval | Quotation | Notification |
| Timeline | Business Events | Customer Workspace |
| Calendar | Activity + Milestones | My Day, Manager Workspace |
| Dashboard | Opportunity + Activity + Events | Manager, Executive |
Deliverables
- Domain Dependency Diagram
- Domain Dependency Matrix
- Coupling Analysis
- High-Risk Dependency List
- Recommended Decoupling Strategy
---
## 16. Preserve / Extend / Refactor Matrix
Produce the official architectural transition strategy for every major module.
Every module must be classified into one of the following categories:
- Preserve
- Preserve + Extend
- Controlled Refactor
- New Capability
- Replace (only if explicitly justified)
The objective is to minimize disruption to the existing production architecture.
Minimum matrix:
| Module | Strategy | Notes |
|----------|----------|-------|
| Organization | Preserve | Core Foundation |
| RBAC | Preserve | Existing Permission Model |
| Customer | Preserve + Extend | Add Customer Workspace capabilities |
| Contact | Preserve | Existing Sharing Rules |
| Lead | Preserve + Minor Enhancement | Align Business Blueprint |
| Opportunity | Preserve + Extend | Forecast, Activity, Hot Project |
| Quotation | Preserve | Document Lifecycle Owner |
| Approval | Preserve | Existing Approval Engine |
| PDF | Preserve | Existing Document Engine |
| Dashboard | Preserve + Extend | Business-driven Metrics |
| Notification | Preserve + Extend | Consume Business Events |
| Activity | New Capability | Shared Operational Domain |
| Timeline | New Projection | Generated View |
| Calendar | New Projection | Workspace Layer |
| My Day | New Workspace | Personalized Operational Workspace |
| Manager Workspace | New Workspace | Operational Control Tower |
| Executive Workspace | New Workspace | Strategic Monitoring |
Deliverables
- Preserve Matrix
- Extension Matrix
- Refactor Matrix
- New Capability Matrix
- Breaking Change Impact Matrix
- Migration Recommendation
Frozen Rule
Every implementation epic after AR.1 must explicitly reference this matrix before modifying any production module.
If a proposed implementation changes a module classified as "Preserve", the implementation must include an Architecture Decision Record (ADR) explaining the reason, impact analysis, rollback strategy, and backward compatibility assessment.
# Constraints
This phase is architecture only.
DO NOT
- modify schema
- modify migrations
- modify APIs
- modify services
- modify UI
- modify permissions
- modify business logic
No production implementation is allowed.
---
# Success Criteria
A complete transition architecture exists.
Every new capability has a defined architectural location.
Every existing capability has a preservation strategy.
Every integration point is documented.
Every migration path is documented.
Breaking changes are minimized.
Core CRM foundation remains preserved.
Architecture is implementation-ready.
Ready for
AR.2 Epic & Technical Design
## Architecture Governance Rule
AR.1 becomes the official architecture transition baseline.
Every future Epic, ADR, schema change, API change, or implementation task must reference:
- Business Constitution (BU-R.0 / BU-R.0.1)
- Business Capability Audit (BU-R.1)
- Architecture Transition Plan (AR.1)
Any implementation that violates the approved transition strategy must provide explicit architectural justification and receive governance approval before implementation begins.

56
plans/task-ar.2.md Normal file
View File

@@ -0,0 +1,56 @@
# Task AR.2 - Epic & Technical Design
Status: Completed
## UI / UX Design Governance
All future workspace, dashboard, calendar, timeline, activity, customer, opportunity, quotation, manager, and executive UI design work must use the `ui-ux-pro-max` skill as part of the design process.
Before proposing or implementing any UI, the agent must review:
- `layout.md`
- `docs/standards/ui-ux-rules.md`
- `docs/standards/architecture-rules.md`
- existing shadcn/ui usage patterns
- existing dashboard layout conventions
- existing CRM page/detail/table/form patterns
### Design Rules
- Preserve existing shadcn/ui foundation.
- Prefer extension over visual redesign.
- Follow existing layout rhythm unless the blueprint explicitly requires a new workspace pattern.
- Do not introduce a disconnected design system.
- New workspace UI must align with ALLA OS role-based workspace philosophy.
- Calendar, My Day, Manager Workspace, Customer Workspace, Timeline, and Activity surfaces must be designed as operational workspaces, not generic CRUD pages.
- Every UI proposal must include:
- page purpose
- primary user role
- layout structure
- key actions
- empty state
- loading state
- permission-aware visibility
- responsive behavior
- relationship to existing CRM navigation
### Required Design Inputs
Use:
- `ui-ux-pro-max` for UX quality, interaction flow, visual hierarchy, and enterprise usability
- `layout.md` for layout structure and page composition rules
- `ui-ux-rules.md` for ALLA OS UI standards
- existing shadcn/ui components as the implementation baseline
### Frozen Rule
No future workspace implementation may start with code first.
Every new workspace or major UI surface must first produce a UI/UX design note that references:
- Business Constitution
- AR.1 Architecture Transition Plan
- `layout.md`
- `ui-ux-rules.md`
- `ui-ux-pro-max`

501
plans/task-bu-r.0.1.md Normal file
View File

@@ -0,0 +1,501 @@
# Task BU-R.0.1 Workspace & Activity Business Blueprint Completion
Status: Planning
Priority: Critical
Type: Business Discovery / Business Architecture
Depends On:
- BU-R.0 Business Blueprint Freeze
---
# Objective
Complete the remaining business architecture that was intentionally left outside BU-R.0 before beginning Phase R.1 Business Capability Audit.
This phase completes the operational workspace philosophy of ALLA OS by defining:
- Activity Blueprint
- Workspace Interaction Blueprint
- Customer Workspace Blueprint
This task remains documentation-only.
No implementation, schema, API, UI, or migration work is allowed.
---
# Background
BU-R.0 successfully froze the core business architecture.
However several operational concepts still require business definition before implementation or capability audit.
The largest remaining gaps are:
- Activity Lifecycle
- Workspace Interaction
- Customer Workspace
- Daily User Workflow
Without these definitions, future Calendar, Timeline, Notification, Dashboard, and My Day implementations may diverge.
---
# Review Required
Review:
- Relationship & Sales Workspace Blueprint v1
- BU-R.0 Implementation Report
- Existing CRM UI
- Existing Customer UI
- Existing Opportunity Workspace
- Existing Quotation Workspace
- Existing Dashboard
- Existing Calendar discussions
- Existing Follow-up implementation
- Existing Task implementation
- Existing Notification implementation
Review current schema only for understanding.
Do not redesign existing foundations.
---
# Scope
## Part 1 — Activity Blueprint
Freeze Activity as the shared operational business model.
Determine
- Activity Definition
- Activity Ownership
- Activity Lifecycle
- Activity Types
- Activity Priority
- Activity Status
- Activity Assignment
- Activity Completion Rules
- Activity Visibility
- Activity Permission
- Activity Relationships
Determine whether Activity may reference
- Customer
- Contact
- Lead
- Opportunity
- Quotation
- PO
- Internal Only
Determine
- Follow-up Strategy
- Reminder Strategy
- Automation Strategy
Determine
- Calendar Synchronization
- Timeline Synchronization
- Notification Synchronization
Freeze the Activity State Machine.
---
## Part 2 — Workspace Interaction Blueprint
Freeze how each business role operates throughout an entire working day.
Define complete workflows for
Marketing
Sales
Sales Manager
Executive
Determine
Login Experience
Primary Landing Page
Workspace Navigation
My Day
Calendar Usage
Timeline Usage
Notification Usage
Customer Navigation
Lead Workflow
Opportunity Workflow
Quotation Workflow
Determine
Daily User Journey
instead of only entity relationships.
---
## Part 3 — Customer Workspace Blueprint
Freeze Customer as the Relationship Workspace.
Determine Customer Workspace layout.
Example sections
Overview
Contacts
Activities
Timeline
Leads
Opportunities
Quotations
PO
Files
Notes
Relationship Summary
Recent Visits
Upcoming Activities
Determine
Customer Timeline
Customer Relationship Health
Customer Follow-up Health
Customer Project History
Determine
Customer 360 View.
---
## Part 4 — Calendar Workspace Blueprint
Expand the Calendar philosophy.
Determine
Personal Calendar
Team Calendar
Manager Calendar
Executive Calendar
Determine
Calendar Sources
Activities
Meetings
Visits
Site Survey
Reminder
Task
Expected Award Date
Expected Delivery Date
Approval Due
Quotation Expiration
Determine
Calendar Filters
Calendar Colors
Calendar Views
Day
Week
Month
Agenda
---
## Part 5 — My Day Blueprint
Freeze
My Day
as the operational home screen.
Determine
Today's Activities
Overdue Activities
Upcoming Meetings
Pending Approvals
Hot Projects
Today's Follow-up
Urgent Quotations
Manager Tasks
Relationship Alerts
Determine
Priority Ordering
Sorting Rules
Urgency Rules
---
## Part 6 — Manager Workspace Blueprint
Freeze
Manager Workspace
Determine
Team Calendar
Team Activities
Overdue Activities
Idle Opportunities
Forecast
Hot Projects
Approval Queue
Workload Balance
Determine
Daily Management Workflow
---
## Part 7 — Business Event Blueprint
Identify every business event that should create
Timeline
Notification
Reminder
Dashboard Update
Audit Log
Future Automation
Examples
Activity Completed
Lead Assigned
Opportunity Created
Quotation Sent
Quotation Approved
Hot Project Flagged
Expected Award Date Changed
PO Received
---
# Deliverables
Produce
## 1
Activity Blueprint
---
## 2
Workspace Interaction Blueprint
---
## 3
Customer Workspace Blueprint
---
## 4
Calendar Workspace Blueprint
---
## 5
My Day Blueprint
---
## 6
Manager Workspace Blueprint
---
## 7
Business Event Matrix
---
## 8
Activity State Machine
---
## 9
Workspace Navigation Diagram
---
## 10
Customer 360 Diagram
---
## 11
Daily User Journey
Marketing
Sales
Manager
Executive
---
## 12
Updated Relationship & Sales Workspace Blueprint v1
Append these new chapters without changing frozen principles from BU-R.0.
---
# Constraints
This task is documentation only.
DO NOT
- modify schema
- modify migrations
- modify API
- modify services
- modify UI
- modify permissions
- modify database
- modify existing ADR decisions
Preserve current foundation.
Prefer extension over replacement.
---
# Success Criteria
The business operation of every role is fully documented.
Activity becomes the officially governed operational model.
Workspace interaction is fully standardized.
Customer Workspace is finalized.
Calendar philosophy is finalized.
My Day philosophy is finalized.
Manager Workspace is finalized.
Business Event model is frozen.
Blueprint reaches implementation-ready maturity.
Ready for
Phase R.1 Business Capability Audit.

597
plans/task-bu-r.0.md Normal file
View File

@@ -0,0 +1,597 @@
# Phase R.0 Business Blueprint Freeze
Status: In Progress
Priority: Critical
Type: Business Discovery / Architecture
---
# Objective
Freeze the long-term business architecture and business terminology of ALLA OS before performing any schema refactoring or implementation work.
This phase focuses on validating business concepts, ownership boundaries, user workflows, lifecycle definitions, and workspace philosophy.
No database schema, API, UI, or implementation changes are allowed during this phase.
The output of this phase becomes the official Business Blueprint for all future development.
---
# Background
ALLA OS has evolved from a traditional CRM into a Relationship-Driven Sales Workspace.
Several new business concepts have emerged during Business Discovery:
- Relationship First
- Activity-Centric Workspace
- Opportunity owns Project Lifecycle
- Quotation owns Document Lifecycle
- Calendar as a Workspace instead of a standalone module
- Timeline as a generated business view
- Customer Account Type (Customer / Prospect)
- Expected Award Date
- Hot Project Business Model
These concepts must be frozen before auditing the existing implementation.
---
# Review Required
Review all existing business decisions before making any proposal.
Must review:
- AGENTS.md
- Project Standards
- Architecture Rules
- Task Catalog
- Existing CRM Blueprint
- Business Discovery notes
- Previous ADRs
- Existing Schema
- Existing API structure
- Existing Workspace design
Do not assume any undocumented business rule.
---
# Scope
## 1. Product Vision
Freeze:
- Product Vision
- Product Mission
- Design Philosophy
- Core Principles
Validate that ALLA OS is positioned as
Relationship-Driven Sales Workspace
instead of a traditional CRM.
---
## 2. Business Terminology
Freeze official definitions.
Examples:
Customer
Prospect
Lead
Opportunity
Quotation
PO
Activity
Follow-up
Task
Reminder
Visit
Meeting
Site Survey
Presentation
Timeline
Workspace
Pipeline
Forecast
Expected Award Date
Expected Delivery Date
Hot Project
Status
Process Status
Outcome
Every business term must have exactly one official definition.
---
## 3. Customer Relationship Model
Review and freeze
Customer
Contacts
Activities
Leads
Opportunities
Quotations
PO
Validate
Customer Account Type
- Customer
- Prospect
Confirm that Customer remains the master entity.
---
## 4. Activity Model
Determine whether Activity becomes the single business activity model.
Review:
Follow-up
Visit
Meeting
Reminder
Task
Email
Phone Call
Presentation
Site Survey
Internal Task
Determine
Activity ownership
Reference strategy
Timeline strategy
Calendar strategy
Notification strategy
Future automation capability
---
## 5. Project Lifecycle
Freeze
Lead
Opportunity
Quotation
PO
Review ownership of each stage.
---
## 6. Status Strategy
Review
Lead
Opportunity
Quotation
Determine
Process Status
Operational Status
Business Outcome
Ownership
Transition Rules
State Machine
Confirm that
Opportunity owns Project Process
Quotation owns Document Status
---
## 7. Hot Project Strategy
Freeze business definition.
Determine
Purpose
Owner
Calculation
Manual Override
Approval
Dashboard behavior
Forecast relationship
---
## 8. Forecast Strategy
Freeze
Expected Award Date
Definition
Business meaning
Forecast usage
Dashboard usage
Calendar usage
Reminder usage
Freeze
Expected Delivery Date
Definition
Business meaning
Operation usage
---
## 9. Workspace Model
Review
Marketing Workspace
Sales Workspace
Manager Workspace
Executive Workspace
Determine
Daily workflow
Workspace navigation
Primary landing page
Calendar philosophy
My Day philosophy
Timeline philosophy
---
## 10. Timeline Strategy
Determine
Timeline owner
Timeline generation
Timeline source
Timeline ordering
Timeline visibility
Timeline permission
Confirm whether Timeline is generated dynamically instead of stored.
---
## 11. Calendar Strategy
Determine
Calendar data source
Activity synchronization
Opportunity synchronization
Quotation synchronization
Reminder synchronization
Manager View
Team Calendar
Personal Calendar
---
## 12. Dashboard Strategy
Review
Sales Dashboard
Manager Dashboard
Executive Dashboard
Forecast
Pipeline
Hot Project
Overdue Activities
Follow-up
KPI
---
# Deliverables
The implementation must produce
## 1
Relationship & Sales Workspace Blueprint v1
---
## 2
Business Terminology Dictionary
---
## 3
Core Business Principles
---
## 4
Business Capability Map
---
## 5
Business Lifecycle Diagram
---
## 6
Workspace Architecture
---
## 7
Customer Journey
---
## 8
Project Journey
---
## 9
Activity Journey
---
## 10
State Machine Diagram
Lead
Opportunity
Quotation
---
## 11
Gap Summary
Current System
vs
Business Blueprint
---
## 12
Recommended Refactoring Strategy
Categorize into
No Change
Enhancement
Refactor
New Capability
---
# Constraints
During this phase
DO NOT
- modify schema
- modify migrations
- modify database
- modify API
- modify services
- modify UI
- modify permissions
- modify business logic
This phase is documentation and architecture validation only.
---
# Success Criteria
Business terminology is fully standardized.
Every entity has a single owner.
No duplicated responsibility exists.
No conflicting business rules remain.
Customer Journey is finalized.
Project Journey is finalized.
Workspace philosophy is finalized.
Timeline philosophy is finalized.
Calendar philosophy is finalized.
Ready for
Phase R.1 Business Capability Audit
without changing the core architecture.
Architecture Protection Rules
- Reuse existing Foundation whenever possible.
- Prefer Extension over Replacement.
- Prefer Composition over Duplication.
- Preserve existing API compatibility unless a breaking change is approved.
- Preserve Organization, RBAC, Approval, PDF, and Document foundations.
- Any schema refactoring must include backward compatibility analysis.
- Business Blueprint has higher priority than implementation details.
---
# Implementation Progress
## 2026-07-07
Status: In Progress
Summary:
- Reviewed governance, standards, CRM terminology, security boundaries, ADR lineage, and current schema before proposing any freeze output.
- Created the first official blueprint draft:
`docs/business/relationship-sales-workspace-blueprint-v1.md`
- Recorded the review baseline, freeze decisions, and current governance gaps:
`docs/implementation/task-bu-r.0-business-blueprint-freeze-2026-07-07.md`
Current outcome:
- Phase R.0 has started as a documentation-only architecture task.
- The repo now has one central draft document for business vision, terminology, lifecycle ownership, workspace philosophy, timeline, calendar, forecast, Hot Project, and gap summary.
- Historical `enquiry` vs `opportunity` conflicts are documented for follow-up reconciliation in Phase R.1.

617
plans/task-bu-r.1.md Normal file
View File

@@ -0,0 +1,617 @@
# Task BU-R.1 Business Capability Audit
Status: Planning
Priority: Critical
Type: Business Discovery / Capability Audit
Depends On
- BU-R.0 Business Blueprint Freeze
- BU-R.0.1 Workspace & Activity Business Blueprint Completion
---
# Objective
Audit the current ALLA OS implementation against the frozen Business Blueprint.
This phase must identify which business capabilities already exist, which are partially implemented, which require enhancement, and which should be introduced as new capabilities.
The purpose is to minimize disruption to the existing architecture while ensuring future implementation aligns with the approved business blueprint.
This is a documentation and analysis phase only.
No implementation work is allowed.
---
# Background
BU-R.0 and BU-R.0.1 established the official Business Constitution of ALLA OS.
The current repository already contains a mature CRM foundation including:
- Customer
- Contact
- Lead
- Opportunity
- Quotation
- Approval
- Document
- Organization
- RBAC
- PDF
- Dashboard
The next step is to determine how closely the current implementation aligns with the frozen business architecture.
---
# Review Required
Review all relevant documentation before auditing.
Minimum review
- AGENTS.md
- Project Standards
- Architecture Rules
- UI/UX Rules
- Task Catalog
- CRM Terminology
- Relationship & Sales Workspace Blueprint
- Existing ADRs
- Current Schema
- Current CRM APIs
- Existing Dashboard
- Existing Customer Workspace
- Existing Lead Workspace
- Existing Opportunity Workspace
- Existing Quotation Workspace
- Existing Approval Module
- Existing Notification Module
Review source code only for capability verification.
Do not redesign.
Do not implement.
---
# Audit Scope
---
## Part 1 — Relationship Capability Audit
Review
Customer
Contact
Customer Account Type
Customer Ownership
Contact Sharing
Relationship Health
Customer Timeline
Customer Workspace
Determine
Existing capability
Missing capability
Refactor candidates
Future enhancements
---
## Part 2 — Activity Capability Audit
Review
Follow-up
Meeting
Visit
Phone Call
Reminder
Task
Notification
Internal Task
Determine
Current activity implementation
Duplicate activity concepts
Reusable components
Potential shared Activity model
Activity ownership
Activity lifecycle support
Calendar readiness
Timeline readiness
---
## Part 3 — Lead Capability Audit
Review
Lead lifecycle
Lead ownership
Lead assignment
Lead qualification
Lead conversion
Lead follow-up
Lead dashboard
Determine
Blueprint compliance
Missing capabilities
Legacy behaviour
---
## Part 4 — Opportunity Capability Audit
Review
Opportunity ownership
Forecast
Expected Award Date
Expected Delivery Date
Probability
Hot Project
Pipeline
Outcome
Process Status
Status
Determine
Business alignment
Schema readiness
Service readiness
UI readiness
---
## Part 5 — Commercial Capability Audit
Review
Quotation
Revision
Approval
Pricing
PDF
Customer Package
PO integration
Determine
Blueprint compliance
Missing capabilities
---
## Part 6 — Workspace Capability Audit
Review
Customer Workspace
Sales Workspace
Manager Workspace
Executive Workspace
Calendar
Timeline
My Day
Notification
Dashboard
Determine
Existing workspaces
Navigation gaps
Role support
Operational readiness
---
## Part 7 — Dashboard Capability Audit
Review
Pipeline
Forecast
Hot Project
Follow-up
Overdue Work
Sales KPI
Manager KPI
Executive KPI
Determine
Current implementation
Metric ownership
Business alignment
---
## Part 8 — Notification Capability Audit
Review
Approval Notification
Reminder
Follow-up Notification
Manager Escalation
Assignment Notification
Determine
Current notification model
Missing business events
Automation opportunities
---
## Part 9 — Business Event Audit
Review
Lead Events
Opportunity Events
Quotation Events
Approval Events
Customer Events
Determine
Existing events
Missing events
Timeline support
Calendar support
Automation readiness
---
# Deliverables
Produce
## 1
Business Capability Matrix
For every capability identify
- Existing
- Partial
- Missing
- Legacy
- Refactor Candidate
---
## 2
Capability Coverage Report
Percentage coverage
Customer
Activity
Lead
Opportunity
Quotation
Approval
Dashboard
Calendar
Timeline
Notification
Workspace
---
## 3
Gap Analysis
Current System
vs
Business Blueprint
Categorize
No Change
Enhancement
Refactor
New Capability
---
## 4
Architecture Preservation Report
Identify
Core components that should remain unchanged
Core components requiring extension only
Components requiring controlled refactoring
Potential breaking changes
Backward compatibility concerns
---
## 5
Business Capability Dependency Map
Show dependency between
Customer
Activity
Lead
Opportunity
Quotation
Approval
Dashboard
Calendar
Timeline
Notification
---
## 6
Capability Priority Matrix
Prioritize
Critical
High
Medium
Low
based on business value and implementation impact.
---
## 7
Implementation Readiness Report
Identify
Ready immediately
Requires schema enhancement
Requires service enhancement
Requires UI enhancement
Requires future phase
---
## 8
Recommended Epic Breakdown
Group future implementation into epics.
Example
Epic
Customer Workspace
Epic
Activity Platform
Epic
Calendar Workspace
Epic
Timeline
Epic
Manager Workspace
Epic
My Day
Epic
Notification
Epic
Dashboard
---
# Constraints
This phase is documentation only.
DO NOT
- modify schema
- modify database
- modify migrations
- modify APIs
- modify services
- modify UI
- modify permissions
- modify business logic
No code changes are permitted.
---
# Success Criteria
Every business capability has been audited.
Current implementation is mapped to the approved Business Blueprint.
No duplicate business capability remains unidentified.
The impact of every future feature is understood.
Core architecture preservation strategy is documented.
A prioritized implementation roadmap can be produced without re-discovering business requirements.
Ready for
BU-R.2 Domain & Schema Audit.
# Implementation Result - 2026-07-07
Status: Completed
Completed:
- audited current customer, lead, opportunity, quotation, approval, dashboard, notification, workspace, and security capabilities against the frozen BU-R.0 and BU-R.0.1 blueprint
- classified current implementation into `Existing`, `Partial`, `Missing`, `Legacy`, and `Refactor Candidate` capability states
- documented preservation-safe direction for later phases without changing code or schema
Primary findings:
- customer ownership, contact sharing, opportunity lifecycle, quotation lifecycle, approval runtime, PDF/document delivery, dashboard KPIs, and CRM security are already strong foundations and should be preserved
- follow-up capability is real across lead, opportunity, quotation, and dashboard analytics, but a shared production Activity platform does not yet exist
- customer detail is a strong partial Customer Workspace, but generated timeline, relationship health, and activity-centered workspace sections are still missing
- calendar, My Day, manager workspace, executive workspace, and generated timeline remain mostly future capability rather than current implementation
- notification foundation exists and is production-ready, but business-event coverage is still mostly approval-centric
Recorded outputs:
- [task-bu-r.1-business-capability-audit-2026-07-07.md](/C:/Users/mtpphtaps/Documents/gitea/alla-allaos-fullstack/docs/implementation/task-bu-r.1-business-capability-audit-2026-07-07.md)
Verification:
- documentation-only phase
- no schema, API, service, UI, permission, or business-logic changes made
- no build or test command required

375
plans/task-eng.0.md Normal file
View File

@@ -0,0 +1,375 @@
# Task ENG.0 - Engineering Governance & Delivery Rules
Status: Completed
Priority: Critical
Type: Engineering Governance / Technical Standards
Depends On
- BU-R.0 Business Constitution
- BU-R.0.1 Workspace & Activity Blueprint
- BU-R.1 Business Capability Audit
- AR.1 Architecture Transition Plan
- AR.2 Epic & Technical Design
- AR.2 Workspace UI/UX Design Note
---
# Objective
Establish the official engineering governance for ALLA OS.
This task defines how future implementation work must be designed, coded, reviewed, tested, and delivered while preserving the approved Business Constitution, Architecture Constitution, and UI Constitution.
The objective is to ensure every implementation follows the same engineering standards regardless of whether the contributor is a human developer or an AI agent.
---
# Background
Business architecture and transition architecture are now complete. Future work shifts from architecture planning into implementation delivery.
Without engineering governance, future implementations may diverge in:
- folder structure
- service composition
- API contracts
- projection implementation
- event handling
- UI composition
- testing
- migration
- code quality
ENG.0 freezes the engineering rules before implementation begins.
---
# Review Required
Review before producing governance.
Business
- Relationship & Sales Workspace Blueprint
- Business Capability Audit
Architecture
- Architecture Transition Plan
- Epic Technical Design
UI
- Workspace UI/UX Design Note
- `layout.md`
- `ui-ux-rules.md`
- `ui-ux-pro-max` guidance
Codebase
- `AGENTS.md`
- `project-foundations.md`
- `architecture-rules.md`
- `task-review-checklist.md`
- existing feature folder conventions
- current shared UI components
- current service layer
- repository layer
- existing API patterns
- permission model
- audit logging
- storage
- approval engine
---
# Scope
## Part 1 - Feature Architecture Rules
Freeze:
- feature folder convention
- layer responsibilities
- public API boundary
- internal modules
- shared modules
- cross-feature dependency rules
- feature registration
- import rules
- dependency direction
- allowed dependency graph
- forbidden dependency graph
## Part 2 - Service Layer Rules
Freeze:
- service composition
- business services
- query services
- projection services
- application services
- domain services
- shared services
- allowed service interaction
- business ownership
- transaction boundary
## Part 3 - Repository Standards
Freeze:
- persistence ownership
- internal repository expectations
- Drizzle placement rules
- cross-feature persistence access rules
## Part 4 - API Rules
Freeze:
- REST conventions
- request validation
- response format
- error handling
- authorization
- pagination
- filtering
- sorting
- search
- versioning strategy
- API naming
## Part 5 - Event & Projection Rules
Freeze:
- business event
- domain event
- projection builder
- timeline projection
- calendar projection
- dashboard projection
- notification projection
- projection ownership
- event publishing
- event subscription
- idempotency
## Part 6 - UI Engineering Rules
UI implementation must:
- review `layout.md`
- review `ui-ux-rules.md`
- review the Workspace UI/UX Design Note
- use `ui-ux-pro-max`
- use the existing shadcn/ui component library
Determine:
- page composition
- workspace composition
- component composition
- state management
- form strategy
- dialog strategy
- drawer strategy
- accessibility
- responsive strategy
- loading strategy
- empty state
- permission-aware rendering
No implementation may redesign the existing CRM shell without architectural approval.
## Part 7 - Code Quality Rules
Freeze:
- naming conventions
- file naming
- folder naming
- TypeScript conventions
- error handling
- logging
- validation
- code comments
- reusable utilities
- technical debt policy
## Part 8 - Database & Migration Rules
Freeze:
- migration strategy
- backward compatibility migration
- schema ownership
- projection schema
- rollback strategy
- seed strategy
## Part 9 - Testing Rules
Freeze:
- unit test
- integration test
- API test
- permission test
- projection test
- event test
- regression test
- UAT checklist
- definition done
## Part 10 - Delivery Rules
Freeze:
- epic lifecycle
- feature lifecycle
- review checklist
- architecture validation
- business validation
- UI validation
- testing
- migration
- release checklist
- rollback checklist
## Part 11 - AI Engineering Rules
Define how AI contributors must work.
Every implementation task must:
- review Business Constitution
- review Architecture Constitution
- review Engineering Constitution
- review Workspace UI Constitution
- review existing implementation first
- reuse existing modules whenever possible
Preferred change strategy:
- Preserve
- Extend
- Controlled Refactor
- Replace
Never:
- duplicate business logic
- duplicate business lifecycle
- duplicate projection logic
Always identify existing reusable services before creating new code.
---
# Deliverables
Produce:
## 1 Engineering Constitution
## 2 Feature Architecture Rules
## 3 Service Layer Standards
## 4 Repository Standards
## 5 API Standards
## 6 Business Event Engineering & Quality Standards
## 7 UI Engineering Rules
## 8 Code Quality Standards
## 9 Database & Migration Standards
## 10 Testing Standards
## 11 Definition Done for Feature, Epic, Bug Fix, Refactor
## 12 AI Implementation Checklist
Checklist implementation tasks must complete before writing code.
## 13 Code Review Checklist
- Architecture
- Business
- Security
- Performance
- Accessibility
- Maintainability
- Backward Compatibility
- UI Consistency
## 14 Engineering Decision Matrix
Classify implementation decisions as:
- Preserve
- Extend
- Refactor
- Replace
with architectural justification.
---
# Constraints
Documentation only.
DO NOT:
- modify schema
- modify migrations
- modify APIs
- modify services
- modify UI
- modify permissions
- modify runtime logic
No production implementation.
---
# Success Criteria
Engineering standards fully documented.
Every future implementation follows one engineering model.
Feature development becomes predictable.
Business Constitution, Architecture Constitution, Engineering Constitution, and UI Constitution stay aligned.
The project is ready for implementation epics.
---
# Output
- `docs/standards/engineering-constitution.md`
- `docs/implementation/task-eng.0-engineering-governance-2026-07-07.md`
---
# Engineering Governance Rule
Every future implementation epic must explicitly reference:
- Business Constitution (`BU-R.0` / `BU-R.0.1`)
- Business Capability Audit (`BU-R.1`)
- Architecture Transition Plan (`AR.1`)
- Epic Technical Design (`AR.2`)
- Workspace UI/UX Design Note (`AR.2`)
- Engineering Constitution (`ENG.0`)
before implementation begins.
No implementation may bypass governance documents.

382
plans/task-ep.1.2.md Normal file
View File

@@ -0,0 +1,382 @@
# EP.1.2 Activity Integration & Legacy Follow-up Adapter
Status: Completed
Priority: Critical
Type: Feature Integration
Depends On
- EP.1.1 Activity Domain Foundation
- ENG.0 Engineering Constitution
- AR.1 Architecture Transition Plan
- AR.2 Epic Technical Design
---
# Objective
Introduce the Activity Integration Layer that connects the new Activity Domain with existing CRM modules while preserving all legacy follow-up implementations.
This phase establishes adapter-based integration without introducing data migration, dual-write, or projection consumers.
The objective is to prepare existing modules to consume Activity incrementally while maintaining production stability.
---
# Background
EP.1.1 introduced the additive `crm_activities` write model.
Current CRM still contains multiple follow-up implementations:
- Lead (audit-log-backed)
- Opportunity (row-backed)
- Quotation (row-backed)
Business Constitution defines Activity as the long-term operational model.
Architecture Constitution requires:
- Preserve existing production behavior.
- Introduce Activity through adapters.
- Delay consolidation until adapters are validated.
---
# Review Required
Review
Business
- Relationship & Sales Workspace Blueprint
- Activity Blueprint
Architecture
- AR.1 Architecture Transition Plan
- AR.2 Epic Technical Design
Engineering
- Engineering Constitution
Implementation
- EP.1.1 Activity Foundation
- Existing Lead Follow-up
- Existing Opportunity Follow-up
- Existing Quotation Follow-up
- Existing Dashboard queries
- Existing Reports
- Existing Audit Log
UI
- Workspace UI/UX Design Note
- layout.md
- ui-ux-rules.md
- ui-ux-pro-max
---
# Scope
## Part 1 — Legacy Follow-up Adapter Layer
Introduce adapter interfaces.
Create
LeadFollowUpAdapter
OpportunityFollowUpAdapter
QuotationFollowUpAdapter
Responsibilities
Read legacy follow-up
Translate legacy model
Produce Activity-compatible DTO
No database migration.
No dual write.
---
## Part 2 — Activity Context Integration
Integrate Activity with
Customer
Contact
Lead
Opportunity
Quotation
PO
Activity must resolve context without changing ownership of those domains.
---
## Part 3 — Activity Type Governance
Freeze the official activity catalog.
Minimum types
- Meeting
- Visit
- Phone Call
- Email
- Site Survey
- Presentation
- Reminder
- Internal Task
- Follow-up
- Delivery Coordination
- Approval Action
Introduce `crm_activity_type` master options.
No hard-coded types.
---
## Part 4 — Activity Context Strategy
Freeze
contextType
contextId
supported contexts
Exactly one primary context.
Optional related references.
Prepare Timeline and Calendar compatibility.
---
## Part 5 — Activity Number Strategy
Introduce configurable document numbering.
Example
ACT2607-0001
Support
Organization
Branch
Period
Sequence
Reuse existing Document Sequence Foundation.
---
## Part 6 — Activity Attachment Foundation
Integrate Activity with existing Storage Foundation.
Support
Images
PDF
Office Documents
Voice Recording
Drawing
No new storage engine.
Reuse current abstraction.
---
## Part 7 — Activity UI Integration
Extend existing pages.
Customer Detail
Opportunity Detail
Quotation Detail
Lead Detail
Add
Recent Activities
Create Activity
Activity Summary
Do NOT replace Follow-up UI.
Run both in parallel.
---
## Part 8 — Permission Integration
Reuse existing CRM permission model.
Support
Owner
Assignee
Manager
Administrator
Organization
Branch
Product Type
Pricing visibility
Internal-only Activity
---
## Part 9 — Compatibility Layer
Introduce compatibility interfaces.
Legacy Follow-up
Activity Adapter
Activity DTO
Current consumers remain unchanged.
No existing API removed.
No schema migration.
---
## Part 10 — Migration Readiness
Produce
Adapter validation
Compatibility report
Legacy usage inventory
Projection readiness report
Migration checklist
No migration execution.
---
# Deliverables
1. Lead Follow-up Adapter
2. Opportunity Follow-up Adapter
3. Quotation Follow-up Adapter
4. Activity Context Integration
5. Activity Type Governance
6. Activity Numbering Strategy
7. Activity Attachment Foundation
8. Activity UI Integration
9. Compatibility Layer
10. Migration Readiness Report
---
# Constraints
Must preserve
- Existing Follow-up
- Existing APIs
- Existing Reports
- Existing Dashboard
- Existing Audit History
- Existing Permissions
No Timeline.
No Calendar.
No Notification.
No My Day.
No Dashboard Projection.
No Event Publishing.
No data migration.
No dual write.
---
# Acceptance Criteria
- Activity integrates with all major CRM modules.
- Existing Follow-up continues to function.
- Adapter Layer becomes the only integration point.
- Activity Types are centrally governed.
- Activity Numbering follows Document Sequence Foundation.
- Activity Attachments reuse existing Storage Foundation.
- No production behavior changes.
- No breaking API changes.
- No duplicated business logic.
- No duplicated lifecycle.
---
# Success Criteria
Activity is fully integrated into the CRM foundation.
Legacy Follow-up remains operational.
All future projection epics can consume Activity through stable interfaces.
Repository remains backward compatible.
Ready for
EP.1.3 Business Event Foundation & Projection Contracts.

1034
plans/task-ep.1.3.md Normal file

File diff suppressed because it is too large Load Diff

909
plans/task-ep.1.4.1.md Normal file
View File

@@ -0,0 +1,909 @@
# Task EP.1.4.1 Transactional Outbox Activation & Worker Runtime
Status: Completed
Priority: Critical
Type: Platform Runtime / Delivery Reliability / Background Processing
## Depends On
* EP.1.1 Activity Domain Foundation
* EP.1.2 Activity Integration & Legacy Follow-up Adapter
* EP.1.3 Business Event Foundation
* EP.1.4 Projection Foundation & Delivery Reliability
* AR.1 Architecture Transition Plan
* AR.2 Epic & Technical Design
* ENG.0 Engineering Constitution
---
# Objective
Activate the Transactional Outbox delivery model introduced in EP.1.4 and provide a production-ready worker runtime for reliable Business Event dispatch.
This task must ensure that:
* source-domain mutations and Business Event outbox persistence occur atomically
* only committed outbox events are dispatched
* projection consumers process events independently and idempotently
* worker execution is safe across multiple application instances
* retryable failures are rescheduled
* terminal failures are moved to governed dead-letter state
* successful source mutations are never reported as failed merely because a projection consumer fails after commit
* existing CRM APIs and business behavior remain backward compatible
The first activated source domain is Activity.
Other source domains may adopt the same transaction pattern in later controlled tasks.
---
# Background
EP.1.3 introduced:
* canonical `BusinessEvent`
* Event Registry
* Publisher and Dispatcher abstractions
* Activity lifecycle event publication
* Event Sequence Matrix
* Event Consumer Matrix
EP.1.4 introduced:
* `business_event_outbox`
* projection consumer checkpoints
* projection dead letters
* projection rebuild runs
* projection health snapshots
* persistent projection runtime contracts
* retry policy
* Projection Registry
* no-op skeleton consumers
However, production delivery guarantees are not active yet because:
1. Activity source mutations do not yet persist events atomically through the outbox in every mutation path.
2. No worker currently drains pending outbox events.
3. No multi-instance-safe event claiming process exists.
4. No operational retry or dead-letter processing loop is active.
5. Manual retry and drain contracts are not yet available.
EP.1.4.1 activates these foundations.
---
# Architecture Principles
## 1. Atomic Source Mutation and Event Persistence
The source mutation and related outbox event must use the same database transaction.
Required pattern:
```text
BEGIN
Update source-domain state
Insert Business Event Outbox record
Write mandatory Audit Log where required
COMMIT
```
If outbox persistence fails inside the transaction, the source mutation must roll back.
---
## 2. Post-Commit Consumer Processing
Projection consumers process events only after the source transaction commits.
A projection failure must not change the result of the already committed source mutation.
Example:
```text
Activity completed
Activity row + Outbox event committed
API returns success
Worker dispatches event
Timeline consumer fails
Checkpoint schedules retry
```
---
## 3. At-Least-Once Delivery
The runtime must assume that an event may be delivered more than once.
Exactly-once transport is not required.
Consumer idempotency and persistent checkpoints provide duplicate protection.
---
## 4. Consumer Isolation
An event may have multiple consumers.
Example:
```text
activity.completed
├── Timeline
├── Calendar
├── Dashboard
└── Notification
```
One failed consumer must not erase successful processing by other consumers.
Retries must target only incomplete consumers.
---
## 5. Multi-Instance Safety
More than one application or worker process may run concurrently.
Only one worker may claim a given event delivery unit at a time.
The design must support:
* database-level claiming
* lease expiration
* stale claim recovery
* duplicate-safe retries
* graceful worker shutdown
---
## 6. Preserve Existing Behavior
Existing Activity APIs and source-domain behavior remain unchanged from the callers perspective.
This task must not replace:
* existing follow-up APIs
* Dashboard datasets
* Report datasets
* existing Notification behavior
* existing Approval notification flow
* recent-activity compatibility reads
---
# Review Required
## Governance and Architecture
* `AGENTS.md`
* `docs/standards/engineering-constitution.md`
* `docs/standards/project-foundations.md`
* `docs/standards/architecture-rules.md`
* `docs/standards/task-review-checklist.md`
* `docs/security/crm-authorization-boundaries.md`
* AR.1 Architecture Transition Plan
* AR.2 Epic & Technical Design
## Existing Implementations
* EP.1.3 Business Event Foundation report and code
* EP.1.4 Projection Foundation report and code
* Activity service mutation paths
* Business Event publisher
* persistent outbox publisher
* projection runtime
* projection consumer checkpoints
* projection dead letters
* projection health snapshots
* current Drizzle transaction conventions
* existing scheduled-job or worker patterns
* current application startup and shutdown behavior
* current deployment topology and Docker runtime, where documented
---
# Scope
## Part 1 — Activity Transaction Boundary Refactor
Refactor Activity mutation paths to support transaction-scoped persistence.
Minimum operations:
* create
* update
* assign
* reassign
* start
* reschedule
* complete
* cancel
* delete, if governed deletion remains supported
Each operation must persist:
* Activity mutation
* Business Event Outbox record
* required Audit Log record
inside one shared transaction where applicable.
### Rules
* Route Handlers remain thin.
* Activity Service owns business transitions.
* Transaction object must be propagated through approved service and persistence seams.
* No nested independent transaction may break atomicity.
* Event payload is created from the successful mutation result.
* An event must never be inserted before business validation completes.
---
## Part 2 — Transaction-Aware Outbox Publisher
Implement or complete a transaction-aware outbox publisher.
Recommended contract:
```ts
export interface TransactionalBusinessEventPublisher {
enqueue(
transaction: DatabaseTransaction,
event: BusinessEvent
): Promise<void>;
enqueueMany(
transaction: DatabaseTransaction,
events: BusinessEvent[]
): Promise<void>;
}
```
### Responsibilities
* validate registry entry
* validate schema version
* validate payload
* persist event using the supplied transaction
* preserve correlation and causation metadata
* prevent duplicate event insertion where an operation idempotency key exists
* not dispatch consumers synchronously
---
## Part 3 — Outbox Worker Runtime
Introduce a worker that drains committed outbox events.
Worker lifecycle:
```text
poll
claim batch
load active consumers
dispatch each incomplete consumer
record checkpoints
mark event delivery state
sleep / repeat
```
### Worker Configuration
Support configurable:
* enabled state
* polling interval
* batch size
* maximum concurrent events
* maximum concurrent consumers
* claim lease duration
* stale claim threshold
* worker instance identifier
* graceful shutdown timeout
Configuration must use existing environment and configuration conventions.
---
## Part 4 — Multi-Instance Event Claiming
Implement a database-safe claiming strategy.
Approved approaches may include:
* `FOR UPDATE SKIP LOCKED`
* atomic conditional update with lease token
* another PostgreSQL-safe claiming mechanism
Each claimed record should track, as appropriate:
* claimed by
* claimed at
* lease expires at
* processing started at
* attempt count
### Rules
* two workers must not actively process the same event claim concurrently
* expired claims must be recoverable
* a crashed worker must not permanently block delivery
* claiming must be organization-safe but may process multiple organizations in one batch
---
## Part 5 — Event and Consumer Completion Semantics
Freeze and implement event completion rules.
Recommended model:
* Outbox state represents event dispatch lifecycle.
* Consumer checkpoint represents per-consumer result.
* Event is terminally completed only when all applicable active consumers reach a terminal state.
Terminal consumer states:
* completed
* skipped unsupported version, when governed as terminal
* dead letter
* explicitly ignored by registry policy
### Important Rule
A successful consumer must not be called again merely because another consumer failed.
---
## Part 6 — Retry Processor
Activate retry processing using the EP.1.4 policy.
Default policy:
```text
attempt 1: immediate
attempt 2: +1 minute
attempt 3: +5 minutes
attempt 4: +30 minutes
attempt 5: dead letter
```
Implement:
* retry scheduling
* retryable-error classification
* non-retryable-error classification
* next-attempt selection
* attempt count update
* consumer-specific retry
* jitter where appropriate
* stale-processing recovery
Retry values must be configurable or governed centrally.
---
## Part 7 — Dead-Letter Runtime
Activate dead-letter creation when:
* maximum retries are exceeded
* failure is explicitly non-retryable
* event version is unsupported and policy requires review
* registry or payload incompatibility cannot be resolved automatically
Dead-letter records must include:
* event reference
* consumer name
* event type
* organization id
* attempt count
* sanitized error code
* sanitized error message
* first failure time
* final failure time
* resolution status
Do not duplicate unrestricted event payloads into dead-letter storage.
---
## Part 8 — Manual Retry and Drain Service Contracts
Provide protected service contracts for operations support.
Minimum capabilities:
* drain pending outbox events
* retry one failed consumer checkpoint
* retry all failed consumers for one event
* retry one dead-letter item
* mark dead letter resolved with reason
* inspect sanitized processing status
* recover stale claims
Recommended service methods:
```ts
drainOutbox(options)
retryConsumer(eventId, consumerName, actor)
retryEvent(eventId, actor)
retryDeadLetter(deadLetterId, actor)
resolveDeadLetter(deadLetterId, reason, actor)
recoverStaleClaims(options)
```
Public UI is not required.
Admin API routes are optional and should only be added when authorization and operational need are clearly defined.
---
## Part 9 — Worker Startup Strategy
Define how the worker runs in current deployment environments.
Evaluate:
* worker inside application process
* dedicated Node worker process
* scheduled CLI drain command
* separate Docker service using the same application image
Preferred production direction:
> Dedicated worker process or service using shared runtime modules.
The implementation must not accidentally start duplicate unmanaged polling loops during:
* Next.js hot reload
* serverless request execution
* build phase
* migration commands
* test execution
---
## Part 10 — Graceful Shutdown
Worker shutdown must:
* stop claiming new events
* allow active handlers to finish within timeout
* release or expire unfinished leases safely
* flush structured logs
* report final worker status
Handle common termination signals according to runtime conventions.
---
## Part 11 — Projection Health Updates
Update projection health from real worker processing.
Track:
* last event observed
* last successful event
* pending count
* processing count
* retry count
* dead-letter count
* unsupported-version count
* last failure time
* processing lag
* worker heartbeat
* last successful drain
Health storage must remain payload-free.
---
## Part 12 — Delivery SLA Baseline
Freeze initial operational targets.
Suggested targets:
| Capability | Initial Target |
| ---------------------------------------------- | ----------------------------------------------: |
| Outbox event available after commit | immediate |
| Worker polling interval | 15 seconds |
| Activity projection delivery under normal load | under 10 seconds |
| Retry scheduling accuracy | within one polling interval |
| Stale claim recovery | within lease duration plus one polling interval |
| Worker health heartbeat | every 3060 seconds |
Final values must reflect deployment constraints and can be classified as initial non-binding SLOs.
---
## Part 13 — Security and Authorization
Worker processing must:
* maintain organization scoping
* not bypass pricing-sensitive event restrictions
* not expose internal-only Activity details
* use consumer-specific security rules
* avoid persisting decrypted credentials or secrets
* sanitize operational errors
* audit manual retry and dead-letter resolution actions
Manual operations must require explicit foundation or system administration permission.
---
## Part 14 — Existing Publisher Compatibility
Preserve the existing in-process publisher where needed for:
* tests
* non-production isolated execution
* compatibility consumers not yet cut over
However, Activity production mutation paths must use the transactional outbox publisher after activation.
Do not publish the same Activity event through both in-process and outbox paths.
Add a guard or configuration strategy preventing accidental double publication.
---
## Part 15 — Consumer Registration Snapshot
Freeze the consumer set used when determining applicable checkpoints.
Define behavior when:
* a new consumer is registered after an event was completed
* a consumer is disabled
* a consumer is renamed
* a consumers supported versions change
* an event has no consumers
* a projection remains contract-only
Recommended approach:
* persist or deterministically resolve applicable consumers at dispatch time
* document replay or backfill behavior for newly introduced consumers
* avoid reopening old completed events automatically without explicit rebuild
---
## Part 16 — Delivery State Machine
Freeze the outbox delivery lifecycle.
Recommended model:
```text
pending
claimed
processing
├── completed
├── retry_scheduled
└── dead_letter
```
Possible recovery paths:
```text
claimed / processing
↓ lease expired
pending
```
Consumer checkpoint lifecycle:
```text
pending
processing
├── completed
├── retry_scheduled
├── skipped_unsupported_version
└── dead_letter
```
Implement valid transitions and reject invalid direct state jumps.
---
## Part 17 — End-to-End Reliability Scenario
Implement an end-to-end validation path for at least one Activity operation.
Required scenario:
```text
Complete Activity
Activity mutation persisted
activity.completed Outbox event persisted in same transaction
API returns success
Worker claims event
Skeleton consumers execute
Consumer checkpoints become completed
Duplicate dispatch is skipped
```
Failure variation:
```text
One consumer fails
Activity remains completed
Other consumers remain completed
Failed consumer schedules retry
Retry completes or moves to dead letter
```
---
# Deliverables
## 1. Activity Transactional Outbox Adoption
Atomic source mutation, audit, and event enqueue.
## 2. Transaction-Aware Outbox Publisher
Shared transaction-scoped persistence contract.
## 3. Outbox Worker Runtime
Polling, claiming, dispatch, and completion processing.
## 4. Multi-Instance Claiming Strategy
Lease-based or lock-based safe batch processing.
## 5. Per-Consumer Dispatch Semantics
Independent checkpoints and isolated retries.
## 6. Retry Processor
Configured retry and backoff implementation.
## 7. Dead-Letter Runtime
Terminal failure persistence and recovery contract.
## 8. Manual Retry and Drain Services
Protected operational service interfaces.
## 9. Worker Startup and Deployment Strategy
Current development, UAT, and production execution model.
## 10. Graceful Shutdown Handling
Safe worker termination behavior.
## 11. Projection Health Integration
Real worker health and lag metrics.
## 12. Delivery SLA Baseline
Initial delivery and recovery targets.
## 13. Security and Operations Matrix
Worker, retry, dead-letter, and administrative access rules.
## 14. Existing Publisher Compatibility Strategy
No-double-publication and transition rules.
## 15. Consumer Registration Strategy
Rules for new, disabled, renamed, and version-changed consumers.
## 16. Delivery State Machine
Outbox and checkpoint lifecycle rules.
## 17. End-to-End Reliability Tests
Atomicity, worker delivery, retry, duplicate, and recovery scenarios.
## 18. EP.1.5 Readiness Report
Confirm that Timeline can rely on durable incremental Activity events.
---
# Constraints
Must preserve:
* existing Activity API contracts
* Activity lifecycle business behavior
* legacy follow-up APIs and storage
* recent-activity compatibility layer
* existing Dashboard and Report consumers
* existing Approval and Notification behavior
* existing Audit Log behavior
* existing CRM security and pricing boundaries
* EP.1.3 Event Contract and Registry
* EP.1.4 Projection Registry and checkpoint model
Do not implement:
* final Timeline projection
* Timeline UI
* final Calendar projection
* Calendar UI
* Notification event expansion
* My Day
* Manager Workspace
* Executive Workspace
* Relationship Health
* Forecast replacement
* external Kafka, RabbitMQ, or Redis Streams
* legacy follow-up migration
* follow-up dual-write
* Dashboard source replacement
Do not start unmanaged worker loops inside request handlers or Client Components.
No breaking API changes.
---
# Compatibility Strategy
Activity is the first source domain activated with transactional outbox delivery.
Other source domains remain on their current event or notification paths until dedicated cutover tasks are approved.
During transition:
* Activity uses transactional outbox.
* Existing Approval notifications remain unchanged.
* Existing in-process publisher remains available for tests and approved compatibility use.
* No event may be emitted through both outbox and in-process production paths.
* Existing projection skeletons remain non-user-facing.
---
# Testing Requirements
## Atomicity Tests
* Activity mutation and outbox insert commit together
* outbox insert failure rolls back Activity mutation
* validation failure creates neither mutation nor event
* audit failure behavior follows existing transaction governance
* multiple events enqueue in deterministic sequence
## Worker Claim Tests
* one worker claims one event
* two workers do not process the same active lease
* stale lease becomes recoverable
* batch size is honored
* claim order is deterministic where required
* graceful shutdown stops new claims
## Consumer Tests
* all active consumers receive the event
* one failure does not invalidate successful consumers
* completed checkpoint skips duplicate execution
* unsupported version follows policy
* no-consumer event terminates safely
* disabled consumer behavior follows registry rule
## Retry Tests
* retryable failure schedules next attempt
* non-retryable failure creates dead letter
* maximum attempts create dead letter
* retry affects only incomplete consumer
* manual retry does not rerun completed consumers
* dead-letter retry preserves original event id
## Restart and Recovery Tests
* pending event survives process restart simulation
* retry-scheduled checkpoint survives restart
* stale processing claim is recovered
* duplicate delivery after restart creates no duplicate effect
## Security Tests
* organization isolation
* sensitive payload remains minimized
* dead-letter error is sanitized
* manual retry requires authorization
* internal-only Activity does not leak through operational status APIs
## Compatibility Tests
* Activity API response remains unchanged
* existing recent-activity reads remain unchanged
* existing follow-up APIs remain unchanged
* Dashboard and Reports remain unchanged
* Approval notifications remain unchanged
* no duplicate Activity Business Event publication occurs
## End-to-End Tests
* Activity create through outbox to consumer completion
* Activity complete through outbox to consumer completion
* consumer failure followed by successful retry
* consumer failure reaching dead letter
* duplicate dispatch skipped
* stale claim recovery
---
# Acceptance Criteria
* Activity source mutations persist outbox events atomically.
* Event persistence failure inside the transaction rolls back the Activity mutation.
* Worker dispatch occurs only after transaction commit.
* Activity APIs return success even when a non-owning consumer later fails.
* Outbox events are claimed safely in multi-instance execution.
* Consumer checkpoints are persistent and idempotent.
* Successful consumers are not rerun because another consumer failed.
* Retry scheduling and backoff operate according to policy.
* Terminal failures create sanitized dead-letter records.
* Manual retry and stale-claim recovery service contracts exist.
* Worker can shut down gracefully.
* Projection health reflects actual worker state.
* No Activity event is double-published through in-process and outbox publishers.
* Existing Follow-up, Dashboard, Report, Approval, Notification, and recent-activity behavior remains unchanged.
* End-to-end atomicity, dispatch, duplicate, retry, and recovery tests pass.
* TypeScript, migration checks, targeted lint, and worker/runtime tests pass.
---
# Success Criteria
ALLA OS has an active, production-ready Transactional Outbox delivery path for Activity Business Events.
Source mutations and event persistence are atomic.
Projection delivery is post-commit, independently retryable, idempotent, observable, and safe across process restarts and multiple worker instances.
Future Timeline and Calendar projections can rely on durable incremental Activity events without coupling projection failures to source-domain mutation results.
Ready for:
# EP.1.5 Timeline Projection Foundation

971
plans/task-ep.1.4.md Normal file
View File

@@ -0,0 +1,971 @@
# Task EP.1.4 Projection Foundation & Delivery Reliability
Status: Completed
Priority: Critical
Type: Platform Foundation / Projection Runtime / Delivery Reliability
## Depends On
* EP.1.1 Activity Domain Foundation
* EP.1.2 Activity Integration & Legacy Follow-up Adapter
* EP.1.3 Business Event Foundation
* BU-R.0 Business Blueprint Freeze
* BU-R.0.1 Workspace & Activity Business Blueprint Completion
* BU-R.1 Business Capability Audit
* AR.1 Architecture Transition Plan
* AR.2 Epic & Technical Design
* ENG.0 Engineering Constitution
---
# Objective
Introduce the shared Projection Foundation and reliable Business Event delivery model for ALLA OS.
This task establishes the runtime required for future Timeline, Calendar, Dashboard, Notification, My Day, Manager Workspace, Executive Workspace, Forecast, and Relationship Health projections.
The task must ensure that:
* successful source-domain mutations are not reported as failed merely because a projection consumer fails
* projection consumers process events idempotently
* failed projection processing is observable and retryable
* projections can be rebuilt from governed source data
* projection state remains derived and non-authoritative
* current production CRM behavior remains backward compatible
This task does not implement final Timeline, Calendar, My Day, Manager Workspace, Executive Workspace, or dashboard replacement features.
---
# Background
EP.1.3 introduced:
* canonical `BusinessEvent`
* machine-readable Event Registry
* event ownership rules
* in-process publisher and dispatcher
* Activity lifecycle event publishing
* Event Sequence Matrix
* Event Consumer Matrix
* replay contracts
* versioning and idempotency contracts
The remaining reliability gaps are:
1. subscriber failure may currently cause publisher dispatch to fail after the source mutation has already succeeded
2. idempotency is in-memory and does not survive process restart or multi-instance deployment
3. no persistent event delivery or projection processing state exists
4. replay is contract-only because there is no persistent event source
5. projections do not yet have a shared runtime, checkpoint, rebuild, retry, or health model
EP.1.4 resolves these gaps before business-facing projection features are implemented.
---
# Architecture Principles
## 1. Source Mutation Independence
A committed source-domain mutation must not be rolled back or reported as unsuccessful solely because a non-owning projection consumer failed.
Example:
```text
Activity completed successfully
activity.completed emitted
Timeline projection fails
Activity remains completed
Projection failure is recorded for retry
```
---
## 2. Eventual Consistency
Timeline, Calendar, Dashboard, Notification, My Day, Forecast, and other projections are eventually consistent read models.
They must not become authoritative owners of source-domain lifecycle state.
---
## 3. Persistent Idempotency
Projection consumers must prevent duplicate side effects across:
* retries
* process restarts
* multi-instance deployment
* repeated event delivery
* manual rebuild operations
In-memory duplicate detection is insufficient for production projection consumers.
---
## 4. Rebuildable Projections
Every projection must be reproducible from governed source data, persisted business events, or an approved hybrid strategy.
Deleting or rebuilding a projection must not delete operational business records.
---
## 5. Preserve Before Replace
Current dashboard, report, follow-up, approval, notification, and detail-page behavior remains operational.
Projection consumers are introduced additively before any existing consumer is replaced.
---
## 6. Security at Projection Boundaries
Projection reads and writes must preserve:
* organization isolation
* branch and product scope
* CRM record visibility
* internal-only visibility
* pricing-sensitive redaction
* source-domain authorization boundaries
Projection storage must not become a route around source security rules.
---
# Review Required
## Governance
* `AGENTS.md`
* `docs/standards/engineering-constitution.md`
* `docs/standards/project-foundations.md`
* `docs/standards/architecture-rules.md`
* `docs/standards/task-review-checklist.md`
* `docs/security/crm-authorization-boundaries.md`
## Business and Architecture
* Relationship & Sales Workspace Blueprint
* BU-R.1 Business Capability Audit
* AR.1 Architecture Transition Plan
* AR.2 Epic & Technical Design
## Existing Implementation
* EP.1.1 Activity Domain Foundation
* EP.1.2 Activity Integration & Legacy Follow-up Adapter
* EP.1.3 Business Event Foundation
* Business Event Registry
* Event Sequence Matrix
* Event Consumer Matrix
* in-process dispatcher
* Activity event publishers
* current Audit Log foundation
* current Notification foundation
* current Dashboard and Report services
* current Approval event behavior
* existing database transaction patterns
* current background-job or scheduled-task foundations, if any
---
# Scope
## Part 1 — Delivery Semantics
Freeze the official event delivery semantics.
### Required Rules
* Business mutation commits first.
* Non-owning consumer failure must not undo a committed source mutation.
* Event delivery status must be distinguishable from mutation status.
* Consumers may be synchronous internally, but projection processing must follow post-commit semantics.
* Source services must not return a misleading mutation failure after successful commit.
* Critical synchronous side effects, if any, must be explicitly approved and documented.
### Required Decision
Select and document the initial delivery strategy:
* Transactional Outbox
* Post-commit persistent delivery record
* Hybrid source-query plus event checkpoint strategy
* another approach explicitly justified against AR.1 and ENG.0
Preferred direction:
> Transactional Outbox or an equivalent persistent post-commit delivery mechanism.
---
## Part 2 — Persistent Event Delivery Model
Introduce persistent delivery records for Business Events.
Recommended responsibilities:
* persist canonical event envelope
* record publication state
* record availability for dispatch
* record dispatch attempts
* record successful completion
* record dead-letter or terminal failure
* preserve correlation and causation metadata
* support organization-scoped querying
Recommended conceptual states:
```text
pending
processing
completed
failed
dead_letter
```
### Rules
* Event records are immutable business facts after publication.
* Delivery metadata may change independently.
* Event payload must continue respecting data minimization and pricing sensitivity.
* A persistent delivery record does not make Event storage the owner of source lifecycle state.
---
## Part 3 — Transactional Outbox Decision and Foundation
Assess and, if approved, implement the Outbox foundation.
### Outbox Requirements
* source mutation and event persistence occur in the same database transaction
* dispatcher processes only committed events
* retries are safe
* duplicate delivery is expected and handled
* outbox cleanup or retention is governed
* events are organization scoped
* no external broker is required
### Deliverables
* Outbox architecture decision
* schema or storage contract
* transaction integration pattern
* dispatcher polling or draining strategy
* failure recovery behavior
* retention recommendation
If Outbox implementation is deferred, an equivalent persistent reliability mechanism must be approved and documented.
---
## Part 4 — Projection Consumer Runtime
Introduce the shared Projection Consumer Runtime.
Minimum contract:
```ts
export interface ProjectionConsumer<TEvent extends BusinessEvent = BusinessEvent> {
readonly consumerName: string;
readonly supportedEventTypes: string[];
readonly supportedVersions: Record<string, number[]>;
handle(event: TEvent): Promise<ProjectionHandleResult>;
}
```
Runtime responsibilities:
* resolve registered consumers
* validate supported event version
* acquire idempotency guard
* execute consumer
* record result
* release or finalize checkpoint
* record duration and failure details
* schedule retry when appropriate
---
## Part 5 — Persistent Consumer Checkpoints
Introduce persistent consumer processing state.
Minimum uniqueness:
```text
consumerName + eventId
```
Recommended data:
* consumer name
* event id
* event type
* schema version
* organization id
* processing status
* attempt count
* first attempted at
* last attempted at
* completed at
* next retry at
* error code
* sanitized error message
* processing duration
### Required States
```text
pending
processing
completed
retry_scheduled
failed
dead_letter
skipped_unsupported_version
```
### Rules
* completed checkpoints prevent duplicate projection effects
* failed consumers may retry independently
* one consumer failure must not block unrelated consumers permanently
* checkpoints remain technical delivery state, not business lifecycle state
---
## Part 6 — Retry Policy
Freeze retry behavior.
Determine:
* retryable error categories
* non-retryable error categories
* maximum attempts
* exponential or fixed backoff
* jitter
* retry scheduling
* dead-letter threshold
* manual retry support
* organization-level safety limits
Recommended defaults must be documented rather than hidden in code.
### Example
```text
attempt 1: immediate
attempt 2: +1 minute
attempt 3: +5 minutes
attempt 4: +30 minutes
attempt 5: dead letter
```
Exact values must align with existing operational standards.
---
## Part 7 — Dead-Letter and Failure Records
Introduce a governed dead-letter model for projection failures that cannot be processed automatically.
Required capabilities:
* retain failed event reference
* retain consumer name
* retain sanitized failure information
* retain attempt history
* support manual review
* support manual retry
* support mark-resolved with reason
* prevent sensitive payload exposure
No user-facing operations UI is required in this task unless explicitly approved.
An admin/service interface and documentation are sufficient.
---
## Part 8 — Projection Registry
Create the machine-readable Projection Registry.
Every projection definition must document:
* projection name
* owning feature
* consumed event types
* supported versions
* rebuild source
* persistence strategy
* security classification
* idempotency key
* retry policy
* retention policy
* implementation status
Minimum planned projections:
* Timeline
* Calendar
* Dashboard
* Notification
* My Day
* Manager Workspace
* Executive Workspace
* Relationship Health
* Forecast
### Example
```ts
export type ProjectionDefinition = {
projectionName: string;
consumerName: string;
consumedEventTypes: string[];
supportedVersions: Record<string, number[]>;
rebuildStrategy: "source-query" | "event-replay" | "hybrid";
persistence: "query-time" | "cached-read-model" | "materialized";
securityClassification: string;
implementationStatus: "contract-only" | "active" | "deprecated";
};
```
---
## Part 9 — Projection Storage Standards
Freeze the allowed projection storage patterns.
Supported patterns:
### Query-Time Projection
* calculated from source domains when requested
* no persisted read-model table
* suitable for low-volume or early rollout
### Cached Read Model
* persisted derivative data
* invalidated or updated by events
* fully rebuildable
### Materialized Projection
* optimized projection table or materialized view
* suitable for high-volume Timeline, Calendar, Dashboard, or reporting use cases
* never authoritative
### Rules
* projection tables must include organization scope
* projection data must be rebuildable
* source IDs and event provenance must be retained
* unique constraints must protect against duplicate projection rows
* deletion of projection data must not delete source records
* projection schema changes require rebuild compatibility analysis
---
## Part 10 — Initial Projection Skeletons
Create contracts and no-op or test projection consumers for:
* Timeline
* Calendar
* Notification
* Dashboard
These consumers must validate:
* registration
* version support
* idempotency
* checkpointing
* retry behavior
* consumer isolation
They must not yet implement business-facing projection behavior.
No final projection tables or UI are required.
---
## Part 11 — Hybrid Rebuild Strategy
Freeze the initial rebuild strategy.
Recommended strategy:
```text
Initial build
Governed source queries + legacy adapters
Incremental updates
Business Events
Recovery
Source rebuild + event checkpoint reconciliation
```
This hybrid strategy is preferred because:
* legacy follow-up events were not historically persisted
* current source tables remain authoritative
* Activity and new domain events can support incremental projection updates
* migration risk remains controlled
### Required Outputs
* source-query rebuild contract
* event-based incremental contract
* legacy adapter role
* reconciliation rules
* cutover criteria
* rollback behavior
---
## Part 12 — Projection Rebuild Contract
Implement or define a shared rebuild contract.
Suggested interface:
```ts
export type ProjectionRebuildRequest = {
projectionName: string;
organizationId: string;
entityType?: string;
entityId?: string;
occurredFrom?: string;
occurredTo?: string;
dryRun?: boolean;
resetExisting?: boolean;
};
```
Required behavior:
* organization scoped
* permission protected
* dry-run supported
* progress report supported
* idempotent
* restartable
* source strategy recorded
* existing projection reset explicitly controlled
No public UI is required.
---
## Part 13 — Projection Health Model
Define projection operational health.
Suggested statuses:
```text
healthy
lagging
degraded
failed
rebuilding
paused
```
Track:
* last processed event
* last successful processing time
* pending count
* retry count
* dead-letter count
* unsupported-version count
* processing lag
* average processing duration
* last rebuild time
* rebuild source
Projection health must be observable without exposing sensitive business payloads.
---
## Part 14 — Delivery Observability
Add structured observability for:
* event persisted
* event ready
* dispatch started
* dispatch completed
* consumer started
* consumer completed
* consumer retry scheduled
* consumer failed
* dead-letter created
* duplicate skipped
* unsupported version skipped
* projection rebuild started
* projection rebuild completed
* projection rebuild failed
Required metadata:
* event id
* event type
* schema version
* organization id
* correlation id
* causation id
* consumer name
* projection name
* attempt
* duration
* status
* sanitized error code
---
## Part 15 — Security and Data Redaction
Freeze projection security rules.
### Required Rules
* Projection persistence must not widen record visibility.
* Pricing-sensitive event payloads must remain redacted.
* Consumers needing full details must re-query through governed source services or security-aware query modules.
* Internal-only Activities must not leak into general projections.
* Organization scope must be mandatory.
* Manager and Executive consumers must still obey branch, product, role, and pricing boundaries.
* Dead-letter and failure records must not persist unrestricted sensitive payloads.
Produce a security matrix for every planned projection.
---
## Part 16 — Existing Consumer Compatibility
Document compatibility with:
* current approval notification events
* current notification inbox
* current Dashboard datasets
* current Report datasets
* legacy lead follow-ups
* legacy opportunity follow-ups
* legacy quotation follow-ups
* recent-activity compatibility reads
### Frozen Rule
No current production consumer is replaced in EP.1.4.
The new runtime operates beside existing consumers until later projection-specific tasks approve cutover.
---
## Part 17 — Projection Delivery Matrix
Create the official Projection Delivery Matrix.
Minimum columns:
| Projection | Event Source | Initial Build Source | Incremental Strategy | Persistence | Idempotency | Retry | Cutover Phase |
| ------------------- | --------------------------------------------- | ------------------------- | -------------------- | ---------------------------- | ----------- | -------- | ------------------------ |
| Timeline | Activity + CRM events | source queries + adapters | Business Events | query-time or cached | required | required | Timeline epic |
| Calendar | Activity + milestones + approval | source queries | Business Events | cached/query-time | required | required | Calendar epic |
| Dashboard | Opportunity + Quotation + Approval + Activity | existing datasets | hybrid | existing + future read model | required | required | Dashboard enhancement |
| Notification | Activity + Approval + CRM events | not applicable | Business Events | notification records | required | required | Notification expansion |
| My Day | Activity + approvals + Hot Project | source composition | hybrid | query-time/cached | required | required | My Day epic |
| Relationship Health | Customer + Activity + Opportunity | source rebuild | Business Events | cached score | required | required | Relationship Health epic |
| Forecast | Opportunity events | source opportunity query | hybrid | report/read model | required | required | Forecast enhancement |
The final matrix must reflect repository-specific decisions.
---
## Part 18 — Delivery Failure Matrix
Create the official Delivery Failure Matrix.
Minimum scenarios:
| Failure Scenario | Source Mutation Result | Event State | Consumer State | Retry | User/API Result |
| ------------------------------------------------- | ---------------------- | ----------- | -------------------------- | -------------- | ----------------- |
| Event persistence fails inside source transaction | rollback | none | none | no | mutation fails |
| Source mutation commits, consumer fails | committed | persisted | retry scheduled | yes | mutation succeeds |
| One of multiple consumers fails | committed | persisted | isolated failure | yes | mutation succeeds |
| Unsupported event version | committed | persisted | skipped/failed by consumer | governed | mutation succeeds |
| Duplicate event delivered | committed | persisted | duplicate skipped | no side effect | unchanged |
| Projection storage unavailable | committed | persisted | retry scheduled | yes | mutation succeeds |
| Dead-letter threshold reached | committed | persisted | dead letter | manual | mutation succeeds |
| Rebuild fails | unchanged | unchanged | rebuild failed | restartable | no source impact |
This matrix becomes the reliability baseline for future projection tasks.
---
# Deliverables
## 1. Delivery Semantics Decision
Official post-commit and eventual-consistency behavior.
## 2. Persistent Event Delivery Foundation
Event persistence and delivery state.
## 3. Transactional Outbox or Equivalent Reliability Mechanism
Approved and implemented foundation.
## 4. Projection Consumer Runtime
Shared processing runtime.
## 5. Persistent Consumer Checkpoints
Production-grade idempotency and processing state.
## 6. Retry Policy
Governed retry and backoff behavior.
## 7. Dead-Letter Foundation
Terminal failure handling and manual recovery contract.
## 8. Projection Registry
Machine-readable projection catalog.
## 9. Projection Storage Standards
Query-time, cached, and materialized projection rules.
## 10. Initial Projection Consumer Skeletons
Timeline, Calendar, Dashboard, and Notification runtime validation consumers.
## 11. Hybrid Rebuild Strategy
Source-query initial build plus event-driven incremental update.
## 12. Projection Rebuild Contract
Dry-run, scoped, restartable rebuild foundation.
## 13. Projection Health Model
Lag, failure, retry, dead-letter, and rebuild visibility.
## 14. Delivery Observability
Structured event and consumer processing telemetry.
## 15. Projection Security Matrix
Visibility, pricing, internal-only, and organization-boundary rules.
## 16. Existing Consumer Compatibility Report
No-regression and cutover readiness documentation.
## 17. Projection Delivery Matrix
Official source, persistence, retry, and cutover mapping.
## 18. Delivery Failure Matrix
Official behavior for mutation, dispatch, consumer, and rebuild failures.
## 19. Projection Readiness Report
Readiness for Timeline, Calendar, Notification, Dashboard, My Day, Manager, Executive, Forecast, and Relationship Health tasks.
---
# Constraints
Must preserve:
* Customer
* Contact
* Lead
* Opportunity
* Quotation
* Approval
* Activity
* current Activity APIs
* legacy follow-up APIs and storage
* recent-activity compatibility reads
* current Dashboard and Report datasets
* current Notification behavior
* current Approval notification publishing
* current Audit Log behavior
* current security and pricing visibility boundaries
Do not implement:
* final Timeline projection
* Timeline UI
* final Calendar projection
* Calendar UI
* Dashboard source replacement
* Notification business-event expansion
* My Day
* Manager Workspace
* Executive Workspace
* Relationship Health calculation
* Forecast replacement
* Automation rules
* external Kafka, RabbitMQ, or Redis Streams infrastructure
* public replay UI
* legacy follow-up migration
* dual-write from follow-up to Activity
* source-domain replacement
No breaking API changes.
---
# Compatibility Strategy
The Projection Foundation is additive.
Current production consumers remain active.
Business Events may be persisted and processed through the new delivery runtime without changing existing API responses.
Projection skeleton consumers must not produce user-visible duplicate records.
Any future cutover from legacy datasets to a projection requires:
* parity validation
* regression tests
* reconciliation report
* feature flag or controlled rollout
* rollback strategy
* explicit implementation task approval
---
# Testing Requirements
## Delivery Tests
* source transaction plus event persistence succeeds atomically
* failed event persistence rolls back source transaction
* committed source mutation survives consumer failure
* multiple consumers process independently
* no-subscriber event completes safely
* persistent event resumes after process restart simulation
## Checkpoint Tests
* first processing succeeds
* duplicate event is skipped
* failed attempt schedules retry
* completed checkpoint prevents duplicate side effect
* unsupported version is recorded
* concurrent processing does not execute twice
## Retry Tests
* retryable failure
* non-retryable failure
* backoff scheduling
* maximum-attempt behavior
* dead-letter creation
* manual retry contract
## Projection Runtime Tests
* consumer registration
* consumer version support
* consumer isolation
* projection registry lookup
* projection health updates
* sanitized failure recording
## Rebuild Tests
* organization-scoped dry run
* source-query rebuild
* restartable rebuild
* reset-existing protection
* duplicate-safe rebuild
* failed rebuild does not affect source data
## Security Tests
* organization isolation
* branch/product scope enforcement
* pricing-sensitive redaction
* internal-only activity exclusion
* unauthorized rebuild rejection
* sensitive payload not exposed in dead-letter records
## Compatibility Tests
* existing Activity API behavior unchanged
* current follow-up APIs unchanged
* existing Dashboard datasets unchanged
* existing Reports unchanged
* existing approval notifications unchanged
* existing recent-activity reads unchanged
---
# Acceptance Criteria
* Source mutations and non-owning projection failures are operationally separated.
* A committed source mutation is not reported as failed because a projection consumer failed.
* Business Events have a persistent delivery or outbox mechanism.
* Projection consumers use persistent checkpoints.
* Duplicate event processing does not create duplicate side effects.
* Retry and dead-letter behavior is implemented and tested.
* Projection consumers remain independently recoverable.
* Projection Registry is machine-readable.
* Projection storage rules are frozen.
* Timeline, Calendar, Dashboard, and Notification skeleton consumers validate the runtime without creating final business-facing projections.
* Hybrid source rebuild and event-driven incremental strategy is documented.
* Projection rebuild contract supports dry-run and organization scope.
* Projection health is measurable.
* Delivery observability captures processing and failure state.
* Projection security rules preserve all current CRM authorization boundaries.
* Existing Dashboard, Report, Notification, Approval, Follow-up, and Activity behavior remains unchanged.
* No final workspace or projection UI is introduced.
* TypeScript, migration generation, targeted lint, and projection/runtime tests pass.
---
# Success Criteria
ALLA OS has a reliable, production-ready Projection Foundation.
Business Events can be delivered and processed without coupling projection failure to source-domain mutation success.
Projection consumers can process events idempotently, retry safely, expose health, recover from failure, and rebuild from governed source data.
Future Timeline, Calendar, Notification, Dashboard, My Day, Manager Workspace, Executive Workspace, Relationship Health, and Forecast tasks can focus on business read models rather than re-solving event delivery reliability.
Ready for:
# EP.1.5 Timeline Projection Foundation

878
plans/task-ep.1.5.md Normal file
View File

@@ -0,0 +1,878 @@
# Task EP.1.5 Timeline Projection Foundation
Status: Completed
Priority: Critical
Type: Projection Platform / Timeline Read Model
---
# Depends On
* EP.1.1 Activity Domain Foundation
* EP.1.2 Activity Integration & Legacy Follow-up Adapter
* EP.1.3 Business Event Foundation
* EP.1.4 Projection Foundation & Delivery Reliability
* EP.1.4.1 Transactional Outbox Activation & Worker Runtime
* BU-R.0 Business Blueprint Freeze
* BU-R.0.1 Workspace & Activity Blueprint
* BU-R.1 Business Capability Audit
* AR.1 Architecture Transition Plan
* AR.2 Epic Technical Design
* ENG.0 Engineering Constitution
---
# Objective
Introduce the first production Business Projection of ALLA OS.
This phase builds the Timeline Projection that becomes the official chronological business history of CRM.
Timeline is **not** Activity.
Timeline is **not** Audit Log.
Timeline is **not** Follow-up.
Timeline is a generated business projection that explains everything that happened to a Customer, Lead, Opportunity, Quotation, Activity, Approval, and PO in business language.
Timeline becomes the foundation for:
* Customer Workspace
* Lead Workspace
* Opportunity Workspace
* Quotation Workspace
* Relationship Workspace
* Manager Workspace
* Executive Workspace
Future Calendar, My Day, Dashboard and Forecast continue consuming Business Events independently.
Timeline never owns business state.
---
# Background
Current repository already contains
* Audit Log
* Activity Domain
* Business Event Foundation
* Transactional Outbox
* Projection Runtime
* Legacy Follow-up Adapters
What still does not exist is a single business timeline.
Today users must inspect
* Audit Log
* Lead Follow-up
* Opportunity Follow-up
* Quotation Follow-up
* Activity
* Approval History
to understand one customer journey.
Timeline consolidates these into one governed projection.
---
# Timeline Principles
## Timeline is Business History
Timeline answers
> "What happened?"
not
> "What changed in the database?"
---
## Timeline is Read Only
Timeline never edits data.
Timeline never owns lifecycle.
Timeline is always rebuildable.
---
## Timeline is Projection
Timeline is generated from
* Business Events
* Activity
* Legacy Follow-up adapters
* Approval events
* Source queries during rebuild
---
## Timeline is Human Friendly
Example
Instead of
```text
activity.completed
```
Timeline shows
```text
Completed site survey.
Outcome:
Customer requested revised quotation.
```
---
## Timeline is Chronological
Every Timeline Item has
* occurredAt
* source
* actor
* entity
* business meaning
---
# Review Required
Review
Governance
* AGENTS.md
* Engineering Constitution
* Project Foundations
* Architecture Rules
* UI/UX Rules
* Task Review Checklist
Business
* Relationship Sales Workspace Blueprint
* BU-R.1
Architecture
* AR.1
* AR.2
Implementation
* EP.1.2
* EP.1.3
* EP.1.4
* EP.1.4.1
Existing
* Activity Timeline Compatibility Layer
* Audit Log
* Approval History
* Customer Detail
* Lead Detail
* Opportunity Detail
* Quotation Detail
Review
layout.md
Review
ui-ux-rules.md
Review
ui-ux-pro-max
Timeline UI must follow the Workspace UI Constitution.
---
# Scope
## Part 1 — Timeline Projection Schema
Introduce
crm_timeline_projection
Minimum fields
* id
* organizationId
* branchId
* customerId
* leadId
* opportunityId
* quotationId
* activityId
* eventId
* entityType
* entityId
* timelineType
* timelineCategory
* occurredAt
* actorId
* actorDisplay
* title
* summary
* icon
* color
* priority
* visibility
* metadata
* sourceEvent
* sourceProjectionVersion
* createdAt
Timeline rows are rebuildable.
Timeline rows are never source-of-truth.
---
## Part 2 — Timeline Builder
Implement
TimelineProjectionBuilder
Responsibilities
* consume Business Events
* transform event
* enrich display
* resolve references
* write projection
Builder must never mutate source domains.
---
## Part 3 — Timeline Event Mapping
Create mapping from
Business Event
Timeline Item
Example
```text
activity.completed
Completed Site Survey
```
Another
```text
quotation.approved
Quotation Approved
```
Every supported Business Event requires mapping.
---
## Part 4 — Legacy Timeline Adapter
Support timeline rebuild using
Lead Follow-up
Opportunity Follow-up
Quotation Follow-up
Activity
Approval
Audit
No migration.
Adapters remain read-only.
---
## Part 5 — Timeline Projection Consumer
Activate Timeline consumer.
Consumer responsibilities
* consume event
* build timeline item
* persist projection
* checkpoint
* retry
* rebuild safe
Consumer uses Projection Runtime.
---
## Part 6 — Timeline Categories
Freeze categories
Activity
Communication
Meeting
Site Survey
Approval
Quotation
Revision
Assignment
Status
Forecast
PO
Customer
System
Categories must be configurable using Master Options later.
---
## Part 7 — Timeline Rendering Model
Freeze UI model
Timeline Item
contains
Header
Timestamp
Actor
Business Message
Reference Links
Status Badge
Icon
Optional Detail
Attachment Indicator
No editing inside Timeline.
---
## Part 8 — Timeline Visibility
Timeline follows CRM Authorization.
Visibility
Organization
Branch
Product
Pricing
Ownership
Internal Only
Timeline never exposes hidden quotation pricing.
---
## Part 9 — Timeline Projection Rebuild
Implement
Timeline rebuild
Supports
Organization
Customer
Lead
Opportunity
Quotation
Date Range
Dry Run
Reset Existing
Progress
Restart
---
## Part 10 — Timeline Projection Health
Track
Items
Lag
Last Event
Checkpoint
Rebuild Time
Health
Errors
---
## Part 11 — Timeline UI Components
Introduce reusable components
TimelineList
TimelineCard
TimelineItem
TimelineFilter
TimelineGroup
TimelineEmpty
TimelineLoading
TimelineSkeleton
TimelineVirtualList
Use
shadcn/ui
Follow
layout.md
Follow
ui-ux-rules.md
Apply
ui-ux-pro-max
Timeline must feel modern and readable.
---
## Part 12 — Workspace Integration
Embed Timeline into
Customer Detail
Lead Detail
Opportunity Detail
Quotation Detail
Activity Detail
Timeline replaces fragmented history panels over time.
Existing panels remain until cutover.
---
## Part 13 — Filtering
Support
Date
Actor
Timeline Category
Entity
Activity
Approval
Quotation
Customer
Opportunity
Lead
PO
Attachment
Search
Unread
Internal Only
---
## Part 14 — Timeline Grouping
Support grouping
Today
Yesterday
Earlier This Week
Last Week
Earlier This Month
Older
Optional
Group by Entity
Group by Customer
Group by Opportunity
---
## Part 15 — Timeline Search
Introduce
Full text search
Business Message
Summary
Actor
Customer
Opportunity
Quotation
Activity
Category
---
## Part 16 — Timeline Performance
Support
Virtual scrolling
Incremental loading
Cursor pagination
Projection indexes
Lazy detail expansion
No N+1 queries.
---
## Part 17 — Timeline Delivery Matrix
Freeze
Business Event
Timeline Item
Workspace
Consumer
Projection
Every event path documented.
---
## Part 18 — Timeline UX Review
Before merge
Review
layout.md
Review
ui-ux-rules.md
Review
ui-ux-pro-max
Verify
Typography
Spacing
Hierarchy
Icons
Density
Mobile
Accessibility
Keyboard
Dark Mode
Loading
Empty State
Skeleton
Sticky Date Header
Infinite Scroll
---
# Deliverables
1. Timeline Projection Schema
2. Timeline Builder
3. Timeline Projection Consumer
4. Timeline Projection Registry
5. Event-to-Timeline Mapping
6. Legacy Timeline Adapter
7. Timeline Projection Storage
8. Timeline Rebuild
9. Timeline Health
10. Timeline UI Components
11. Workspace Timeline Integration
12. Timeline Filtering
13. Timeline Search
14. Timeline Grouping
15. Timeline Delivery Matrix
16. Timeline UX Review Report
17. Projection Readiness Report
---
# Constraints
Must preserve
* Activity
* Audit Log
* Approval History
* Follow-up APIs
* Dashboard
* Reports
* Notifications
* Current Detail Pages
Timeline is additive.
Do not replace
Audit Log.
Do not replace
Activity.
Do not replace
Calendar.
Do not replace
Notification.
Do not replace
Dashboard.
No business state mutation.
No workflow changes.
No permission changes.
---
# Testing
Projection
Consumer
Builder
Rebuild
Retry
Checkpoint
Timeline UI
Search
Grouping
Filtering
Pagination
Virtual List
Authorization
Pricing Visibility
Legacy Adapter
Workspace Integration
Dark Mode
Accessibility
Performance
---
# Acceptance Criteria
* Timeline Projection is generated entirely from Business Events and approved adapters.
* Timeline never mutates source domains.
* Timeline can be rebuilt safely.
* Timeline respects all CRM authorization rules.
* Timeline integrates into Customer, Lead, Opportunity, Quotation, and Activity detail pages without replacing existing history panels.
* Legacy Follow-up history appears through adapters without data migration.
* Timeline UI follows `layout.md`, `ui-ux-rules.md`, and `ui-ux-pro-max`.
* Timeline supports filtering, grouping, search, cursor pagination, and virtual scrolling.
* Projection health, rebuild, retry, and checkpoints integrate with the Projection Runtime.
* Existing APIs, Dashboard, Notifications, Reports, and Approval flows remain unchanged.
---
# Success Criteria
ALLA OS gains its first production-grade Business Projection.
Users can understand the complete customer and sales journey from one unified Timeline instead of opening multiple follow-up tabs, audit logs, and approval histories.
Timeline becomes the canonical business history used by future Calendar, My Day, Manager Workspace, Executive Workspace, Relationship Health, Forecast, and Customer 360 features.
Ready for:
# EP.1.6 Calendar Projection Foundation

248
plans/task-ep.1.6.1.md Normal file
View File

@@ -0,0 +1,248 @@
## Calendar Empty State Rendering Improvement
**Status:** Completed
### Background
The current Calendar Workspace renders a full-page empty state when no Calendar events are returned.
Current behavior:
```text
No scheduled work in this period
Try changing filters or create an Activity from the owning CRM record.
```
This behavior is acceptable for an Agenda (list) view but is not appropriate for graphical calendar views.
A calendar should remain a calendar even when there are no events.
Users still need to:
* understand the selected date range
* navigate between dates
* visualize available time slots
* click an empty day or time slot
* create a new Activity
* understand that the schedule is empty rather than the Calendar failing
---
## Required UX Change
### Day View
Always render the complete Calendar grid.
When there are no events:
* render the timeline/grid normally
* show an inline empty-state hint inside the calendar body
* keep toolbar, navigation, and date controls visible
* allow selecting a time slot
* allow creating a new Activity
Do **NOT** replace the Calendar with a full-page empty screen.
---
### Week View
Always render the Week grid.
When there are no events:
* render all days
* render working hours
* render current time indicator where applicable
* render an inline empty hint only
* preserve drag target areas for future Activity creation
Do **NOT** hide the calendar grid.
---
### Month View
Always render the Month calendar.
When there are no events:
* render the entire month
* render all weeks
* render date cells
* render navigation
* optionally display a small "No events" message inside the month body
The Month view should always look like a usable calendar.
---
### Agenda View
Agenda is different.
Agenda is a list-based presentation.
When there are no scheduled items:
Show the existing empty state:
```text
No scheduled work in this period.
Try changing filters or create an Activity.
```
This behavior remains correct.
---
## Rendering Rules
Preferred rendering logic:
```text
Day
Always render Calendar Grid
Week
Always render Calendar Grid
Month
Always render Calendar Grid
Agenda
If empty
Show Empty State
Else
Show Agenda List
```
The Calendar component should never disappear simply because there are zero events.
---
## Empty Calendar Overlay
Instead of replacing the Calendar component, render a lightweight overlay or inline message.
Example:
```text
──────────────────────────────
No scheduled work
Your schedule is free.
[+ Add Activity]
──────────────────────────────
```
The overlay must not block:
* changing dates
* changing view
* selecting days
* selecting time slots
* opening filters
---
## Add Activity Entry Point
When the Calendar contains no events, provide a visible primary action:
```text
+ Add Activity
```
The button must reuse the existing Activity form and Activity API.
Do not introduce a Calendar-specific create flow.
---
## Calendar Interaction
Even when empty, users must still be able to:
* navigate month/week/day
* click a date
* click a time slot
* switch views
* change filters
* search
* create Activities
The Calendar should remain fully interactive.
---
## Big Calendar Integration
Because the project uses **lramos33/big-calendar** as the UI foundation:
* always render the Big Calendar component for Day, Week, and Month
* pass an empty event array when appropriate
* never short-circuit rendering based on `events.length === 0`
* use the Calendar's native layout even when there are no events
Avoid patterns such as:
```tsx
if (events.length === 0) {
return <CalendarEmptyState />;
}
```
Instead:
```tsx
<BigCalendar events={events} />
{events.length === 0 && view === "agenda" && (
<AgendaEmptyState />
)}
{events.length === 0 && view !== "agenda" && (
<CalendarEmptyOverlay />
)}
```
---
## UX Principles
The Calendar is a navigation surface, not only an event list.
Users should always understand:
* where they are
* which dates are visible
* which time slots are available
* where new Activities can be scheduled
An empty schedule should feel like available capacity—not like a missing screen.
---
## Acceptance Criteria
* Day view always renders the calendar grid.
* Week view always renders the calendar grid.
* Month view always renders the calendar grid.
* Agenda keeps the existing list-style empty state.
* Empty Day/Week/Month views display a lightweight overlay instead of replacing the Calendar.
* Calendar navigation remains available when no events exist.
* Date and time slots remain clickable.
* Users can create Activities directly from an empty calendar.
* No full-page empty state is shown for Day, Week, or Month views.
* The implementation remains compatible with `lramos33/big-calendar` and ALLA OS projection architecture.

1017
plans/task-ep.1.6.2-r1.md Normal file

File diff suppressed because it is too large Load Diff

599
plans/task-ep.1.6.2.md Normal file
View File

@@ -0,0 +1,599 @@
# Task EP.1.6.2 Direct Big Calendar UI Adoption
**Status:** Foundation Implemented
**Priority:** High
**Type:** UI Modernization / Calendar UX / Frontend Refactor
---
# Depends On
* EP.1.6 Calendar Projection, Workspace & Big Calendar Foundation
* EP.1.6.1 Calendar Empty State Rendering Improvement
* EP.1.7 My Day Workspace Foundation
---
# Objective
Replace the current custom Calendar UI implementation with the original **lramos33/big-calendar** component architecture while preserving the ALLA OS backend architecture.
This task is **UI-only**.
No business logic, projection model, Activity ownership, Business Events, or Calendar Projection architecture shall change.
---
# Background
EP.1.6 successfully introduced:
* Calendar Projection
* Calendar Query Service
* Calendar Workspace
* Calendar Projection Runtime
* Activity integration
* Calendar API
* Calendar security
However the current UI only references the upstream project conceptually.
It does **NOT** use the original Calendar UI implementation.
The goal of EP.1.6.2 is to adopt the upstream Calendar components directly so ALLA OS gains the same interaction quality and polished UX.
---
# Review Required
Review before implementation:
* AGENTS.md
* LAYOUT.md
* ui-ux-rules.md
* Engineering Constitution
* EP.1.6
* EP.1.6.1
* EP.1.7
Review upstream project:
```text
https://github.com/lramos33/big-calendar
```
Review:
* component structure
* provider architecture
* drag & drop
* resize
* agenda
* month
* week
* day
* year
* event rendering
* toolbar
* filters
* responsive behavior
* dark mode
---
# Architecture Principle
ALLA OS owns:
* Activity
* Calendar Projection
* Business Events
* Projection Runtime
* Permissions
* APIs
Big Calendar owns:
* Calendar rendering
* Calendar interactions
* View switching
* Event layout
* Drag UX
* Resize UX
* Toolbar
* Navigation
---
# Data Flow
Must remain
```text
Activity
Business Event
Projection Runtime
crm_calendar_projection
CalendarProjectionItem
Calendar Adapter
Big Calendar Components
```
No upstream component may read database rows directly.
---
# Scope
---
# Part 1 — Direct Component Adoption
Adopt upstream source components directly.
Do NOT recreate:
* Month Grid
* Week Grid
* Day Grid
* Agenda Layout
* Calendar Header
* Calendar Navigation
Use upstream implementation instead.
---
# Part 2 — Remove Custom Calendar Rendering
Deprecate current custom:
* Day
* Week
* Month
renderers.
Only keep:
* ALLA adapter
* wrapper
* permissions
* Activity actions
---
# Part 3 — Calendar Provider
Introduce upstream provider.
Wrap ALLA Calendar.
The provider receives
CalendarProjectionItem
through an adapter.
---
# Part 4 — Adapter Layer
Implement
```ts
mapCalendarProjectionToBigCalendarEvent()
```
and
```ts
mapBigCalendarActionToActivityCommand()
```
No business logic inside UI.
---
# Part 5 — Toolbar Integration
Use upstream toolbar.
Adapt:
* Today
* Previous
* Next
* Date Picker
* View Selector
Integrate with:
* URL search params
* React Query
---
# Part 6 — View Integration
Support
* Day
* Week
* Month
* Agenda
Optional
* Year
Do not change upstream layout unnecessarily.
---
# Part 7 — Event Rendering
Differentiate
Editable Activity
vs
Read-only Milestone
Show
* icon
* badge
* customer
* title
* priority
* overdue
* Hot Project
using ALLA adapter.
---
# Part 8 — Drag & Drop
Enable upstream drag.
Rules
Editable
Activity API
Business Event
Projection Refresh
Read-only milestones
cannot move.
---
# Part 9 — Resize
Enable resize.
Only
Activity
may resize.
Never
Milestones.
---
# Part 10 — Event Detail
Selecting an event opens
ALLA Activity Sheet
or
Source Detail Sheet
depending on
Activity
or
Milestone.
---
# Part 11 — Working Hours
Reuse upstream configuration.
Default
Asia/Bangkok
Support
Organization setting
Future
User preference.
---
# Part 12 — Current Time
Enable
Now Indicator
from upstream.
---
# Part 13 — Today Highlight
Use upstream
Today
highlight behavior.
---
# Part 14 — User Filter
Replace upstream demo users.
Use
CRM Users API
Permission aware.
---
# Part 15 — Theme Integration
Align
fonts
spacing
cards
buttons
badges
colors
with ALLA OS.
Avoid redesigning upstream.
---
# Part 16 — Responsive Review
Validate
Desktop
Tablet
Mobile
Agenda remains mobile default.
---
# Part 17 — Accessibility
Validate
* keyboard
* focus
* screen reader
* contrast
* dark mode
---
# Part 18 — Calendar Empty State
Keep
EP.1.6.1 behavior.
Day
Week
Month
render calendar.
Agenda
render empty list.
---
# Part 19 — Source Audit
Document
* adopted files
* modified files
* untouched files
* removed demo code
---
# Part 20 — Upgrade Strategy
Record
* upstream commit hash
* version reviewed
* local patches
* synchronization strategy
Future updates must be traceable.
---
# Part 21 — Performance
Verify
* Month rendering
* Week rendering
* Day rendering
* Agenda rendering
* Virtualization where applicable
* React re-render counts
* Lazy event loading
---
# Part 22 — Regression Testing
Verify
Calendar
does not break
* Activity
* Timeline
* Dashboard
* My Day
* Projection Runtime
* Permission model
---
# Deliverables
1. Direct Big Calendar integration
2. Calendar Provider
3. Calendar Adapter Layer
4. Toolbar Integration
5. Day View
6. Week View
7. Month View
8. Agenda View
9. Optional Year View
10. Drag & Drop
11. Resize
12. Current Time Indicator
13. Today Highlight
14. User Filter
15. Theme Integration
16. Responsive Review
17. Accessibility Review
18. Source Audit Report
19. Upgrade Strategy Document
20. Regression Report
---
# Constraints
Must preserve
* Activity ownership
* Calendar Projection
* Timeline
* Projection Runtime
* Business Events
* CRM permissions
* API contracts
Must NOT
* replace Calendar Projection
* introduce local event storage
* duplicate Activity logic
* bypass Activity API
* bypass Business Events
* mutate projection rows directly
---
# Verification
* `npm run typecheck`
* `npm run db:generate`
* `npx oxlint src/features/crm/calendar`
* Calendar interaction tests
* Drag & Drop tests
* Resize tests
* Permission tests
* Responsive tests
* Accessibility tests
---
# Acceptance Criteria
* The Calendar UI is built directly on the **lramos33/big-calendar** source components rather than custom-rendered Day, Week, and Month grids.
* ALLA OS retains ownership of Activity, Calendar Projection, Business Events, Projection Runtime, and CRM authorization.
* `CalendarProjectionItem` is adapted into the upstream event model through a dedicated adapter layer.
* Upstream toolbar, navigation, Day, Week, Month, and Agenda views are reused with minimal structural modification.
* Editable Activities support drag-and-drop and resize through the Activity API only.
* Read-only milestones cannot be dragged or resized.
* The current-time indicator and Today highlighting are enabled.
* Day, Week, and Month always render the Calendar grid; Agenda retains the list-style empty state from EP.1.6.1.
* The upstream MIT license is preserved, and an audit documents all adopted and modified source files.
* The Calendar visually matches the upstream interaction quality while following `LAYOUT.md`, `ui-ux-rules.md`, and `ui-ux-pro-max`.
* Existing Activity, Calendar Projection, Timeline, My Day, Dashboard, Reports, Notifications, and Projection Runtime behavior remain unchanged.
---
# Success Criteria
ALLA OS transitions from a custom Calendar implementation to a **direct integration of the `lramos33/big-calendar` UI**, delivering a polished enterprise calendar experience while preserving the existing backend architecture, business rules, security model, and Projection-based design.

844
plans/task-ep.1.6.md Normal file
View File

@@ -0,0 +1,844 @@
# Task EP.1.6 Calendar Projection, Workspace & Big Calendar Foundation
**Status:** Foundation Implemented
**Priority:** Critical
**Type:** Projection Platform / Workspace / UI Foundation
---
# Depends On
* EP.1.1 Activity Domain Foundation
* EP.1.2 Activity Integration & Legacy Follow-up Adapter
* EP.1.3 Business Event Foundation
* EP.1.4 Projection Foundation & Delivery Reliability
* EP.1.4.1 Transactional Outbox Activation & Worker Runtime
* EP.1.5 Timeline Projection Foundation
* BU-R.0 Business Blueprint Freeze
* BU-R.0.1 Workspace & Activity Blueprint
* BU-R.1 Business Capability Audit
* AR.1 Architecture Transition Plan
* AR.2 Epic Technical Design
* AR.2 Workspace UI/UX Design Note
* ENG.0 Engineering Constitution
---
# Objective
Introduce the first production Calendar Workspace of ALLA OS by combining:
* Calendar Projection
* Calendar Workspace
* Big Calendar UI Foundation
* Activity scheduling
* Business milestones
* Team visibility
* Calendar interaction
Calendar becomes the operational scheduling workspace for CRM while preserving existing source-domain ownership.
Calendar is **NOT**:
* a source-of-truth
* another Activity module
* another Opportunity module
* another Quotation module
Calendar is a governed projection and operational workspace.
---
# Business Outcome
After EP.1.6 users can answer:
* What should I do today?
* Which customers should I visit?
* Which follow-ups are overdue?
* Which quotations are expiring?
* Which projects are Hot Projects?
* Which approvals are waiting?
* Which customer visits are scheduled?
* What is my team's workload today?
* What are tomorrow's priorities?
Managers gain a Team Calendar without creating duplicate business data.
---
# Calendar Business Principles
## Calendar is Projection
Calendar is generated from:
* Activity
* Business Events
* Opportunity milestones
* Quotation milestones
* Approval milestones
* Legacy adapters during rebuild
Calendar rows are rebuildable.
---
## Activity Owns Operational Work
Editable Calendar events always belong to Activity.
Examples:
* Follow-up
* Meeting
* Site Survey
* Visit
* Internal Task
* Phone Call
* Reminder
Calendar actions must call Activity Services.
Calendar never edits projection rows directly.
---
## Milestones Remain Source-Owned
Read-only Calendar milestones include:
* Expected Award Date
* Expected Delivery Date
* Quotation Expiration
* Approval Due
* Approval Escalation
* PO Milestones
Editing must always occur through the owning domain.
---
## Calendar ≠ Timeline
Timeline answers:
> What happened?
Calendar answers:
> What must happen?
Both consume Business Events but serve different business purposes.
---
# UI Foundation Decision
The Calendar UI must adopt:
**lramos33/big-calendar**
Repository:
```text
https://github.com/lramos33/big-calendar
```
This repository becomes the Calendar UI foundation only.
It does **NOT** become:
* Calendar database
* Calendar backend
* Activity service
* Projection runtime
* CRM authorization
* Source-of-truth
The governed architecture remains:
```text
Activity / Opportunity / Quotation / Approval
Business Events
Projection Runtime
crm_calendar_projection
Calendar Adapter
lramos33/big-calendar
```
---
# Review Required
## Governance
Review:
* AGENTS.md
* Engineering Constitution
* Project Foundations
* Architecture Rules
* UI UX Rules
* Task Review Checklist
* CRM Authorization Boundaries
* LAYOUT.md
## Business
Review:
* Relationship Sales Workspace Blueprint
* BU-R.1
## Architecture
Review:
* AR.1
* AR.2
* Workspace UI UX Design Note
## Existing Runtime
Review:
* Projection Runtime
* Worker Runtime
* Timeline Projection
* Activity Service
* Business Event Registry
* Calendar-related Activity scheduling
* Date formatting utilities
* CRM security
## UI Review
Must use:
* ui-ux-pro-max
* LAYOUT.md
* ui-ux-rules.md
No code-first Calendar implementation is allowed.
---
# Scope
# Part 1 — Calendar Projection Schema
Introduce:
```text
crm_calendar_projection
```
Containing:
* organization
* branch
* product
* activity
* customer
* lead
* opportunity
* quotation
* approval
* owner
* assignee
* title
* summary
* location
* start
* end
* timezone
* priority
* category
* editable
* milestone
* overdue
* hot project
* projection version
* source event
* metadata
Projection rows remain rebuildable.
---
# Part 2 — Calendar Projection Builder
Implement
CalendarProjectionBuilder
Responsibilities
* Business Event mapping
* milestone mapping
* display enrichment
* permission metadata
* icon registry
* urgency calculation
* projection persistence
Builder never mutates source domains.
---
# Part 3 — Activity Event Mapping
Support
* activity.created
* activity.assigned
* activity.reassigned
* activity.started
* activity.rescheduled
* activity.completed
* activity.cancelled
* activity.deleted
Projection updates must be incremental through Business Events.
---
# Part 4 — Milestone Projection
Project:
Opportunity
* Expected Award Date
* Expected Delivery Date
* Hot Project review
Quotation
* Valid Until
* Delivery milestone
Approval
* Due
* Escalation
Milestones are read-only.
---
# Part 5 — Calendar Projection Consumer
Activate Calendar consumer through Projection Runtime.
Must support:
* checkpoint
* retry
* dead letter
* rebuild
* idempotency
---
# Part 6 — Calendar Query Service
Provide secured query service.
Filters:
* date range
* user
* team
* branch
* product
* customer
* lead
* opportunity
* quotation
* activity type
* category
* priority
* overdue
* milestone
* Hot Project
* search
Supported lenses:
* Personal
* Team
* Manager
* Executive
---
# Part 7 — Calendar Rebuild
Hybrid rebuild.
Initial build:
* Activity
* Opportunity
* Quotation
* Approval
Incremental:
* Business Events
Support:
* organization
* customer
* opportunity
* quotation
* date range
* dry run
* reset existing
* restart
---
# Part 8 — Calendar Workspace
Route:
```text
/dashboard/crm/calendar
```
Views:
* Day
* Week
* Month
* Agenda
Agenda is default for:
* mobile
* manager
* high-density work
---
# Part 9 — Big Calendar Source Audit
Before implementation:
Audit:
* dependency compatibility
* React compatibility
* Next.js compatibility
* shadcn compatibility
* Tailwind compatibility
* accessibility
* dark mode
* timezone assumptions
* drag/drop libraries
* demo code
* mock data
Document:
* files adopted
* files rejected
* modification strategy
---
# Part 10 — Big Calendar Adaptation
Do **NOT** import the whole repository.
Only adapt:
* Calendar Views
* Toolbar
* Event Rendering
* Drag & Drop
* Resize
* Navigation
* User Filter
* Working Hours
Move adapted source into:
```text
src/features/crm/calendar/
```
Maintain ALLA OS architecture.
---
# Part 11 — Calendar Adapter Layer
Introduce
CalendarProjectionItem
BigCalendarEvent
Implement
```ts
mapCalendarProjectionToBigCalendarEvent()
```
UI must never consume database rows directly.
---
# Part 12 — Calendar Rendering Strategy
Differentiate:
## Editable Activity
Examples
* Follow-up
* Meeting
* Visit
* Site Survey
Supports
* Complete
* Reschedule
* Cancel
* Open
* Reassign
---
## Read-only Milestone
Examples
* Expected Award
* Expected Delivery
* Approval Due
* Quotation Expiration
Supports
* Open Source
* View Detail
* Create Related Activity
No editing.
---
# Part 13 — Drag and Drop
For Activities
Drag
Activity API
Business Event
Projection Update
Calendar Refresh
No direct Calendar persistence.
Milestones cannot be dragged.
---
# Part 14 — User Filter
Integrate upstream user filter with:
* CRM permissions
* team hierarchy
* ownership
* manager scope
Never load unauthorized users.
---
# Part 15 — Working Hours
Support
* organization defaults
* user preferences
* Bangkok timezone
* all-day events
* current time
* business hours
---
# Part 16 — Schedule Conflict Detection
Detect
* overlapping visits
* meetings
* site surveys
* tasks
Read-only advisory.
No blocking.
---
# Part 17 — Calendar Summary
Display
* Due Today
* Overdue
* Meetings
* Visits
* Milestones
* Approval Due
* Hot Projects
Derived from Calendar projection.
---
# Part 18 — Calendar Performance
Support
* indexes
* bounded queries
* lazy loading
* virtual agenda
* cursor pagination
* projection batching
No N+1.
---
# Part 19 — Projection Health
Track
* lag
* retries
* dead letters
* row count
* stale rows
* rebuild status
---
# Part 20 — Calendar Delivery Matrix
Document
Source
Business Event
Projection
Calendar Item
Workspace
Include:
* Activity
* Opportunity
* Quotation
* Approval
---
# Part 21 — Calendar UI / UX Review
Review with
* ui-ux-pro-max
* LAYOUT.md
* ui-ux-rules.md
Validate
* hierarchy
* spacing
* typography
* icons
* filters
* density
* accessibility
* dark mode
* responsiveness
* keyboard
* loading
* empty state
---
# Part 22 — Upstream License
Retain
MIT License
Document
* adopted files
* attribution
* modification history
---
# Deliverables
1. Calendar Projection Schema
2. Calendar Builder
3. Calendar Projection Consumer
4. Calendar Query Service
5. Calendar Rebuild
6. Calendar Workspace
7. Day / Week / Month / Agenda
8. Big Calendar Source Audit
9. Big Calendar Adaptation
10. Calendar Adapter Layer
11. Activity Calendar Actions
12. Milestone Projection
13. Drag & Drop Integration
14. User Filter Integration
15. Working Hours Support
16. Schedule Conflict Detection
17. Calendar Summary Panels
18. Projection Health
19. Calendar Delivery Matrix
20. UI/UX Review Report
21. Upstream License & Attribution Report
22. EP.1.7 Readiness Report
---
# Constraints
Must preserve:
* Activity ownership
* Opportunity ownership
* Quotation ownership
* Approval ownership
* Timeline
* Dashboard
* Reports
* Notifications
* Audit Log
* CRM security
* Projection Runtime
Do NOT implement:
* Google Calendar Sync
* Outlook Sync
* recurring engine
* My Day
* Manager Workspace
* Executive Workspace
* Relationship Health
* Forecast
* automatic Activity generation
* legacy migration
Calendar remains an additive projection.
---
# Testing
* Projection
* Builder
* Consumer
* Rebuild
* Activity Events
* Milestones
* Query
* Permissions
* Drag & Drop
* Resize
* Agenda
* Day
* Week
* Month
* Timezone
* Conflict Detection
* UI
* Accessibility
* Dark Mode
* Performance
* Compatibility
---
# Acceptance Criteria
* Calendar Projection is fully rebuildable.
* Calendar uses Business Events for incremental updates.
* Activity remains the only editable operational work source.
* Opportunity, Quotation, and Approval milestones are projected as read-only Calendar items.
* `lramos33/big-calendar` is successfully adopted as the UI foundation without importing its application shell or backend.
* A dedicated adapter maps `CalendarProjectionItem` to the Calendar UI event model.
* Drag-and-drop and resize update Activities only through Activity Services and Business Events.
* Read-only milestones cannot be dragged or resized.
* Personal, Team, Manager, and Executive lenses enforce existing CRM authorization.
* Day, Week, Month, and Agenda views are available, with Agenda optimized for managers and mobile.
* Working hours, timezone, and schedule conflict detection behave consistently.
* Calendar integrates with the Projection Runtime (checkpoint, retry, rebuild, health).
* UI conforms to `LAYOUT.md`, `ui-ux-rules.md`, and `ui-ux-pro-max`.
* Existing Activity, Timeline, Dashboard, Reports, Notifications, Approval flows, and Follow-up APIs remain unchanged.
---
# Success Criteria
ALLA OS gains a production-ready Calendar Workspace built on a governed Projection architecture.
Users can plan and execute daily operational work from a single Calendar while preserving Activity, Opportunity, Quotation, and Approval as the authoritative business domains.
The Calendar foundation is ready for:
**EP.1.7 My Day Workspace Foundation**

649
plans/task-ep.1.7.md Normal file
View File

@@ -0,0 +1,649 @@
# Task EP.1.7 My Day Workspace Foundation
**Status:** Foundation Implemented
**Priority:** Critical
**Type:** Workspace / Operational Dashboard / Productivity Foundation
---
# Depends On
* EP.1.1 Activity Domain Foundation
* EP.1.2 Activity Integration & Legacy Follow-up Adapter
* EP.1.3 Business Event Foundation
* EP.1.4 Projection Foundation & Delivery Reliability
* EP.1.4.1 Transactional Outbox Activation & Worker Runtime
* EP.1.5 Timeline Projection Foundation
* EP.1.6 Calendar Projection, Workspace & Big Calendar Foundation
* BU-R.0
* BU-R.0.1
* BU-R.1
* AR.1
* AR.2
* ENG.0
---
# Objective
Introduce the first operational workspace that users open every morning.
My Day becomes the single productivity workspace where Marketing, Sales, Managers, and Executives immediately understand:
* what must be done today
* what is overdue
* what is important
* what is blocked
* what is approaching
* what requires attention
without navigating through Lead, Opportunity, Quotation, Calendar, Timeline, and Approval individually.
My Day is **NOT** another business domain.
It is a read-oriented operational workspace.
---
# Business Outcome
When a Sales user logs in, the first screen should answer:
> What should I do next?
When a Manager logs in, the first screen should answer:
> Which team members require attention?
---
# Workspace Principles
## My Day never owns business data
My Day aggregates information.
It never becomes the source of:
* Activity
* Lead
* Opportunity
* Quotation
* Approval
* Calendar
---
## Activity remains the operational owner
Every actionable task ultimately resolves to Activity.
My Day never edits projection rows.
---
## Calendar owns scheduling
My Day consumes Calendar Projection.
Calendar remains responsible for scheduling.
---
## Timeline owns history
Timeline answers:
"What happened?"
My Day answers:
"What should happen next?"
---
# Review Required
Review:
* AGENTS.md
* Engineering Constitution
* Project Foundations
* Architecture Rules
* UI UX Rules
* LAYOUT.md
* AR.2 Workspace UI UX Design Note
* EP.1.5 Timeline
* EP.1.6 Calendar
* CRM Security
* Activity Domain
* Projection Runtime
UI review must use:
* ui-ux-pro-max
---
# Scope
# Part 1 — Workspace Route
Introduce
```text
/dashboard/crm/my-day
```
This becomes the default landing page for CRM users after login.
---
# Part 2 — Workspace Composition
The page is composed of widgets.
Widgets are independent read models.
Widgets never own business logic.
---
# Part 3 — Workspace Widget Registry
Introduce
WorkspaceWidgetRegistry
Each widget defines:
* id
* title
* priority
* icon
* permission
* visibility
* refresh strategy
* empty state
Widgets must be individually reusable.
---
# Part 4 — Default Widgets
Minimum widgets
## Today's Schedule
Consumes Calendar.
Shows:
* Meeting
* Visit
* Site Survey
* Follow-up
---
## Overdue Activities
Consumes Activity.
---
## Due Today
Consumes Calendar.
---
## Hot Projects
Consumes Opportunity.
Only projects currently flagged as Hot Project.
---
## Quotations Requiring Attention
Examples
* expiring soon
* pending approval
* returned for revision
---
## Pending Approvals
Consumes Approval.
---
## Recently Updated
Consumes Timeline.
---
## My Statistics
Examples
* Activities Today
* Meetings
* Visits
* Closed Activities
---
# Part 5 — Workspace Data Priority Matrix
Freeze rendering priority.
Example
Priority 1
Overdue
Priority 2
Today's Meetings
Priority 3
Today's Visits
Priority 4
Approvals
Priority 5
Quotation Expiring
Priority 6
Expected Award
Priority 7
Hot Project
Priority 8
Upcoming Activities
Widgets should appear in business importance order.
---
# Part 6 — Add Activity
Primary workspace action.
Display
```text
+ Add Activity
```
Must reuse existing
Activity Form
Activity API
Activity Service
No duplicate Activity implementation.
Support
* Meeting
* Visit
* Follow-up
* Phone Call
* Reminder
* Internal Task
* Site Survey
Context-aware prefills:
* Customer
* Lead
* Opportunity
* Quotation
* selected Calendar date
---
# Part 7 — Quick Actions
Examples
Complete
Reschedule
Cancel
Open Activity
Open Customer
Open Opportunity
Open Quotation
Open Calendar
Actions must call source services.
---
# Part 8 — Workspace Filters
Support
* Today
* Tomorrow
* This Week
* User
* Team
* Branch
* Product
* Customer
* Activity Type
* Hot Project
---
# Part 9 — Workspace Layout
Suggested layout
```text
----------------------------------------------------
Good Morning
Summary KPIs
----------------------------------------------------
Today's Schedule
Overdue
Hot Projects
Pending Approvals
----------------------------------------------------
Quotation Attention
Recent Activity
----------------------------------------------------
Quick Actions
----------------------------------------------------
```
Must follow
LAYOUT.md
and
ui-ux-rules.md
---
# Part 10 — Personal vs Manager Mode
Personal
Shows
* own Activities
* own Calendar
* own Quotations
* own Approvals
Manager
Adds
* team workload
* overdue team Activities
* team Calendar
* team approvals
Uses existing CRM permissions.
---
# Part 11 — Widget Refresh
Widgets should refresh independently.
No full-page reload.
Reuse React Query.
---
# Part 12 — Empty States
Every widget must define
* loading
* empty
* permission denied
---
# Part 13 — Notification Entry Points
Widgets provide links into
* Calendar
* Timeline
* Activity
* Customer
* Opportunity
* Quotation
Notifications remain outside scope.
---
# Part 14 — Workspace Performance
Support
* lazy loading
* independent queries
* cached widgets
* bounded datasets
---
# Part 15 — Responsive Layout
Desktop
Tablet
Mobile
Mobile defaults to
Today's Schedule
---
# Part 16 — Workspace Security
Every widget must reuse
CRM Security Context
No widget bypasses permission.
---
# Part 17 — My Day UX Review
Review with
* ui-ux-pro-max
* LAYOUT.md
* ui-ux-rules.md
Review
* hierarchy
* density
* responsiveness
* accessibility
* dark mode
* keyboard
* empty state
* loading
* interaction consistency
---
# Part 18 — Workspace Analytics
Track
* widget usage
* quick action usage
* Add Activity usage
* completion rate
No sensitive business payload stored.
---
# Deliverables
1. My Day Workspace
2. Widget Registry
3. Workspace Layout
4. Today's Schedule Widget
5. Overdue Widget
6. Due Today Widget
7. Hot Project Widget
8. Pending Approval Widget
9. Quotation Attention Widget
10. Recent Activity Widget
11. My Statistics Widget
12. Add Activity Action
13. Quick Actions
14. Workspace Filters
15. Manager Mode
16. Widget Refresh Strategy
17. Responsive Layout
18. Workspace Analytics
19. My Day UX Review Report
20. EP.2.0 Readiness Report
---
# Constraints
Must preserve
* Activity ownership
* Calendar ownership
* Timeline ownership
* Opportunity ownership
* Quotation ownership
* Approval ownership
Must not introduce
* duplicate Activity
* duplicate Calendar
* duplicate Timeline
* duplicate Notification
No breaking API changes.
---
# Testing
* Widget rendering
* Widget refresh
* Permission
* Add Activity
* Quick Actions
* Filters
* Responsive
* Mobile
* Manager Mode
* Empty State
* Loading
* Dark Mode
* Accessibility
* Performance
---
# Acceptance Criteria
* My Day becomes the default operational workspace for CRM users.
* All widgets consume existing source domains or projections without introducing duplicate business logic.
* The primary **Add Activity** action reuses the existing Activity form, API, and service.
* Widgets refresh independently using React Query and do not require full-page reloads.
* Personal and Manager modes enforce existing CRM permissions and team visibility rules.
* The Workspace Widget Registry defines widget composition, priority, permissions, and refresh behavior.
* The Workspace Data Priority Matrix determines the display order of actionable information.
* UI follows `LAYOUT.md`, `ui-ux-rules.md`, and is reviewed with `ui-ux-pro-max`.
* Existing Activity, Calendar, Timeline, Approval, Dashboard, Reports, and Notification behavior remain unchanged.
---
# Success Criteria
ALLA OS gains a unified **My Day Operational Workspace** where users can start their day, understand priorities, create and manage Activities, and navigate seamlessly to Calendar, Timeline, Customers, Opportunities, Quotations, and Approvals from a single productivity-focused workspace.

445
plans/task-ep1.1.md Normal file
View File

@@ -0,0 +1,445 @@
# EP.1.1 Activity Domain Foundation
Status: Planning
Priority: Critical
Type: Feature Foundation
Depends On
- BU-R.0 Business Constitution
- BU-R.0.1 Workspace & Activity Blueprint
- BU-R.1 Business Capability Audit
- AR.1 Architecture Transition Plan
- AR.2 Epic Technical Design
- AR.2 Workspace UI/UX Design Note
- ENG.0 Engineering Constitution
---
# Objective
Introduce the shared Activity Domain as the operational foundation of ALLA OS.
This epic establishes the Activity aggregate, ownership model, business rules, service boundaries, and integration points while preserving all existing CRM modules.
This phase delivers only the Activity foundation.
Timeline, Calendar, My Day, Dashboard, and Notification are intentionally excluded and will consume Activity in later epics.
---
# Background
Business Constitution defines Activity as the shared operational model.
Architecture Constitution defines Activity as the source domain for future projections.
Engineering Constitution requires:
- Preserve existing CRM domains
- Extend rather than replace
- Keep Activity as the single operational write model
Current implementation already contains several activity-like concepts:
- Follow-up
- Reminder
- Site Visit
- Meeting
- Internal Task
These must be audited and unified without breaking existing production behavior.
---
# Review Required
Review before implementation:
Business
- Relationship & Sales Workspace Blueprint
- Activity Blueprint
Architecture
- AR.1 Architecture Transition Plan
- AR.2 Epic Technical Design
Engineering
- Engineering Constitution
UI
- Workspace UI/UX Design Note
- layout.md
- ui-ux-rules.md
- ui-ux-pro-max
Implementation
- Existing Follow-up modules
- Existing Customer detail
- Existing Opportunity detail
- Existing Quotation detail
- Existing Notification logic
- Existing Permission model
---
# Scope
## Part 1 — Activity Domain Model
Create
Activity Aggregate
Define
- Activity
- Activity Owner
- Activity Assignee
- Activity Type
- Activity Status
- Activity Priority
Freeze ownership.
Activity becomes the only operational work entity.
---
## Part 2 — Activity Lifecycle
Define
Draft
Scheduled
In Progress
Completed
Cancelled
Skipped
Overdue
Define
Allowed transitions
Completion rules
Reschedule rules
Assignment rules
---
## Part 3 — Activity Relationship Model
Activity may reference
Customer
Contact
Lead
Opportunity
Quotation
PO
Internal Only
Exactly one source context is required.
---
## Part 4 — Activity Business Rules
Freeze
Assignment
Ownership
Visibility
Permission
Completion
Deletion
Editing
History
Determine
Recurring activities
Parent / Child activities
Future dependency support
---
## Part 5 — Activity Service
Introduce
Activity Service
Responsibilities
Create
Update
Assign
Complete
Cancel
Reschedule
Permission validation
Audit logging
Business validation
Route Handlers remain thin.
---
## Part 6 — Activity Repository
Create repository abstraction.
Separate
Write model
Read model
Prepare for future projections.
---
## Part 7 — Activity API
Introduce REST endpoints
Create
Update
Delete
Assign
Complete
Cancel
Reschedule
List
Get Detail
API must follow existing CRM conventions.
---
## Part 8 — Permission Model
Integrate with existing CRM authorization.
Support
Owner
Assignee
Manager
Administrator
Respect
Organization
Branch
Product Type
Pricing visibility boundaries.
---
## Part 9 — Existing Module Integration
Integrate Activity with
Customer
Lead
Opportunity
Quotation
without changing their ownership.
Do not migrate Follow-up yet.
Only establish extension points.
---
## Part 10 — Future Projection Preparation
Publish integration contracts for
Timeline
Calendar
Notification
My Day
Dashboard
Do NOT implement projections.
Only define extension interfaces.
---
# UI Scope
Implement only foundational UI.
Review
- layout.md
- ui-ux-rules.md
- ui-ux-pro-max
Deliver
- Activity Sheet
- Activity Form
- Activity Detail
- Activity Badge
- Activity Status Badge
- Activity Priority Badge
Do NOT build
- Calendar
- Timeline
- My Day
- Manager Workspace
UI must extend existing CRM patterns.
---
# Deliverables
1. Activity Domain Model
2. Activity Lifecycle
3. Activity Service
4. Activity Repository
5. Activity API
6. Activity Permission Integration
7. Activity UI Foundation
8. Activity Integration Contracts
9. Existing Follow-up Gap Analysis
10. Migration Preparation Report
---
# Constraints
Must preserve
- Customer
- Lead
- Opportunity
- Quotation
- Approval
- RBAC
- Organization
- Existing APIs
No Calendar.
No Timeline.
No Dashboard.
No Notification Expansion.
No projection implementation.
No migration of existing Follow-up records.
---
# Acceptance Criteria
- Activity becomes the official operational domain.
- Existing CRM behavior remains unchanged.
- Activity is reusable by all future workspaces.
- Existing Follow-up implementation continues working.
- Projection interfaces are prepared without implementation.
- Thin Route Handler pattern is preserved.
- Service-owned business logic is preserved.
- All new UI follows layout.md, ui-ux-rules.md, and ui-ux-pro-max.
- Existing shadcn/ui patterns are reused.
- No breaking changes are introduced.
---
# Success Criteria
ALLA OS now has a production-ready Activity Foundation.
Future epics can consume Activity without redesign.
Ready for
EP.1.2 Activity Integration & Follow-up Consolidation.
## Compatibility Strategy
During EP.1.1, existing Follow-up implementations remain operational.
The new Activity Domain must coexist with legacy Follow-up structures.
No existing API contracts may be removed.
No database records are migrated.
No UI flow changes are introduced.
Activity is introduced as an additive capability only.
Legacy Follow-up consolidation is deferred to EP.1.2 after Activity Foundation has been validated.

View File

@@ -311,6 +311,7 @@ async function main() {
matchingVersion,
});
const targetVersionId = matchingVersion?.id ?? randomUUID();
const isProductVariant = options.variant === 'product-v1';
await sql.begin(async (tx) => {
if (!matchingVersion) {
@@ -322,6 +323,7 @@ async function main() {
version,
file_path,
schema_json,
metadata_json,
preview_image_url,
is_active,
deleted_at,
@@ -333,6 +335,10 @@ async function main() {
${targetVersionLabel},
${templateSource.relativePath},
${JSON.stringify(targetSchema)},
case
when ${isProductVariant} then jsonb_build_object('templateVariant', 'product-v1')
else null
end,
${null},
${options.activate},
${null},
@@ -344,6 +350,11 @@ async function main() {
update crm_document_template_versions
set
file_path = ${templateSource.relativePath},
metadata_json = case
when ${isProductVariant}
then coalesce(metadata_json, '{}'::jsonb) || jsonb_build_object('templateVariant', 'product-v1')
else metadata_json
end,
is_active = ${options.activate ? true : matchingVersion.isActive},
updated_at = now()
where id = ${targetVersionId}

View File

@@ -1,2 +1,13 @@
organization_code,entity_type,workflow_code,branch_code,product_type_code,min_amount,max_amount,currency,priority,is_default,is_active,description
ALLA,quotation,quotation_standard,head_office,crane,0,500000,THB,100,true,true,Default ALLA crane quotation approval up to 500k
ALLA,quotation,quotation_manager_review,head_office,crane,500000.01,5000000,THB,90,false,true,ALLA crane quotations requiring manager and department review
ALLA,quotation,quotation_executive_review,head_office,crane,5000000.01,,THB,80,false,true,ALLA crane high value quotations requiring executive review
ALLA,quotation,quotation_standard,head_office,dockdoor,0,300000,THB,70,false,true,ALLA dock door standard approval up to 300k
ALLA,quotation,quotation_manager_review,head_office,dockdoor,300000.01,3000000,THB,60,false,true,ALLA dock door medium value approval
ALLA,quotation,quotation_executive_review,head_office,dockdoor,3000000.01,,THB,50,false,true,ALLA dock door high value approval
ONVALLA,quotation,quotation_standard,head_office,crane,0,500000,THB,100,true,true,Default ONVALLA crane quotation approval up to 500k
ONVALLA,quotation,quotation_manager_review,head_office,crane,500000.01,5000000,THB,90,false,true,ONVALLA crane quotations requiring manager and department review
ONVALLA,quotation,quotation_executive_review,head_office,crane,5000000.01,,THB,80,false,true,ONVALLA crane high value quotations requiring executive review
ONVALLA,quotation,quotation_standard,head_office,solarcell,0,300000,THB,70,false,true,ONVALLA solar cell standard approval up to 300k
ONVALLA,quotation,quotation_manager_review,head_office,solarcell,300000.01,3000000,THB,60,false,true,ONVALLA solar cell medium value approval
ONVALLA,quotation,quotation_executive_review,head_office,solarcell,3000000.01,,THB,50,false,true,ONVALLA solar cell high value approval
1 organization_code entity_type workflow_code branch_code product_type_code min_amount max_amount currency priority is_default is_active description
2 ALLA quotation quotation_standard head_office crane 0 500000 THB 100 true true Default ALLA crane quotation approval up to 500k
3 ALLA quotation quotation_manager_review head_office crane 500000.01 5000000 THB 90 false true ALLA crane quotations requiring manager and department review
4 ALLA quotation quotation_executive_review head_office crane 5000000.01 THB 80 false true ALLA crane high value quotations requiring executive review
5 ALLA quotation quotation_standard head_office dockdoor 0 300000 THB 70 false true ALLA dock door standard approval up to 300k
6 ALLA quotation quotation_manager_review head_office dockdoor 300000.01 3000000 THB 60 false true ALLA dock door medium value approval
7 ALLA quotation quotation_executive_review head_office dockdoor 3000000.01 THB 50 false true ALLA dock door high value approval
8 ONVALLA quotation quotation_standard head_office crane 0 500000 THB 100 true true Default ONVALLA crane quotation approval up to 500k
9 ONVALLA quotation quotation_manager_review head_office crane 500000.01 5000000 THB 90 false true ONVALLA crane quotations requiring manager and department review
10 ONVALLA quotation quotation_executive_review head_office crane 5000000.01 THB 80 false true ONVALLA crane high value quotations requiring executive review
11 ONVALLA quotation quotation_standard head_office solarcell 0 300000 THB 70 false true ONVALLA solar cell standard approval up to 300k
12 ONVALLA quotation quotation_manager_review head_office solarcell 300000.01 3000000 THB 60 false true ONVALLA solar cell medium value approval
13 ONVALLA quotation quotation_executive_review head_office solarcell 3000000.01 THB 50 false true ONVALLA solar cell high value approval

View File

@@ -1 +1,13 @@
organization_code,workflow_code,step_number,role_code,role_name,approval_mode,is_required,sla_hours,calendar_mode,timeout_action
ALLA,quotation_standard,1,sales_manager,Sales Manager,sequential,true,24,calendar_days,escalate
ALLA,quotation_manager_review,1,sales_manager,Sales Manager,sequential,true,24,calendar_days,escalate
ALLA,quotation_manager_review,2,department_manager,Department Manager,sequential,true,48,calendar_days,escalate
ALLA,quotation_executive_review,1,sales_manager,Sales Manager,sequential,true,24,calendar_days,escalate
ALLA,quotation_executive_review,2,department_manager,Department Manager,sequential,true,48,calendar_days,escalate
ALLA,quotation_executive_review,3,top_manager,Top Manager,sequential,true,72,calendar_days,none
ONVALLA,quotation_standard,1,sales_manager,Sales Manager,sequential,true,24,calendar_days,escalate
ONVALLA,quotation_manager_review,1,sales_manager,Sales Manager,sequential,true,24,calendar_days,escalate
ONVALLA,quotation_manager_review,2,department_manager,Department Manager,sequential,true,48,calendar_days,escalate
ONVALLA,quotation_executive_review,1,sales_manager,Sales Manager,sequential,true,24,calendar_days,escalate
ONVALLA,quotation_executive_review,2,department_manager,Department Manager,sequential,true,48,calendar_days,escalate
ONVALLA,quotation_executive_review,3,top_manager,Top Manager,sequential,true,72,calendar_days,none
1 organization_code workflow_code step_number role_code role_name approval_mode is_required sla_hours calendar_mode timeout_action
2 ALLA quotation_standard 1 sales_manager Sales Manager sequential true 24 calendar_days escalate
3 ALLA quotation_manager_review 1 sales_manager Sales Manager sequential true 24 calendar_days escalate
4 ALLA quotation_manager_review 2 department_manager Department Manager sequential true 48 calendar_days escalate
5 ALLA quotation_executive_review 1 sales_manager Sales Manager sequential true 24 calendar_days escalate
6 ALLA quotation_executive_review 2 department_manager Department Manager sequential true 48 calendar_days escalate
7 ALLA quotation_executive_review 3 top_manager Top Manager sequential true 72 calendar_days none
8 ONVALLA quotation_standard 1 sales_manager Sales Manager sequential true 24 calendar_days escalate
9 ONVALLA quotation_manager_review 1 sales_manager Sales Manager sequential true 24 calendar_days escalate
10 ONVALLA quotation_manager_review 2 department_manager Department Manager sequential true 48 calendar_days escalate
11 ONVALLA quotation_executive_review 1 sales_manager Sales Manager sequential true 24 calendar_days escalate
12 ONVALLA quotation_executive_review 2 department_manager Department Manager sequential true 48 calendar_days escalate
13 ONVALLA quotation_executive_review 3 top_manager Top Manager sequential true 72 calendar_days none

View File

@@ -1 +1,7 @@
organization_code,workflow_code,workflow_name,entity_type,description,is_system,is_active
ALLA,quotation_standard,Quotation Standard Approval,quotation,Standard quotation approval for branch and product test cases,false,true
ALLA,quotation_manager_review,Quotation Manager Review,quotation,Manager approval for medium value quotations,false,true
ALLA,quotation_executive_review,Quotation Executive Review,quotation,Executive approval for high value quotations,false,true
ONVALLA,quotation_standard,Quotation Standard Approval,quotation,Standard quotation approval for branch and product test cases,false,true
ONVALLA,quotation_manager_review,Quotation Manager Review,quotation,Manager approval for medium value quotations,false,true
ONVALLA,quotation_executive_review,Quotation Executive Review,quotation,Executive approval for high value quotations,false,true
1 organization_code workflow_code workflow_name entity_type description is_system is_active
2 ALLA quotation_standard Quotation Standard Approval quotation Standard quotation approval for branch and product test cases false true
3 ALLA quotation_manager_review Quotation Manager Review quotation Manager approval for medium value quotations false true
4 ALLA quotation_executive_review Quotation Executive Review quotation Executive approval for high value quotations false true
5 ONVALLA quotation_standard Quotation Standard Approval quotation Standard quotation approval for branch and product test cases false true
6 ONVALLA quotation_manager_review Quotation Manager Review quotation Manager approval for medium value quotations false true
7 ONVALLA quotation_executive_review Quotation Executive Review quotation Executive approval for high value quotations false true

View File

@@ -1,2 +1,28 @@
organization_code,user_email,crm_role_code,is_primary,branch_scope_mode,branch_codes,product_type_scope_mode,product_type_codes,is_active
ALLA,super_admin@allaos.local,crm_admin,true,all,,all,,true
ALLA,gm.uat@allaos.local,department_manager,true,all,,all,,true
ALLA,gm.uat@allaos.local,sales_manager,false,all,,all,,true
ALLA,ceo.uat@allaos.local,top_manager,true,all,,all,,true
ALLA,head_mk.uat@allaos.local,marketing,true,all,,all,,true
ALLA,mk.uat@allaos.local,marketing,true,selected,head_office,all,,true
ALLA,manager.uat@allaos.local,sales_manager,true,all,,all,,true
ALLA,mgr_crane.uat@allaos.local,sales_manager,true,selected,head_office,selected,crane,true
ALLA,sales_crane.uat@allaos.local,sales,true,selected,head_office,selected,crane,true
ALLA,mgr_dock.uat@allaos.local,sales_manager,true,selected,head_office,selected,dockdoor,true
ALLA,sales_dock.uat@allaos.local,sales,true,selected,head_office,selected,dockdoor,true
ALLA,mgr_solar.uat@allaos.local,sales_manager,true,all,,all,,true
ALLA,sales_solar.uat@allaos.local,sales,true,all,,all,,true
ALLA,sales.uat@allaos.local,sales,true,all,,all,,true
ONVALLA,gm.onvalla@allaos.local,department_manager,true,all,,all,,true
ONVALLA,gm.onvalla@allaos.local,sales_manager,false,all,,all,,true
ONVALLA,ceo.onvalla@allaos.local,top_manager,true,all,,all,,true
ONVALLA,head_mk.onvalla@allaos.local,marketing,true,all,,all,,true
ONVALLA,mk.onvalla@allaos.local,marketing,true,selected,head_office,all,,true
ONVALLA,manager.onvalla@allaos.local,sales_manager,true,all,,all,,true
ONVALLA,mgr_crane.onvalla@allaos.local,sales_manager,true,selected,head_office,selected,crane,true
ONVALLA,sales_crane.onvalla@allaos.local,sales,true,selected,head_office,selected,crane,true
ONVALLA,mgr_dock.onvalla@allaos.local,sales_manager,true,all,,all,,true
ONVALLA,sales_dock.onvalla@allaos.local,sales,true,all,,all,,true
ONVALLA,mgr_solar.onvalla@allaos.local,sales_manager,true,selected,head_office,selected,solarcell,true
ONVALLA,sales_solar.onvalla@allaos.local,sales,true,selected,head_office,selected,solarcell,true
ONVALLA,sales.onvalla@allaos.local,sales,true,all,,all,,true
1 organization_code user_email crm_role_code is_primary branch_scope_mode branch_codes product_type_scope_mode product_type_codes is_active
2 ALLA super_admin@allaos.local crm_admin true all all true
3 ALLA gm.uat@allaos.local department_manager true all all true
4 ALLA gm.uat@allaos.local sales_manager false all all true
5 ALLA ceo.uat@allaos.local top_manager true all all true
6 ALLA head_mk.uat@allaos.local marketing true all all true
7 ALLA mk.uat@allaos.local marketing true selected head_office all true
8 ALLA manager.uat@allaos.local sales_manager true all all true
9 ALLA mgr_crane.uat@allaos.local sales_manager true selected head_office selected crane true
10 ALLA sales_crane.uat@allaos.local sales true selected head_office selected crane true
11 ALLA mgr_dock.uat@allaos.local sales_manager true selected head_office selected dockdoor true
12 ALLA sales_dock.uat@allaos.local sales true selected head_office selected dockdoor true
13 ALLA mgr_solar.uat@allaos.local sales_manager true all all true
14 ALLA sales_solar.uat@allaos.local sales true all all true
15 ALLA sales.uat@allaos.local sales true all all true
16 ONVALLA gm.onvalla@allaos.local department_manager true all all true
17 ONVALLA gm.onvalla@allaos.local sales_manager false all all true
18 ONVALLA ceo.onvalla@allaos.local top_manager true all all true
19 ONVALLA head_mk.onvalla@allaos.local marketing true all all true
20 ONVALLA mk.onvalla@allaos.local marketing true selected head_office all true
21 ONVALLA manager.onvalla@allaos.local sales_manager true all all true
22 ONVALLA mgr_crane.onvalla@allaos.local sales_manager true selected head_office selected crane true
23 ONVALLA sales_crane.onvalla@allaos.local sales true selected head_office selected crane true
24 ONVALLA mgr_dock.onvalla@allaos.local sales_manager true all all true
25 ONVALLA sales_dock.onvalla@allaos.local sales true all all true
26 ONVALLA mgr_solar.onvalla@allaos.local sales_manager true selected head_office selected solarcell true
27 ONVALLA sales_solar.onvalla@allaos.local sales true selected head_office selected solarcell true
28 ONVALLA sales.onvalla@allaos.local sales true all all true

View File

@@ -1,6 +1,13 @@
organization_code,branch_code,product_type_code,document_type,prefix,period,current_number,padding_length,format,reset_policy,is_active
ALLA,head_office,crane,customer,CUS,yearly,0,4,{prefix}-{yyyy}-{running},period,true
ALLA,head_office,crane,lead,LD,yearly,0,4,{prefix}-{yyyy}-{running},period,true
ALLA,head_office,crane,opportunity,OP,yearly,0,4,{prefix}-{yyyy}-{running},period,true
ALLA,head_office,crane,quotation,QT-AL-CR,monthly,0,4,{prefix}-{yy}{MM}-{running},period,true
ONVALLA,head_office,crane,quotation,QT-ON-CR,monthly,0,4,{prefix}-{yy}{MM}-{running},period,true
ALLA,head_office,,customer,CUS,2607,0,3,{prefix}{period}-{running},period,true
ALLA,head_office,,crm_lead,LD,2607,0,3,{prefix}{period}-{running},period,true
ALLA,head_office,,opportunity,OP,2607,0,3,{prefix}{period}-{running},period,true
ALLA,head_office,,crm_opportunity,EN,2607,0,3,{prefix}{period}-{running},period,true
ALLA,head_office,crane,quotation,QT-AL-CR,2607,0,4,{prefix}{period}-{running},period,true
ALLA,head_office,dockdoor,quotation,QT-AL-DK,2607,0,4,{prefix}{period}-{running},period,true
ONVALLA,head_office,,customer,CUS,2607,0,3,{prefix}{period}-{running},period,true
ONVALLA,head_office,,crm_lead,LD,2607,0,3,{prefix}{period}-{running},period,true
ONVALLA,head_office,,opportunity,OP,2607,0,3,{prefix}{period}-{running},period,true
ONVALLA,head_office,,crm_opportunity,EN,2607,0,3,{prefix}{period}-{running},period,true
ONVALLA,head_office,crane,quotation,QT-ON-CR,2607,0,4,{prefix}{period}-{running},period,true
ONVALLA,head_office,solarcell,quotation,QT-ON-SC,2607,0,4,{prefix}{period}-{running},period,true
1 organization_code branch_code product_type_code document_type prefix period current_number padding_length format reset_policy is_active
2 ALLA head_office crane customer CUS yearly 2607 0 4 3 {prefix}-{yyyy}-{running} {prefix}{period}-{running} period true
3 ALLA head_office crane lead crm_lead LD yearly 2607 0 4 3 {prefix}-{yyyy}-{running} {prefix}{period}-{running} period true
4 ALLA head_office crane opportunity OP yearly 2607 0 4 3 {prefix}-{yyyy}-{running} {prefix}{period}-{running} period true
5 ALLA head_office crane quotation crm_opportunity QT-AL-CR EN monthly 2607 0 4 3 {prefix}-{yy}{MM}-{running} {prefix}{period}-{running} period true
6 ONVALLA ALLA head_office crane quotation QT-ON-CR QT-AL-CR monthly 2607 0 4 {prefix}-{yy}{MM}-{running} {prefix}{period}-{running} period true
7 ALLA head_office dockdoor quotation QT-AL-DK 2607 0 4 {prefix}{period}-{running} period true
8 ONVALLA head_office customer CUS 2607 0 3 {prefix}{period}-{running} period true
9 ONVALLA head_office crm_lead LD 2607 0 3 {prefix}{period}-{running} period true
10 ONVALLA head_office opportunity OP 2607 0 3 {prefix}{period}-{running} period true
11 ONVALLA head_office crm_opportunity EN 2607 0 3 {prefix}{period}-{running} period true
12 ONVALLA head_office crane quotation QT-ON-CR 2607 0 4 {prefix}{period}-{running} period true
13 ONVALLA head_office solarcell quotation QT-ON-SC 2607 0 4 {prefix}{period}-{running} period true

View File

@@ -1,4 +1,16 @@
organization_code,category,code,label,value,parent_category,parent_code,sort_order,is_active,metadata_json
ALLA,crm_currency,thb,Thai Baht,THB,,,1,true,{}
ONVALLA,crm_currency,thb,Thai Baht,THB,,,1,true,{}
ALLA,crm_lead_status,new_job,New Job,new_job,,,1,true,{}
ALLA,crm_lead_status,follow_up,Follow Up,follow_up,,,2,true,{}
ALLA,crm_lead_status,assigned,Assigned,assigned,,,3,true,{}
ALLA,crm_lead_status,closed_lost,Closed Lost,closed_lost,,,4,true,{}
ALLA,crm_lead_status,cancel,Cancel,cancel,,,5,true,{}
ONVALLA,crm_lead_status,new_job,New Job,new_job,,,1,true,{}
ONVALLA,crm_lead_status,follow_up,Follow Up,follow_up,,,2,true,{}
ONVALLA,crm_lead_status,assigned,Assigned,assigned,,,3,true,{}
ONVALLA,crm_lead_status,closed_lost,Closed Lost,closed_lost,,,4,true,{}
ONVALLA,crm_lead_status,cancel,Cancel,cancel,,,5,true,{}
ALLA,crm_customer_group,construction,Construction,,,,1,true,{}
ALLA,crm_customer_group,industrial,Industrial,,,,2,true,{}
1 organization_code category code label value parent_category parent_code sort_order is_active metadata_json
2 ALLA crm_currency thb Thai Baht THB 1 true {}
3 ONVALLA crm_currency thb Thai Baht THB 1 true {}
4 ALLA crm_lead_status new_job New Job new_job 1 true {}
5 ALLA crm_lead_status follow_up Follow Up follow_up 2 true {}
6 ALLA crm_lead_status assigned Assigned assigned 3 true {}
7 ALLA crm_lead_status closed_lost Closed Lost closed_lost 4 true {}
8 ALLA crm_lead_status cancel Cancel cancel 5 true {}
9 ONVALLA crm_lead_status new_job New Job new_job 1 true {}
10 ONVALLA crm_lead_status follow_up Follow Up follow_up 2 true {}
11 ONVALLA crm_lead_status assigned Assigned assigned 3 true {}
12 ONVALLA crm_lead_status closed_lost Closed Lost closed_lost 4 true {}
13 ONVALLA crm_lead_status cancel Cancel cancel 5 true {}
14 ALLA crm_customer_group construction Construction 1 true {}
15 ALLA crm_customer_group industrial Industrial 2 true {}
16 ALLA crm_customer_sub_group factory Factory crm_customer_group industrial 1 true {}

View File

@@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditAction } from '@/features/foundation/audit-log/service';
import { buildCrmSecurityContext } from '@/features/crm/security/server/service';
import { assignActivity } from '@/features/crm/activities/server/service';
import { activityAssignSchema } from '@/features/crm/activities/schemas/activity.schema';
import { CRM_ACTIVITY_PERMISSIONS } from '@/features/crm/activity/types';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = { params: Promise<{ id: string }> };
export async function POST(request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.reassign
});
const payload = activityAssignSchema.parse(await request.json());
const accessContext = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const activity = await assignActivity(id, organization.id, session.user.id, payload, accessContext);
await auditAction({
organizationId: organization.id,
branchId: activity.branchId,
userId: session.user.id,
entityType: 'crm_activity',
entityId: id,
action: 'assign',
afterData: activity
});
return NextResponse.json(activity);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json(
{ message: error.issues[0]?.message ?? 'Invalid payload' },
{ status: 400 }
);
}
return NextResponse.json({ message: 'Unable to assign activity' }, { status: 500 });
}
}

View File

@@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditAction } from '@/features/foundation/audit-log/service';
import { buildCrmSecurityContext } from '@/features/crm/security/server/service';
import { cancelActivity } from '@/features/crm/activities/server/service';
import { activityCancelSchema } from '@/features/crm/activities/schemas/activity.schema';
import { CRM_ACTIVITY_PERMISSIONS } from '@/features/crm/activity/types';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = { params: Promise<{ id: string }> };
export async function POST(request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.cancel
});
const payload = activityCancelSchema.parse(await request.json());
const accessContext = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const activity = await cancelActivity(id, organization.id, session.user.id, payload, accessContext);
await auditAction({
organizationId: organization.id,
branchId: activity.branchId,
userId: session.user.id,
entityType: 'crm_activity',
entityId: id,
action: 'cancel',
afterData: activity
});
return NextResponse.json(activity);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json(
{ message: error.issues[0]?.message ?? 'Invalid payload' },
{ status: 400 }
);
}
return NextResponse.json({ message: 'Unable to cancel activity' }, { status: 500 });
}
}

View File

@@ -0,0 +1,53 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditAction } from '@/features/foundation/audit-log/service';
import { buildCrmSecurityContext } from '@/features/crm/security/server/service';
import { completeActivity } from '@/features/crm/activities/server/service';
import { activityCompleteSchema } from '@/features/crm/activities/schemas/activity.schema';
import { CRM_ACTIVITY_PERMISSIONS } from '@/features/crm/activity/types';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = { params: Promise<{ id: string }> };
export async function POST(request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.complete
});
const payload = activityCompleteSchema.parse(await request.json());
const accessContext = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const activity = await completeActivity(
id,
organization.id,
session.user.id,
payload,
accessContext
);
await auditAction({
organizationId: organization.id,
branchId: activity.branchId,
userId: session.user.id,
entityType: 'crm_activity',
entityId: id,
action: 'complete',
afterData: activity
});
return NextResponse.json(activity);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json(
{ message: error.issues[0]?.message ?? 'Invalid payload' },
{ status: 400 }
);
}
return NextResponse.json({ message: 'Unable to complete activity' }, { status: 500 });
}
}

View File

@@ -0,0 +1,53 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditAction } from '@/features/foundation/audit-log/service';
import { buildCrmSecurityContext } from '@/features/crm/security/server/service';
import { rescheduleActivity } from '@/features/crm/activities/server/service';
import { activityRescheduleSchema } from '@/features/crm/activities/schemas/activity.schema';
import { CRM_ACTIVITY_PERMISSIONS } from '@/features/crm/activity/types';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = { params: Promise<{ id: string }> };
export async function POST(request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.update
});
const payload = activityRescheduleSchema.parse(await request.json());
const accessContext = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const activity = await rescheduleActivity(
id,
organization.id,
session.user.id,
payload,
accessContext
);
await auditAction({
organizationId: organization.id,
branchId: activity.branchId,
userId: session.user.id,
entityType: 'crm_activity',
entityId: id,
action: 'reschedule',
afterData: activity
});
return NextResponse.json(activity);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json(
{ message: error.issues[0]?.message ?? 'Invalid payload' },
{ status: 400 }
);
}
return NextResponse.json({ message: 'Unable to reschedule activity' }, { status: 500 });
}
}

View File

@@ -0,0 +1,104 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditDelete, auditUpdate } from '@/features/foundation/audit-log/service';
import { buildCrmSecurityContext } from '@/features/crm/security/server/service';
import {
deleteActivity,
getActivityById,
updateActivity
} from '@/features/crm/activities/server/service';
import { activityUpdateSchema } from '@/features/crm/activities/schemas/activity.schema';
import { CRM_ACTIVITY_PERMISSIONS } from '@/features/crm/activity/types';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = { params: Promise<{ id: string }> };
export async function GET(_request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.read
});
const accessContext = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const activity = await getActivityById(id, organization.id, accessContext);
return NextResponse.json(activity);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
return NextResponse.json({ message: 'Unable to load activity' }, { status: 500 });
}
}
export async function PATCH(request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.update
});
const payload = activityUpdateSchema.parse(await request.json());
const accessContext = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const before = await getActivityById(id, organization.id, accessContext);
const updated = await updateActivity(id, organization.id, session.user.id, payload, accessContext);
await auditUpdate({
organizationId: organization.id,
branchId: updated.branchId,
userId: session.user.id,
entityType: 'crm_activity',
entityId: id,
beforeData: before,
afterData: updated
});
return NextResponse.json(updated);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json(
{ message: error.issues[0]?.message ?? 'Invalid payload' },
{ status: 400 }
);
}
return NextResponse.json({ message: 'Unable to update activity' }, { status: 500 });
}
}
export async function DELETE(_request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.delete
});
const accessContext = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const before = await getActivityById(id, organization.id, accessContext);
await deleteActivity(id, organization.id, session.user.id, accessContext);
await auditDelete({
organizationId: organization.id,
branchId: before.branchId,
userId: session.user.id,
entityType: 'crm_activity',
entityId: id,
beforeData: before,
afterData: { deletedAt: new Date().toISOString() }
});
return NextResponse.json({ success: true });
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
return NextResponse.json({ message: 'Unable to delete activity' }, { status: 500 });
}
}

View File

@@ -0,0 +1,19 @@
import { NextResponse } from 'next/server';
import { getActivityReferenceData } from '@/features/crm/activities/server/service';
import { CRM_ACTIVITY_PERMISSIONS } from '@/features/crm/activity/types';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
export async function GET() {
try {
const { organization } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.read
});
const data = await getActivityReferenceData(organization.id);
return NextResponse.json(data);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
return NextResponse.json({ message: 'Unable to load activity reference data' }, { status: 500 });
}
}

View File

@@ -0,0 +1,90 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditCreate } from '@/features/foundation/audit-log/service';
import { buildCrmSecurityContext } from '@/features/crm/security/server/service';
import {
createActivity,
listActivities
} from '@/features/crm/activities/server/service';
import { activityMutationSchema } from '@/features/crm/activities/schemas/activity.schema';
import {
CRM_ACTIVITY_ENTITY_TYPES,
CRM_ACTIVITY_PERMISSIONS,
CRM_ACTIVITY_STATUSES,
CRM_ACTIVITY_TYPES
} from '@/features/crm/activity/types';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
const activityListQuerySchema = z.object({
search: z.string().optional(),
primaryEntityType: z.enum(CRM_ACTIVITY_ENTITY_TYPES).optional(),
primaryEntityId: z.string().optional(),
activityType: z.enum(CRM_ACTIVITY_TYPES).optional(),
status: z.enum(CRM_ACTIVITY_STATUSES).optional(),
ownerId: z.string().optional(),
assignedToId: z.string().optional()
});
export async function GET(request: NextRequest) {
try {
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.read
});
const filters = activityListQuerySchema.parse({
search: request.nextUrl.searchParams.get('search') ?? undefined,
primaryEntityType: request.nextUrl.searchParams.get('primaryEntityType') ?? undefined,
primaryEntityId: request.nextUrl.searchParams.get('primaryEntityId') ?? undefined,
activityType: request.nextUrl.searchParams.get('activityType') ?? undefined,
status: request.nextUrl.searchParams.get('status') ?? undefined,
ownerId: request.nextUrl.searchParams.get('ownerId') ?? undefined,
assignedToId: request.nextUrl.searchParams.get('assignedToId') ?? undefined
});
const accessContext = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const response = await listActivities(organization.id, filters, accessContext);
return NextResponse.json(response);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
return NextResponse.json({ message: 'Unable to load activities' }, { status: 500 });
}
}
export async function POST(request: NextRequest) {
try {
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.create
});
const payload = activityMutationSchema.parse(await request.json());
const accessContext = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const created = await createActivity(organization.id, session.user.id, payload, accessContext);
await auditCreate({
organizationId: organization.id,
branchId: created.branchId,
userId: session.user.id,
entityType: 'crm_activity',
entityId: created.id,
afterData: created
});
return NextResponse.json(created);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json(
{ message: error.issues[0]?.message ?? 'Invalid payload' },
{ status: 400 }
);
}
return NextResponse.json({ message: 'Unable to create activity' }, { status: 500 });
}
}

View File

@@ -0,0 +1,100 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { CRM_ACTIVITY_PERMISSIONS } from '@/features/crm/activity/types';
import { buildCrmSecurityContext } from '@/features/crm/security/server/service';
import { listCalendarItems } from '@/features/crm/calendar/server/service';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
const calendarQuerySchema = z.object({
startFrom: z.string().datetime(),
startTo: z.string().datetime(),
lens: z.enum(['personal', 'team', 'manager', 'executive']).optional(),
userId: z.string().optional(),
teamUserIds: z
.string()
.optional()
.transform((value) => value?.split(',').filter(Boolean)),
branchId: z.string().optional(),
productType: z.string().optional(),
customerId: z.string().optional(),
leadId: z.string().optional(),
opportunityId: z.string().optional(),
quotationId: z.string().optional(),
activityType: z.string().optional(),
category: z
.enum([
'activity',
'meeting',
'visit',
'site_survey',
'task',
'reminder',
'opportunity_milestone',
'quotation_milestone',
'approval_milestone'
])
.optional(),
priority: z.enum(['low', 'normal', 'high', 'critical']).optional(),
overdue: z.coerce.boolean().optional(),
milestone: z.coerce.boolean().optional(),
hotProject: z.coerce.boolean().optional(),
search: z.string().optional(),
limit: z.coerce.number().int().positive().max(500).optional(),
cursor: z.string().optional()
});
export async function GET(request: NextRequest) {
try {
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.read
});
const query = calendarQuerySchema.parse({
startFrom: request.nextUrl.searchParams.get('startFrom') ?? undefined,
startTo: request.nextUrl.searchParams.get('startTo') ?? undefined,
lens: request.nextUrl.searchParams.get('lens') ?? undefined,
userId: request.nextUrl.searchParams.get('userId') ?? undefined,
teamUserIds: request.nextUrl.searchParams.get('teamUserIds') ?? undefined,
branchId: request.nextUrl.searchParams.get('branchId') ?? undefined,
productType: request.nextUrl.searchParams.get('productType') ?? undefined,
customerId: request.nextUrl.searchParams.get('customerId') ?? undefined,
leadId: request.nextUrl.searchParams.get('leadId') ?? undefined,
opportunityId: request.nextUrl.searchParams.get('opportunityId') ?? undefined,
quotationId: request.nextUrl.searchParams.get('quotationId') ?? undefined,
activityType: request.nextUrl.searchParams.get('activityType') ?? undefined,
category: request.nextUrl.searchParams.get('category') ?? undefined,
priority: request.nextUrl.searchParams.get('priority') ?? undefined,
overdue: request.nextUrl.searchParams.get('overdue') ?? undefined,
milestone: request.nextUrl.searchParams.get('milestone') ?? undefined,
hotProject: request.nextUrl.searchParams.get('hotProject') ?? undefined,
search: request.nextUrl.searchParams.get('search') ?? undefined,
limit: request.nextUrl.searchParams.get('limit') ?? undefined,
cursor: request.nextUrl.searchParams.get('cursor') ?? undefined
});
const context = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const response = await listCalendarItems(
{
...query,
organizationId: organization.id
},
context
);
return NextResponse.json(response);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json({ message: 'Invalid calendar filters' }, { status: 400 });
}
console.error('[crm/calendar] failed to list calendar projection', error);
return NextResponse.json({ message: 'Failed to load calendar' }, { status: 500 });
}
}

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { listRecentActivitiesForContext } from '@/features/crm/activities/server/timeline-service';
import { auditDelete, auditUpdate } from '@/features/foundation/audit-log/service';
import {
auditCrmSecurityEvent,
@@ -38,10 +39,17 @@ export async function GET(_request: NextRequest, { params }: Params) {
userId: session.user.id,
membership
});
const [customer, activity, ownerHistory] = await Promise.all([
const [customer, activity, ownerHistory, recentActivities] = await Promise.all([
getCustomerDetail(id, organization.id, accessContext),
getCustomerActivity(id, organization.id),
getCustomerOwnerHistory(id, organization.id, accessContext)
getCustomerOwnerHistory(id, organization.id, accessContext),
listRecentActivitiesForContext({
organizationId: organization.id,
entityType: 'customer',
entityId: id,
context: accessContext,
limit: 6
})
]);
return NextResponse.json({
@@ -50,6 +58,7 @@ export async function GET(_request: NextRequest, { params }: Params) {
message: 'Customer loaded successfully',
customer,
activity,
recentActivities,
ownerHistory
});
} catch (error) {

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { listRecentActivitiesForContext } from '@/features/crm/activities/server/timeline-service';
import { auditAction } from '@/features/foundation/audit-log/service';
import { updateLeadSchema } from '@/features/crm/leads/schemas/lead.schema';
import {
@@ -28,10 +29,17 @@ export async function GET(_request: NextRequest, { params }: Params) {
userId: session.user.id,
membership
});
const [lead, followups, referenceData] = await Promise.all([
const [lead, followups, referenceData, recentActivities] = await Promise.all([
getLeadById(id, organization.id, accessContext),
listLeadFollowups(id, organization.id, accessContext),
getLeadReferenceData(organization.id)
getLeadReferenceData(organization.id),
listRecentActivitiesForContext({
organizationId: organization.id,
entityType: 'lead',
entityId: id,
context: accessContext,
limit: 6
})
]);
await auditAction({
@@ -52,7 +60,8 @@ export async function GET(_request: NextRequest, { params }: Params) {
message: 'Lead loaded successfully',
lead,
followups,
referenceData
referenceData,
recentActivities
});
} catch (error) {
if (error instanceof AuthError) {

View File

@@ -0,0 +1,59 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { CRM_ACTIVITY_PERMISSIONS, CRM_ACTIVITY_TYPES } from '@/features/crm/activity/types';
import { buildCrmSecurityContext } from '@/features/crm/security/server/service';
import { getMyDayWorkspace } from '@/features/crm/my-day/server/service';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
const myDayQuerySchema = z.object({
mode: z.enum(['personal', 'manager']).optional(),
range: z.enum(['today', 'tomorrow', 'this_week']).optional(),
userId: z.string().optional(),
teamUserIds: z
.string()
.optional()
.transform((value) => value?.split(',').filter(Boolean)),
branchId: z.string().optional(),
productType: z.string().optional(),
customerId: z.string().optional(),
activityType: z.enum(CRM_ACTIVITY_TYPES).optional(),
hotProject: z.coerce.boolean().optional()
});
export async function GET(request: NextRequest) {
try {
const { organization, session, membership } = await requireOrganizationAccess({
permission: CRM_ACTIVITY_PERMISSIONS.read
});
const filters = myDayQuerySchema.parse({
mode: request.nextUrl.searchParams.get('mode') ?? undefined,
range: request.nextUrl.searchParams.get('range') ?? undefined,
userId: request.nextUrl.searchParams.get('userId') ?? undefined,
teamUserIds: request.nextUrl.searchParams.get('teamUserIds') ?? undefined,
branchId: request.nextUrl.searchParams.get('branchId') ?? undefined,
productType: request.nextUrl.searchParams.get('productType') ?? undefined,
customerId: request.nextUrl.searchParams.get('customerId') ?? undefined,
activityType: request.nextUrl.searchParams.get('activityType') ?? undefined,
hotProject: request.nextUrl.searchParams.get('hotProject') ?? undefined
});
const context = buildCrmSecurityContext({
organizationId: organization.id,
userId: session.user.id,
membership
});
const response = await getMyDayWorkspace(organization.id, context, filters);
return NextResponse.json(response);
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json({ message: 'Invalid My Day filters' }, { status: 400 });
}
console.error('[crm/my-day] failed to load workspace', error);
return NextResponse.json({ message: 'Failed to load My Day workspace' }, { status: 500 });
}
}

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { listRecentActivitiesForContext } from '@/features/crm/activities/server/timeline-service';
import { auditDelete, auditUpdate } from '@/features/foundation/audit-log/service';
import {
getOpportunityActivity,
@@ -42,9 +43,16 @@ export async function GET(_request: NextRequest, { params }: Params) {
branchScopeMode: membership.branchScopeMode === 'assigned' ? 'assigned' : 'all',
productScopeMode: membership.productScopeMode === 'assigned' ? 'assigned' : 'all'
};
const [opportunity, activity] = await Promise.all([
const [opportunity, activity, recentActivities] = await Promise.all([
getOpportunityDetail(id, organization.id, accessContext),
getOpportunityActivity(id, organization.id, accessContext)
getOpportunityActivity(id, organization.id, accessContext),
listRecentActivitiesForContext({
organizationId: organization.id,
entityType: 'opportunity',
entityId: id,
context: accessContext,
limit: 6
})
]);
return NextResponse.json({
@@ -52,7 +60,8 @@ export async function GET(_request: NextRequest, { params }: Params) {
time: new Date().toISOString(),
message: 'Opportunity loaded successfully',
opportunity,
activity
activity,
recentActivities
});
} catch (error) {
if (error instanceof AuthError) {

View File

@@ -12,9 +12,15 @@ type Params = {
params: Promise<{ id: string }>;
};
export async function GET(_request: NextRequest, { params }: Params) {
export async function GET(request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const templateVariant = request.nextUrl.searchParams.get('templateVariant');
if (templateVariant && templateVariant !== 'product-v1') {
return NextResponse.json({ message: 'Unsupported template variant' }, { status: 400 });
}
const { organization, session, membership } = await requireOrganizationAccess({
permission: PERMISSIONS.crmQuotationPdfPreview
});
@@ -36,7 +42,9 @@ export async function GET(_request: NextRequest, { params }: Params) {
return NextResponse.json({ message: 'Forbidden' }, { status: 403 });
}
const pdf = await generateQuotationPreviewPdf(id, organization.id);
const pdf = await generateQuotationPreviewPdf(id, organization.id, {
templateVariant
});
return new NextResponse(pdf.buffer, {
headers: {

View File

@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { listRecentActivitiesForContext } from '@/features/crm/activities/server/timeline-service';
import { auditDelete, auditUpdate } from '@/features/foundation/audit-log/service';
import { buildCrmSecurityContext } from '@/features/crm/security/server/service';
import {
@@ -27,9 +28,16 @@ export async function GET(_request: NextRequest, { params }: Params) {
userId: session.user.id,
membership
});
const [quotation, activity] = await Promise.all([
const [quotation, activity, recentActivities] = await Promise.all([
getQuotationDetail(id, organization.id, accessContext),
getQuotationActivity(id, organization.id)
getQuotationActivity(id, organization.id),
listRecentActivitiesForContext({
organizationId: organization.id,
entityType: 'quotation',
entityId: id,
context: accessContext,
limit: 6
})
]);
return NextResponse.json({
@@ -37,7 +45,8 @@ export async function GET(_request: NextRequest, { params }: Params) {
time: new Date().toISOString(),
message: 'Quotation loaded successfully',
quotation,
activity
activity,
recentActivities
});
} catch (error) {
if (error instanceof AuthError) {

View File

@@ -0,0 +1,62 @@
import { NextRequest, NextResponse } from 'next/server';
import { uploadDocumentLibraryVersion } from '@/features/foundation/document-library/server/service';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
export async function POST(request: NextRequest) {
try {
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmDocumentLibraryUpload
});
const formData = await request.formData();
const libraryId = formData.get('libraryId');
const version = formData.get('version');
const file = formData.get('file');
if (typeof libraryId !== 'string' || !libraryId.trim()) {
return NextResponse.json({ message: 'Library ID is required' }, { status: 400 });
}
if (typeof version !== 'string' || !version.trim()) {
return NextResponse.json({ message: 'Version is required' }, { status: 400 });
}
if (!(file instanceof File)) {
return NextResponse.json({ message: 'PDF file required' }, { status: 400 });
}
const created = await uploadDocumentLibraryVersion({
libraryId,
organizationId: organization.id,
userId: session.user.id,
version,
file: {
name: file.name,
type: file.type,
size: file.size,
buffer: Buffer.from(await file.arrayBuffer())
}
});
return NextResponse.json({
success: true,
message: 'Document library version uploaded successfully',
version: created
});
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json(
{ message: 'Unable to upload document library version' },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,27 @@
import { HydrationBoundary, dehydrate } from '@tanstack/react-query';
import PageContainer from '@/components/layout/page-container';
import { ActivityListing } from '@/features/crm/activities/components/activity-listing';
import {
activitiesQueryOptions,
activityReferenceDataOptions
} from '@/features/crm/activities/api/queries';
import { getQueryClient } from '@/lib/query-client';
export default function ActivitiesPage() {
const queryClient = getQueryClient();
const filters = {};
void queryClient.prefetchQuery(activitiesQueryOptions(filters));
void queryClient.prefetchQuery(activityReferenceDataOptions());
return (
<PageContainer
pageTitle='Activities'
pageDescription='Shared operational activity foundation across CRM entities.'
>
<HydrationBoundary state={dehydrate(queryClient)}>
<ActivityListing initialFilters={filters} />
</HydrationBoundary>
</PageContainer>
);
}

View File

@@ -0,0 +1,40 @@
import { HydrationBoundary, dehydrate } from '@tanstack/react-query';
import { Suspense } from 'react';
import PageContainer from '@/components/layout/page-container';
import {
CalendarWorkspace,
CalendarWorkspaceSkeleton
} from '@/features/crm/calendar/components/calendar-workspace';
import { calendarItemsQueryOptions } from '@/features/crm/calendar/api/queries';
import { getQueryClient } from '@/lib/query-client';
function defaultCalendarRange() {
const now = new Date();
const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1, 0, 0, 0));
const end = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0, 23, 59, 59));
return {
startFrom: start.toISOString(),
startTo: end.toISOString(),
lens: 'personal' as const,
limit: 300
};
}
export default function CrmCalendarPage() {
const queryClient = getQueryClient();
const filters = defaultCalendarRange();
void queryClient.prefetchQuery(calendarItemsQueryOptions(filters));
return (
<PageContainer
pageTitle='Calendar'
pageDescription='Operational schedule workspace for activities and governed CRM milestones.'
>
<HydrationBoundary state={dehydrate(queryClient)}>
<Suspense fallback={<CalendarWorkspaceSkeleton />}>
<CalendarWorkspace filters={filters} />
</Suspense>
</HydrationBoundary>
</PageContainer>
);
}

View File

@@ -0,0 +1,29 @@
import { HydrationBoundary, dehydrate } from '@tanstack/react-query';
import { Suspense } from 'react';
import PageContainer from '@/components/layout/page-container';
import { MyDayWorkspace, MyDayWorkspaceSkeleton } from '@/features/crm/my-day/components/my-day-workspace';
import { myDayQueryOptions } from '@/features/crm/my-day/api/queries';
import { getQueryClient } from '@/lib/query-client';
export const metadata = {
title: 'CRM: My Day'
};
export default function MyDayPage() {
const queryClient = getQueryClient();
const filters = { mode: 'personal' as const, range: 'today' as const };
void queryClient.prefetchQuery(myDayQueryOptions(filters));
return (
<PageContainer
pageTitle='My Day'
pageDescription='Personal operational workspace for today, overdue work, approvals, hot projects, and recent updates.'
>
<HydrationBoundary state={dehydrate(queryClient)}>
<Suspense fallback={<MyDayWorkspaceSkeleton />}>
<MyDayWorkspace initialFilters={filters} />
</Suspense>
</HydrationBoundary>
</PageContainer>
);
}

View File

@@ -1,25 +1,29 @@
import KBar from '@/components/kbar';
import AppSidebar from '@/components/layout/app-sidebar';
import Header from '@/components/layout/header';
import { InfoSidebar } from '@/components/layout/info-sidebar';
import { InfobarProvider } from '@/components/ui/infobar';
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
import type { Metadata } from 'next';
import { cookies } from 'next/headers';
import KBar from "@/components/kbar";
import AppSidebar from "@/components/layout/app-sidebar";
import Header from "@/components/layout/header";
import { InfoSidebar } from "@/components/layout/info-sidebar";
import { InfobarProvider } from "@/components/ui/infobar";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import type { Metadata } from "next";
import { cookies } from "next/headers";
export const metadata: Metadata = {
title: 'Next Shadcn Dashboard Starter',
description: 'Basic dashboard with Next.js and Shadcn',
title: "ALLA OS",
description: "ALLA OS",
robots: {
index: false,
follow: false
}
follow: false,
},
};
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
// Persisting the sidebar state in the cookie.
const cookieStore = await cookies();
const defaultOpen = cookieStore.get('sidebar_state')?.value === 'true';
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true";
return (
<KBar>
<SidebarProvider defaultOpen={defaultOpen}>
@@ -28,7 +32,7 @@ export default async function DashboardLayout({ children }: { children: React.Re
<Header />
<InfobarProvider defaultOpen={false}>
{children}
<InfoSidebar side='right' />
<InfoSidebar side="right" />
</InfobarProvider>
</SidebarInset>
</SidebarProvider>

View File

@@ -4,6 +4,14 @@ import { NavGroup } from "@/types";
{
label: "CRM",
items: [
// {
// title: "My Day",
// url: "/dashboard/crm/my-day",
// icon: "calendar",
// isActive: false,
// items: [],
// access: { requireOrg: true, permission: "crm.activity.read" },
// },
{
title: "Dashboard",
url: "/dashboard/crm",
@@ -12,6 +20,14 @@ import { NavGroup } from "@/types";
items: [],
access: { requireOrg: true, permission: "crm.dashboard.read" },
},
// {
// title: "Calendar",
// url: "/dashboard/crm/calendar",
// icon: "calendar",
// isActive: false,
// items: [],
// access: { requireOrg: true, permission: "crm.activity.read" },
// },
{
title: "ลูกค้า",
url: "/dashboard/crm/customers",

View File

@@ -1,6 +1,7 @@
import {
boolean,
doublePrecision,
index,
integer,
jsonb,
pgTable,
@@ -451,6 +452,311 @@ export const appNotificationDeliveries = pgTable('app_notification_deliveries',
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
});
export const businessEventOutbox = pgTable(
'business_event_outbox',
{
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
eventType: text('event_type').notNull(),
schemaVersion: integer('schema_version').notNull(),
entityType: text('entity_type').notNull(),
entityId: text('entity_id').notNull(),
correlationId: text('correlation_id').notNull(),
causationId: text('causation_id'),
eventEnvelope: jsonb('event_envelope').notNull(),
deliveryStatus: text('delivery_status').default('pending').notNull(),
attemptCount: integer('attempt_count').default(0).notNull(),
availableAt: timestamp('available_at', { withTimezone: true }).defaultNow().notNull(),
claimedBy: text('claimed_by'),
claimedAt: timestamp('claimed_at', { withTimezone: true }),
claimExpiresAt: timestamp('claim_expires_at', { withTimezone: true }),
processingStartedAt: timestamp('processing_started_at', { withTimezone: true }),
dispatchedAt: timestamp('dispatched_at', { withTimezone: true }),
failedAt: timestamp('failed_at', { withTimezone: true }),
deadLetteredAt: timestamp('dead_lettered_at', { withTimezone: true }),
lastErrorCode: text('last_error_code'),
lastErrorMessage: text('last_error_message'),
occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => ({
statusAvailableIdx: index('business_event_outbox_status_available_idx').on(
table.deliveryStatus,
table.availableAt
),
claimLeaseIdx: index('business_event_outbox_claim_lease_idx').on(
table.deliveryStatus,
table.claimExpiresAt
),
organizationEventIdx: index('business_event_outbox_org_event_idx').on(
table.organizationId,
table.eventType
)
})
);
export const projectionConsumerCheckpoints = pgTable(
'projection_consumer_checkpoints',
{
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
consumerName: text('consumer_name').notNull(),
projectionName: text('projection_name').notNull(),
eventId: text('event_id').notNull(),
eventType: text('event_type').notNull(),
schemaVersion: integer('schema_version').notNull(),
processingStatus: text('processing_status').default('pending').notNull(),
attemptCount: integer('attempt_count').default(0).notNull(),
firstAttemptedAt: timestamp('first_attempted_at', { withTimezone: true }),
lastAttemptedAt: timestamp('last_attempted_at', { withTimezone: true }),
completedAt: timestamp('completed_at', { withTimezone: true }),
nextRetryAt: timestamp('next_retry_at', { withTimezone: true }),
errorCode: text('error_code'),
errorMessage: text('error_message'),
processingDurationMs: integer('processing_duration_ms'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => ({
consumerEventIdx: uniqueIndex('projection_checkpoints_consumer_event_idx').on(
table.consumerName,
table.eventId
),
statusRetryIdx: index('projection_checkpoints_status_retry_idx').on(
table.processingStatus,
table.nextRetryAt
)
})
);
export const projectionDeadLetters = pgTable(
'projection_dead_letters',
{
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
consumerName: text('consumer_name').notNull(),
projectionName: text('projection_name').notNull(),
eventId: text('event_id').notNull(),
eventType: text('event_type').notNull(),
schemaVersion: integer('schema_version').notNull(),
attemptCount: integer('attempt_count').notNull(),
errorCode: text('error_code').notNull(),
errorMessage: text('error_message').notNull(),
failureMetadata: jsonb('failure_metadata'),
status: text('status').default('open').notNull(),
resolvedAt: timestamp('resolved_at', { withTimezone: true }),
resolvedBy: text('resolved_by'),
resolutionReason: text('resolution_reason'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => ({
consumerEventIdx: uniqueIndex('projection_dead_letters_consumer_event_idx').on(
table.consumerName,
table.eventId
),
organizationStatusIdx: index('projection_dead_letters_org_status_idx').on(
table.organizationId,
table.status
)
})
);
export const projectionRebuildRuns = pgTable(
'projection_rebuild_runs',
{
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
projectionName: text('projection_name').notNull(),
entityType: text('entity_type'),
entityId: text('entity_id'),
occurredFrom: timestamp('occurred_from', { withTimezone: true }),
occurredTo: timestamp('occurred_to', { withTimezone: true }),
dryRun: boolean('dry_run').default(true).notNull(),
resetExisting: boolean('reset_existing').default(false).notNull(),
sourceStrategy: text('source_strategy').notNull(),
status: text('status').default('pending').notNull(),
processedCount: integer('processed_count').default(0).notNull(),
skippedCount: integer('skipped_count').default(0).notNull(),
failedCount: integer('failed_count').default(0).notNull(),
errorCode: text('error_code'),
errorMessage: text('error_message'),
requestedBy: text('requested_by').notNull(),
startedAt: timestamp('started_at', { withTimezone: true }),
completedAt: timestamp('completed_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => ({
organizationProjectionIdx: index('projection_rebuild_runs_org_projection_idx').on(
table.organizationId,
table.projectionName
)
})
);
export const projectionHealthSnapshots = pgTable(
'projection_health_snapshots',
{
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
projectionName: text('projection_name').notNull(),
consumerName: text('consumer_name').notNull(),
status: text('status').default('healthy').notNull(),
lastProcessedEventId: text('last_processed_event_id'),
lastSuccessfulProcessingAt: timestamp('last_successful_processing_at', { withTimezone: true }),
pendingCount: integer('pending_count').default(0).notNull(),
retryCount: integer('retry_count').default(0).notNull(),
deadLetterCount: integer('dead_letter_count').default(0).notNull(),
unsupportedVersionCount: integer('unsupported_version_count').default(0).notNull(),
processingLagMs: integer('processing_lag_ms').default(0).notNull(),
averageProcessingDurationMs: integer('average_processing_duration_ms').default(0).notNull(),
lastRebuildAt: timestamp('last_rebuild_at', { withTimezone: true }),
rebuildSource: text('rebuild_source'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => ({
organizationConsumerIdx: uniqueIndex('projection_health_org_consumer_idx').on(
table.organizationId,
table.consumerName
)
})
);
export const crmTimelineProjection = pgTable(
'crm_timeline_projection',
{
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
branchId: text('branch_id'),
customerId: text('customer_id'),
leadId: text('lead_id'),
opportunityId: text('opportunity_id'),
quotationId: text('quotation_id'),
activityId: text('activity_id'),
eventId: text('event_id').notNull(),
entityType: text('entity_type').notNull(),
entityId: text('entity_id').notNull(),
timelineType: text('timeline_type').notNull(),
timelineCategory: text('timeline_category').notNull(),
occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),
actorId: text('actor_id'),
actorDisplay: text('actor_display'),
title: text('title').notNull(),
summary: text('summary'),
icon: text('icon').notNull(),
color: text('color').notNull(),
priority: text('priority').default('normal').notNull(),
visibility: jsonb('visibility').notNull(),
metadata: jsonb('metadata'),
sourceEvent: jsonb('source_event').notNull(),
sourceProjectionVersion: integer('source_projection_version').default(1).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => ({
eventVersionIdx: uniqueIndex('crm_timeline_projection_event_version_idx').on(
table.eventId,
table.sourceProjectionVersion
),
organizationOccurredIdx: index('crm_timeline_projection_org_occurred_idx').on(
table.organizationId,
table.occurredAt
),
customerOccurredIdx: index('crm_timeline_projection_customer_occurred_idx').on(
table.organizationId,
table.customerId,
table.occurredAt
),
activityEventIdx: index('crm_timeline_projection_activity_event_idx').on(
table.activityId,
table.eventId
)
})
);
export const crmCalendarProjection = pgTable(
'crm_calendar_projection',
{
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
branchId: text('branch_id'),
productType: text('product_type'),
customerId: text('customer_id'),
leadId: text('lead_id'),
opportunityId: text('opportunity_id'),
quotationId: text('quotation_id'),
approvalId: text('approval_id'),
activityId: text('activity_id'),
ownerId: text('owner_id'),
assigneeId: text('assignee_id'),
eventId: text('event_id').notNull(),
entityType: text('entity_type').notNull(),
entityId: text('entity_id').notNull(),
title: text('title').notNull(),
summary: text('summary'),
location: text('location'),
startAt: timestamp('start_at', { withTimezone: true }).notNull(),
endAt: timestamp('end_at', { withTimezone: true }).notNull(),
timezone: text('timezone').default('Asia/Bangkok').notNull(),
priority: text('priority').default('normal').notNull(),
category: text('category').notNull(),
activityType: text('activity_type'),
editable: boolean('editable').default(false).notNull(),
milestone: boolean('milestone').default(false).notNull(),
overdue: boolean('overdue').default(false).notNull(),
hotProject: boolean('hot_project').default(false).notNull(),
allDay: boolean('all_day').default(false).notNull(),
visibility: jsonb('visibility').notNull(),
metadata: jsonb('metadata'),
sourceEvent: jsonb('source_event').notNull(),
sourceProjectionVersion: integer('source_projection_version').default(1).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => ({
eventVersionIdx: uniqueIndex('crm_calendar_projection_event_version_idx').on(
table.eventId,
table.sourceProjectionVersion
),
organizationStartIdx: index('crm_calendar_projection_org_start_idx').on(
table.organizationId,
table.startAt
),
organizationAssigneeStartIdx: index('crm_calendar_projection_org_assignee_start_idx').on(
table.organizationId,
table.assigneeId,
table.startAt
),
organizationOwnerStartIdx: index('crm_calendar_projection_org_owner_start_idx').on(
table.organizationId,
table.ownerId,
table.startAt
),
organizationBranchStartIdx: index('crm_calendar_projection_org_branch_start_idx').on(
table.organizationId,
table.branchId,
table.startAt
),
organizationCategoryStartIdx: index('crm_calendar_projection_org_category_start_idx').on(
table.organizationId,
table.category,
table.startAt
),
customerStartIdx: index('crm_calendar_projection_customer_start_idx').on(
table.organizationId,
table.customerId,
table.startAt
),
activityEventIdx: index('crm_calendar_projection_activity_event_idx').on(
table.organizationId,
table.activityId,
table.eventId
)
})
);
export const crmCustomers = pgTable(
'crm_customers',
{
@@ -674,6 +980,48 @@ export const crmOpportunityFollowups = pgTable('crm_opportunity_followups', {
updatedBy: text('updated_by').notNull()
});
export const crmActivities = pgTable('crm_activities', {
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),
primaryEntityType: text('primary_entity_type').notNull(),
primaryEntityId: text('primary_entity_id'),
primaryRecordLabel: text('primary_record_label'),
relatedRecords: jsonb('related_records').default([]).notNull(),
customerId: text('customer_id'),
contactId: text('contact_id'),
leadId: text('lead_id'),
opportunityId: text('opportunity_id'),
quotationId: text('quotation_id'),
activityType: text('activity_type').notNull(),
subject: text('subject').notNull(),
description: text('description'),
ownerId: text('owner_id').notNull(),
assignedToId: text('assigned_to_id'),
scheduledStartAt: timestamp('scheduled_start_at', { withTimezone: true }),
scheduledEndAt: timestamp('scheduled_end_at', { withTimezone: true }),
dueAt: timestamp('due_at', { withTimezone: true }),
completedAt: timestamp('completed_at', { withTimezone: true }),
cancelledAt: timestamp('cancelled_at', { withTimezone: true }),
skippedAt: timestamp('skipped_at', { withTimezone: true }),
status: text('status').notNull(),
priority: text('priority').notNull(),
outcomeSummary: text('outcome_summary'),
cancellationReason: text('cancellation_reason'),
skipReason: text('skip_reason'),
nextAction: text('next_action'),
branchId: text('branch_id'),
productType: text('product_type'),
isInternalOnly: boolean('is_internal_only').default(false).notNull(),
isPricingSensitive: boolean('is_pricing_sensitive').default(false).notNull(),
parentActivityId: text('parent_activity_id'),
metadata: jsonb('metadata'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
deletedAt: timestamp('deleted_at', { withTimezone: true }),
createdBy: text('created_by').notNull(),
updatedBy: text('updated_by').notNull()
});
export const crmOpportunityCustomers = pgTable('crm_opportunity_customers', {
id: text('id').primaryKey(),
organizationId: text('organization_id').notNull(),

View File

@@ -0,0 +1,104 @@
import { mutationOptions } from '@tanstack/react-query';
import { getQueryClient } from '@/lib/query-client';
import {
assignActivity,
cancelActivity,
completeActivity,
createActivity,
deleteActivity,
rescheduleActivity,
updateActivity
} from './service';
import { activityKeys } from './queries';
import type {
ActivityAssignPayload,
ActivityCancelPayload,
ActivityCompletePayload,
ActivityMutationPayload,
ActivityReschedulePayload,
ActivityUpdatePayload
} from './types';
async function invalidateActivityLists() {
await getQueryClient().invalidateQueries({ queryKey: activityKeys.lists() });
}
async function invalidateActivityDetail(id: string) {
await getQueryClient().invalidateQueries({ queryKey: activityKeys.detail(id) });
}
async function invalidateActivityMutationQueries(id: string) {
await Promise.all([invalidateActivityLists(), invalidateActivityDetail(id)]);
}
export const createActivityMutation = mutationOptions({
mutationFn: (payload: ActivityMutationPayload) => createActivity(payload),
onSettled: async (data, error) => {
if (!error) {
await Promise.all([
invalidateActivityLists(),
data ? invalidateActivityDetail(data.id) : Promise.resolve()
]);
}
}
});
export const updateActivityMutation = mutationOptions({
mutationFn: ({ id, values }: { id: string; values: ActivityUpdatePayload }) =>
updateActivity(id, values),
onSettled: async (_data, error, variables) => {
if (!error) {
await invalidateActivityMutationQueries(variables.id);
}
}
});
export const deleteActivityMutation = mutationOptions({
mutationFn: ({ id }: { id: string }) => deleteActivity(id),
onSettled: async (_data, error, variables) => {
if (!error) {
await invalidateActivityLists();
getQueryClient().removeQueries({ queryKey: activityKeys.detail(variables.id) });
}
}
});
export const completeActivityMutation = mutationOptions({
mutationFn: ({ id, values }: { id: string; values: ActivityCompletePayload }) =>
completeActivity(id, values),
onSettled: async (_data, error, variables) => {
if (!error) {
await invalidateActivityMutationQueries(variables.id);
}
}
});
export const cancelActivityMutation = mutationOptions({
mutationFn: ({ id, values }: { id: string; values: ActivityCancelPayload }) =>
cancelActivity(id, values),
onSettled: async (_data, error, variables) => {
if (!error) {
await invalidateActivityMutationQueries(variables.id);
}
}
});
export const assignActivityMutation = mutationOptions({
mutationFn: ({ id, values }: { id: string; values: ActivityAssignPayload }) =>
assignActivity(id, values),
onSettled: async (_data, error, variables) => {
if (!error) {
await invalidateActivityMutationQueries(variables.id);
}
}
});
export const rescheduleActivityMutation = mutationOptions({
mutationFn: ({ id, values }: { id: string; values: ActivityReschedulePayload }) =>
rescheduleActivity(id, values),
onSettled: async (_data, error, variables) => {
if (!error) {
await invalidateActivityMutationQueries(variables.id);
}
}
});

View File

@@ -0,0 +1,30 @@
import { queryOptions } from '@tanstack/react-query';
import { getActivities, getActivityById, getActivityReferenceData } from './service';
import type { ActivityListFilters } from './types';
export const activityKeys = {
all: ['crm-activities'] as const,
lists: () => [...activityKeys.all, 'list'] as const,
list: (filters: ActivityListFilters) => [...activityKeys.lists(), filters] as const,
details: () => [...activityKeys.all, 'detail'] as const,
detail: (id: string) => [...activityKeys.details(), id] as const,
reference: () => [...activityKeys.all, 'reference'] as const
};
export const activitiesQueryOptions = (filters: ActivityListFilters) =>
queryOptions({
queryKey: activityKeys.list(filters),
queryFn: () => getActivities(filters)
});
export const activityByIdOptions = (id: string) =>
queryOptions({
queryKey: activityKeys.detail(id),
queryFn: () => getActivityById(id)
});
export const activityReferenceDataOptions = () =>
queryOptions({
queryKey: activityKeys.reference(),
queryFn: () => getActivityReferenceData()
});

View File

@@ -0,0 +1,103 @@
import { apiClient } from '@/lib/api-client';
import type {
ActivityAssignPayload,
ActivityCancelPayload,
ActivityCompletePayload,
ActivityListFilters,
ActivityListResponse,
ActivityMutationPayload,
ActivityRecord,
ActivityReferenceData,
ActivityReschedulePayload,
ActivityUpdatePayload
} from './types';
function buildSearchParams(filters: ActivityListFilters): string {
const params = new URLSearchParams();
if (filters.search) params.set('search', filters.search);
if (filters.primaryEntityType) params.set('primaryEntityType', filters.primaryEntityType);
if (filters.primaryEntityId) params.set('primaryEntityId', filters.primaryEntityId);
if (filters.activityType) params.set('activityType', filters.activityType);
if (filters.status) params.set('status', filters.status);
if (filters.ownerId) params.set('ownerId', filters.ownerId);
if (filters.assignedToId) params.set('assignedToId', filters.assignedToId);
return params.toString();
}
export async function getActivities(filters: ActivityListFilters): Promise<ActivityListResponse> {
const search = buildSearchParams(filters);
return apiClient<ActivityListResponse>(`/crm/activities${search ? `?${search}` : ''}`);
}
export async function getActivityById(id: string): Promise<ActivityRecord> {
return apiClient<ActivityRecord>(`/crm/activities/${id}`);
}
export async function getActivityReferenceData(): Promise<ActivityReferenceData> {
return apiClient<ActivityReferenceData>('/crm/activities/reference');
}
export async function createActivity(payload: ActivityMutationPayload): Promise<ActivityRecord> {
return apiClient<ActivityRecord>('/crm/activities', {
method: 'POST',
body: JSON.stringify(payload)
});
}
export async function updateActivity(
id: string,
payload: ActivityUpdatePayload
): Promise<ActivityRecord> {
return apiClient<ActivityRecord>(`/crm/activities/${id}`, {
method: 'PATCH',
body: JSON.stringify(payload)
});
}
export async function deleteActivity(id: string): Promise<{ success: true }> {
return apiClient<{ success: true }>(`/crm/activities/${id}`, {
method: 'DELETE'
});
}
export async function completeActivity(
id: string,
payload: ActivityCompletePayload
): Promise<ActivityRecord> {
return apiClient<ActivityRecord>(`/crm/activities/${id}/complete`, {
method: 'POST',
body: JSON.stringify(payload)
});
}
export async function cancelActivity(
id: string,
payload: ActivityCancelPayload
): Promise<ActivityRecord> {
return apiClient<ActivityRecord>(`/crm/activities/${id}/cancel`, {
method: 'POST',
body: JSON.stringify(payload)
});
}
export async function assignActivity(
id: string,
payload: ActivityAssignPayload
): Promise<ActivityRecord> {
return apiClient<ActivityRecord>(`/crm/activities/${id}/assign`, {
method: 'POST',
body: JSON.stringify(payload)
});
}
export async function rescheduleActivity(
id: string,
payload: ActivityReschedulePayload
): Promise<ActivityRecord> {
return apiClient<ActivityRecord>(`/crm/activities/${id}/reschedule`, {
method: 'POST',
body: JSON.stringify(payload)
});
}

View File

@@ -0,0 +1,131 @@
import type {
CrmActivityEntityType,
CrmActivityPriority,
CrmActivityRecord,
CrmActivityRelatedRecord,
CrmActivityStatus,
CrmActivityStoredStatus,
CrmActivityType
} from '@/features/crm/activity/types';
export interface ActivityOption {
value: string;
label: string;
}
export interface ActivityAssignableUser {
id: string;
name: string;
membershipRole: 'admin' | 'user';
businessRole: string;
}
export interface ActivityRecord extends CrmActivityRecord {
activityCode: string | null;
effectiveStatus: CrmActivityStatus;
ownerName: string | null;
assignedToName: string | null;
createdByName: string | null;
updatedByName: string | null;
canViewPricingSensitiveContent: boolean;
isContentRedacted: boolean;
}
export type ActivityTimelineItemSource =
| 'activity'
| 'lead_followup'
| 'opportunity_followup'
| 'quotation_followup';
export interface ActivityTimelineItem {
id: string;
source: ActivityTimelineItemSource;
sourceLabel: string;
isLegacy: boolean;
activityId: string | null;
activityCode: string | null;
primaryEntityType: CrmActivityEntityType;
primaryEntityId: string | null;
primaryRecordLabel: string | null;
activityType: string;
activityTypeLabel: string;
subject: string;
description: string | null;
status: string;
statusLabel: string;
occurredAt: string;
scheduledAt: string | null;
dueAt: string | null;
nextAction: string | null;
ownerName: string | null;
assignedToName: string | null;
createdByName: string | null;
}
export interface ActivityListFilters {
search?: string;
primaryEntityType?: CrmActivityEntityType;
primaryEntityId?: string;
activityType?: CrmActivityType;
status?: CrmActivityStatus;
ownerId?: string;
assignedToId?: string;
}
export interface ActivityListResponse {
items: ActivityRecord[];
totalItems: number;
}
export interface ActivityReferenceData {
entityTypes: ActivityOption[];
activityTypes: ActivityOption[];
statuses: ActivityOption[];
priorities: ActivityOption[];
assignableUsers: ActivityAssignableUser[];
}
export interface ActivityMutationPayload {
primaryEntityType: CrmActivityEntityType;
primaryEntityId?: string | null;
relatedRecords?: CrmActivityRelatedRecord[];
activityType: CrmActivityType;
subject: string;
description?: string | null;
ownerId: string;
assignedToId?: string | null;
scheduledStartAt?: string | null;
scheduledEndAt?: string | null;
dueAt?: string | null;
status: CrmActivityStoredStatus;
priority: CrmActivityPriority;
nextAction?: string | null;
isInternalOnly?: boolean;
isPricingSensitive?: boolean;
metadata?: Record<string, unknown> | null;
}
export interface ActivityUpdatePayload extends Partial<ActivityMutationPayload> {
outcomeSummary?: string | null;
cancellationReason?: string | null;
skipReason?: string | null;
}
export interface ActivityCompletePayload {
outcomeSummary: string;
nextAction?: string | null;
}
export interface ActivityCancelPayload {
cancellationReason: string;
}
export interface ActivityAssignPayload {
assignedToId: string | null;
}
export interface ActivityReschedulePayload {
scheduledStartAt?: string | null;
scheduledEndAt?: string | null;
dueAt?: string | null;
}

View File

@@ -0,0 +1,6 @@
import { Badge } from '@/components/ui/badge';
import { formatCrmActivityLabel, type CrmActivityType } from '@/features/crm/activity/types';
export function ActivityBadge({ activityType }: { activityType: CrmActivityType }) {
return <Badge variant='outline'>{formatCrmActivityLabel(activityType)}</Badge>;
}

View File

@@ -0,0 +1,83 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { formatDateTime } from '@/lib/date-format';
import type { ActivityRecord } from '../api/types';
import { TimelineList } from '@/features/crm/timeline/components/timeline-list';
import type { TimelineItem } from '@/features/crm/timeline/api/types';
import { ActivityBadge } from './activity-badge';
import { ActivityPriorityBadge } from './activity-priority-badge';
import { ActivityStatusBadge } from './activity-status-badge';
function Field({ label, value }: { label: string; value: string | null | undefined }) {
return (
<div className='space-y-1'>
<div className='text-muted-foreground text-xs'>{label}</div>
<div className='text-sm'>{value || '-'}</div>
</div>
);
}
export function ActivityDetail({
activity,
timelineItems
}: {
activity: ActivityRecord | null;
timelineItems?: TimelineItem[];
}) {
if (!activity) {
return (
<Card>
<CardContent className='text-muted-foreground p-6 text-sm'>
Select an activity to review its detail.
</CardContent>
</Card>
);
}
return (
<div className='space-y-4'>
<Card>
<CardHeader className='space-y-3'>
<div className='flex flex-wrap items-start justify-between gap-3'>
<div className='space-y-1'>
<CardTitle>{activity.subject}</CardTitle>
<CardDescription>{activity.primaryRecordLabel ?? 'Unlinked activity'}</CardDescription>
</div>
<div className='flex flex-wrap gap-2'>
<ActivityStatusBadge status={activity.effectiveStatus} />
<ActivityPriorityBadge priority={activity.priority} />
<ActivityBadge activityType={activity.activityType} />
</div>
</div>
</CardHeader>
<CardContent className='space-y-4'>
<div className='grid gap-4 md:grid-cols-2'>
<Field label='Owner' value={activity.ownerName} />
<Field label='Activity Code' value={activity.activityCode} />
<Field label='Assignee' value={activity.assignedToName} />
<Field label='Primary Entity' value={activity.primaryEntityType} />
<Field label='Primary Record Id' value={activity.primaryEntityId} />
<Field label='Scheduled' value={formatDateTime(activity.scheduledStartAt)} />
<Field label='Due' value={formatDateTime(activity.dueAt)} />
</div>
<Separator />
<div className='grid gap-4'>
<Field label='Description' value={activity.description} />
<Field label='Outcome Summary' value={activity.outcomeSummary} />
<Field label='Next Action' value={activity.nextAction} />
<Field label='Cancellation Reason' value={activity.cancellationReason} />
<Field label='Skip Reason' value={activity.skipReason} />
</div>
</CardContent>
</Card>
{timelineItems ? (
<TimelineList
items={timelineItems}
title='Activity Timeline'
description='Business history generated from Activity events.'
emptyMessage='No timeline entries recorded for this activity yet.'
/>
) : null}
</div>
);
}

View File

@@ -0,0 +1,249 @@
'use client';
import { useEffect, useMemo } from 'react';
import { useMutation } from '@tanstack/react-query';
import { toast } from 'sonner';
import { Icons } from '@/components/icons';
import { Button } from '@/components/ui/button';
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle
} from '@/components/ui/sheet';
import { useAppForm, useFormFields } from '@/components/ui/tanstack-form';
import { createActivityMutation, updateActivityMutation } from '../api/mutations';
import type {
ActivityMutationPayload,
ActivityRecord,
ActivityReferenceData
} from '../api/types';
import { activityFormSchema, type ActivityFormValues } from '../schemas/activity.schema';
function dateToIso(value?: string): string | null {
if (!value) {
return null;
}
return new Date(`${value}T00:00:00.000Z`).toISOString();
}
function toDefaultValues(activity?: ActivityRecord): ActivityFormValues {
return {
primaryEntityType: activity?.primaryEntityType ?? 'internal',
primaryEntityId: activity?.primaryEntityId ?? '',
activityType: activity?.activityType ?? 'follow_up',
subject: activity?.subject ?? '',
description: activity?.description ?? '',
ownerId: activity?.ownerId ?? '',
assignedToId: activity?.assignedToId ?? '',
scheduledDate: activity?.scheduledStartAt?.slice(0, 10) ?? '',
dueDate: activity?.dueAt?.slice(0, 10) ?? '',
status: activity?.status ?? 'draft',
priority: activity?.priority ?? 'normal',
nextAction: activity?.nextAction ?? '',
isInternalOnly: activity?.isInternalOnly ?? false,
isPricingSensitive: activity?.isPricingSensitive ?? false
};
}
function toPayload(values: ActivityFormValues): ActivityMutationPayload {
return {
primaryEntityType: values.primaryEntityType,
primaryEntityId: values.primaryEntityId?.trim() ? values.primaryEntityId.trim() : null,
activityType: values.activityType,
subject: values.subject.trim(),
description: values.description?.trim() || null,
ownerId: values.ownerId,
assignedToId: values.assignedToId?.trim() ? values.assignedToId : null,
scheduledStartAt: dateToIso(values.scheduledDate),
dueAt: dateToIso(values.dueDate),
status: values.status,
priority: values.priority,
nextAction: values.nextAction?.trim() || null,
isInternalOnly: values.isInternalOnly,
isPricingSensitive: values.isPricingSensitive
};
}
export function ActivityFormSheet({
activity,
open,
onOpenChange,
referenceData,
defaultPrimaryEntityType,
defaultPrimaryEntityId
}: {
activity?: ActivityRecord;
open: boolean;
onOpenChange: (open: boolean) => void;
referenceData: ActivityReferenceData;
defaultPrimaryEntityType?: ActivityFormValues['primaryEntityType'];
defaultPrimaryEntityId?: string;
}) {
const isEdit = Boolean(activity);
const defaultValues = useMemo(() => {
const values = toDefaultValues(activity);
if (!activity && defaultPrimaryEntityType) {
values.primaryEntityType = defaultPrimaryEntityType;
}
if (!activity && defaultPrimaryEntityId) {
values.primaryEntityId = defaultPrimaryEntityId;
}
return values;
}, [activity, defaultPrimaryEntityId, defaultPrimaryEntityType]);
const { FormTextField, FormTextareaField, FormDatePickerField, FormSelectField, FormCheckboxField } =
useFormFields<ActivityFormValues>();
const createMutation = useMutation({
...createActivityMutation,
onSuccess: async () => {
toast.success('Activity created');
onOpenChange(false);
},
onError: (error) => {
toast.error(error instanceof Error ? error.message : 'Unable to create activity');
}
});
const updateMutation = useMutation({
...updateActivityMutation,
onSuccess: async () => {
toast.success('Activity updated');
onOpenChange(false);
},
onError: (error) => {
toast.error(error instanceof Error ? error.message : 'Unable to update activity');
}
});
const isPending = createMutation.isPending || updateMutation.isPending;
const form = useAppForm({
defaultValues,
validators: {
onSubmit: activityFormSchema
},
onSubmit: async ({ value }) => {
const payload = toPayload(value);
if (isEdit && activity) {
await updateMutation.mutateAsync({ id: activity.id, values: payload });
return;
}
await createMutation.mutateAsync(payload);
}
});
useEffect(() => {
form.reset(defaultValues);
}, [defaultValues, form, open]);
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className='flex flex-col sm:max-w-2xl'>
<SheetHeader>
<SheetTitle>{isEdit ? 'Edit Activity' : 'Create Activity'}</SheetTitle>
<SheetDescription>
Foundation form for shared operational activity records.
</SheetDescription>
</SheetHeader>
<div className='min-h-0 flex-1 overflow-y-auto'>
<form.AppForm>
<form.Form id='crm-activity-form'>
<div className='grid gap-4 md:grid-cols-2'>
<FormTextField name='subject' label='Subject' required placeholder='Follow up on revised quotation' />
<FormSelectField
name='activityType'
label='Activity Type'
required
options={referenceData.activityTypes.map((item) => ({
value: item.value,
label: item.label
}))}
/>
<FormSelectField
name='primaryEntityType'
label='Primary Entity'
required
options={referenceData.entityTypes.map((item) => ({
value: item.value,
label: item.label
}))}
disabled={Boolean(defaultPrimaryEntityType)}
/>
<FormTextField
name='primaryEntityId'
label='Primary Record Id'
placeholder='Existing CRM record id'
disabled={Boolean(defaultPrimaryEntityId)}
/>
<FormSelectField
name='ownerId'
label='Owner'
required
options={referenceData.assignableUsers.map((item) => ({
value: item.id,
label: item.name
}))}
/>
<FormSelectField
name='assignedToId'
label='Assignee'
options={referenceData.assignableUsers.map((item) => ({
value: item.id,
label: item.name
}))}
/>
<FormSelectField
name='status'
label='Status'
required
options={referenceData.statuses.map((item) => ({
value: item.value,
label: item.label
}))}
/>
<FormSelectField
name='priority'
label='Priority'
required
options={referenceData.priorities.map((item) => ({
value: item.value,
label: item.label
}))}
/>
<FormDatePickerField name='scheduledDate' label='Scheduled Date' />
<FormDatePickerField name='dueDate' label='Due Date' />
<div className='md:col-span-2'>
<FormTextField name='nextAction' label='Next Action' placeholder='Book follow-up meeting' />
</div>
<div className='md:col-span-2'>
<FormTextareaField
name='description'
size='md'
label='Description'
placeholder='Business context and execution notes'
/>
</div>
<FormCheckboxField name='isInternalOnly' label='Internal only' />
<FormCheckboxField name='isPricingSensitive' label='Pricing sensitive' />
</div>
</form.Form>
</form.AppForm>
</div>
<SheetFooter>
<Button type='button' variant='outline' onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button type='submit' form='crm-activity-form' isLoading={isPending}>
<Icons.check className='mr-2 h-4 w-4' />
{isEdit ? 'Update Activity' : 'Create Activity'}
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}

View File

@@ -0,0 +1,88 @@
'use client';
import { useMemo, useState } from 'react';
import { useSuspenseQuery } from '@tanstack/react-query';
import { Icons } from '@/components/icons';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import type { ActivityListFilters } from '../api/types';
import { activitiesQueryOptions, activityReferenceDataOptions } from '../api/queries';
import { ActivityBadge } from './activity-badge';
import { ActivityDetail } from './activity-detail';
import { ActivityFormSheet } from './activity-form-sheet';
import { ActivityPriorityBadge } from './activity-priority-badge';
import { ActivityStatusBadge } from './activity-status-badge';
export function ActivityListing({ initialFilters }: { initialFilters: ActivityListFilters }) {
const [sheetOpen, setSheetOpen] = useState(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const { data } = useSuspenseQuery(activitiesQueryOptions(initialFilters));
const { data: referenceData } = useSuspenseQuery(activityReferenceDataOptions());
const selectedActivity = useMemo(
() => data.items.find((item) => item.id === (selectedId ?? data.items[0]?.id)) ?? null,
[data.items, selectedId]
);
return (
<div className='space-y-4'>
<div className='flex items-center justify-end'>
<Button onClick={() => setSheetOpen(true)}>
<Icons.add className='mr-2 h-4 w-4' />
Create Activity
</Button>
</div>
<div className='grid gap-4 lg:grid-cols-[1.2fr_0.8fr]'>
<div className='space-y-3'>
{!data.items.length ? (
<Card>
<CardContent className='text-muted-foreground p-6 text-sm'>
No activities created yet.
</CardContent>
</Card>
) : (
data.items.map((item) => (
<button
key={item.id}
type='button'
className='w-full text-left'
aria-label={`View activity ${item.subject}`}
onClick={() => setSelectedId(item.id)}
>
<Card className='transition-colors hover:bg-muted/40'>
<CardContent className='space-y-3 p-4'>
<div className='flex flex-wrap items-start justify-between gap-3'>
<div className='space-y-1'>
<div className='font-medium'>{item.subject}</div>
<div className='text-muted-foreground text-sm'>
{item.primaryRecordLabel ?? 'Unlinked activity'}
</div>
</div>
<div className='flex flex-wrap gap-2'>
<ActivityStatusBadge status={item.effectiveStatus} />
<ActivityPriorityBadge priority={item.priority} />
<ActivityBadge activityType={item.activityType} />
</div>
</div>
<div className='text-muted-foreground text-sm'>
Owner: {item.ownerName ?? '-'} | Assignee: {item.assignedToName ?? '-'}
</div>
</CardContent>
</Card>
</button>
))
)}
</div>
<ActivityDetail activity={selectedActivity ?? null} />
</div>
<ActivityFormSheet
activity={undefined}
open={sheetOpen}
onOpenChange={(open) => {
setSheetOpen(open);
}}
referenceData={referenceData}
/>
</div>
);
}

View File

@@ -0,0 +1,16 @@
import { Badge } from '@/components/ui/badge';
import { formatCrmActivityLabel, type CrmActivityPriority } from '@/features/crm/activity/types';
const PRIORITY_VARIANTS: Record<
CrmActivityPriority,
'secondary' | 'outline' | 'default' | 'destructive'
> = {
low: 'outline',
normal: 'secondary',
high: 'default',
critical: 'destructive'
};
export function ActivityPriorityBadge({ priority }: { priority: CrmActivityPriority }) {
return <Badge variant={PRIORITY_VARIANTS[priority]}>{formatCrmActivityLabel(priority)}</Badge>;
}

View File

@@ -0,0 +1,16 @@
import { Badge } from '@/components/ui/badge';
import { formatCrmActivityLabel, type CrmActivityStatus } from '@/features/crm/activity/types';
const STATUS_VARIANTS: Record<CrmActivityStatus, 'secondary' | 'outline' | 'default' | 'destructive'> = {
draft: 'outline',
scheduled: 'secondary',
in_progress: 'default',
completed: 'secondary',
cancelled: 'destructive',
skipped: 'outline',
overdue: 'destructive'
};
export function ActivityStatusBadge({ status }: { status: CrmActivityStatus }) {
return <Badge variant={STATUS_VARIANTS[status]}>{formatCrmActivityLabel(status)}</Badge>;
}

View File

@@ -0,0 +1,94 @@
"use client";
import { Badge } from "@/components/ui/badge";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { formatDateTime } from "@/lib/date-format";
import type { ActivityTimelineItem } from "../api/types";
interface RecentActivitiesPanelProps {
items: ActivityTimelineItem[];
title?: string;
description?: string;
emptyMessage?: string;
}
function MetaItem({ label, value }: { label: string; value: string | null }) {
if (!value) {
return null;
}
return (
<span>
{label}: {value}
</span>
);
}
export function RecentActivitiesPanel({
items,
title = "Recent Follow up",
description = "Unified view of new activities and legacy follow-up records.",
emptyMessage = "No recent activities yet.",
}: RecentActivitiesPanelProps) {
return (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{items.length === 0 ? (
<div className="text-muted-foreground rounded-lg border border-dashed p-4 text-sm">
{emptyMessage}
</div>
) : (
items.map((item) => (
<div
key={`${item.source}-${item.id}`}
className="rounded-lg border p-4"
>
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="space-y-2">
<div className="flex flex-wrap items-center gap-2">
<div className="font-medium">{item.subject}</div>
{item.activityCode ? (
<Badge variant="outline">{item.activityCode}</Badge>
) : null}
<Badge variant={item.isLegacy ? "secondary" : "default"}>
{item.sourceLabel}
</Badge>
<Badge variant="outline">{item.statusLabel}</Badge>
</div>
<div className="text-muted-foreground text-sm">
{item.activityTypeLabel}
{item.primaryRecordLabel
? `${item.primaryRecordLabel}`
: ""}
</div>
{item.description ? (
<div className="text-sm">{item.description}</div>
) : null}
<div className="text-muted-foreground flex flex-wrap gap-x-4 gap-y-1 text-xs">
<MetaItem
label="When"
value={formatDateTime(item.occurredAt)}
/>
<MetaItem label="Owner" value={item.ownerName} />
<MetaItem label="Created by" value={item.createdByName} />
<MetaItem label="Next action" value={item.nextAction} />
</div>
</div>
</div>
</div>
))
)}
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,86 @@
import { z } from 'zod';
import {
CRM_ACTIVITY_ENTITY_TYPES,
CRM_ACTIVITY_PRIORITIES,
CRM_ACTIVITY_STORED_STATUSES,
CRM_ACTIVITY_TYPES
} from '@/features/crm/activity/types';
const isoDateTimeSchema = z
.string()
.datetime({ offset: true })
.or(z.literal(''))
.nullable()
.optional()
.transform((value) => (value ? value : null));
export const activityRelatedRecordSchema = z.object({
entityType: z.enum(CRM_ACTIVITY_ENTITY_TYPES),
entityId: z.string().min(1, 'Related record id is required')
});
export const activityMutationSchema = z.object({
primaryEntityType: z.enum(CRM_ACTIVITY_ENTITY_TYPES),
primaryEntityId: z.string().nullable().optional(),
relatedRecords: z.array(activityRelatedRecordSchema).optional(),
activityType: z.enum(CRM_ACTIVITY_TYPES),
subject: z.string().trim().min(1, 'Subject is required'),
description: z.string().nullable().optional(),
ownerId: z.string().min(1, 'Owner is required'),
assignedToId: z.string().nullable().optional(),
scheduledStartAt: isoDateTimeSchema,
scheduledEndAt: isoDateTimeSchema,
dueAt: isoDateTimeSchema,
status: z.enum(CRM_ACTIVITY_STORED_STATUSES),
priority: z.enum(CRM_ACTIVITY_PRIORITIES),
nextAction: z.string().nullable().optional(),
isInternalOnly: z.boolean().optional(),
isPricingSensitive: z.boolean().optional(),
metadata: z.record(z.string(), z.unknown()).nullable().optional()
});
export const activityUpdateSchema = activityMutationSchema
.partial()
.extend({
outcomeSummary: z.string().nullable().optional(),
cancellationReason: z.string().nullable().optional(),
skipReason: z.string().nullable().optional()
});
export const activityCompleteSchema = z.object({
outcomeSummary: z.string().trim().min(1, 'Outcome summary is required'),
nextAction: z.string().nullable().optional()
});
export const activityCancelSchema = z.object({
cancellationReason: z.string().trim().min(1, 'Cancellation reason is required')
});
export const activityAssignSchema = z.object({
assignedToId: z.string().nullable()
});
export const activityRescheduleSchema = z.object({
scheduledStartAt: isoDateTimeSchema,
scheduledEndAt: isoDateTimeSchema,
dueAt: isoDateTimeSchema
});
export const activityFormSchema = z.object({
primaryEntityType: z.enum(CRM_ACTIVITY_ENTITY_TYPES),
primaryEntityId: z.string().optional(),
activityType: z.enum(CRM_ACTIVITY_TYPES),
subject: z.string().trim().min(1, 'Subject is required'),
description: z.string().optional(),
ownerId: z.string().min(1, 'Owner is required'),
assignedToId: z.string().optional(),
scheduledDate: z.string().optional(),
dueDate: z.string().optional(),
status: z.enum(CRM_ACTIVITY_STORED_STATUSES),
priority: z.enum(CRM_ACTIVITY_PRIORITIES),
nextAction: z.string().optional(),
isInternalOnly: z.boolean(),
isPricingSensitive: z.boolean()
});
export type ActivityFormValues = z.infer<typeof activityFormSchema>;

View File

@@ -0,0 +1,41 @@
import { formatCrmActivityLabel } from '@/features/crm/activity/types';
import type { LeadFollowupSummary } from '@/features/crm/leads/types';
import type { ActivityTimelineItem } from '../../api/types';
export interface LeadFollowupAdapterCandidate {
source: 'lead_followup';
followup: LeadFollowupSummary;
}
export function adaptLeadFollowupCandidate(
candidate: LeadFollowupAdapterCandidate
): ActivityTimelineItem {
const statusLabel =
candidate.followup.followupStatusLabel ??
formatCrmActivityLabel(candidate.followup.followupStatus);
return {
id: candidate.followup.id,
source: 'lead_followup',
sourceLabel: 'Lead Follow-up',
isLegacy: true,
activityId: null,
activityCode: null,
primaryEntityType: 'lead',
primaryEntityId: candidate.followup.leadId,
primaryRecordLabel: null,
activityType: 'follow_up',
activityTypeLabel: 'Follow Up',
subject: candidate.followup.note?.trim() || 'Lead follow-up',
description: candidate.followup.note,
status: candidate.followup.followupStatus,
statusLabel,
occurredAt: candidate.followup.followupDate,
scheduledAt: candidate.followup.followupDate,
dueAt: candidate.followup.nextFollowupDate,
nextAction: null,
ownerName: null,
assignedToName: null,
createdByName: candidate.followup.createdByName
};
}

View File

@@ -0,0 +1,42 @@
import { formatCrmActivityLabel } from '@/features/crm/activity/types';
import type { ActivityTimelineItem } from '../../api/types';
import type { OpportunityFollowupRecord } from '@/features/crm/opportunities/api/types';
export interface OpportunityFollowupAdapterCandidate {
source: 'opportunity_followup';
followup: OpportunityFollowupRecord;
followupTypeLabel?: string | null;
createdByName?: string | null;
}
export function adaptOpportunityFollowupCandidate(
candidate: OpportunityFollowupAdapterCandidate
): ActivityTimelineItem {
const typeLabel =
candidate.followupTypeLabel ?? formatCrmActivityLabel(candidate.followup.followupType);
return {
id: candidate.followup.id,
source: 'opportunity_followup',
sourceLabel: 'Opportunity Follow-up',
isLegacy: true,
activityId: null,
activityCode: null,
primaryEntityType: 'opportunity',
primaryEntityId: candidate.followup.opportunityId,
primaryRecordLabel: null,
activityType: 'follow_up',
activityTypeLabel: typeLabel,
subject: typeLabel,
description: candidate.followup.outcome ?? candidate.followup.notes,
status: 'completed',
statusLabel: 'Completed',
occurredAt: candidate.followup.followupDate,
scheduledAt: candidate.followup.followupDate,
dueAt: candidate.followup.nextFollowupDate,
nextAction: candidate.followup.nextAction,
ownerName: null,
assignedToName: null,
createdByName: candidate.createdByName ?? null
};
}

View File

@@ -0,0 +1,42 @@
import { formatCrmActivityLabel } from '@/features/crm/activity/types';
import type { ActivityTimelineItem } from '../../api/types';
import type { QuotationFollowupRecord } from '@/features/crm/quotations/api/types';
export interface QuotationFollowupAdapterCandidate {
source: 'quotation_followup';
followup: QuotationFollowupRecord;
followupTypeLabel?: string | null;
createdByName?: string | null;
}
export function adaptQuotationFollowupCandidate(
candidate: QuotationFollowupAdapterCandidate
): ActivityTimelineItem {
const typeLabel =
candidate.followupTypeLabel ?? formatCrmActivityLabel(candidate.followup.followupType);
return {
id: candidate.followup.id,
source: 'quotation_followup',
sourceLabel: 'Quotation Follow-up',
isLegacy: true,
activityId: null,
activityCode: null,
primaryEntityType: 'quotation',
primaryEntityId: candidate.followup.quotationId,
primaryRecordLabel: null,
activityType: 'follow_up',
activityTypeLabel: typeLabel,
subject: typeLabel,
description: candidate.followup.outcome ?? candidate.followup.notes,
status: 'completed',
statusLabel: 'Completed',
occurredAt: candidate.followup.followupDate,
scheduledAt: candidate.followup.followupDate,
dueAt: candidate.followup.nextFollowupDate,
nextAction: candidate.followup.nextAction,
ownerName: null,
assignedToName: null,
createdByName: candidate.createdByName ?? null
};
}

View File

@@ -0,0 +1,128 @@
import { CRM_ACTIVITY_PERMISSIONS } from '@/features/crm/activity/types';
import {
createArtifact,
downloadArtifact,
listArtifactsForEntity,
type DocumentArtifactRecord
} from '@/features/foundation/document-artifact/server/service';
import {
buildOrganizationStorageKey,
getStorageProvider
} from '@/features/foundation/storage/service';
import { AuthError } from '@/lib/auth/session';
import type { StoredObjectStream } from '@/features/foundation/storage/types';
import type { CrmSecurityContext } from '@/features/crm/security/server/service';
import { getActivityById } from './service';
const ACTIVITY_ATTACHMENT_ENTITY_TYPE = 'crm_activity';
const ACTIVITY_ATTACHMENT_DOCUMENT_TYPE = 'activity';
const ACTIVITY_ATTACHMENT_ARTIFACT_TYPE = 'attachment';
export type ActivityAttachmentKind =
| 'image'
| 'pdf'
| 'office_document'
| 'voice_recording'
| 'drawing'
| 'other';
export interface UploadActivityAttachmentInput {
organizationId: string;
userId: string;
activityId: string;
fileName: string;
contentType: string;
body: Buffer;
attachmentKind?: ActivityAttachmentKind;
context: CrmSecurityContext;
}
function assertAttachmentWritePermission(context: CrmSecurityContext) {
if (
!context.permissions.includes(CRM_ACTIVITY_PERMISSIONS.create) &&
!context.permissions.includes(CRM_ACTIVITY_PERMISSIONS.update)
) {
throw new AuthError('Forbidden', 403);
}
}
function buildAttachmentStorageKey(input: {
organizationId: string;
activityId: string;
fileName: string;
}) {
return buildOrganizationStorageKey(input.organizationId, [
'crm',
'activities',
input.activityId,
crypto.randomUUID(),
input.fileName
]);
}
export async function listActivityAttachments(input: {
organizationId: string;
activityId: string;
context: CrmSecurityContext;
}): Promise<DocumentArtifactRecord[]> {
await getActivityById(input.activityId, input.organizationId, input.context);
return listArtifactsForEntity({
organizationId: input.organizationId,
entityType: ACTIVITY_ATTACHMENT_ENTITY_TYPE,
entityId: input.activityId,
artifactType: ACTIVITY_ATTACHMENT_ARTIFACT_TYPE
});
}
export async function uploadActivityAttachment(
input: UploadActivityAttachmentInput
): Promise<DocumentArtifactRecord> {
assertAttachmentWritePermission(input.context);
await getActivityById(input.activityId, input.organizationId, input.context);
const storedObject = await getStorageProvider().putObject({
key: buildAttachmentStorageKey(input),
body: input.body,
contentType: input.contentType,
fileName: input.fileName
});
return createArtifact({
organizationId: input.organizationId,
userId: input.userId,
entityType: ACTIVITY_ATTACHMENT_ENTITY_TYPE,
entityId: input.activityId,
documentType: ACTIVITY_ATTACHMENT_DOCUMENT_TYPE,
artifactType: ACTIVITY_ATTACHMENT_ARTIFACT_TYPE,
storageProvider: storedObject.provider,
storageKey: storedObject.key,
fileName: storedObject.fileName ?? input.fileName,
contentType: storedObject.contentType,
fileSize: storedObject.size,
checksum: storedObject.checksum ?? null,
metadata: {
attachmentKind: input.attachmentKind ?? 'other'
}
});
}
export async function downloadActivityAttachment(input: {
organizationId: string;
userId: string;
activityId: string;
artifactId: string;
context: CrmSecurityContext;
}): Promise<{
artifact: DocumentArtifactRecord;
object: StoredObjectStream;
}> {
await getActivityById(input.activityId, input.organizationId, input.context);
return downloadArtifact({
artifactId: input.artifactId,
organizationId: input.organizationId,
userId: input.userId,
auditDownload: true
});
}

View File

@@ -0,0 +1,84 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createActivityBusinessEvent } from './business-events.ts';
import type { ActivityRecord } from '../api/types.ts';
function activity(overrides: Partial<ActivityRecord> = {}): ActivityRecord {
return {
id: 'activity-1',
activityCode: 'ACT2607-001',
organizationId: 'org-1',
primaryEntityType: 'opportunity',
primaryEntityId: 'opp-1',
primaryRecordLabel: 'EN-001 Project',
relatedRecords: [],
customerId: 'customer-1',
contactId: null,
leadId: null,
opportunityId: 'opp-1',
quotationId: null,
activityType: 'follow_up',
subject: 'Follow up',
description: 'Pricing detail',
ownerId: 'owner-1',
ownerName: 'Owner',
assignedToId: 'assignee-1',
assignedToName: 'Assignee',
scheduledStartAt: '2026-07-13T00:00:00.000Z',
scheduledEndAt: null,
dueAt: null,
completedAt: null,
cancelledAt: null,
skippedAt: null,
status: 'scheduled',
effectiveStatus: 'scheduled',
priority: 'normal',
outcomeSummary: 'Sensitive outcome',
cancellationReason: null,
skipReason: null,
nextAction: 'Call buyer',
branchId: 'branch-1',
productType: 'crane',
isInternalOnly: false,
isPricingSensitive: true,
parentActivityId: null,
metadata: null,
createdBy: 'user-1',
createdByName: 'User',
createdAt: '2026-07-13T00:00:00.000Z',
updatedBy: 'user-1',
updatedByName: 'User',
updatedAt: '2026-07-13T00:00:00.000Z',
deletedAt: null,
canViewPricingSensitiveContent: false,
isContentRedacted: true,
...overrides
};
}
test('activity business event redacts pricing-sensitive narrative fields', () => {
const event = createActivityBusinessEvent({
eventType: 'activity.completed',
activity: activity(),
actorUserId: 'user-1'
});
assert.equal(event.visibility.pricingSensitive, true);
assert.equal(event.payload.description, null);
assert.equal(event.payload.outcomeSummary, null);
assert.equal(event.payload.contentRedacted, true);
});
test('activity business event preserves correlation and related context', () => {
const event = createActivityBusinessEvent({
eventType: 'activity.created',
activity: activity({ isPricingSensitive: false, description: 'Normal note' }),
actorUserId: 'user-1',
correlationId: 'corr-1'
});
assert.equal(event.correlationId, 'corr-1');
assert.equal(event.primaryRecord.type, 'opportunity');
assert.equal(event.relatedRecords.some((record) => record.type === 'customer'), true);
assert.equal(event.payload.description, 'Normal note');
});

Some files were not shown because too many files have changed in this diff Show More