swiftops-backend / docs /dev /NOTIFICATION_SYSTEM.md
kamau1's picture
feat: unified sync notification system, realtime timesheets and payroll, simplified project role compensation
95005e1
# Notification System - Developer Guide
## 🎯 Overview
The SwiftOps notification system is a **2-tier architecture** designed for reliability, scalability, and maintainability. It handles all notifications across the platform with a consistent, world-class approach.
### Architecture
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ NOTIFICATION SYSTEM β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ TIER 1: Notification Creation (Synchronous) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ NotificationCreator β”‚ β”‚
β”‚ β”‚ - Creates notification records in database β”‚ β”‚
β”‚ β”‚ - Synchronous, transaction-safe β”‚ β”‚
β”‚ β”‚ - Guaranteed to be saved β”‚ β”‚
β”‚ β”‚ - Rolls back with parent operation β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ ↓ β”‚
β”‚ TIER 2: Notification Delivery (Asynchronous) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ NotificationDelivery β”‚ β”‚
β”‚ β”‚ - Delivers via external channels (WhatsApp, etc.) β”‚ β”‚
β”‚ β”‚ - Non-blocking background tasks β”‚ β”‚
β”‚ β”‚ - Handles failures gracefully β”‚ β”‚
β”‚ β”‚ - Configurable per channel β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
---
## πŸš€ Quick Start
### Basic Usage
```python
from fastapi import BackgroundTasks
from app.services.notification_creator import NotificationCreator
from app.services.notification_delivery import NotificationDelivery
# In your endpoint
@router.post("/tickets/assign")
def assign_ticket(
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
# Your business logic
ticket = create_ticket(...)
assignment = assign_to_agent(...)
# TIER 1: Create notification (synchronous)
notification = NotificationCreator.create(
db=db,
user_id=agent.id,
title="New Ticket Assigned",
message=f"You have been assigned to {ticket.ticket_name}",
source_type="ticket",
source_id=ticket.id,
notification_type="assignment",
channel="whatsapp",
project_id=ticket.project_id,
metadata={
"ticket_number": ticket.ticket_number,
"priority": "high",
"action_url": f"/tickets/{ticket.id}"
}
)
# Commit notification with your business logic
db.commit()
# TIER 2: Queue delivery (asynchronous, non-blocking)
NotificationDelivery.queue_delivery(
background_tasks=background_tasks,
notification_id=notification.id
)
return {"status": "success"}
```
---
## πŸ“š API Reference
### NotificationCreator
#### `create()` - Create Single Notification
```python
notification = NotificationCreator.create(
db: Session, # Database session
user_id: UUID, # User to notify
title: str, # Short title (for display)
message: str, # Detailed message
source_type: str, # Entity type (ticket, expense, payroll, etc.)
source_id: Optional[UUID], # Entity ID (None for bulk operations)
notification_type: str, # Notification type (assignment, payment, alert, etc.)
channel: str = "in_app", # Delivery channel (in_app, whatsapp, email, sms, push)
metadata: Optional[Dict] = None,# Additional data (action URLs, context, etc.)
project_id: Optional[UUID] = None # Optional project ID for filtering
) -> Notification
```
**Example:**
```python
notification = NotificationCreator.create(
db=db,
user_id=agent.id,
title="Ticket Assigned",
message="You have been assigned to install fiber at Customer A",
source_type="ticket",
source_id=ticket.id,
notification_type="assignment",
channel="whatsapp",
project_id=ticket.project_id,
metadata={
"ticket_number": "TKT-001",
"priority": "high",
"action_url": f"/tickets/{ticket.id}"
}
)
db.commit()
```
#### `create_bulk()` - Create Multiple Notifications
```python
notifications = NotificationCreator.create_bulk(
db: Session,
user_ids: List[UUID], # List of users to notify
title: str, # Same title for all
message: str, # Same message for all
source_type: str,
source_id: Optional[UUID],
notification_type: str,
channel: str = "in_app",
metadata: Optional[Dict] = None,
project_id: Optional[UUID] = None
) -> List[Notification]
```
**Example:**
```python
# Notify all workers about payroll export
worker_ids = [worker1.id, worker2.id, worker3.id]
notifications = NotificationCreator.create_bulk(
db=db,
user_ids=worker_ids,
title="πŸ’° Payment Processed",
message="Your payment has been processed",
source_type="payroll",
source_id=None,
notification_type="payment",
channel="whatsapp"
)
db.commit()
```
#### `notify_project_team()` - Notify Team Members by Role
```python
notifications = NotificationCreator.notify_project_team(
db: Session,
project_id: UUID, # Project ID
title: str,
message: str,
source_type: str,
source_id: Optional[UUID],
notification_type: str,
roles: Optional[List[AppRole]] = None, # Filter by roles (PM, Dispatcher, etc.)
channel: str = "in_app",
metadata: Optional[Dict] = None,
exclude_user_ids: Optional[List[UUID]] = None # Exclude specific users
) -> List[Notification]
```
**Example:**
```python
# Notify all managers when ticket is dropped
notifications = NotificationCreator.notify_project_team(
db=db,
project_id=ticket.project_id,
title="⚠️ Ticket Dropped - Action Required",
message=f"{agent.name} dropped ticket: {ticket.name}",
source_type="ticket",
source_id=ticket.id,
notification_type="ticket_dropped",
roles=[AppRole.PROJECT_MANAGER, AppRole.DISPATCHER],
exclude_user_ids=[agent.id] # Don't notify the agent who dropped
)
db.commit()
```
---
### NotificationDelivery
#### `queue_delivery()` - Queue Single Notification
```python
NotificationDelivery.queue_delivery(
background_tasks: BackgroundTasks, # FastAPI BackgroundTasks
notification_id: UUID # Notification ID to deliver
) -> None
```
**Example:**
```python
NotificationDelivery.queue_delivery(
background_tasks=background_tasks,
notification_id=notification.id
)
```
#### `queue_bulk_delivery()` - Queue Multiple Notifications
```python
NotificationDelivery.queue_bulk_delivery(
background_tasks: BackgroundTasks,
notification_ids: List[UUID] # List of notification IDs
) -> None
```
**Example:**
```python
NotificationDelivery.queue_bulk_delivery(
background_tasks=background_tasks,
notification_ids=[n.id for n in notifications]
)
```
---
## 🎨 Common Patterns
### Pattern 1: Single User Notification
```python
@router.post("/tickets/assign")
def assign_ticket(
background_tasks: BackgroundTasks,
db: Session = Depends(get_db)
):
# Business logic
assignment = create_assignment(...)
# Create notification
notification = NotificationCreator.create(
db=db,
user_id=agent.id,
title="Ticket Assigned",
message=f"You have been assigned to {ticket.name}",
source_type="ticket",
source_id=ticket.id,
notification_type="assignment",
channel="whatsapp"
)
db.commit()
# Queue delivery
NotificationDelivery.queue_delivery(
background_tasks=background_tasks,
notification_id=notification.id
)
return assignment
```
### Pattern 2: Notify Project Team by Role
```python
@router.post("/tickets/{ticket_id}/drop")
def drop_ticket(
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
# Business logic
ticket = drop_ticket(...)
# Notify all managers and dispatchers
notifications = NotificationCreator.notify_project_team(
db=db,
project_id=ticket.project_id,
title="⚠️ Ticket Dropped",
message=f"{current_user.name} dropped ticket: {ticket.name}",
source_type="ticket",
source_id=ticket.id,
notification_type="ticket_dropped",
roles=[AppRole.PROJECT_MANAGER, AppRole.DISPATCHER],
exclude_user_ids=[current_user.id]
)
db.commit()
# Queue delivery
NotificationDelivery.queue_bulk_delivery(
background_tasks=background_tasks,
notification_ids=[n.id for n in notifications]
)
return ticket
```
---
## βš™οΈ Configuration
### Channel Configuration
For MVP, all external channels are **disabled by default**. Notifications are created but not delivered externally.
**File:** `src/app/services/notification_delivery.py`
```python
class NotificationDeliveryConfig:
# For MVP, all external channels are disabled
ENABLE_WHATSAPP = False # Set to True when ready
ENABLE_EMAIL = False # Set to True when ready
ENABLE_SMS = False # Set to True when ready
ENABLE_PUSH = False # Set to True when ready
# In-app notifications are always enabled
ENABLE_IN_APP = True
```
**To enable a channel:**
1. Set the flag to `True` in `NotificationDeliveryConfig`
2. Implement the delivery method (e.g., `_deliver_whatsapp()`)
3. Test thoroughly
4. Deploy
---
## πŸ” Best Practices
### 1. Always Commit Notifications
```python
# βœ… CORRECT
notification = NotificationCreator.create(...)
db.commit() # Commit before queuing delivery
NotificationDelivery.queue_delivery(...)
```
```python
# ❌ WRONG
notification = NotificationCreator.create(...)
NotificationDelivery.queue_delivery(...) # Notification not committed yet!
db.commit()
```
### 2. Use Metadata for Context
```python
# βœ… CORRECT - Rich metadata
notification = NotificationCreator.create(
db=db,
user_id=user.id,
title="Ticket Assigned",
message="You have been assigned...",
source_type="ticket",
source_id=ticket.id,
notification_type="assignment",
metadata={
"ticket_number": ticket.ticket_number,
"priority": "high",
"action_url": f"/tickets/{ticket.id}",
"customer_name": ticket.customer_name
}
)
```
### 3. Handle Notification Failures Gracefully
```python
# βœ… CORRECT - Don't fail business logic if notification fails
try:
notification = NotificationCreator.create(...)
db.commit()
NotificationDelivery.queue_delivery(...)
except Exception as e:
logger.error(f"Failed to create notification: {e}")
# Continue with business logic
```
---
## πŸš€ Migration from Old System
### Old Pattern (NotificationHelper - Async)
```python
# ❌ OLD - Don't use this anymore
await NotificationHelper.notify_ticket_assigned(
db=db,
ticket=ticket,
agent=agent
)
```
### New Pattern (NotificationCreator - Sync)
```python
# βœ… NEW - Use this instead
notification = NotificationCreator.create(
db=db,
user_id=agent.id,
title="Ticket Assigned",
message=f"You have been assigned to {ticket.name}",
source_type="ticket",
source_id=ticket.id,
notification_type="assignment",
channel="whatsapp"
)
db.commit()
NotificationDelivery.queue_delivery(
background_tasks=background_tasks,
notification_id=notification.id
)
```
---
## πŸ“ž Support
For questions or issues with the notification system:
1. Check this documentation
2. Review existing implementations in:
- `src/app/api/v1/payroll.py` (payroll export)
- `src/app/api/v1/ticket_assignments.py` (ticket drop)
- `src/app/services/expense_service.py` (expense submission)
3. Contact the development team
---
**Last Updated:** 2024-12-12
**Version:** 1.0.0
**Status:** Production Ready βœ