task-f.4
This commit is contained in:
675
plans/task-f.4.md
Normal file
675
plans/task-f.4.md
Normal file
@@ -0,0 +1,675 @@
|
||||
# Task F.4: Notification Framework Foundation
|
||||
|
||||
## Status
|
||||
|
||||
Planned
|
||||
|
||||
## Objective
|
||||
|
||||
Build a reusable Notification Framework for CRM events, starting with Approval events.
|
||||
|
||||
This task introduces a centralized notification foundation so Approval, Lead, Opportunity, Quotation, Follow-up, and future modules can publish events without each feature directly sending email or UI notifications.
|
||||
|
||||
The goal is to support:
|
||||
|
||||
* event-driven notification creation
|
||||
* in-app notification inbox / bell foundation
|
||||
* notification recipient resolution
|
||||
* notification templates
|
||||
* read/unread state
|
||||
* approval event integration
|
||||
* future channel support for email, LINE, webhook, and reminder/escalation
|
||||
|
||||
This task does not implement full email delivery, LINE integration, or escalation scheduling yet.
|
||||
|
||||
---
|
||||
|
||||
## Mandatory Review
|
||||
|
||||
Review before implementation:
|
||||
|
||||
* `AGENTS.md`
|
||||
* `docs/standards/project-foundations.md`
|
||||
* `docs/standards/task-catalog.md`
|
||||
* `docs/implementation/task-f2-approval-matrix-foundation.md`
|
||||
* `docs/implementation/task-f3-approval-workflow-builder-foundation.md`
|
||||
* existing Approval submit/approve/reject/cancel flow
|
||||
* `src/db/schema.ts`
|
||||
* `src/features/crm/approval/**`
|
||||
* `src/features/foundation/approval/**`
|
||||
* `src/features/foundation/audit-log/**`
|
||||
* `src/lib/auth/rbac.ts`
|
||||
* `docs/security/crm-authorization-boundaries.md`
|
||||
|
||||
---
|
||||
|
||||
## Current Foundation
|
||||
|
||||
Already available:
|
||||
|
||||
```txt
|
||||
Approval Matrix
|
||||
Approval Workflow Builder
|
||||
Approval Requests
|
||||
Approval Actions
|
||||
Audit Log Foundation
|
||||
CRM Authorization Foundation
|
||||
```
|
||||
|
||||
Current limitation:
|
||||
|
||||
```txt
|
||||
Approval actions do not yet publish reusable notifications.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Business Requirement
|
||||
|
||||
When approval activity happens, related users should be notified.
|
||||
|
||||
Examples:
|
||||
|
||||
```txt
|
||||
Quotation submitted for approval
|
||||
→ notify current approver(s)
|
||||
|
||||
Quotation approved
|
||||
→ notify requester and next approver if any
|
||||
|
||||
Quotation rejected
|
||||
→ notify requester
|
||||
|
||||
Quotation returned / cancelled
|
||||
→ notify requester or related actors
|
||||
```
|
||||
|
||||
The notification system should be generic enough for future events:
|
||||
|
||||
```txt
|
||||
Lead Assigned
|
||||
Opportunity Assigned
|
||||
Follow-up Due
|
||||
Quotation Expiring
|
||||
Opportunity Won
|
||||
Opportunity Lost
|
||||
PO Received
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.1 Notification Schema
|
||||
|
||||
Add tables:
|
||||
|
||||
```txt
|
||||
app_notifications
|
||||
app_notification_events
|
||||
app_notification_templates
|
||||
app_notification_deliveries
|
||||
```
|
||||
|
||||
### app_notification_events
|
||||
|
||||
Stores source events published by modules.
|
||||
|
||||
Recommended columns:
|
||||
|
||||
```txt
|
||||
id
|
||||
organization_id
|
||||
event_type
|
||||
entity_type
|
||||
entity_id
|
||||
actor_user_id
|
||||
payload jsonb
|
||||
dedupe_key
|
||||
status
|
||||
published_at
|
||||
processed_at
|
||||
created_at
|
||||
```
|
||||
|
||||
Purpose:
|
||||
|
||||
```txt
|
||||
Event source stream
|
||||
```
|
||||
|
||||
### app_notifications
|
||||
|
||||
Stores user-facing in-app notifications.
|
||||
|
||||
Recommended columns:
|
||||
|
||||
```txt
|
||||
id
|
||||
organization_id
|
||||
recipient_user_id
|
||||
event_id
|
||||
title
|
||||
body
|
||||
link_url
|
||||
severity
|
||||
status
|
||||
read_at
|
||||
archived_at
|
||||
metadata jsonb
|
||||
created_at
|
||||
updated_at
|
||||
```
|
||||
|
||||
Purpose:
|
||||
|
||||
```txt
|
||||
Notification inbox / bell
|
||||
```
|
||||
|
||||
### app_notification_templates
|
||||
|
||||
Stores reusable message templates.
|
||||
|
||||
Recommended columns:
|
||||
|
||||
```txt
|
||||
id
|
||||
organization_id
|
||||
event_type
|
||||
channel
|
||||
title_template
|
||||
body_template
|
||||
link_template
|
||||
is_active
|
||||
metadata jsonb
|
||||
created_at
|
||||
updated_at
|
||||
deleted_at
|
||||
created_by
|
||||
updated_by
|
||||
```
|
||||
|
||||
Purpose:
|
||||
|
||||
```txt
|
||||
Event message rendering
|
||||
```
|
||||
|
||||
### app_notification_deliveries
|
||||
|
||||
Tracks channel delivery attempts.
|
||||
|
||||
Recommended columns:
|
||||
|
||||
```txt
|
||||
id
|
||||
organization_id
|
||||
notification_id
|
||||
channel
|
||||
recipient_address
|
||||
status
|
||||
attempt_count
|
||||
last_error
|
||||
sent_at
|
||||
created_at
|
||||
updated_at
|
||||
```
|
||||
|
||||
Purpose:
|
||||
|
||||
```txt
|
||||
Future email / LINE / webhook delivery tracking
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* In-app notification is implemented first
|
||||
* Email/LINE delivery can remain queued or pending
|
||||
* Use organization scope everywhere
|
||||
* Use soft delete where appropriate
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.2 Notification Event Service
|
||||
|
||||
Create:
|
||||
|
||||
```txt
|
||||
src/features/foundation/notifications/server/event-service.ts
|
||||
```
|
||||
|
||||
Functions:
|
||||
|
||||
```ts
|
||||
publishNotificationEvent(input)
|
||||
processNotificationEvent(eventId)
|
||||
processPendingNotificationEvents()
|
||||
```
|
||||
|
||||
Input example:
|
||||
|
||||
```ts
|
||||
{
|
||||
organizationId: string;
|
||||
eventType: string;
|
||||
entityType: string;
|
||||
entityId: string;
|
||||
actorUserId?: string | null;
|
||||
payload?: Record<string, unknown>;
|
||||
dedupeKey?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* publishing an event does not send email directly
|
||||
* event creates one or more in-app notifications
|
||||
* duplicate event with same `dedupeKey` should be ignored or safely reused
|
||||
* processing should be idempotent
|
||||
* failed processing should leave inspectable state
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.3 Recipient Resolver
|
||||
|
||||
Create:
|
||||
|
||||
```txt
|
||||
src/features/foundation/notifications/server/recipient-resolver.ts
|
||||
```
|
||||
|
||||
Support recipient types:
|
||||
|
||||
```txt
|
||||
explicit_user
|
||||
approval_current_step_approvers
|
||||
approval_requester
|
||||
approval_previous_actor
|
||||
entity_owner
|
||||
sales_owner
|
||||
```
|
||||
|
||||
For F.4 required recipient resolvers:
|
||||
|
||||
```txt
|
||||
approval_current_step_approvers
|
||||
approval_requester
|
||||
explicit_user
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* return user IDs only
|
||||
* never notify deleted/inactive users if such state exists
|
||||
* avoid notifying the actor if event rules say actor should be excluded
|
||||
* de-duplicate recipients
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.4 Template Renderer
|
||||
|
||||
Create:
|
||||
|
||||
```txt
|
||||
src/features/foundation/notifications/server/template-renderer.ts
|
||||
```
|
||||
|
||||
Functions:
|
||||
|
||||
```ts
|
||||
renderNotificationTemplate(template, payload)
|
||||
```
|
||||
|
||||
Supported syntax can be simple:
|
||||
|
||||
```txt
|
||||
{{quotationCode}}
|
||||
{{workflowName}}
|
||||
{{actorName}}
|
||||
{{stepName}}
|
||||
{{entityLink}}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* missing variables render as `-` or empty string
|
||||
* no arbitrary code execution
|
||||
* no unsafe HTML injection
|
||||
* plain text first
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.5 Notification Inbox APIs
|
||||
|
||||
Create:
|
||||
|
||||
```txt
|
||||
GET /api/notifications
|
||||
GET /api/notifications/unread-count
|
||||
POST /api/notifications/[id]/read
|
||||
POST /api/notifications/read-all
|
||||
POST /api/notifications/[id]/archive
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* users can only see their own notifications
|
||||
* organization scope must apply
|
||||
* unread count must be efficient
|
||||
* support pagination
|
||||
* default ordering: newest first
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.6 Notification UI Foundation
|
||||
|
||||
Add or integrate notification bell.
|
||||
|
||||
Required UI:
|
||||
|
||||
```txt
|
||||
Notification Bell
|
||||
Unread Count Badge
|
||||
Notification Dropdown / Sheet
|
||||
Mark as Read
|
||||
Mark All as Read
|
||||
Open Link
|
||||
```
|
||||
|
||||
Recommended placement:
|
||||
|
||||
```txt
|
||||
top nav / header
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* in-app only in F.4
|
||||
* no real-time requirement yet
|
||||
* polling or manual refetch is acceptable
|
||||
* empty state required
|
||||
* error state required
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.7 Approval Event Integration
|
||||
|
||||
Publish notification events from approval lifecycle.
|
||||
|
||||
Required events:
|
||||
|
||||
```txt
|
||||
approval.requested
|
||||
approval.step.approved
|
||||
approval.step.rejected
|
||||
approval.completed
|
||||
approval.cancelled
|
||||
approval.returned
|
||||
```
|
||||
|
||||
Required recipients:
|
||||
|
||||
### approval.requested
|
||||
|
||||
Notify:
|
||||
|
||||
```txt
|
||||
current step approvers
|
||||
```
|
||||
|
||||
### approval.step.approved
|
||||
|
||||
Notify:
|
||||
|
||||
```txt
|
||||
requester
|
||||
next step approvers if any
|
||||
```
|
||||
|
||||
### approval.step.rejected
|
||||
|
||||
Notify:
|
||||
|
||||
```txt
|
||||
requester
|
||||
```
|
||||
|
||||
### approval.completed
|
||||
|
||||
Notify:
|
||||
|
||||
```txt
|
||||
requester
|
||||
```
|
||||
|
||||
### approval.cancelled
|
||||
|
||||
Notify:
|
||||
|
||||
```txt
|
||||
requester
|
||||
current approvers if needed
|
||||
```
|
||||
|
||||
### approval.returned
|
||||
|
||||
Notify:
|
||||
|
||||
```txt
|
||||
requester
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* publish events from approval service layer, not route handlers
|
||||
* do not block approval transaction on notification delivery failure if possible
|
||||
* notification failure must be logged/inspectable
|
||||
* approval action should still succeed if notification creation fails unless strict mode is explicitly required
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.8 Default Notification Templates
|
||||
|
||||
Seed default templates for approval events.
|
||||
|
||||
Example:
|
||||
|
||||
```txt
|
||||
approval.requested
|
||||
Title: Approval Required: {{quotationCode}}
|
||||
Body: {{actorName}} submitted {{quotationCode}} for approval.
|
||||
Link: /dashboard/crm/quotations/{{quotationId}}
|
||||
```
|
||||
|
||||
```txt
|
||||
approval.step.rejected
|
||||
Title: Approval Rejected: {{quotationCode}}
|
||||
Body: {{actorName}} rejected {{quotationCode}}. Reason: {{remark}}
|
||||
Link: /dashboard/crm/quotations/{{quotationId}}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* templates should be organization-scoped
|
||||
* seed should be idempotent
|
||||
* no customer-sensitive data beyond document code/title in default body
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.9 Delivery Channel Foundation
|
||||
|
||||
Support channel enum:
|
||||
|
||||
```txt
|
||||
in_app
|
||||
email
|
||||
line
|
||||
webhook
|
||||
```
|
||||
|
||||
F.4 implementation:
|
||||
|
||||
```txt
|
||||
in_app = active
|
||||
email = delivery record only / pending
|
||||
line = future
|
||||
webhook = future
|
||||
```
|
||||
|
||||
Do not implement real SMTP/LINE/Webhook sending yet.
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.10 Audit / Observability
|
||||
|
||||
Audit or log:
|
||||
|
||||
```txt
|
||||
notification_event_published
|
||||
notification_event_processed
|
||||
notification_created
|
||||
notification_read
|
||||
notification_archived
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* user read/archive actions should be auditable if current audit policy requires it
|
||||
* processing failures must store error detail safely
|
||||
* do not expose stack traces to users
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.11 Permissions
|
||||
|
||||
Add or verify permissions:
|
||||
|
||||
```txt
|
||||
notifications.read
|
||||
notifications.update
|
||||
notifications.admin
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* normal users can read/update only their own notifications
|
||||
* admin can inspect templates/events only if admin API is added later
|
||||
* notification bell should not require CRM module-specific permission
|
||||
|
||||
---
|
||||
|
||||
## Scope F.4.12 Documentation
|
||||
|
||||
Create:
|
||||
|
||||
```txt
|
||||
docs/implementation/task-f4-notification-framework-foundation.md
|
||||
```
|
||||
|
||||
Document:
|
||||
|
||||
```txt
|
||||
schema
|
||||
event publishing flow
|
||||
recipient resolver
|
||||
template syntax
|
||||
approval event integration
|
||||
delivery channel limitations
|
||||
known limitations
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Event Flow
|
||||
|
||||
Recommended architecture:
|
||||
|
||||
```txt
|
||||
Approval Service
|
||||
↓
|
||||
publishNotificationEvent()
|
||||
↓
|
||||
app_notification_events
|
||||
↓
|
||||
processNotificationEvent()
|
||||
↓
|
||||
resolveRecipients()
|
||||
↓
|
||||
renderTemplate()
|
||||
↓
|
||||
app_notifications
|
||||
↓
|
||||
Notification Bell / Inbox
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Explicit Non-Scope
|
||||
|
||||
Do not implement:
|
||||
|
||||
* SMTP email sending
|
||||
* LINE OA integration
|
||||
* webhook delivery
|
||||
* real-time websocket / SSE
|
||||
* reminder scheduling
|
||||
* escalation rules
|
||||
* notification preference center
|
||||
* admin template editor UI
|
||||
* full notification analytics
|
||||
* mobile push notifications
|
||||
|
||||
These belong to later F tasks.
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
Run:
|
||||
|
||||
```txt
|
||||
npm exec tsc --noEmit
|
||||
npm run build
|
||||
```
|
||||
|
||||
Manual verification:
|
||||
|
||||
```txt
|
||||
Submit quotation for approval
|
||||
Current approver receives notification
|
||||
Approve step
|
||||
Requester receives notification
|
||||
Next approver receives notification if next step exists
|
||||
Reject step
|
||||
Requester receives notification
|
||||
Unread count updates
|
||||
Mark one notification as read
|
||||
Mark all notifications as read
|
||||
Archive notification
|
||||
Open notification link
|
||||
Approval still succeeds if notification processing fails gracefully
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Task is complete when:
|
||||
|
||||
* Notification event tables exist
|
||||
* Notification event service exists
|
||||
* Recipient resolver exists
|
||||
* Template renderer exists
|
||||
* In-app notification records are created from approval events
|
||||
* Notification inbox APIs exist
|
||||
* Notification bell UI exists
|
||||
* Approval submit/approve/reject events publish notifications
|
||||
* Notification read/unread workflow works
|
||||
* Existing approval flow remains operational
|
||||
|
||||
Result:
|
||||
|
||||
```txt
|
||||
Notification Framework Foundation = Established
|
||||
Approval Notifications = Operational In-App
|
||||
Ready for F.5 Reminder / Escalation
|
||||
```
|
||||
Reference in New Issue
Block a user