Files
bakery-ia/services/notification/MULTI_TENANT_WHATSAPP_IMPLEMENTATION.md

659 lines
18 KiB
Markdown

# Multi-Tenant WhatsApp Configuration Implementation
## Overview
This document describes the implementation of per-tenant WhatsApp Business phone number configuration, allowing each bakery to use their own WhatsApp Business account for sending notifications to suppliers.
---
## Problem Statement
**Before**: All tenants shared a single global WhatsApp Business account configured via environment variables.
**After**: Each tenant can configure their own WhatsApp Business account in their bakery settings, with credentials stored securely in the database.
---
## Architecture
### Data Flow
```
Bakery Settings UI (Frontend)
Tenant Settings API
tenant_settings.notification_settings (Database)
Notification Service fetches tenant settings
WhatsAppBusinessService uses tenant-specific credentials
Meta WhatsApp Cloud API
```
###Tenant Isolation
Each tenant has:
- Own WhatsApp Phone Number ID
- Own WhatsApp Access Token
- Own WhatsApp Business Account ID
- Independent enable/disable toggle
---
## Implementation Progress
### ✅ Phase 1: Backend - Tenant Service (COMPLETED)
#### 1.1 Database Model
**File**: `services/tenant/app/models/tenant_settings.py`
**Changes**:
- Added `notification_settings` JSON column to `TenantSettings` model
- Includes WhatsApp and Email configuration
- Default values set for new tenants
**Fields Added**:
```python
notification_settings = {
# WhatsApp Configuration
"whatsapp_enabled": False,
"whatsapp_phone_number_id": "",
"whatsapp_access_token": "",
"whatsapp_business_account_id": "",
"whatsapp_api_version": "v18.0",
"whatsapp_default_language": "es",
# Email Configuration
"email_enabled": True,
"email_from_address": "",
"email_from_name": "",
"email_reply_to": "",
# Notification Preferences
"enable_po_notifications": True,
"enable_inventory_alerts": True,
"enable_production_alerts": True,
"enable_forecast_alerts": True,
# Notification Channels
"po_notification_channels": ["email"],
"inventory_alert_channels": ["email"],
"production_alert_channels": ["email"],
"forecast_alert_channels": ["email"]
}
```
#### 1.2 Pydantic Schema
**File**: `services/tenant/app/schemas/tenant_settings.py`
**Changes**:
- Created `NotificationSettings` Pydantic schema
- Added validation for required fields when WhatsApp is enabled
- Added to `TenantSettingsResponse` and `TenantSettingsUpdate`
**Validators**:
- Validates channels are valid (`email`, `whatsapp`, `sms`, `push`)
- Requires `whatsapp_phone_number_id` when WhatsApp enabled
- Requires `whatsapp_access_token` when WhatsApp enabled
#### 1.3 Service Layer
**File**: `services/tenant/app/services/tenant_settings_service.py`
**Changes**:
- Added `NotificationSettings` to imports
- Added `"notification"` to `CATEGORY_SCHEMAS` map
- Added `"notification": "notification_settings"` to `CATEGORY_COLUMNS` map
**Effect**: The service now automatically handles notification settings through existing methods:
- `get_category(tenant_id, "notification")`
- `update_category(tenant_id, "notification", updates)`
- `reset_category(tenant_id, "notification")`
#### 1.4 Database Migration
**File**: `services/tenant/migrations/versions/002_add_notification_settings.py`
**Purpose**: Adds `notification_settings` column to existing `tenant_settings` table
**Migration Details**:
- Adds JSONB column with default values
- All existing tenants get default notification settings
- Reversible (downgrade removes column)
**To Run**:
```bash
cd services/tenant
alembic upgrade head
```
---
### 🔄 Phase 2: Backend - Notification Service (IN PROGRESS)
This phase needs to be completed. Here's what needs to be done:
#### 2.1 Add Tenant Client Dependency
**File**: `services/notification/app/core/config.py` or dependency injection
**Action**: Add `TenantServiceClient` to notification service
**Code needed**:
```python
from shared.clients.tenant_client import TenantServiceClient
# In service initialization or dependency
tenant_client = TenantServiceClient(config)
```
#### 2.2 Modify WhatsAppBusinessService
**File**: `services/notification/app/services/whatsapp_business_service.py`
**Changes needed**:
1. Accept tenant_id as parameter
2. Fetch tenant notification settings
3. Use tenant-specific credentials if available
4. Fall back to global config if tenant settings empty
**Example Implementation**:
```python
async def send_message(self, request: SendWhatsAppMessageRequest, tenant_client: TenantServiceClient):
# Fetch tenant settings
settings = await tenant_client.get_notification_settings(request.tenant_id)
# Use tenant-specific credentials if WhatsApp enabled
if settings.get("whatsapp_enabled"):
access_token = settings.get("whatsapp_access_token")
phone_number_id = settings.get("whatsapp_phone_number_id")
business_account_id = settings.get("whatsapp_business_account_id")
else:
# Fall back to global config
access_token = self.access_token
phone_number_id = self.phone_number_id
business_account_id = self.business_account_id
# Send message using selected credentials
...
```
#### 2.3 Update PO Event Consumer
**File**: `services/notification/app/consumers/po_event_consumer.py`
**Changes needed**:
1. Inject `TenantServiceClient`
2. Fetch tenant settings before sending WhatsApp
3. Check if WhatsApp is enabled for tenant
4. Use appropriate channels
**Example**:
```python
# In __init__
self.tenant_client = tenant_client
# In send_po_approved_whatsapp
async def send_po_approved_whatsapp(self, event_data):
tenant_id = event_data.get('data', {}).get('tenant_id')
# Get tenant notification settings
settings = await self.tenant_client.get_notification_settings(tenant_id)
# Check if WhatsApp enabled
if not settings.get("whatsapp_enabled"):
logger.info("WhatsApp not enabled for tenant", tenant_id=tenant_id)
return False
# Check if PO notifications include WhatsApp channel
channels = settings.get("po_notification_channels", [])
if "whatsapp" not in channels:
logger.info("WhatsApp not in PO notification channels", tenant_id=tenant_id)
return False
# Send WhatsApp (service will use tenant-specific credentials)
...
```
---
### 📋 Phase 3: Frontend - Settings UI (PENDING)
#### 3.1 TypeScript Types
**File**: `frontend/src/api/types/settings.ts`
**Add**:
```typescript
export interface NotificationSettings {
// WhatsApp Configuration
whatsapp_enabled: boolean;
whatsapp_phone_number_id: string;
whatsapp_access_token: string;
whatsapp_business_account_id: string;
whatsapp_api_version: string;
whatsapp_default_language: string;
// Email Configuration
email_enabled: boolean;
email_from_address: string;
email_from_name: string;
email_reply_to: string;
// Notification Preferences
enable_po_notifications: boolean;
enable_inventory_alerts: boolean;
enable_production_alerts: boolean;
enable_forecast_alerts: boolean;
// Notification Channels
po_notification_channels: string[];
inventory_alert_channels: string[];
production_alert_channels: string[];
forecast_alert_channels: string[];
}
export interface BakerySettings {
...existing fields...
notification_settings: NotificationSettings;
}
```
#### 3.2 Settings Page - Add Tab
**File**: `frontend/src/pages/app/settings/bakery/BakerySettingsPage.tsx`
**Add new tab**:
```tsx
<TabsTrigger value="notifications">
<Bell className="w-4 h-4 mr-2" />
{t('bakery.tabs.notifications')}
</TabsTrigger>
<TabsContent value="notifications">
<NotificationSettingsCard settings={settings} onUpdate={handleUpdate} />
</TabsContent>
```
#### 3.3 Notification Settings Card Component
**File**: `frontend/src/components/settings/NotificationSettingsCard.tsx` (new file)
**Features**:
- Toggle for WhatsApp enabled/disabled
- Input fields for WhatsApp credentials (Phone Number ID, Access Token, Business Account ID)
- Password-style input for Access Token
- Test Connection button
- Email configuration fields
- Channel selection checkboxes for each notification type
**Example Structure**:
```tsx
export function NotificationSettingsCard({ settings, onUpdate }: Props) {
return (
<Card>
<CardHeader>
<CardTitle>{t('notifications.whatsapp_config')}</CardTitle>
</CardHeader>
<CardContent>
{/* WhatsApp Enable Toggle */}
<Switch
checked={settings.whatsapp_enabled}
onCheckedChange={(enabled) => onUpdate({ whatsapp_enabled: enabled })}
/>
{/* WhatsApp Credentials (shown when enabled) */}
{settings.whatsapp_enabled && (
<>
<Input
label={t('notifications.phone_number_id')}
value={settings.whatsapp_phone_number_id}
onChange={(e) => onUpdate({ whatsapp_phone_number_id: e.target.value })}
/>
<Input
type="password"
label={t('notifications.access_token')}
value={settings.whatsapp_access_token}
onChange={(e) => onUpdate({ whatsapp_access_token: e.target.value })}
/>
<Button onClick={testConnection}>
{t('notifications.test_connection')}
</Button>
</>
)}
{/* Channel Selection */}
<div>
<Label>{t('notifications.po_channels')}</Label>
<Checkbox
checked={settings.po_notification_channels.includes('email')}
label="Email"
/>
<Checkbox
checked={settings.po_notification_channels.includes('whatsapp')}
label="WhatsApp"
disabled={!settings.whatsapp_enabled}
/>
</div>
</CardContent>
</Card>
);
}
```
#### 3.4 i18n Translations
**Files**:
- `frontend/src/locales/es/settings.json`
- `frontend/src/locales/eu/settings.json`
**Add translations**:
```json
{
"notifications": {
"title": "Notificaciones",
"whatsapp_config": "Configuración de WhatsApp",
"whatsapp_enabled": "Activar WhatsApp",
"phone_number_id": "ID de Número de Teléfono",
"access_token": "Token de Acceso",
"business_account_id": "ID de Cuenta de Negocio",
"test_connection": "Probar Conexión",
"email_config": "Configuración de Email",
"po_channels": "Canales para Órdenes de Compra",
"inventory_channels": "Canales para Alertas de Inventario",
"test_success": "Conexión exitosa",
"test_failed": "Error en la conexión",
"save_success": "Configuración guardada",
"save_error": "Error al guardar"
}
}
```
---
## Testing Guide
### Backend Testing
#### 1. Test Tenant Settings API
```bash
# Get notification settings
curl -X GET "http://localhost:8001/api/v1/tenants/{tenant_id}/settings/category/notification" \
-H "Authorization: Bearer {token}"
# Update notification settings
curl -X PUT "http://localhost:8001/api/v1/tenants/{tenant_id}/settings/category/notification" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"settings": {
"whatsapp_enabled": true,
"whatsapp_phone_number_id": "123456789",
"whatsapp_access_token": "EAAxxxx",
"whatsapp_business_account_id": "987654321",
"po_notification_channels": ["email", "whatsapp"]
}
}'
```
#### 2. Test WhatsApp Message with Tenant Config
```bash
# Send test PO notification (should use tenant's WhatsApp config)
# Trigger a PO approval and check logs:
kubectl logs -f deployment/notification-service | grep "WhatsApp"
# Should see logs indicating tenant-specific credentials being used
```
#### 3. Verify Database
```sql
-- Check notification settings for all tenants
SELECT
tenant_id,
notification_settings->>'whatsapp_enabled' as whatsapp_enabled,
notification_settings->>'whatsapp_phone_number_id' as phone_id
FROM tenant_settings;
-- Check WhatsApp messages sent
SELECT
tenant_id,
recipient_phone,
status,
template_name,
created_at
FROM whatsapp_messages
ORDER BY created_at DESC
LIMIT 10;
```
### Frontend Testing
1. **Navigate to Settings**:
- Go to Bakery Settings page
- Click on "Notifications" tab
2. **Configure WhatsApp**:
- Toggle WhatsApp enabled
- Enter WhatsApp credentials from Meta Business Suite
- Click "Test Connection" button
- Should see success message if credentials valid
3. **Configure Channels**:
- Enable WhatsApp for PO notifications
- Save settings
- Verify settings persist after page reload
4. **Test End-to-End**:
- Configure WhatsApp for a tenant
- Create and approve a purchase order
- Verify WhatsApp message sent to supplier
- Check message appears in WhatsApp messages table
---
## Security Considerations
### ⚠️ Access Token Storage
**Current**: Access tokens stored as plain text in JSON field
**Recommended for Production**:
1. Encrypt access tokens before storing
2. Use field-level encryption
3. Decrypt only when needed
**Implementation**:
```python
from cryptography.fernet import Fernet
class EncryptionService:
def encrypt(self, value: str) -> str:
# Encrypt using Fernet or AWS KMS
pass
def decrypt(self, encrypted_value: str) -> str:
# Decrypt
pass
# In tenant settings service
encrypted_token = encryption_service.encrypt(access_token)
settings["whatsapp_access_token"] = encrypted_token
```
### Role-Based Access Control
Only owners and admins should be able to:
- View WhatsApp credentials
- Update notification settings
- Test WhatsApp connection
**Implementation**: Add role check in API endpoint
```python
@router.put("/api/v1/tenants/{tenant_id}/settings/category/notification")
async def update_notification_settings(
tenant_id: UUID,
settings: CategoryUpdateRequest,
current_user: User = Depends(get_current_user)
):
# Check role
if current_user.role not in ["owner", "admin"]:
raise HTTPException(status_code=403, detail="Insufficient permissions")
# Update settings
...
```
---
## Migration Guide
### For Existing Tenants
When the migration runs, all existing tenants will get default notification settings with WhatsApp disabled.
To enable WhatsApp for existing tenants:
1. **Get WhatsApp Business API credentials** from Meta Business Suite
2. **Update tenant settings** via API or UI
3. **Test configuration** using test endpoint
4. **Enable WhatsApp** for desired notification types
### From Global to Per-Tenant
If you have a global WhatsApp configuration you want to migrate:
```python
# Migration script (run once)
async def migrate_global_to_tenant():
# Get global config
global_phone_id = os.getenv("WHATSAPP_PHONE_NUMBER_ID")
global_token = os.getenv("WHATSAPP_ACCESS_TOKEN")
global_account_id = os.getenv("WHATSAPP_BUSINESS_ACCOUNT_ID")
# Update all tenant settings
tenants = await get_all_tenants()
for tenant in tenants:
settings = {
"whatsapp_enabled": True,
"whatsapp_phone_number_id": global_phone_id,
"whatsapp_access_token": global_token,
"whatsapp_business_account_id": global_account_id
}
await update_tenant_notification_settings(tenant.id, settings)
```
---
## Deployment Steps
### 1. Backend Deployment
```bash
# 1. Deploy tenant service with new schema
cd services/tenant
alembic upgrade head
# 2. Deploy notification service with updated code
kubectl apply -f kubernetes/notification-deployment.yaml
# 3. Verify migration
kubectl exec -it deployment/tenant-service -- alembic current
# 4. Check logs
kubectl logs -f deployment/notification-service | grep "notification_settings"
```
### 2. Frontend Deployment
```bash
cd frontend
npm run build
# Deploy built frontend
```
### 3. Verification
- Check tenant settings API responds with notification_settings
- Verify frontend shows Notifications tab
- Test WhatsApp configuration for one tenant
- Send test PO notification
---
## Troubleshooting
### Issue: "notification_settings not found in database"
**Cause**: Migration not run
**Solution**:
```bash
cd services/tenant
alembic upgrade head
```
### Issue: "WhatsApp still using global config"
**Cause**: Notification service not updated to fetch tenant settings
**Solution**: Complete Phase 2 implementation (see above)
### Issue: "Access token validation fails"
**Cause**: Invalid or expired token
**Solution**:
1. Generate new permanent token from Meta Business Suite
2. Update tenant settings with new token
3. Test connection
---
## Next Steps
1. **Complete Phase 2**: Update notification service to fetch and use tenant settings
2. **Complete Phase 3**: Build frontend UI for configuration
3. **Add Encryption**: Implement field-level encryption for access tokens
4. **Add Audit Logging**: Log all changes to notification settings
5. **Add Test Endpoint**: Create endpoint to test WhatsApp connection
6. **Update Documentation**: Add tenant-specific setup to WhatsApp setup guide
---
## Files Modified
### Backend - Tenant Service
-`services/tenant/app/models/tenant_settings.py`
-`services/tenant/app/schemas/tenant_settings.py`
-`services/tenant/app/services/tenant_settings_service.py`
-`services/tenant/migrations/versions/002_add_notification_settings.py`
### Backend - Notification Service (Pending)
-`services/notification/app/services/whatsapp_business_service.py`
-`services/notification/app/consumers/po_event_consumer.py`
-`services/notification/app/core/config.py` or DI setup
### Frontend (Pending)
-`frontend/src/api/types/settings.ts`
-`frontend/src/pages/app/settings/bakery/BakerySettingsPage.tsx`
-`frontend/src/components/settings/NotificationSettingsCard.tsx` (new)
-`frontend/src/locales/es/settings.json`
-`frontend/src/locales/eu/settings.json`
---
## Summary
**Completed (Phase 1)**:
- ✅ Database schema for per-tenant notification settings
- ✅ Pydantic validation schemas
- ✅ Service layer support
- ✅ Database migration
**Remaining**:
- ⏳ Notification service integration with tenant settings
- ⏳ Frontend UI for configuration
- ⏳ Security enhancements (encryption, RBAC)
- ⏳ Testing and documentation updates
This implementation provides a solid foundation for multi-tenant WhatsApp configuration. Each bakery can now configure their own WhatsApp Business account, with credentials stored securely and settings easily manageable through the UI.