300 lines
9.1 KiB
Markdown
300 lines
9.1 KiB
Markdown
# Notifications Internes du Support
|
|
|
|
## Vue d'ensemble
|
|
|
|
Système de notification par email vers `paie@odentas.fr` pour alerter l'équipe support quand :
|
|
1. Un utilisateur crée un nouveau ticket
|
|
2. Un utilisateur répond à un ticket existant
|
|
|
|
## 📧 Types de notifications
|
|
|
|
### 1. Nouveau ticket créé (`support-ticket-created`)
|
|
|
|
**Déclencheur :** Quand un utilisateur (non-staff) crée un nouveau ticket via l'API `POST /api/tickets`
|
|
|
|
**Template Email :**
|
|
- **Sujet :** `[SUPPORT] Nouveau ticket : {sujet du ticket}`
|
|
- **Titre :** 🎫 Nouveau ticket support
|
|
- **Bouton CTA :** "Voir le ticket" → `/staff/tickets/{id}`
|
|
- **Couleur bouton :** Bleu (#3B82F6)
|
|
|
|
**Informations affichées :**
|
|
|
|
**InfoCard :**
|
|
- Organisation
|
|
- Code employeur
|
|
- Créé par (nom de l'utilisateur)
|
|
- Email (email de l'utilisateur)
|
|
|
|
**DetailsCard :**
|
|
- ID du ticket
|
|
- Sujet
|
|
- Catégorie (actuellement "Support général")
|
|
- Message initial (avec sauts de ligne préservés)
|
|
|
|
### 2. Réponse utilisateur (`support-ticket-reply`)
|
|
|
|
**Déclencheur :** Quand un utilisateur (non-staff) ajoute un message à un ticket via `POST /api/tickets/[id]/messages`
|
|
|
|
**Template Email :**
|
|
- **Sujet :** `[SUPPORT] Réponse au ticket : {sujet du ticket}`
|
|
- **Titre :** 💬 Réponse sur un ticket
|
|
- **Bouton CTA :** "Voir le ticket" → `/staff/tickets/{id}`
|
|
- **Couleur bouton :** Bleu (#3B82F6)
|
|
|
|
**Informations affichées :**
|
|
|
|
**InfoCard :**
|
|
- Organisation
|
|
- Code employeur
|
|
- Répondu par (nom de l'utilisateur)
|
|
- Email (email de l'utilisateur)
|
|
|
|
**DetailsCard :**
|
|
- ID du ticket
|
|
- Sujet
|
|
- Statut (open, in_progress, resolved, closed)
|
|
- Réponse (avec sauts de ligne préservés)
|
|
|
|
## 🔧 Implémentation technique
|
|
|
|
### Fichiers modifiés
|
|
|
|
#### 1. `/lib/emailTemplateService.ts`
|
|
```typescript
|
|
// Nouveaux types ajoutés
|
|
| 'support-ticket-created'
|
|
| 'support-ticket-reply'
|
|
|
|
// Nouveaux champs dans EmailDataV2
|
|
ticketCategory?: string;
|
|
ticketMessage?: string;
|
|
ticketStatus?: string;
|
|
userEmail?: string;
|
|
userMessage?: string;
|
|
```
|
|
|
|
#### 2. `/lib/emailMigrationHelpers.ts`
|
|
|
|
**Nouvelle fonction : `sendInternalTicketCreatedEmail()`**
|
|
```typescript
|
|
export async function sendInternalTicketCreatedEmail(
|
|
data: {
|
|
ticketId: string;
|
|
ticketSubject: string;
|
|
ticketCategory: string;
|
|
ticketMessage: string;
|
|
userName: string;
|
|
userEmail: string;
|
|
organizationName?: string;
|
|
employerCode?: string;
|
|
}
|
|
)
|
|
```
|
|
- Convertit automatiquement les sauts de ligne en `<br>`
|
|
- Envoie toujours à `paie@odentas.fr`
|
|
- CTA vers `/staff/tickets/{id}`
|
|
|
|
**Nouvelle fonction : `sendInternalTicketReplyEmail()`**
|
|
```typescript
|
|
export async function sendInternalTicketReplyEmail(
|
|
data: {
|
|
ticketId: string;
|
|
ticketSubject: string;
|
|
ticketStatus: string;
|
|
userMessage: string;
|
|
userName: string;
|
|
userEmail: string;
|
|
organizationName?: string;
|
|
employerCode?: string;
|
|
}
|
|
)
|
|
```
|
|
- Convertit automatiquement les sauts de ligne en `<br>`
|
|
- Envoie toujours à `paie@odentas.fr`
|
|
- CTA vers `/staff/tickets/{id}`
|
|
|
|
#### 3. `/app/api/tickets/route.ts`
|
|
|
|
**Modifications dans `POST` :**
|
|
```typescript
|
|
// Après la création du ticket, si !isStaff
|
|
if (!isStaff) {
|
|
try {
|
|
// Récupérer les infos utilisateur avec createSbServiceRole()
|
|
const sbAdmin = createSbServiceRole();
|
|
const { data: userData } = await sbAdmin.auth.admin.getUserById(user.id);
|
|
|
|
// Récupérer org et code employeur
|
|
const { data: organization } = await sb.from('organizations')...
|
|
const { data: orgDetails } = await sb.from('organization_details')...
|
|
|
|
// Envoyer la notification
|
|
await sendInternalTicketCreatedEmail({...});
|
|
} catch (emailError) {
|
|
// Ne pas bloquer la création du ticket
|
|
console.error('Failed to send internal notification');
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 4. `/app/api/tickets/[id]/messages/route.ts`
|
|
|
|
**Modifications dans `POST` :**
|
|
```typescript
|
|
// Après l'insertion du message, si !isStaff
|
|
if (!isStaff) {
|
|
try {
|
|
// Récupérer les infos du ticket
|
|
const { data: ticket } = await sb.from("tickets")...
|
|
|
|
// Récupérer org et code employeur
|
|
const { data: organization } = await sb.from('organizations')...
|
|
const { data: orgDetails } = await sb.from('organization_details')...
|
|
|
|
// Récupérer les infos utilisateur avec createSbServiceRole()
|
|
const sbAdmin = createSbServiceRole();
|
|
const { data: userData } = await sbAdmin.auth.admin.getUserById(user.id);
|
|
|
|
// Envoyer la notification
|
|
await sendInternalTicketReplyEmail({...});
|
|
} catch (emailError) {
|
|
// Ne pas bloquer l'ajout du message
|
|
console.error('Failed to send internal notification');
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🎨 Design des emails
|
|
|
|
### Structure standardisée
|
|
|
|
Les deux types d'emails utilisent le système Universal Email V2 avec :
|
|
- **Header :** Logo Odentas avec fond standard
|
|
- **InfoCard :** Informations sur l'organisation et l'utilisateur (fond gris clair)
|
|
- **DetailsCard :** Détails du ticket/message (fond blanc)
|
|
- **CTA Button :** Bouton bleu "Voir le ticket"
|
|
- **Footer :** Mentions légales Odentas
|
|
|
|
### Couleurs
|
|
- Header : `#171424` (violet foncé standard)
|
|
- Bouton CTA : `#3B82F6` (bleu) - différent des emails clients pour distinguer les notifications internes
|
|
- Texte bouton : `#FFFFFF` (blanc)
|
|
|
|
## 📊 Sources de données
|
|
|
|
### Nom de l'utilisateur
|
|
```typescript
|
|
// Priorité de récupération
|
|
userData?.user?.user_metadata?.display_name ||
|
|
userData?.user?.user_metadata?.first_name ||
|
|
'Utilisateur inconnu'
|
|
```
|
|
|
|
### Code employeur
|
|
```typescript
|
|
// Depuis organization_details
|
|
const { data: orgDetails } = await sb
|
|
.from('organization_details')
|
|
.select('code_employeur')
|
|
.eq('org_id', ticket.org_id)
|
|
.maybeSingle();
|
|
```
|
|
|
|
### Email utilisateur
|
|
```typescript
|
|
user.email || 'Email non disponible'
|
|
```
|
|
|
|
## 🔐 Permissions
|
|
|
|
- Utilise `createSbServiceRole()` pour accéder à `auth.admin.getUserById()`
|
|
- Les requêtes vers `organizations` et `organization_details` utilisent `createSbServer()` (permissions RLS normales)
|
|
|
|
## ⚠️ Gestion des erreurs
|
|
|
|
**Important :** Les erreurs d'envoi d'email ne bloquent JAMAIS la création du ticket ou l'ajout du message.
|
|
|
|
```typescript
|
|
try {
|
|
await sendInternalTicketCreatedEmail({...});
|
|
} catch (emailError) {
|
|
// Log uniquement, ne pas throw
|
|
console.error('Failed to send internal notification:', emailError);
|
|
}
|
|
```
|
|
|
|
Ceci garantit que même si AWS SES est en panne ou si les données sont incomplètes, le ticket est toujours créé/mis à jour.
|
|
|
|
## 📝 Logs
|
|
|
|
Les logs suivent le format standardisé :
|
|
```
|
|
[TICKET CREATE] Sending internal notification email...
|
|
[TICKET CREATE] Internal notification email sent successfully
|
|
[TICKET CREATE] Failed to send internal notification email: {error}
|
|
```
|
|
|
|
```
|
|
📧 [POST messages] Envoi de notification interne...
|
|
✅ [POST messages] Notification interne envoyée pour le ticket {id}
|
|
❌ [POST messages] Erreur lors de l'envoi de la notification interne: {error}
|
|
```
|
|
|
|
## 🧪 Tests suggérés
|
|
|
|
1. **Création de ticket par utilisateur :**
|
|
- Créer un ticket en tant qu'utilisateur normal
|
|
- Vérifier que `paie@odentas.fr` reçoit l'email
|
|
- Vérifier que le bouton CTA mène vers `/staff/tickets/{id}`
|
|
- Vérifier que les sauts de ligne sont préservés
|
|
|
|
2. **Réponse utilisateur :**
|
|
- Répondre à un ticket en tant qu'utilisateur normal
|
|
- Vérifier que `paie@odentas.fr` reçoit l'email
|
|
- Vérifier que le statut est correct
|
|
- Vérifier que les sauts de ligne sont préservés
|
|
|
|
3. **Pas de notification staff :**
|
|
- Créer un ticket en tant que staff → Pas d'email interne
|
|
- Répondre en tant que staff → Email à l'utilisateur uniquement
|
|
|
|
4. **Messages internes :**
|
|
- Créer un message `internal: true` → Aucun email envoyé
|
|
|
|
## 📋 Checklist de validation
|
|
|
|
- ✅ Template `support-ticket-created` ajouté à `emailTemplateService.ts`
|
|
- ✅ Template `support-ticket-reply` ajouté à `emailTemplateService.ts`
|
|
- ✅ Fonction `sendInternalTicketCreatedEmail()` créée
|
|
- ✅ Fonction `sendInternalTicketReplyEmail()` créée
|
|
- ✅ API `POST /api/tickets` modifiée pour envoyer notification
|
|
- ✅ API `POST /api/tickets/[id]/messages` modifiée pour envoyer notification
|
|
- ✅ Sauts de ligne convertis en `<br>` dans les deux fonctions
|
|
- ✅ Gestion d'erreur non-bloquante implémentée
|
|
- ✅ Utilisation de `createSbServiceRole()` pour getUserById
|
|
- ✅ Code employeur récupéré depuis `organization_details`
|
|
- ✅ Tous les fichiers compilent sans erreur TypeScript
|
|
|
|
## 🔄 Différence avec les emails clients
|
|
|
|
| Fonctionnalité | Email Client | Email Interne |
|
|
|----------------|--------------|---------------|
|
|
| Destinataire | Utilisateur créateur du ticket | `paie@odentas.fr` |
|
|
| Déclencheur | Staff répond (non-internal) | Utilisateur crée/répond |
|
|
| Couleur bouton | Jaune (#EFC543) | Bleu (#3B82F6) |
|
|
| Affichage staff | Nom formaté avec badge | Nom utilisateur brut |
|
|
| URL CTA | `/support/{id}` | `/staff/tickets/{id}` |
|
|
|
|
## 🚀 Déploiement
|
|
|
|
Aucune configuration supplémentaire nécessaire :
|
|
- Les templates sont automatiquement disponibles
|
|
- L'email `paie@odentas.fr` est codé en dur (pas de variable d'environnement)
|
|
- AWS SES est déjà configuré via `sendUniversalEmailV2`
|
|
|
|
---
|
|
|
|
**Date de création :** 14 octobre 2025
|
|
**Auteur :** Système de support Odentas
|
|
**Version :** 1.0
|