Checkpoint: Refonte complète du système de rappels pour permettre une gestion granulaire par séquence
Fonctionnalités ajoutées : - Table de liaison rappelSequences pour associer rappels et séquences - Composant SequenceMultiSelect avec recherche et multi-sélection - Gestion des associations dans les procédures create/update - Modification du scheduler pour filtrer les séquences selon les associations - Si aucune séquence n'est sélectionnée, le rappel s'applique à toutes les séquences (comportement par défaut) Le système permet maintenant de configurer des rappels spécifiques pour certaines dates/séquences particulières, offrant une flexibilité complète dans la gestion des rappels automatiques.
This commit is contained in:
52
server/db.ts
52
server/db.ts
@@ -29,7 +29,8 @@ import {
|
||||
Formateur,
|
||||
rappels,
|
||||
InsertRappel,
|
||||
Rappel
|
||||
Rappel,
|
||||
rappelSequences
|
||||
} from "../drizzle/schema";
|
||||
import { ENV } from './_core/env';
|
||||
|
||||
@@ -969,7 +970,20 @@ export async function getRappels() {
|
||||
const db = await getDb();
|
||||
if (!db) return [];
|
||||
|
||||
return await db.select().from(rappels).orderBy(rappels.joursAvant);
|
||||
const allRappels = await db.select().from(rappels).orderBy(rappels.joursAvant);
|
||||
|
||||
// Pour chaque rappel, récupérer les séquences associées
|
||||
const rappelsWithSequences = await Promise.all(
|
||||
allRappels.map(async (rappel) => {
|
||||
const sequenceIds = await getRappelSequences(rappel.id);
|
||||
return {
|
||||
...rappel,
|
||||
sequenceIds,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return rappelsWithSequences;
|
||||
}
|
||||
|
||||
export async function getRappelById(id: number): Promise<Rappel | undefined> {
|
||||
@@ -1022,9 +1036,43 @@ export async function deleteRappel(id: number) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
// Supprimer d'abord les associations
|
||||
await db.delete(rappelSequences).where(eq(rappelSequences.rappelId, id));
|
||||
// Puis le rappel
|
||||
await db.delete(rappels).where(eq(rappels.id, id));
|
||||
}
|
||||
|
||||
// ==================== RAPPEL SEQUENCES ====================
|
||||
|
||||
export async function createRappelSequences(rappelId: number, sequenceIds: number[]) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
if (sequenceIds.length === 0) return;
|
||||
|
||||
const values = sequenceIds.map(sequenceId => ({
|
||||
rappelId,
|
||||
sequenceId,
|
||||
}));
|
||||
|
||||
await db.insert(rappelSequences).values(values);
|
||||
}
|
||||
|
||||
export async function deleteRappelSequences(rappelId: number) {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
|
||||
await db.delete(rappelSequences).where(eq(rappelSequences.rappelId, rappelId));
|
||||
}
|
||||
|
||||
export async function getRappelSequences(rappelId: number): Promise<number[]> {
|
||||
const db = await getDb();
|
||||
if (!db) return [];
|
||||
|
||||
const results = await db.select().from(rappelSequences).where(eq(rappelSequences.rappelId, rappelId));
|
||||
return results.map(r => r.sequenceId);
|
||||
}
|
||||
|
||||
export async function updateRappelExecution(id: number) {
|
||||
const db = await getDb();
|
||||
if (!db) return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getDb } from "./db";
|
||||
import { rappels, sequences, inscriptions, apprenants, formations, datesFormation, formateurs } from "../drizzle/schema";
|
||||
import { eq, and, gte, lte, sql } from "drizzle-orm";
|
||||
import { eq, and, gte, lte, sql, inArray } from "drizzle-orm";
|
||||
import { sendRappelJ7Email, sendRappelJ1Email } from "./emailService";
|
||||
import { logRappelEnvoye, rappelDejaEnvoye } from "./rappelDb";
|
||||
import { notifyOwner } from "./_core/notification";
|
||||
@@ -55,16 +55,38 @@ async function processRappel(rappel: any) {
|
||||
|
||||
console.log(`[Rappels] Recherche des séquences débutant le ${dateCible.toLocaleDateString()}`);
|
||||
|
||||
// CORRECTION DU BUG: Récupérer d'abord toutes les séquences
|
||||
const toutesSequences = await db
|
||||
.select({
|
||||
sequence: sequences,
|
||||
formation: formations,
|
||||
formateur: formateurs,
|
||||
})
|
||||
.from(sequences)
|
||||
.leftJoin(formations, eq(sequences.formationId, formations.id))
|
||||
.leftJoin(formateurs, eq(sequences.formateurId, formateurs.id));
|
||||
// Vérifier si ce rappel est associé à des séquences spécifiques
|
||||
const sequencesAssociees = rappel.sequenceIds || [];
|
||||
const filtreSequences = sequencesAssociees.length > 0;
|
||||
|
||||
console.log(`[Rappels] ${filtreSequences ? `Filtré sur ${sequencesAssociees.length} séquence(s) spécifique(s)` : 'Toutes les séquences'}`);
|
||||
|
||||
// Récupérer toutes les séquences (ou uniquement celles associées)
|
||||
let toutesSequences;
|
||||
if (filtreSequences) {
|
||||
// Seulement les séquences associées à ce rappel
|
||||
toutesSequences = await db
|
||||
.select({
|
||||
sequence: sequences,
|
||||
formation: formations,
|
||||
formateur: formateurs,
|
||||
})
|
||||
.from(sequences)
|
||||
.leftJoin(formations, eq(sequences.formationId, formations.id))
|
||||
.leftJoin(formateurs, eq(sequences.formateurId, formateurs.id))
|
||||
.where(inArray(sequences.id, sequencesAssociees));
|
||||
} else {
|
||||
// Toutes les séquences
|
||||
toutesSequences = await db
|
||||
.select({
|
||||
sequence: sequences,
|
||||
formation: formations,
|
||||
formateur: formateurs,
|
||||
})
|
||||
.from(sequences)
|
||||
.leftJoin(formations, eq(sequences.formationId, formations.id))
|
||||
.leftJoin(formateurs, eq(sequences.formateurId, formateurs.id));
|
||||
}
|
||||
|
||||
// Filtrer les séquences dont la première date correspond à la date cible
|
||||
const sequencesConcernees = [];
|
||||
|
||||
@@ -1070,6 +1070,7 @@ export const appRouter = router({
|
||||
joursAvant: z.number(),
|
||||
heureEnvoi: z.string().default("09:00"),
|
||||
actif: z.boolean(),
|
||||
sequenceIds: z.array(z.number()).optional(),
|
||||
fichier: z.object({
|
||||
nomFichier: z.string(),
|
||||
urlFichier: z.string(),
|
||||
@@ -1079,7 +1080,18 @@ export const appRouter = router({
|
||||
}).nullable().optional(),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
return db.createRappel(input);
|
||||
const { sequenceIds, ...rappelData } = input;
|
||||
const result: any = await db.createRappel(rappelData);
|
||||
|
||||
// Créer les associations rappel-séquence si des séquences sont spécifiées
|
||||
if (sequenceIds && sequenceIds.length > 0) {
|
||||
const rappelId = result.insertId || result[0]?.insertId;
|
||||
if (rappelId) {
|
||||
await db.createRappelSequences(Number(rappelId), sequenceIds);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}),
|
||||
|
||||
update: adminProcedure
|
||||
@@ -1091,6 +1103,7 @@ export const appRouter = router({
|
||||
joursAvant: z.number().optional(),
|
||||
heureEnvoi: z.string().optional(),
|
||||
actif: z.boolean().optional(),
|
||||
sequenceIds: z.array(z.number()).optional(),
|
||||
fichier: z.object({
|
||||
nomFichier: z.string(),
|
||||
urlFichier: z.string(),
|
||||
@@ -1100,8 +1113,17 @@ export const appRouter = router({
|
||||
}).nullable().optional(),
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
const { id, ...data } = input;
|
||||
const { id, sequenceIds, ...data } = input;
|
||||
await db.updateRappel(id, data);
|
||||
|
||||
// Mettre à jour les associations rappel-séquence si spécifiées
|
||||
if (sequenceIds !== undefined) {
|
||||
await db.deleteRappelSequences(id);
|
||||
if (sequenceIds.length > 0) {
|
||||
await db.createRappelSequences(id, sequenceIds);
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user