/**
* Add French translations for client-facing email templates
*
* Translates authentication, ticketing, and billing email templates to French
* for client portal users.
*/
exports.up = async function(knex) {
console.log('Adding French email templates...');
// Get notification subtypes
const subtypes = await knex('notification_subtypes')
.select('id', 'name')
.whereIn('name', [
'email-verification',
'password-reset',
'portal-invitation',
'tenant-recovery',
'no-account-found',
'Ticket Assigned',
'Ticket Created',
'Ticket Updated',
'Ticket Closed',
'Ticket Comment Added',
'Invoice Generated',
'Payment Received',
'Payment Overdue'
]);
const getSubtypeId = (name) => {
const subtype = subtypes.find(s => s.name === name);
if (!subtype) {
throw new Error(`Notification subtype '${name}' not found`);
}
return subtype.id;
};
// Insert French templates
await knex('system_email_templates').insert([
// Authentication templates
// NOTE: email-verification template is managed in migration 20251029100000
{
name: 'password-reset',
language_code: 'fr',
subject: 'Demande de Réinitialisation du Mot de Passe',
notification_subtype_id: getSubtypeId('password-reset'),
html_content: `
Demande de Réinitialisation du Mot de Passe
Bonjour {{userName}},
Nous avons reçu une demande de réinitialisation du mot de passe pour votre compte associé à {{email}}.
🔐 Vérification de Sécurité du Compte
Demandé : À l'instant
E-mail du compte : {{email}}
Valable pendant : {{expirationTime}}
Pour créer un nouveau mot de passe pour votre compte, cliquez sur le bouton ci-dessous :
Ou copiez et collez ce lien dans votre navigateur :
{{resetLink}}
⚠️ Informations de Sécurité Importantes
- Ce lien de réinitialisation expirera dans {{expirationTime}}
- Pour des raisons de sécurité, ce lien ne peut être utilisé qu'une seule fois
- Si vous n'avez pas demandé cette réinitialisation, ignorez cet e-mail
- Votre mot de passe ne changera pas tant que vous n'en créerez pas un nouveau
Et Ensuite ?
- Cliquez sur le bouton de réinitialisation ci-dessus ou utilisez le lien fourni
- Créez un mot de passe fort et unique pour votre compte
- Vous serez automatiquement connecté après la réinitialisation
- Toutes les sessions existantes seront fermées pour des raisons de sécurité
- Envisagez d'activer l'authentification à deux facteurs pour une protection accrue
Besoin d'Aide ?
Si vous rencontrez des difficultés pour réinitialiser votre mot de passe, notre équipe d'assistance est là pour vous aider.
Contacter l'Assistance : {{supportEmail}}
`,
text_content: `Demande de Réinitialisation du Mot de Passe
Bonjour {{userName}},
Nous avons reçu une demande de réinitialisation du mot de passe pour votre compte associé à {{email}}.
VÉRIFICATION DE SÉCURITÉ DU COMPTE
- Demandé : À l'instant
- E-mail du compte : {{email}}
- Valable pendant : {{expirationTime}}
Pour créer un nouveau mot de passe, visitez le lien suivant :
{{resetLink}}
INFORMATIONS DE SÉCURITÉ IMPORTANTES :
- Ce lien expirera dans {{expirationTime}}
- Ne peut être utilisé qu'une seule fois
- Si vous n'avez pas demandé cela, ignorez cet e-mail
- Votre mot de passe ne changera pas tant que vous n'en créerez pas un nouveau
ET ENSUITE :
1. Utilisez le lien fourni ci-dessus
2. Créez un mot de passe fort et unique
3. Vous serez automatiquement connecté
4. Toutes les sessions existantes seront fermées
5. Envisagez d'activer l'authentification à deux facteurs
Besoin d'aide ?
Contacter l'Assistance : {{supportEmail}}
---
Ceci est un e-mail de sécurité automatisé envoyé à {{email}}.
© {{currentYear}} {{clientName}}. Tous droits réservés.`
},
// NOTE: portal-invitation template is managed in migration 20251029100000
{
name: 'tenant-recovery',
language_code: 'fr',
subject: '{{platformName}} - Vos liens de connexion',
notification_subtype_id: getSubtypeId('tenant-recovery'),
html_content: `
{{platformName}}
Bonjour,
Vous avez demandé l'accès à votre portail{{#if isMultiple}}s{{/if}} client{{#if isMultiple}}s{{/if}}.
{{#if isMultiple}}Nous avons trouvé {{tenantCount}} organisations associées à votre adresse e-mail.{{else}}Voici votre lien de connexion :{{/if}}
Note de sécurité : Si vous n'avez pas demandé ces liens de connexion, vous pouvez ignorer cet e-mail en toute sécurité. Votre compte reste sécurisé.
Si vous avez des questions ou besoin d'assistance, veuillez contacter l'équipe d'assistance de votre organisation.
© {{currentYear}} {{platformName}}. Tous droits réservés.
Ceci est un message automatisé. Veuillez ne pas répondre à cet e-mail.
`,
text_content: `{{platformName}} - Vos liens de connexion
Bonjour,
Vous avez demandé l'accès à votre portail{{#if isMultiple}}s{{/if}} client{{#if isMultiple}}s{{/if}}.
{{#if isMultiple}}Nous avons trouvé {{tenantCount}} organisations associées à votre adresse e-mail.{{else}}Voici votre lien de connexion :{{/if}}
Vos liens de connexion :
{{tenantLinksText}}
Note de sécurité : Si vous n'avez pas demandé ces liens de connexion, vous pouvez ignorer cet e-mail en toute sécurité.
Si vous avez des questions ou besoin d'assistance, veuillez contacter l'équipe d'assistance de votre organisation.
---
© {{currentYear}} {{platformName}}. Tous droits réservés.
Ceci est un message automatisé. Veuillez ne pas répondre à cet e-mail.`
},
{
name: 'no-account-found',
language_code: 'fr',
subject: '{{platformName}} - Demande d\'accès',
notification_subtype_id: getSubtypeId('no-account-found'),
html_content: `
{{platformName}}
Bonjour,
Nous avons reçu une demande d'accès au portail client utilisant cette adresse e-mail.
Si vous avez un compte chez nous, vous devriez avoir reçu un e-mail séparé avec vos liens de connexion.
Si vous n'avez pas reçu d'e-mail de connexion, cela peut signifier :
- Cette adresse e-mail n'est associée à aucun compte de portail client
- Votre compte peut être inactif
- L'e-mail peut avoir été filtré vers votre dossier spam
Besoin d'aide ?
Si vous pensez que vous devriez avoir accès à un portail client, veuillez contacter l'équipe d'assistance de votre fournisseur de services pour obtenir de l'aide.
Note de sécurité : Si vous n'avez pas demandé d'accès, vous pouvez ignorer cet e-mail en toute sécurité.
© {{currentYear}} {{platformName}}. Tous droits réservés.
Ceci est un message automatisé. Veuillez ne pas répondre à cet e-mail.
`,
text_content: `{{platformName}} - Demande d'accès
Bonjour,
Nous avons reçu une demande d'accès au portail client utilisant cette adresse e-mail.
Si vous avez un compte chez nous, vous devriez avoir reçu un e-mail séparé avec vos liens de connexion.
Si vous n'avez pas reçu d'e-mail de connexion, cela peut signifier :
- Cette adresse e-mail n'est associée à aucun compte de portail client
- Votre compte peut être inactif
- L'e-mail peut avoir été filtré vers votre dossier spam
Besoin d'aide ?
Si vous pensez que vous devriez avoir accès à un portail client, veuillez contacter l'équipe d'assistance de votre fournisseur de services pour obtenir de l'aide.
Note de sécurité : Si vous n'avez pas demandé d'accès, vous pouvez ignorer cet e-mail en toute sécurité.
---
© {{currentYear}} {{platformName}}. Tous droits réservés.
Ceci est un message automatisé. Veuillez ne pas répondre à cet e-mail.`
},
// Ticketing templates
{
name: 'ticket-assigned',
language_code: 'fr',
subject: 'Ticket Assigné • {{ticket.title}} ({{ticket.priority}})',
notification_subtype_id: getSubtypeId('Ticket Assigned'),
html_content: `
|
Ticket Assigné
{{ticket.title}}
{{ticket.metaLine}}
|
|
Ce ticket vous a été assigné pour {{ticket.clientName}}. Consultez les détails ci-dessous et prenez les mesures appropriées.
| Priorité |
{{ticket.priority}}
|
| Statut |
{{ticket.status}} |
| Assigné par |
{{ticket.assignedBy}} |
| Assigné à |
{{ticket.assignedToName}}
{{ticket.assignedToEmail}}
|
| Demandeur |
{{ticket.requesterName}}
{{ticket.requesterContact}}
|
| Tableau |
{{ticket.board}} |
| Catégorie |
{{ticket.categoryDetails}} |
| Emplacement |
{{ticket.locationSummary}} |
Description
{{ticket.description}}
Voir le Ticket
|
| Powered by Alga PSA • Gardons les équipes alignées |
|
`,
text_content: `
Ticket Assigné à Vous
{{ticket.metaLine}}
Assigné par: {{ticket.assignedBy}}
Priorité: {{ticket.priority}}
Statut: {{ticket.status}}
Assigné à: {{ticket.assignedDetails}}
Demandeur: {{ticket.requesterDetails}}
Tableau: {{ticket.board}}
Catégorie: {{ticket.categoryDetails}}
Emplacement: {{ticket.locationSummary}}
Description:
{{ticket.description}}
Voir le ticket: {{ticket.url}}
`
},
{
name: 'ticket-created',
language_code: 'fr',
subject: 'Nouveau Ticket • {{ticket.title}} ({{ticket.priority}})',
notification_subtype_id: getSubtypeId('Ticket Created'),
html_content: `
|
Nouveau Ticket Créé
{{ticket.title}}
{{ticket.metaLine}}
|
|
Un nouveau ticket a été enregistré pour {{ticket.clientName}}. Consultez le résumé ci-dessous et suivez le lien pour agir.
| Priorité |
{{ticket.priority}}
|
| Statut |
{{ticket.status}} |
| Créé |
{{ticket.createdAt}} · {{ticket.createdBy}} |
| Assigné à |
{{ticket.assignedToName}}
{{ticket.assignedToEmail}}
|
| Demandeur |
{{ticket.requesterName}}
{{ticket.requesterContact}}
|
| Tableau |
{{ticket.board}} |
| Catégorie |
{{ticket.categoryDetails}} |
| Emplacement |
{{ticket.locationSummary}} |
Description
{{ticket.description}}
Voir le Ticket
|
| Powered by Alga PSA • Maintenir les équipes alignées |
|
`,
text_content: `
Nouveau Ticket Créé pour {{ticket.clientName}}
{{ticket.metaLine}}
Créé : {{ticket.createdAt}} · {{ticket.createdBy}}
Priorité : {{ticket.priority}}
Statut : {{ticket.status}}
Assigné à : {{ticket.assignedDetails}}
Demandeur : {{ticket.requesterDetails}}
Tableau : {{ticket.board}}
Catégorie : {{ticket.categoryDetails}}
Emplacement : {{ticket.locationSummary}}
Description :
{{ticket.description}}
Voir le ticket : {{ticket.url}}
`
},
{
name: 'ticket-updated',
language_code: 'fr',
subject: 'Ticket Mis à Jour • {{ticket.title}} ({{ticket.priority}})',
notification_subtype_id: getSubtypeId('Ticket Updated'),
html_content: `
|
Ticket Mis à Jour
{{ticket.title}}
{{ticket.metaLine}}
|
|
Un ticket a été mis à jour pour {{ticket.clientName}}. Consultez les modifications ci-dessous.
| Priorité |
{{ticket.priority}}
|
| Statut |
{{ticket.status}} |
| Mis à jour par |
{{ticket.updatedBy}} |
| Assigné à |
{{ticket.assignedToName}}
{{ticket.assignedToEmail}}
|
| Demandeur |
{{ticket.requesterName}}
{{ticket.requesterContact}}
|
| Tableau |
{{ticket.board}} |
| Catégorie |
{{ticket.categoryDetails}} |
| Emplacement |
{{ticket.locationSummary}} |
Modifications
{{ticket.changes}}
Voir le Ticket
|
| Powered by Alga PSA • Gardons les équipes alignées |
|
`,
text_content: `
Ticket Mis à Jour
{{ticket.metaLine}}
Mis à jour par: {{ticket.updatedBy}}
Priorité: {{ticket.priority}}
Statut: {{ticket.status}}
Assigné à: {{ticket.assignedDetails}}
Demandeur: {{ticket.requesterDetails}}
Tableau: {{ticket.board}}
Catégorie: {{ticket.categoryDetails}}
Emplacement: {{ticket.locationSummary}}
Modifications:
{{ticket.changes}}
Voir le ticket: {{ticket.url}}
`
},
{
name: 'ticket-closed',
language_code: 'fr',
subject: 'Ticket Fermé • {{ticket.title}}',
notification_subtype_id: getSubtypeId('Ticket Closed'),
html_content: `
|
Ticket Fermé
{{ticket.title}}
{{ticket.metaLine}}
|
|
Un ticket a été résolu et fermé pour {{ticket.clientName}}. Consultez les détails de la résolution ci-dessous.
| Statut |
Fermé
|
| Fermé par |
{{ticket.closedBy}} |
| Assigné à |
{{ticket.assignedToName}}
{{ticket.assignedToEmail}}
|
| Demandeur |
{{ticket.requesterName}}
{{ticket.requesterContact}}
|
| Tableau |
{{ticket.board}} |
| Catégorie |
{{ticket.categoryDetails}} |
| Emplacement |
{{ticket.locationSummary}} |
Résolution
{{ticket.resolution}}
Voir le Ticket
|
| Powered by Alga PSA • Gardons les équipes alignées |
|
`,
text_content: `
Ticket Fermé
{{ticket.metaLine}}
Fermé par: {{ticket.closedBy}}
Statut: Fermé
Assigné à: {{ticket.assignedDetails}}
Demandeur: {{ticket.requesterDetails}}
Tableau: {{ticket.board}}
Catégorie: {{ticket.categoryDetails}}
Emplacement: {{ticket.locationSummary}}
Résolution:
{{ticket.resolution}}
Voir le ticket: {{ticket.url}}
`
},
{
name: 'ticket-comment-added',
language_code: 'fr',
subject: 'Nouveau Commentaire • {{ticket.title}}',
notification_subtype_id: getSubtypeId('Ticket Comment Added'),
html_content: `
|
Nouveau Commentaire Ajouté
{{ticket.title}}
{{ticket.metaLine}}
|
|
Un nouveau commentaire a été ajouté à un ticket pour {{ticket.clientName}}.
| Priorité |
{{ticket.priority}}
|
| Statut |
{{ticket.status}} |
| Commentaire de |
{{comment.author}} |
| Assigné à |
{{ticket.assignedToName}}
{{ticket.assignedToEmail}}
|
| Demandeur |
{{ticket.requesterName}}
{{ticket.requesterContact}}
|
| Tableau |
{{ticket.board}} |
| Catégorie |
{{ticket.categoryDetails}} |
| Emplacement |
{{ticket.locationSummary}} |
💬 Commentaire
{{comment.content}}
Voir le Ticket
|
| Powered by Alga PSA • Gardons les équipes alignées |
|
`,
text_content: `
Nouveau Commentaire Ajouté
{{ticket.metaLine}}
Commentaire de: {{comment.author}}
Priorité: {{ticket.priority}}
Statut: {{ticket.status}}
Assigné à: {{ticket.assignedDetails}}
Demandeur: {{ticket.requesterDetails}}
Tableau: {{ticket.board}}
Catégorie: {{ticket.categoryDetails}}
Emplacement: {{ticket.locationSummary}}
Commentaire:
{{comment.content}}
Voir le ticket: {{ticket.url}}
`
},
// Billing templates
{
name: 'invoice-generated',
language_code: 'fr',
subject: 'Nouvelle facture #{{invoice.number}}',
notification_subtype_id: getSubtypeId('Invoice Generated'),
html_content: `
Facture {{invoice.number}}
Une nouvelle facture a été générée pour votre examen :
Numéro de facture : {{invoice.number}}
Montant : {{invoice.amount}}
Date d'échéance : {{invoice.dueDate}}
Client : {{invoice.clientName}}
Voir la facture
`,
text_content: `
Facture {{invoice.number}}
Une nouvelle facture a été générée pour votre examen :
Numéro de facture : {{invoice.number}}
Montant : {{invoice.amount}}
Date d'échéance : {{invoice.dueDate}}
Client : {{invoice.clientName}}
Voir la facture : {{invoice.url}}
`
},
{
name: 'payment-received',
language_code: 'fr',
subject: 'Paiement reçu : Facture #{{invoice.number}}',
notification_subtype_id: getSubtypeId('Payment Received'),
html_content: `
Paiement reçu
Le paiement a été reçu pour la facture #{{invoice.number}} :
Numéro de facture : {{invoice.number}}
Montant payé : {{invoice.amountPaid}}
Date de paiement : {{invoice.paymentDate}}
Méthode de paiement : {{invoice.paymentMethod}}
Voir la facture
`,
text_content: `
Paiement reçu
Le paiement a été reçu pour la facture #{{invoice.number}} :
Numéro de facture : {{invoice.number}}
Montant payé : {{invoice.amountPaid}}
Date de paiement : {{invoice.paymentDate}}
Méthode de paiement : {{invoice.paymentMethod}}
Voir la facture : {{invoice.url}}
`
},
{
name: 'payment-overdue',
language_code: 'fr',
subject: 'Paiement en retard : Facture #{{invoice.number}}',
notification_subtype_id: getSubtypeId('Payment Overdue'),
html_content: `
Paiement en retard
Le paiement de la facture #{{invoice.number}} est en retard :
Numéro de facture : {{invoice.number}}
Montant dû : {{invoice.amountDue}}
Date d'échéance : {{invoice.dueDate}}
Jours de retard : {{invoice.daysOverdue}}
Voir la facture
`,
text_content: `
Paiement en retard
Le paiement de la facture #{{invoice.number}} est en retard :
Numéro de facture : {{invoice.number}}
Montant dû : {{invoice.amountDue}}
Date d'échéance : {{invoice.dueDate}}
Jours de retard : {{invoice.daysOverdue}}
Voir la facture : {{invoice.url}}
`
}
]).onConflict(['name', 'language_code']).merge({
subject: knex.raw('excluded.subject'),
html_content: knex.raw('excluded.html_content'),
text_content: knex.raw('excluded.text_content'),
notification_subtype_id: knex.raw('excluded.notification_subtype_id')
});
console.log('✓ French email templates added (auth + notifications)');
};
exports.down = async function(knex) {
// Remove French email templates
// NOTE: email-verification and portal-invitation are NOT removed as they're managed by migration 20251029100000
await knex('system_email_templates')
.where({ language_code: 'fr' })
.whereIn('name', [
'password-reset',
'tenant-recovery',
'no-account-found',
'ticket-assigned',
'ticket-created',
'ticket-updated',
'ticket-closed',
'ticket-comment-added',
'invoice-generated',
'payment-received',
'payment-overdue'
])
.del();
console.log('French email templates removed');
};