Checkpoint: Implémentation du QR code dédié pour l'émargement formateur avec signature tactile
- Création de la page /emargement-formateur/:token avec formulaire simplifié - Procédure backend presences.validerFormateur dédiée - Deux boutons QR code dans l'interface formateur (apprenants + formateur) - Génération automatique des URL distinctes pour chaque type d'utilisateur - Déploiement réussi sur le VPS de production
This commit is contained in:
72
server/presenceDb-formateur-fix.ts
Normal file
72
server/presenceDb-formateur-fix.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
// Correction pour validerPresenceFormateur - à intégrer dans presenceDb.ts ligne 189-256
|
||||
|
||||
export async function validerPresenceFormateur(params: {
|
||||
formateurId: number;
|
||||
sequenceId: number;
|
||||
dateFormationId?: number;
|
||||
periode: "matin" | "apres_midi";
|
||||
modeValidation: "qrcode" | "manuel";
|
||||
commentaire?: string;
|
||||
signatureUrl?: string;
|
||||
signatureS3Key?: string;
|
||||
}) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
// Si dateFormationId est fourni, vérifier la date
|
||||
if (params.dateFormationId) {
|
||||
const dateFormation = await db
|
||||
.select()
|
||||
.from(datesFormation)
|
||||
.where(eq(datesFormation.id, params.dateFormationId))
|
||||
.limit(1);
|
||||
|
||||
if (dateFormation.length === 0) {
|
||||
throw new Error("Date de formation introuvable");
|
||||
}
|
||||
|
||||
// Vérifier que la date actuelle est le jour de la formation ou après
|
||||
const now = new Date();
|
||||
const dateDebut = new Date(dateFormation[0].dateDebut);
|
||||
|
||||
// Réinitialiser les heures pour comparer uniquement les dates
|
||||
now.setHours(0, 0, 0, 0);
|
||||
dateDebut.setHours(0, 0, 0, 0);
|
||||
|
||||
if (now < dateDebut) {
|
||||
throw new Error("Vous ne pouvez pas émarger avant la date de formation");
|
||||
}
|
||||
|
||||
// Vérifier si la présence existe déjà pour cette période
|
||||
const presenceExistante = await db
|
||||
.select()
|
||||
.from(presences)
|
||||
.where(
|
||||
and(
|
||||
eq(presences.formateurId, params.formateurId),
|
||||
eq(presences.dateFormationId, params.dateFormationId),
|
||||
sql`${presences.periode} = ${params.periode}`,
|
||||
sql`${presences.typeUtilisateur} = 'formateur'`
|
||||
)
|
||||
);
|
||||
|
||||
if (presenceExistante.length > 0) {
|
||||
throw new Error(`Présence déjà validée pour ${params.periode === 'matin' ? 'le matin' : 'l\'après-midi'}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Créer la présence formateur
|
||||
await db.insert(presences).values({
|
||||
typeUtilisateur: "formateur",
|
||||
formateurId: params.formateurId,
|
||||
dateFormationId: params.dateFormationId || null,
|
||||
periode: params.periode,
|
||||
heurePresence: new Date(),
|
||||
modeValidation: params.modeValidation,
|
||||
commentaire: params.commentaire,
|
||||
signatureUrl: params.signatureUrl,
|
||||
signatureS3Key: params.signatureS3Key,
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
@@ -189,7 +189,7 @@ export async function supprimerPresence(presenceId: number) {
|
||||
export async function validerPresenceFormateur(params: {
|
||||
formateurId: number;
|
||||
sequenceId: number;
|
||||
dateFormationId: number;
|
||||
dateFormationId?: number;
|
||||
periode: "matin" | "apres_midi";
|
||||
modeValidation: "qrcode" | "manuel";
|
||||
commentaire?: string;
|
||||
|
||||
287
server/presenceDb.ts.backup
Normal file
287
server/presenceDb.ts.backup
Normal file
@@ -0,0 +1,287 @@
|
||||
import { eq, and, sql } from "drizzle-orm";
|
||||
import { presences, inscriptions, datesFormation, sequences, apprenants } from "../drizzle/schema";
|
||||
import { getDb } from "./db";
|
||||
|
||||
/**
|
||||
* Valider la présence d'un apprenant (par QR code ou manuellement)
|
||||
*/
|
||||
export async function validerPresence(params: {
|
||||
inscriptionId: number;
|
||||
dateFormationId: number;
|
||||
periode: "matin" | "apres_midi";
|
||||
modeValidation: "qrcode" | "manuel";
|
||||
validateurId?: number;
|
||||
commentaire?: string;
|
||||
signatureUrl?: string;
|
||||
signatureS3Key?: string;
|
||||
}) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
// Récupérer la date de formation
|
||||
const dateFormation = await db
|
||||
.select()
|
||||
.from(datesFormation)
|
||||
.where(eq(datesFormation.id, params.dateFormationId))
|
||||
.limit(1);
|
||||
|
||||
if (dateFormation.length === 0) {
|
||||
throw new Error("Date de formation introuvable");
|
||||
}
|
||||
|
||||
// Vérifier que la date actuelle est le jour de la formation ou après
|
||||
const now = new Date();
|
||||
const dateDebut = new Date(dateFormation[0].dateDebut);
|
||||
|
||||
// Réinitialiser les heures pour comparer uniquement les dates
|
||||
now.setHours(0, 0, 0, 0);
|
||||
dateDebut.setHours(0, 0, 0, 0);
|
||||
|
||||
if (now < dateDebut) {
|
||||
throw new Error("Vous ne pouvez pas émarger avant la date de formation");
|
||||
}
|
||||
|
||||
// Vérifier si la présence existe déjà pour cette période
|
||||
const presenceExistante = await db
|
||||
.select()
|
||||
.from(presences)
|
||||
.where(
|
||||
and(
|
||||
eq(presences.inscriptionId, params.inscriptionId),
|
||||
eq(presences.dateFormationId, params.dateFormationId),
|
||||
sql`${presences.periode} = ${params.periode}`
|
||||
)
|
||||
);
|
||||
|
||||
if (presenceExistante.length > 0) {
|
||||
throw new Error(`Présence déjà validée pour ${params.periode === 'matin' ? 'le matin' : 'l\'après-midi'}`);
|
||||
}
|
||||
|
||||
// Créer la présence
|
||||
await db.insert(presences).values({
|
||||
inscriptionId: params.inscriptionId,
|
||||
dateFormationId: params.dateFormationId,
|
||||
periode: params.periode,
|
||||
heurePresence: new Date(),
|
||||
modeValidation: params.modeValidation,
|
||||
validateurId: params.validateurId,
|
||||
commentaire: params.commentaire,
|
||||
signatureUrl: params.signatureUrl,
|
||||
signatureS3Key: params.signatureS3Key,
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer toutes les présences pour une séquence
|
||||
*/
|
||||
export async function getPresencesBySequence(sequenceId: number) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
const result = await db
|
||||
.select({
|
||||
presenceId: presences.id,
|
||||
inscriptionId: presences.inscriptionId,
|
||||
dateFormationId: presences.dateFormationId,
|
||||
periode: presences.periode,
|
||||
heurePresence: presences.heurePresence,
|
||||
modeValidation: presences.modeValidation,
|
||||
validateurId: presences.validateurId,
|
||||
commentaire: presences.commentaire,
|
||||
signatureUrl: sql<string | null>`${presences}.signatureUrl`,
|
||||
signatureS3Key: sql<string | null>`${presences}.signatureS3Key`,
|
||||
apprenantId: apprenants.id,
|
||||
apprenantNom: apprenants.nom,
|
||||
apprenantPrenom: apprenants.prenom,
|
||||
apprenantEmail: apprenants.email,
|
||||
dateDebut: datesFormation.dateDebut,
|
||||
dateFin: datesFormation.dateFin,
|
||||
})
|
||||
.from(presences)
|
||||
.innerJoin(inscriptions, eq(presences.inscriptionId, inscriptions.id))
|
||||
.innerJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
|
||||
.innerJoin(datesFormation, eq(presences.dateFormationId, datesFormation.id))
|
||||
.where(eq(datesFormation.sequenceId, sequenceId));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les présences pour une inscription spécifique
|
||||
*/
|
||||
export async function getPresencesByInscription(inscriptionId: number) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
const result = await db
|
||||
.select({
|
||||
presenceId: presences.id,
|
||||
dateFormationId: presences.dateFormationId,
|
||||
periode: presences.periode,
|
||||
heurePresence: presences.heurePresence,
|
||||
modeValidation: presences.modeValidation,
|
||||
validateurId: presences.validateurId,
|
||||
commentaire: presences.commentaire,
|
||||
signatureUrl: sql<string | null>`${presences}.signatureUrl`,
|
||||
signatureS3Key: sql<string | null>`${presences}.signatureS3Key`,
|
||||
dateDebut: datesFormation.dateDebut,
|
||||
dateFin: datesFormation.dateFin,
|
||||
})
|
||||
.from(presences)
|
||||
.innerJoin(datesFormation, eq(presences.dateFormationId, datesFormation.id))
|
||||
.where(eq(presences.inscriptionId, inscriptionId));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si toutes les présences sont validées pour une inscription
|
||||
*/
|
||||
export async function checkAllPresencesValidated(inscriptionId: number): Promise<boolean> {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
// Récupérer l'inscription avec la séquence
|
||||
const inscription = await db
|
||||
.select({
|
||||
sequenceId: inscriptions.sequenceId,
|
||||
})
|
||||
.from(inscriptions)
|
||||
.where(eq(inscriptions.id, inscriptionId))
|
||||
.limit(1);
|
||||
|
||||
if (inscription.length === 0) {
|
||||
throw new Error("Inscription not found");
|
||||
}
|
||||
|
||||
// Compter le nombre de dates de formation pour cette séquence
|
||||
const datesCount = await db
|
||||
.select({ count: datesFormation.id })
|
||||
.from(datesFormation)
|
||||
.where(eq(datesFormation.sequenceId, inscription[0].sequenceId));
|
||||
|
||||
// Compter le nombre de présences validées pour cette inscription
|
||||
const presencesCount = await db
|
||||
.select({ count: presences.id })
|
||||
.from(presences)
|
||||
.where(eq(presences.inscriptionId, inscriptionId));
|
||||
|
||||
return presencesCount.length === datesCount.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprimer une présence
|
||||
*/
|
||||
export async function supprimerPresence(presenceId: number) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
await db.delete(presences).where(eq(presences.id, presenceId));
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Valider la présence d'un formateur (par QR code ou manuellement)
|
||||
*/
|
||||
export async function validerPresenceFormateur(params: {
|
||||
formateurId: number;
|
||||
sequenceId: number;
|
||||
dateFormationId?: number;
|
||||
periode: "matin" | "apres_midi";
|
||||
modeValidation: "qrcode" | "manuel";
|
||||
commentaire?: string;
|
||||
signatureUrl?: string;
|
||||
signatureS3Key?: string;
|
||||
}) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
// Récupérer la date de formation
|
||||
const dateFormation = await db
|
||||
.select()
|
||||
.from(datesFormation)
|
||||
.where(eq(datesFormation.id, params.dateFormationId))
|
||||
.limit(1);
|
||||
|
||||
if (dateFormation.length === 0) {
|
||||
throw new Error("Date de formation introuvable");
|
||||
}
|
||||
|
||||
// Vérifier que la date actuelle est le jour de la formation ou après
|
||||
const now = new Date();
|
||||
const dateDebut = new Date(dateFormation[0].dateDebut);
|
||||
|
||||
// Réinitialiser les heures pour comparer uniquement les dates
|
||||
now.setHours(0, 0, 0, 0);
|
||||
dateDebut.setHours(0, 0, 0, 0);
|
||||
|
||||
if (now < dateDebut) {
|
||||
throw new Error("Vous ne pouvez pas émarger avant la date de formation");
|
||||
}
|
||||
|
||||
// Vérifier si la présence existe déjà pour cette période
|
||||
const presenceExistante = await db
|
||||
.select()
|
||||
.from(presences)
|
||||
.where(
|
||||
and(
|
||||
eq(presences.formateurId, params.formateurId),
|
||||
eq(presences.dateFormationId, params.dateFormationId),
|
||||
sql`${presences.periode} = ${params.periode}`,
|
||||
sql`${presences.typeUtilisateur} = 'formateur'`
|
||||
)
|
||||
);
|
||||
|
||||
if (presenceExistante.length > 0) {
|
||||
throw new Error(`Présence déjà validée pour ${params.periode === 'matin' ? 'le matin' : 'l\'après-midi'}`);
|
||||
}
|
||||
|
||||
// Créer la présence formateur
|
||||
await db.insert(presences).values({
|
||||
typeUtilisateur: "formateur",
|
||||
formateurId: params.formateurId,
|
||||
dateFormationId: params.dateFormationId,
|
||||
periode: params.periode,
|
||||
heurePresence: new Date(),
|
||||
modeValidation: params.modeValidation,
|
||||
commentaire: params.commentaire,
|
||||
signatureUrl: params.signatureUrl,
|
||||
signatureS3Key: params.signatureS3Key,
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les présences du formateur pour une séquence
|
||||
*/
|
||||
export async function getPresencesFormateurBySequence(sequenceId: number) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
const result = await db
|
||||
.select({
|
||||
presenceId: presences.id,
|
||||
formateurId: presences.formateurId,
|
||||
dateFormationId: presences.dateFormationId,
|
||||
periode: presences.periode,
|
||||
heurePresence: presences.heurePresence,
|
||||
modeValidation: presences.modeValidation,
|
||||
commentaire: presences.commentaire,
|
||||
signatureUrl: sql<string | null>`${presences}.signatureUrl`,
|
||||
signatureS3Key: sql<string | null>`${presences}.signatureS3Key`,
|
||||
})
|
||||
.from(presences)
|
||||
.innerJoin(datesFormation, eq(presences.dateFormationId, datesFormation.id))
|
||||
.where(
|
||||
and(
|
||||
eq(datesFormation.sequenceId, sequenceId),
|
||||
sql`${presences}.typeUtilisateur = 'formateur'`
|
||||
)
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -38,6 +38,35 @@ export async function generateQRCodeDataURL(token: string): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Générer un QR code formateur en base64 à partir d'un token
|
||||
* @param token Le token unique de la séquence
|
||||
* @returns Une image QR code en base64 (data URL) pour l'émargement formateur
|
||||
*/
|
||||
export async function generateQRCodeFormateurDataURL(token: string): Promise<string> {
|
||||
// URL complète pour scanner le QR code formateur
|
||||
const parametres = await getParametres();
|
||||
const url = `${parametres.urlPublique}/emargement-formateur/${token}`;
|
||||
|
||||
try {
|
||||
const qrCodeDataURL = await QRCode.toDataURL(url, {
|
||||
errorCorrectionLevel: "H",
|
||||
type: "image/png",
|
||||
width: 400,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: "#000000",
|
||||
light: "#FFFFFF",
|
||||
},
|
||||
});
|
||||
|
||||
return qrCodeDataURL;
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la génération du QR code formateur:", error);
|
||||
throw new Error("Impossible de générer le QR code formateur");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Générer un QR code en buffer PNG
|
||||
* @param token Le token unique de la séquence
|
||||
|
||||
@@ -2517,6 +2517,42 @@ export const appRouter = router({
|
||||
};
|
||||
}),
|
||||
|
||||
// Générer un QR code formateur pour une séquence
|
||||
generateQRCodeFormateur: protectedProcedure
|
||||
.input(z.object({
|
||||
sequenceId: z.number(),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { generateQRToken, generateQRCodeFormateurDataURL } = await import("./qrCodeGenerator");
|
||||
const { getDb } = await import("./db");
|
||||
const { sequences } = await import("../drizzle/schema");
|
||||
const { eq } = await import("drizzle-orm");
|
||||
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
// Vérifier que la séquence existe
|
||||
const sequence = await db.select().from(sequences).where(eq(sequences.id, input.sequenceId)).limit(1);
|
||||
if (sequence.length === 0) {
|
||||
throw new Error("Séquence introuvable");
|
||||
}
|
||||
|
||||
// Générer un nouveau token si nécessaire
|
||||
let token = sequence[0].qrCodeToken;
|
||||
if (!token) {
|
||||
token = generateQRToken();
|
||||
await db.update(sequences).set({ qrCodeToken: token }).where(eq(sequences.id, input.sequenceId));
|
||||
}
|
||||
|
||||
// Générer le QR code formateur
|
||||
const qrCodeDataURL = await generateQRCodeFormateurDataURL(token);
|
||||
|
||||
return {
|
||||
token,
|
||||
qrCodeDataURL,
|
||||
};
|
||||
}),
|
||||
|
||||
// Récupérer les informations de la séquence par token (pour formateurs)
|
||||
getSequenceByToken: publicProcedure
|
||||
.input(z.object({
|
||||
@@ -2547,15 +2583,86 @@ export const appRouter = router({
|
||||
};
|
||||
}),
|
||||
|
||||
// Valider la présence d'un formateur (QR code dédié)
|
||||
validerFormateur: publicProcedure
|
||||
.input(z.object({
|
||||
token: z.string(),
|
||||
email: z.string().email(),
|
||||
periode: z.enum(["matin", "apres_midi"]),
|
||||
signatureDataUrl: z.string(),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
const { getDb } = await import("./db");
|
||||
const { formateurs, sequences, datesFormation } = await import("../drizzle/schema");
|
||||
const { eq } = await import("drizzle-orm");
|
||||
const { validerPresenceFormateur } = await import("./presenceDb");
|
||||
const { storagePut } = await import("./storage");
|
||||
const crypto = await import("crypto");
|
||||
const path = await import("path");
|
||||
const fs = await import("fs/promises");
|
||||
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
// Vérifier que l'email correspond à un formateur
|
||||
const formateurData = await db.select().from(formateurs).where(eq(formateurs.email, input.email)).limit(1);
|
||||
if (formateurData.length === 0) {
|
||||
throw new Error("Aucun formateur trouvé avec cet email");
|
||||
}
|
||||
|
||||
// Vérifier le token et récupérer la séquence
|
||||
const sequence = await db.select().from(sequences).where(eq(sequences.qrCodeToken, input.token)).limit(1);
|
||||
if (sequence.length === 0) {
|
||||
throw new Error("QR code invalide");
|
||||
}
|
||||
|
||||
// Récupérer la première date de la séquence
|
||||
const dates = await db.select().from(datesFormation).where(eq(datesFormation.sequenceId, sequence[0].id)).orderBy(datesFormation.dateDebut).limit(1);
|
||||
if (dates.length === 0) {
|
||||
throw new Error("Aucune date de formation trouvée pour cette séquence");
|
||||
}
|
||||
|
||||
// Stocker la signature localement
|
||||
const formationId = sequence[0].formationId;
|
||||
const sequenceId = sequence[0].id;
|
||||
const uploadsDir = path.join(process.cwd(), "uploads", "signatures", `${formationId}-${sequenceId}`);
|
||||
await fs.mkdir(uploadsDir, { recursive: true });
|
||||
|
||||
const randomSuffix = crypto.randomBytes(8).toString("hex");
|
||||
const fileName = `signature-formateur-${formateurData[0].id}-${dates[0].id}-${randomSuffix}.png`;
|
||||
const filePath = path.join(uploadsDir, fileName);
|
||||
|
||||
const base64Data = input.signatureDataUrl.split(",")[1];
|
||||
const buffer = Buffer.from(base64Data, "base64");
|
||||
await fs.writeFile(filePath, buffer);
|
||||
|
||||
const signatureUrl = `/uploads/signatures/${formationId}-${sequenceId}/${fileName}`;
|
||||
const signatureS3Key = `signatures/${formationId}-${sequenceId}/${fileName}`;
|
||||
|
||||
// Valider la présence formateur
|
||||
await validerPresenceFormateur({
|
||||
formateurId: formateurData[0].id,
|
||||
sequenceId: sequence[0].id,
|
||||
dateFormationId: dates[0].id,
|
||||
periode: input.periode,
|
||||
modeValidation: "qrcode",
|
||||
signatureUrl,
|
||||
signatureS3Key,
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
// Valider une présence (scan QR code ou manuel)
|
||||
valider: publicProcedure
|
||||
.input(z.object({
|
||||
token: z.string().optional(),
|
||||
email: z.string().email(),
|
||||
inscriptionId: z.number().optional(),
|
||||
dateFormationId: z.number(),
|
||||
dateFormationId: z.number().optional(),
|
||||
dateId: z.number().optional(), // Alias pour dateFormationId
|
||||
periode: z.enum(["matin", "apres_midi"]),
|
||||
modeValidation: z.enum(["qrcode", "manuel"]),
|
||||
modeValidation: z.enum(["qrcode", "manuel"]).optional().default("qrcode"),
|
||||
validateurId: z.number().optional(),
|
||||
commentaire: z.string().optional(),
|
||||
signatureDataUrl: z.string().optional(),
|
||||
@@ -2575,14 +2682,24 @@ export const appRouter = router({
|
||||
|
||||
// Si mode QR code, vérifier le token
|
||||
let sequenceId: number | undefined;
|
||||
let autoDateFormationId: number | undefined;
|
||||
if (input.modeValidation === "qrcode" && input.token) {
|
||||
// Vérifier que le token correspond à une séquence
|
||||
const { datesFormation } = await import("../drizzle/schema");
|
||||
const sequence = await db.select().from(sequences).where(eq(sequences.qrCodeToken, input.token)).limit(1);
|
||||
if (sequence.length === 0) {
|
||||
throw new Error("QR code invalide");
|
||||
}
|
||||
sequenceId = sequence[0].id;
|
||||
|
||||
// Si formateur, récupérer automatiquement la première date de la séquence
|
||||
if (isFormateur && !input.dateFormationId && !input.dateId) {
|
||||
const dates = await db.select().from(datesFormation).where(eq(datesFormation.sequenceId, sequenceId)).orderBy(datesFormation.dateDebut).limit(1);
|
||||
if (dates.length > 0) {
|
||||
autoDateFormationId = dates[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
// Si apprenant, vérifier l'inscription
|
||||
if (!isFormateur && input.inscriptionId) {
|
||||
const inscription = await db.select().from(inscriptions).where(eq(inscriptions.id, input.inscriptionId)).limit(1);
|
||||
@@ -2631,7 +2748,8 @@ export const appRouter = router({
|
||||
const randomSuffix = crypto.randomBytes(8).toString("hex");
|
||||
const userType = isFormateur ? "formateur" : "apprenant";
|
||||
const userId = isFormateur ? formateurData[0].id : input.inscriptionId;
|
||||
const fileName = `signature-${userType}-${userId}-${input.dateFormationId}-${randomSuffix}.png`;
|
||||
const dateRef = input.dateFormationId || input.dateId || "no-date";
|
||||
const fileName = `signature-${userType}-${userId}-${dateRef}-${randomSuffix}.png`;
|
||||
const filePath = path.join(uploadsDir, fileName);
|
||||
|
||||
// Convertir le data URL en buffer et sauvegarder
|
||||
@@ -2651,9 +2769,9 @@ export const appRouter = router({
|
||||
result = await validerPresenceFormateur({
|
||||
formateurId: formateurData[0].id,
|
||||
sequenceId: finalSequenceId,
|
||||
dateFormationId: input.dateFormationId,
|
||||
dateFormationId: input.dateFormationId || input.dateId || undefined,
|
||||
periode: input.periode,
|
||||
modeValidation: input.modeValidation,
|
||||
modeValidation: input.modeValidation || "qrcode",
|
||||
commentaire: input.commentaire,
|
||||
signatureUrl,
|
||||
signatureS3Key,
|
||||
@@ -2661,11 +2779,13 @@ export const appRouter = router({
|
||||
} else {
|
||||
// Valider la présence de l'apprenant
|
||||
if (!input.inscriptionId) throw new Error("Inscription requise pour un apprenant");
|
||||
const dateFormId = input.dateFormationId || input.dateId;
|
||||
if (!dateFormId) throw new Error("Date de formation requise pour un apprenant");
|
||||
result = await validerPresence({
|
||||
inscriptionId: input.inscriptionId,
|
||||
dateFormationId: input.dateFormationId,
|
||||
dateFormationId: dateFormId,
|
||||
periode: input.periode,
|
||||
modeValidation: input.modeValidation,
|
||||
modeValidation: input.modeValidation || "qrcode",
|
||||
validateurId: input.validateurId,
|
||||
commentaire: input.commentaire,
|
||||
signatureUrl,
|
||||
|
||||
Reference in New Issue
Block a user