Checkpoint: Ajout d'une colonne 'Mode d'envoi' dans l'historique des attestations pour distinguer les envois manuels des envois automatiques. Modifications: ajout du champ modeEnvoi dans la table historiqueAttestations, mise à jour du scheduler d'attestations, et ajout de la colonne dans l'interface utilisateur. Déployé sur le VPS de production.
This commit is contained in:
9
.manus/db/db-query-1769766508887.json
Normal file
9
.manus/db/db-query-1769766508887.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"query": "ALTER TABLE `logsNotifications` MODIFY COLUMN `type` enum('remerciement','notification_formateur_inscription','notification_formateur_annulation','alerte_capacite','notification_liste_attente','attestation') NOT NULL;\nALTER TABLE `logsNotifications` ADD `modeEnvoi` enum('manuel','auto') DEFAULT 'manuel';",
|
||||
"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 `logsNotifications` MODIFY COLUMN `type` enum('remerciement','notification_formateur_inscription','notification_formateur_annulation','alerte_capacite','notification_liste_attente','attestation') NOT NULL;\nALTER TABLE `logsNotifications` ADD `modeEnvoi` enum('manuel','auto') DEFAULT 'manuel';",
|
||||
"rows": [],
|
||||
"messages": [],
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"execution_time_ms": 586
|
||||
}
|
||||
9
.manus/db/db-query-1769766562757.json
Normal file
9
.manus/db/db-query-1769766562757.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"query": "ALTER TABLE `historiqueAttestations` ADD `modeEnvoi` enum('manuel','auto') DEFAULT 'manuel';",
|
||||
"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 `historiqueAttestations` ADD `modeEnvoi` enum('manuel','auto') DEFAULT 'manuel';",
|
||||
"rows": [],
|
||||
"messages": [],
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"execution_time_ms": 425
|
||||
}
|
||||
@@ -59,6 +59,7 @@ export default function HistoriqueAttestations() {
|
||||
<TableHead>Apprenant</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Statut</TableHead>
|
||||
<TableHead>Mode d'envoi</TableHead>
|
||||
<TableHead>Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -91,6 +92,11 @@ export default function HistoriqueAttestations() {
|
||||
</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={item.modeEnvoi === "auto" ? "secondary" : "outline"}>
|
||||
{item.modeEnvoi === "auto" ? "Automatique" : "Manuel"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{item.urlAttestation && (
|
||||
<Button
|
||||
|
||||
2277
drizzle/meta/0013_snapshot.json
Normal file
2277
drizzle/meta/0013_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2285
drizzle/meta/0014_snapshot.json
Normal file
2285
drizzle/meta/0014_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -92,6 +92,20 @@
|
||||
"when": 1769705546694,
|
||||
"tag": "0012_puzzling_baron_zemo",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 13,
|
||||
"version": "5",
|
||||
"when": 1769766383057,
|
||||
"tag": "0013_glamorous_forge",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 14,
|
||||
"version": "5",
|
||||
"when": 1769770887912,
|
||||
"tag": "0014_overjoyed_overlord",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -504,7 +504,8 @@ export const logsNotifications = mysqlTable("logsNotifications", {
|
||||
"notification_formateur_inscription",
|
||||
"notification_formateur_annulation",
|
||||
"alerte_capacite",
|
||||
"notification_liste_attente"
|
||||
"notification_liste_attente",
|
||||
"attestation"
|
||||
]).notNull(),
|
||||
/** Email du destinataire */
|
||||
emailDestinataire: varchar("emailDestinataire", { length: 320 }).notNull(),
|
||||
@@ -522,6 +523,8 @@ export const logsNotifications = mysqlTable("logsNotifications", {
|
||||
messageErreur: text("messageErreur"),
|
||||
/** Métadonnées supplémentaires (JSON) */
|
||||
metadata: text("metadata"),
|
||||
/** Mode d'envoi (manuel ou automatique) */
|
||||
modeEnvoi: mysqlEnum("modeEnvoi", ["manuel", "auto"]).default("manuel"),
|
||||
dateEnvoi: timestamp("dateEnvoi").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
@@ -582,6 +585,8 @@ export const historiqueAttestations = mysqlTable("historiqueAttestations", {
|
||||
statut: mysqlEnum("statut", ["envoye", "erreur"]).notNull(),
|
||||
messageErreur: text("messageErreur"),
|
||||
urlAttestation: varchar("urlAttestation", { length: 500 }),
|
||||
/** Mode d'envoi (manuel ou automatique) */
|
||||
modeEnvoi: mysqlEnum("modeEnvoi", ["manuel", "auto"]).default("manuel"),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getDb } from "./db";
|
||||
import { sequences, inscriptions, apprenants, formations, datesFormation, attestations, logsNotifications } from "../drizzle/schema";
|
||||
import { sequences, inscriptions, apprenants, formations, datesFormation, attestations, historiqueAttestations } from "../drizzle/schema";
|
||||
import { eq, and, lte, sql, isNull } from "drizzle-orm";
|
||||
import { sendAttestationEmail } from "./emailService";
|
||||
import { logNotification } from "./notificationLogsDb";
|
||||
import { enregistrerEnvoiAttestation } from "./attestationsDb";
|
||||
|
||||
/**
|
||||
* Vérifie si une attestation a déjà été envoyée pour une inscription
|
||||
@@ -13,21 +13,20 @@ async function attestationDejaEnvoyee(apprenantId: number, sequenceId: number):
|
||||
|
||||
try {
|
||||
const [existing] = await db
|
||||
.select({ id: logsNotifications.id })
|
||||
.from(logsNotifications)
|
||||
.select({ id: historiqueAttestations.id })
|
||||
.from(historiqueAttestations)
|
||||
.where(
|
||||
and(
|
||||
eq(logsNotifications.type, "attestation"),
|
||||
eq(logsNotifications.apprenantId, apprenantId),
|
||||
eq(logsNotifications.sequenceId, sequenceId),
|
||||
eq(logsNotifications.statut, "success")
|
||||
eq(historiqueAttestations.apprenantId, apprenantId),
|
||||
eq(historiqueAttestations.sequenceId, sequenceId),
|
||||
eq(historiqueAttestations.statut, "envoye")
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
return !!existing;
|
||||
} catch (error) {
|
||||
console.error("[Attestations Auto] Erreur lors de la vérification du log:", error);
|
||||
console.error("[Attestations Auto] Erreur lors de la vérification de l'historique:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -141,29 +140,26 @@ async function processAttestationSequence(
|
||||
pdfUrl: attestation.documentUrl,
|
||||
});
|
||||
|
||||
// Logger la notification
|
||||
await logNotification({
|
||||
type: "attestation",
|
||||
// Enregistrer dans l'historique
|
||||
await enregistrerEnvoiAttestation({
|
||||
sequenceId: sequence.id,
|
||||
apprenantId: apprenant.id,
|
||||
emailDestinataire: apprenant.email,
|
||||
sujet: `Votre attestation de formation - ${formation.nom}`,
|
||||
statut: "success",
|
||||
metadata: { attestationId: attestation.id },
|
||||
statut: "envoye",
|
||||
urlAttestation: attestation.documentUrl,
|
||||
modeEnvoi: "auto",
|
||||
});
|
||||
|
||||
envoyesCount++;
|
||||
console.log(`[Attestations Auto] Email envoyé à ${apprenant.email}`);
|
||||
} catch (error: any) {
|
||||
// Logger l'échec
|
||||
await logNotification({
|
||||
type: "attestation",
|
||||
// Enregistrer l'échec dans l'historique
|
||||
await enregistrerEnvoiAttestation({
|
||||
sequenceId: sequence.id,
|
||||
apprenantId: apprenant.id,
|
||||
emailDestinataire: apprenant.email,
|
||||
sujet: `Votre attestation de formation - ${formation.nom}`,
|
||||
statut: "failed",
|
||||
statut: "erreur",
|
||||
messageErreur: error.message,
|
||||
urlAttestation: attestation.documentUrl,
|
||||
modeEnvoi: "auto",
|
||||
});
|
||||
|
||||
errorsCount++;
|
||||
|
||||
@@ -11,6 +11,7 @@ export async function enregistrerEnvoiAttestation(data: {
|
||||
statut: "envoye" | "erreur";
|
||||
messageErreur?: string;
|
||||
urlAttestation?: string;
|
||||
modeEnvoi?: "manuel" | "auto";
|
||||
}) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
@@ -21,6 +22,7 @@ export async function enregistrerEnvoiAttestation(data: {
|
||||
statut: data.statut,
|
||||
messageErreur: data.messageErreur || null,
|
||||
urlAttestation: data.urlAttestation || null,
|
||||
modeEnvoi: data.modeEnvoi || "manuel",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,6 +41,7 @@ export async function getHistoriqueAttestations() {
|
||||
statut: historiqueAttestations.statut,
|
||||
messageErreur: historiqueAttestations.messageErreur,
|
||||
urlAttestation: historiqueAttestations.urlAttestation,
|
||||
modeEnvoi: historiqueAttestations.modeEnvoi,
|
||||
sequence: {
|
||||
id: sequences.id,
|
||||
nom: sequences.nom,
|
||||
@@ -77,6 +80,7 @@ export async function getHistoriqueAttestationsBySequence(sequenceId: number) {
|
||||
statut: historiqueAttestations.statut,
|
||||
messageErreur: historiqueAttestations.messageErreur,
|
||||
urlAttestation: historiqueAttestations.urlAttestation,
|
||||
modeEnvoi: historiqueAttestations.modeEnvoi,
|
||||
apprenant: {
|
||||
id: apprenants.id,
|
||||
nom: apprenants.nom,
|
||||
|
||||
Reference in New Issue
Block a user