Checkpoint: Ajout d'un template d'email personnalisable pour l'envoi des attestations de formation. Le template utilise des variables dynamiques ({{prenomApprenant}}, {{nomApprenant}}, {{nomFormation}}) et peut être modifié depuis l'interface de gestion des templates. Déployé sur le VPS avec migration SQL.

This commit is contained in:
Manus
2026-01-29 11:58:20 -05:00
parent a221deb4f1
commit 493366fa10
12 changed files with 6931 additions and 20 deletions

View File

@@ -0,0 +1,37 @@
{
"query": "SELECT type FROM emailTemplates;",
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute SELECT type FROM emailTemplates;",
"rows": [
{
"type": "inscription"
},
{
"type": "teaser"
},
{
"type": "rappel"
},
{
"type": "rappelJ1"
},
{
"type": "rappel3"
},
{
"type": "rappel4"
},
{
"type": "rappel5"
},
{
"type": "rappel6"
},
{
"type": "reset_password"
}
],
"messages": [],
"stdout": "type\ninscription\nteaser\nrappel\nrappelJ1\nrappel3\nrappel4\nrappel5\nrappel6\nreset_password\n",
"stderr": "",
"execution_time_ms": 51
}

View File

@@ -0,0 +1,9 @@
{
"query": "ALTER TABLE `emailTemplates` MODIFY COLUMN `type` enum('inscription','teaser','rappel','rappelJ1','rappel1','rappel2','rappel3','rappel4','rappel5','rappel6','reset_password','attestation') NOT NULL;",
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute ALTER TABLE `emailTemplates` MODIFY COLUMN `type` enum('inscription','teaser','rappel','rappelJ1','rappel1','rappel2','rappel3','rappel4','rappel5','rappel6','reset_password','attestation') NOT NULL;",
"rows": [],
"messages": [],
"stdout": "",
"stderr": "",
"execution_time_ms": 536
}

View File

@@ -0,0 +1,9 @@
{
"query": "INSERT INTO emailTemplates (type, titre, bodyContent, couleurPrincipale, couleurSecondaire, piedDePage) \nVALUES (\n 'attestation',\n 'Votre attestation de formation',\n '<h2>Votre attestation de formation</h2>\\n\\n<p>Bonjour {{prenomApprenant}} {{nomApprenant}},</p>\\n\\n<p>Nous avons le plaisir de vous transmettre votre attestation de formation pour :</p>\\n\\n<div class=\"info-box\" style=\"background-color: #dbeafe; border-left-color: #3b82f6;\">\\n <h3>{{nomFormation}}</h3>\\n</div>\\n\\n<p>Vous trouverez votre attestation en pièce jointe de cet email.</p>\\n\\n<p>Cette attestation certifie votre participation à la formation et peut être utilisée pour votre dossier professionnel.</p>\\n\\n<p>Nous vous remercions pour votre participation et vous souhaitons une excellente continuation.</p>\\n\\n<p>Cordialement,<br/>L''équipe Formation</p>',\n '#283581',\n '#0578BE',\n NULL\n);",
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute INSERT INTO emailTemplates (type, titre, bodyContent, couleurPrincipale, couleurSecondaire, piedDePage) \nVALUES (\n 'attestation',\n 'Votre attestation de formation',\n '<h2>Votre attestation de formation</h2>\\n\\n<p>Bonjour {{prenomApprenant}} {{nomApprenant}},</p>\\n\\n<p>Nous avons le plaisir de vous transmettre votre attestation de formation pour :</p>\\n\\n<div class=\"info-box\" style=\"background-color: #dbeafe; border-left-color: #3b82f6;\">\\n <h3>{{nomFormation}}</h3>\\n</div>\\n\\n<p>Vous trouverez votre attestation en pièce jointe de cet email.</p>\\n\\n<p>Cette attestation certifie votre participation à la formation et peut être utilisée pour votre dossier professionnel.</p>\\n\\n<p>Nous vous remercions pour votre participation et vous souhaitons une excellente continuation.</p>\\n\\n<p>Cordialement,<br/>L''équipe Formation</p>',\n '#283581',\n '#0578BE',\n NULL\n);",
"rows": [],
"messages": [],
"stdout": "",
"stderr": "",
"execution_time_ms": 66
}

View File

@@ -0,0 +1,8 @@
{
"query": "ALTER TABLE `emailTemplates` MODIFY COLUMN `type` enum('inscription','teaser','rappel1','rappel2','rappel3','rappel4','rappel5','rappel6','reset_password','attestation') NOT NULL;",
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute ALTER TABLE `emailTemplates` MODIFY COLUMN `type` enum('inscription','teaser','rappel1','rappel2','rappel3','rappel4','rappel5','rappel6','reset_password','attestation') NOT NULL;",
"returncode": 1,
"logs": [
"ERROR 1265 (01000) at line 1: Data truncated for column 'type', value is 'rappel'"
]
}

View File

@@ -0,0 +1,8 @@
{
"query": "ALTER TABLE `emailTemplates` MODIFY COLUMN `type` enum('inscription','teaser','rappel','rappel1','rappel2','rappel3','rappel4','rappel5','rappel6','reset_password','attestation') NOT NULL;",
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute ALTER TABLE `emailTemplates` MODIFY COLUMN `type` enum('inscription','teaser','rappel','rappel1','rappel2','rappel3','rappel4','rappel5','rappel6','reset_password','attestation') NOT NULL;",
"returncode": 1,
"logs": [
"ERROR 1265 (01000) at line 1: Data truncated for column 'type', value is 'rappelJ1'"
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -71,6 +71,27 @@
"when": 1769704162958, "when": 1769704162958,
"tag": "0009_nervous_nomad", "tag": "0009_nervous_nomad",
"breakpoints": true "breakpoints": true
},
{
"idx": 10,
"version": "5",
"when": 1769705449970,
"tag": "0010_fixed_slipstream",
"breakpoints": true
},
{
"idx": 11,
"version": "5",
"when": 1769705509311,
"tag": "0011_lucky_the_liberteens",
"breakpoints": true
},
{
"idx": 12,
"version": "5",
"when": 1769705546694,
"tag": "0012_puzzling_baron_zemo",
"breakpoints": true
} }
] ]
} }

View File

@@ -191,7 +191,7 @@ export type InsertEmailConfig = typeof emailConfig.$inferInsert;
export const emailTemplates = mysqlTable("emailTemplates", { export const emailTemplates = mysqlTable("emailTemplates", {
id: int("id").autoincrement().primaryKey(), id: int("id").autoincrement().primaryKey(),
/** Type de template (inscription, teaser, rappel1, rappel2, rappel3, rappel4, rappel5, rappel6, reset_password) */ /** Type de template (inscription, teaser, rappel1, rappel2, rappel3, rappel4, rappel5, rappel6, reset_password) */
type: mysqlEnum("type", ["inscription", "teaser", "rappel1", "rappel2", "rappel3", "rappel4", "rappel5", "rappel6", "reset_password"]).notNull().unique(), type: mysqlEnum("type", ["inscription", "teaser", "rappel", "rappelJ1", "rappel1", "rappel2", "rappel3", "rappel4", "rappel5", "rappel6", "reset_password", "attestation"]).notNull().unique(),
titre: varchar("titre", { length: 255 }).notNull(), titre: varchar("titre", { length: 255 }).notNull(),
/** Corps du message avec variables dynamiques */ /** Corps du message avec variables dynamiques */
bodyContent: text("bodyContent").notNull(), bodyContent: text("bodyContent").notNull(),

View File

@@ -1158,25 +1158,28 @@ export async function sendAttestationEmail(params: {
formationNom: string; formationNom: string;
pdfUrl: string; pdfUrl: string;
}): Promise<boolean> { }): Promise<boolean> {
const content = ` // Récupérer le template attestation depuis la base de données
<h2>Votre attestation de formation</h2> const { getDb } = await import('./db');
const { emailTemplates } = await import('../drizzle/schema');
<p>Bonjour ${params.apprenantPrenom} ${params.apprenantNom},</p> const { eq } = await import('drizzle-orm');
<p>Nous avons le plaisir de vous transmettre votre attestation de formation pour :</p> const db = await getDb();
if (!db) {
<div class="info-box" style="background-color: #dbeafe; border-left-color: #3b82f6;"> throw new Error('Database not available');
<h3>${params.formationNom}</h3> }
</div>
const templates = await db.select().from(emailTemplates).where(eq(emailTemplates.type, 'attestation')).limit(1);
<p>Vous trouverez votre attestation en pièce jointe de cet email.</p> if (templates.length === 0) {
throw new Error('Template attestation not found');
<p>Cette attestation certifie votre participation à la formation et peut être utilisée pour votre dossier professionnel.</p> }
<p>Nous vous remercions pour votre participation et vous souhaitons une excellente continuation.</p> const template = templates[0];
<p>Cordialement,<br/>L'équipe Formation</p> // Remplacer les variables dans le template
`; let content = template.bodyContent;
content = content.replace(/{{prenomApprenant}}/g, params.apprenantPrenom);
content = content.replace(/{{nomApprenant}}/g, params.apprenantNom);
content = content.replace(/{{nomFormation}}/g, params.formationNom);
// Récupérer le PDF (depuis URL ou chemin local) // Récupérer le PDF (depuis URL ou chemin local)
let pdfBuffer: Buffer; let pdfBuffer: Buffer;

View File

@@ -1451,3 +1451,12 @@
- [x] Modifier DashboardLayout pour masquer Configuration aux super formateurs - [x] Modifier DashboardLayout pour masquer Configuration aux super formateurs
- [x] Déployer sur VPS - [x] Déployer sur VPS
- [x] Créer un checkpoint - [x] Créer un checkpoint
## Template email attestations
- [x] Analyser le système actuel d'envoi des attestations
- [x] Ajouter le template "attestation" dans la table emailTemplates
- [x] Créer le template par défaut avec variables dynamiques
- [x] Intégrer le template dans le code d'envoi des attestations
- [x] Déployer sur VPS (migration SQL + fichiers)
- [x] Créer un checkpoint