diff --git a/.manus/db/db-query-1767877951620.json b/.manus/db/db-query-1767877951620.json
new file mode 100644
index 0000000..6554882
--- /dev/null
+++ b/.manus/db/db-query-1767877951620.json
@@ -0,0 +1,9 @@
+{
+ "query": "CREATE TABLE IF NOT EXISTS `rappelSequences` (\n `id` int AUTO_INCREMENT NOT NULL,\n `rappelId` int NOT NULL,\n `sequenceId` int NOT NULL,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n CONSTRAINT `rappelSequences_id` PRIMARY KEY(`id`)\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 CREATE TABLE IF NOT EXISTS `rappelSequences` (\n `id` int AUTO_INCREMENT NOT NULL,\n `rappelId` int NOT NULL,\n `sequenceId` int NOT NULL,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n CONSTRAINT `rappelSequences_id` PRIMARY KEY(`id`)\n);",
+ "rows": [],
+ "messages": [],
+ "stdout": "",
+ "stderr": "",
+ "execution_time_ms": 652
+}
\ No newline at end of file
diff --git a/.manus/db/db-query-1767878502013.json b/.manus/db/db-query-1767878502013.json
new file mode 100644
index 0000000..171aeb0
--- /dev/null
+++ b/.manus/db/db-query-1767878502013.json
@@ -0,0 +1,9 @@
+{
+ "query": "SELECT * FROM rappelSequences;",
+ "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 * FROM rappelSequences;",
+ "rows": [],
+ "messages": [],
+ "stdout": "",
+ "stderr": "",
+ "execution_time_ms": 119
+}
\ No newline at end of file
diff --git a/.manus/db/db-query-1767878680556.json b/.manus/db/db-query-1767878680556.json
new file mode 100644
index 0000000..4da7818
--- /dev/null
+++ b/.manus/db/db-query-1767878680556.json
@@ -0,0 +1,16 @@
+{
+ "query": "SELECT r.id, r.nom, rs.sequenceId, s.nom as sequenceNom FROM rappels r LEFT JOIN rappelSequences rs ON r.id = rs.rappelId LEFT JOIN sequences s ON rs.sequenceId = s.id WHERE r.nom = 'Rappel 2' ORDER BY r.id DESC LIMIT 5;",
+ "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 r.id, r.nom, rs.sequenceId, s.nom as sequenceNom FROM rappels r LEFT JOIN rappelSequences rs ON r.id = rs.rappelId LEFT JOIN sequences s ON rs.sequenceId = s.id WHERE r.nom = 'Rappel 2' ORDER BY r.id DESC LIMIT 5;",
+ "rows": [
+ {
+ "id": "60002",
+ "nom": "Rappel 2",
+ "sequenceId": "90002",
+ "sequenceNom": "Groupe B"
+ }
+ ],
+ "messages": [],
+ "stdout": "id\tnom\tsequenceId\tsequenceNom\n60002\tRappel 2\t90002\tGroupe B\n",
+ "stderr": "",
+ "execution_time_ms": 58
+}
\ No newline at end of file
diff --git a/client/src/components/SequenceMultiSelect.tsx b/client/src/components/SequenceMultiSelect.tsx
new file mode 100644
index 0000000..ff45dde
--- /dev/null
+++ b/client/src/components/SequenceMultiSelect.tsx
@@ -0,0 +1,122 @@
+import { useState } from "react";
+import { Check, ChevronsUpDown } from "lucide-react";
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { Badge } from "@/components/ui/badge";
+
+interface Sequence {
+ id: number;
+ nom: string;
+ formationNom?: string;
+}
+
+interface SequenceMultiSelectProps {
+ sequences: Sequence[];
+ selectedIds: number[];
+ onChange: (selectedIds: number[]) => void;
+ placeholder?: string;
+}
+
+export function SequenceMultiSelect({
+ sequences,
+ selectedIds,
+ onChange,
+ placeholder = "Sélectionnez des séquences...",
+}: SequenceMultiSelectProps) {
+ const [open, setOpen] = useState(false);
+
+ const toggleSequence = (sequenceId: number) => {
+ const newSelection = selectedIds.includes(sequenceId)
+ ? selectedIds.filter((id) => id !== sequenceId)
+ : [...selectedIds, sequenceId];
+ onChange(newSelection);
+ };
+
+ const selectedSequences = sequences.filter((seq) =>
+ selectedIds.includes(seq.id)
+ );
+
+ return (
+
+
+
+
+
+
+
+
+ Aucune séquence trouvée.
+
+ {sequences.map((sequence) => (
+ toggleSequence(sequence.id)}
+ className="cursor-pointer"
+ >
+
+
+ {sequence.nom}
+ {sequence.formationNom && (
+
+ {sequence.formationNom}
+
+ )}
+
+
+ ))}
+
+
+
+
+
+ {selectedSequences.length > 0 && (
+
+ {selectedSequences.map((sequence) => (
+ toggleSequence(sequence.id)}
+ >
+ {sequence.nom}
+ ×
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/client/src/pages/AdminRappels.tsx b/client/src/pages/AdminRappels.tsx
index f4fbe10..4209612 100644
--- a/client/src/pages/AdminRappels.tsx
+++ b/client/src/pages/AdminRappels.tsx
@@ -25,6 +25,7 @@ import { trpc } from "@/lib/trpc";
import { Bell, Edit, Plus, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { FileUpload } from "@/components/FileUpload";
+import { SequenceMultiSelect } from "@/components/SequenceMultiSelect";
// Types de templates disponibles pour les rappels
const TEMPLATE_TYPES = [
@@ -47,6 +48,7 @@ export default function AdminRappels() {
joursAvant: 7,
heureEnvoi: "09:00",
actif: true,
+ sequenceIds: [] as number[],
fichier: null as {
nomFichier: string;
urlFichier: string;
@@ -57,6 +59,7 @@ export default function AdminRappels() {
});
const { data: rappels, refetch } = trpc.rappels.list.useQuery();
+ const { data: sequences } = trpc.sequences.list.useQuery();
const createMutation = trpc.rappels.create.useMutation({
onSuccess: () => {
toast.success("Rappel créé avec succès");
@@ -111,6 +114,7 @@ export default function AdminRappels() {
joursAvant: 7,
heureEnvoi: "09:00",
actif: true,
+ sequenceIds: [],
});
};
@@ -131,6 +135,7 @@ export default function AdminRappels() {
joursAvant: rappel.joursAvant,
heureEnvoi: rappel.heureEnvoi,
actif: rappel.actif,
+ sequenceIds: rappel.sequenceIds || [],
fichier: rappel.nomFichier ? {
nomFichier: rappel.nomFichier,
urlFichier: rappel.urlFichier,
@@ -376,6 +381,23 @@ export default function AdminRappels() {
+
+
+
({
+ id: seq.id,
+ nom: seq.nom,
+ formationNom: seq.formation?.nom
+ })) || []}
+ selectedIds={formData.sequenceIds}
+ onChange={(sequenceIds) => setFormData({ ...formData, sequenceIds })}
+ placeholder="Toutes les séquences (par défaut)"
+ />
+
+ Si aucune séquence n'est sélectionnée, le rappel s'appliquera à toutes les séquences
+
+
+
setFormData({ ...formData, fichier })}
@@ -503,6 +525,23 @@ export default function AdminRappels() {
/>
+
+
+
({
+ id: seq.id,
+ nom: seq.nom,
+ formationNom: seq.formation?.nom
+ })) || []}
+ selectedIds={formData.sequenceIds}
+ onChange={(sequenceIds) => setFormData({ ...formData, sequenceIds })}
+ placeholder="Toutes les séquences (par défaut)"
+ />
+
+ Si aucune séquence n'est sélectionnée, le rappel s'appliquera à toutes les séquences
+
+
+
setFormData({ ...formData, fichier })}
diff --git a/drizzle/meta/0030_snapshot.json b/drizzle/meta/0030_snapshot.json
new file mode 100644
index 0000000..a959428
--- /dev/null
+++ b/drizzle/meta/0030_snapshot.json
@@ -0,0 +1,2124 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "1a38d0bb-a0e3-453e-ad61-9a34adb8fae2",
+ "prevId": "f6cb64eb-d867-4e1d-9dba-f87862923750",
+ "tables": {
+ "alertes": {
+ "name": "alertes",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "nom": {
+ "name": "nom",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "enum('taux_remplissage','seuil_inscriptions')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "condition": {
+ "name": "condition",
+ "type": "varchar(10)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "valeurSeuil": {
+ "name": "valeurSeuil",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "codeEtablissement": {
+ "name": "codeEtablissement",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailsDestinataires": {
+ "name": "emailsDestinataires",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "actif": {
+ "name": "actif",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "derniereVerification": {
+ "name": "derniereVerification",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dernierEnvoi": {
+ "name": "dernierEnvoi",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "alertes_id": {
+ "name": "alertes_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "apprenants": {
+ "name": "apprenants",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "nom": {
+ "name": "nom",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "prenom": {
+ "name": "prenom",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "codeEtablissement": {
+ "name": "codeEtablissement",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fonction": {
+ "name": "fonction",
+ "type": "enum('directeur','chef_service','autre')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "apprenants_id": {
+ "name": "apprenants_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "apprenants_email_unique": {
+ "name": "apprenants_email_unique",
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "attestations": {
+ "name": "attestations",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "inscriptionId": {
+ "name": "inscriptionId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "apprenantId": {
+ "name": "apprenantId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sequenceId": {
+ "name": "sequenceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "s3Key": {
+ "name": "s3Key",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "pdfUrl": {
+ "name": "pdfUrl",
+ "type": "varchar(1000)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dateGeneration": {
+ "name": "dateGeneration",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "emailEnvoye": {
+ "name": "emailEnvoye",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "dateEnvoiEmail": {
+ "name": "dateEnvoiEmail",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "attestations_id": {
+ "name": "attestations_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "configAttestation": {
+ "name": "configAttestation",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "logoS3Key": {
+ "name": "logoS3Key",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "logoUrl": {
+ "name": "logoUrl",
+ "type": "varchar(1000)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "signatureS3Key": {
+ "name": "signatureS3Key",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "signatureUrl": {
+ "name": "signatureUrl",
+ "type": "varchar(1000)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "nomSignataire": {
+ "name": "nomSignataire",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fonctionSignataire": {
+ "name": "fonctionSignataire",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "texteAttestation": {
+ "name": "texteAttestation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "configAttestation_id": {
+ "name": "configAttestation_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "datesFormation": {
+ "name": "datesFormation",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "sequenceId": {
+ "name": "sequenceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dateDebut": {
+ "name": "dateDebut",
+ "type": "datetime",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dateFin": {
+ "name": "dateFin",
+ "type": "datetime",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ordre": {
+ "name": "ordre",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "datesFormation_id": {
+ "name": "datesFormation_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "emailConfig": {
+ "name": "emailConfig",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'resend'"
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fromEmail": {
+ "name": "fromEmail",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "fromName": {
+ "name": "fromName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'Formation Manager Itinova'"
+ },
+ "mode": {
+ "name": "mode",
+ "type": "enum('simulation','production')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'simulation'"
+ },
+ "domainVerified": {
+ "name": "domainVerified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "smtpHost": {
+ "name": "smtpHost",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "smtpPort": {
+ "name": "smtpPort",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "smtpSecure": {
+ "name": "smtpSecure",
+ "type": "enum('none','tls','ssl')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'tls'"
+ },
+ "smtpUser": {
+ "name": "smtpUser",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "smtpPassword": {
+ "name": "smtpPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "emailConfig_id": {
+ "name": "emailConfig_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "emailTemplates": {
+ "name": "emailTemplates",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "type": {
+ "name": "type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "logoUrl": {
+ "name": "logoUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "primaryColor": {
+ "name": "primaryColor",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'#2563eb'"
+ },
+ "headerBgColor": {
+ "name": "headerBgColor",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'#2563eb'"
+ },
+ "headerTextColor": {
+ "name": "headerTextColor",
+ "type": "varchar(7)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'#ffffff'"
+ },
+ "headerTitle": {
+ "name": "headerTitle",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'Formation Manager Itinova'"
+ },
+ "footerText": {
+ "name": "footerText",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bodyContent": {
+ "name": "bodyContent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "active": {
+ "name": "active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "emailTemplates_id": {
+ "name": "emailTemplates_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "emailTemplates_type_unique": {
+ "name": "emailTemplates_type_unique",
+ "columns": [
+ "type"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "envoisQuestionnaires": {
+ "name": "envoisQuestionnaires",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "questionnaireId": {
+ "name": "questionnaireId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "apprenantId": {
+ "name": "apprenantId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sequenceId": {
+ "name": "sequenceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dateEnvoi": {
+ "name": "dateEnvoi",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dateReponse": {
+ "name": "dateReponse",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "envoisQuestionnaires_id": {
+ "name": "envoisQuestionnaires_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "envoisQuestionnaires_token_unique": {
+ "name": "envoisQuestionnaires_token_unique",
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "formateurs": {
+ "name": "formateurs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "nom": {
+ "name": "nom",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "formateurs_id": {
+ "name": "formateurs_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "formations": {
+ "name": "formations",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "nom": {
+ "name": "nom",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lienUnique": {
+ "name": "lienUnique",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "actif": {
+ "name": "actif",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "formations_id": {
+ "name": "formations_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "formations_lienUnique_unique": {
+ "name": "formations_lienUnique_unique",
+ "columns": [
+ "lienUnique"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "inscriptions": {
+ "name": "inscriptions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "apprenantId": {
+ "name": "apprenantId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sequenceId": {
+ "name": "sequenceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "statut": {
+ "name": "statut",
+ "type": "enum('confirmee','liste_attente','annulee')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dateInscription": {
+ "name": "dateInscription",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "inscriptions_id": {
+ "name": "inscriptions_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "logsNotifications": {
+ "name": "logsNotifications",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "type": {
+ "name": "type",
+ "type": "enum('remerciement','notification_formateur_inscription','notification_formateur_annulation','alerte_capacite','notification_liste_attente')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sequenceId": {
+ "name": "sequenceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "apprenantId": {
+ "name": "apprenantId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "formateurId": {
+ "name": "formateurId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "emailDestinataire": {
+ "name": "emailDestinataire",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sujet": {
+ "name": "sujet",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dateEnvoi": {
+ "name": "dateEnvoi",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "statut": {
+ "name": "statut",
+ "type": "enum('success','failed')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "messageErreur": {
+ "name": "messageErreur",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "logsNotifications_id": {
+ "name": "logsNotifications_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "logsRappels": {
+ "name": "logsRappels",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "rappelId": {
+ "name": "rappelId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sequenceId": {
+ "name": "sequenceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "apprenantId": {
+ "name": "apprenantId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailDestinataire": {
+ "name": "emailDestinataire",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "typeRappel": {
+ "name": "typeRappel",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dateEnvoi": {
+ "name": "dateEnvoi",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "statut": {
+ "name": "statut",
+ "type": "enum('success','failed')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "messageErreur": {
+ "name": "messageErreur",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dateSequence": {
+ "name": "dateSequence",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "nbTentatives": {
+ "name": "nbTentatives",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "prochainEssai": {
+ "name": "prochainEssai",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "logsRappels_id": {
+ "name": "logsRappels_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "passwordResetTokens": {
+ "name": "passwordResetTokens",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expiresAt": {
+ "name": "expiresAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "used": {
+ "name": "used",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "passwordResetTokens_id": {
+ "name": "passwordResetTokens_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "passwordResetTokens_token_unique": {
+ "name": "passwordResetTokens_token_unique",
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "questionnaires": {
+ "name": "questionnaires",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "nom": {
+ "name": "nom",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "enum('satisfaction','evaluation_pre','evaluation_post')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "formationId": {
+ "name": "formationId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "actif": {
+ "name": "actif",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "envoiAutomatique": {
+ "name": "envoiAutomatique",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "delaiEnvoiJours": {
+ "name": "delaiEnvoiJours",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "questionnaires_id": {
+ "name": "questionnaires_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "questions": {
+ "name": "questions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "questionnaireId": {
+ "name": "questionnaireId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "ordre": {
+ "name": "ordre",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "texte": {
+ "name": "texte",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "typeQuestion": {
+ "name": "typeQuestion",
+ "type": "enum('choix_multiple','echelle','texte_libre','oui_non')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "options": {
+ "name": "options",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "echelleMin": {
+ "name": "echelleMin",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "echelleMax": {
+ "name": "echelleMax",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "echelleLabelMin": {
+ "name": "echelleLabelMin",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "echelleLabelMax": {
+ "name": "echelleLabelMax",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "obligatoire": {
+ "name": "obligatoire",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "questions_id": {
+ "name": "questions_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "rappelSequences": {
+ "name": "rappelSequences",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "rappelId": {
+ "name": "rappelId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sequenceId": {
+ "name": "sequenceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "rappelSequences_id": {
+ "name": "rappelSequences_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "rappels": {
+ "name": "rappels",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "nom": {
+ "name": "nom",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "templateType": {
+ "name": "templateType",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "timing": {
+ "name": "timing",
+ "type": "enum('pre_formation','post_formation')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'pre_formation'"
+ },
+ "joursAvant": {
+ "name": "joursAvant",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "heureEnvoi": {
+ "name": "heureEnvoi",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'09:00'"
+ },
+ "actif": {
+ "name": "actif",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "derniereExecution": {
+ "name": "derniereExecution",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "nomFichier": {
+ "name": "nomFichier",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "urlFichier": {
+ "name": "urlFichier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "s3Key": {
+ "name": "s3Key",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "typeFichier": {
+ "name": "typeFichier",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tailleFichier": {
+ "name": "tailleFichier",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "rappels_id": {
+ "name": "rappels_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "reponsesQuestionnaires": {
+ "name": "reponsesQuestionnaires",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "questionnaireId": {
+ "name": "questionnaireId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "apprenantId": {
+ "name": "apprenantId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sequenceId": {
+ "name": "sequenceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "dateReponse": {
+ "name": "dateReponse",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "complete": {
+ "name": "complete",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "reponsesQuestionnaires_id": {
+ "name": "reponsesQuestionnaires_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "reponsesQuestions": {
+ "name": "reponsesQuestions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "reponseQuestionnaireId": {
+ "name": "reponseQuestionnaireId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "questionId": {
+ "name": "questionId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "reponseNumerique": {
+ "name": "reponseNumerique",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reponseTexte": {
+ "name": "reponseTexte",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "reponsesQuestions_id": {
+ "name": "reponsesQuestions_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "sequences": {
+ "name": "sequences",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "formationId": {
+ "name": "formationId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "nom": {
+ "name": "nom",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "lieu": {
+ "name": "lieu",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "publicCible": {
+ "name": "publicCible",
+ "type": "enum('directeur','chef_service','tous','autre')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "formateurId": {
+ "name": "formateurId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "capaciteMax": {
+ "name": "capaciteMax",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 12
+ },
+ "dateBlocage": {
+ "name": "dateBlocage",
+ "type": "datetime",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "statut": {
+ "name": "statut",
+ "type": "enum('ouverte','bloquee','terminee')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'ouverte'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "sequences_id": {
+ "name": "sequences_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "supportsFormation": {
+ "name": "supportsFormation",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "sequenceId": {
+ "name": "sequenceId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "formateurId": {
+ "name": "formateurId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "nomFichier": {
+ "name": "nomFichier",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "typeFichier": {
+ "name": "typeFichier",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tailleFichier": {
+ "name": "tailleFichier",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "urlFichier": {
+ "name": "urlFichier",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "s3Key": {
+ "name": "s3Key",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "supportsFormation_id": {
+ "name": "supportsFormation_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "openId": {
+ "name": "openId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "loginMethod": {
+ "name": "loginMethod",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "enum('user','admin','formateur')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'user'"
+ },
+ "isActive": {
+ "name": "isActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "formateurId": {
+ "name": "formateurId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ },
+ "lastSignedIn": {
+ "name": "lastSignedIn",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "users_id": {
+ "name": "users_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "users_openId_unique": {
+ "name": "users_openId_unique",
+ "columns": [
+ "openId"
+ ]
+ },
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "columns": [
+ "username"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index c32a96d..34f5791 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -211,6 +211,13 @@
"when": 1767869539717,
"tag": "0029_classy_warbound",
"breakpoints": true
+ },
+ {
+ "idx": 30,
+ "version": "5",
+ "when": 1767877943207,
+ "tag": "0030_cultured_korg",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/drizzle/schema.ts b/drizzle/schema.ts
index d2c6aec..1e5fa19 100644
--- a/drizzle/schema.ts
+++ b/drizzle/schema.ts
@@ -305,6 +305,23 @@ export const rappels = mysqlTable("rappels", {
export type Rappel = typeof rappels.$inferSelect;
export type InsertRappel = typeof rappels.$inferInsert;
+/**
+ * Table de liaison entre rappels et séquences (many-to-many)
+ * Permet de définir quelles séquences sont concernées par chaque rappel
+ * Si aucune association n'existe pour un rappel, il s'applique à toutes les séquences
+ */
+export const rappelSequences = mysqlTable("rappelSequences", {
+ id: int("id").autoincrement().primaryKey(),
+ /** ID du rappel */
+ rappelId: int("rappelId").notNull(),
+ /** ID de la séquence concernée */
+ sequenceId: int("sequenceId").notNull(),
+ createdAt: timestamp("createdAt").defaultNow().notNull(),
+});
+
+export type RappelSequence = typeof rappelSequences.$inferSelect;
+export type InsertRappelSequence = typeof rappelSequences.$inferInsert;
+
/**
* Table des questionnaires
* Stocke les questionnaires de satisfaction et d'évaluation des acquis
diff --git a/server/db.ts b/server/db.ts
index 828fb53..bf17761 100644
--- a/server/db.ts
+++ b/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 {
@@ -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 {
+ 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;
diff --git a/server/rappelScheduler.ts b/server/rappelScheduler.ts
index e9af8c3..6f95057 100644
--- a/server/rappelScheduler.ts
+++ b/server/rappelScheduler.ts
@@ -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 = [];
diff --git a/server/routers.ts b/server/routers.ts
index bbf6bef..ff72445 100644
--- a/server/routers.ts
+++ b/server/routers.ts
@@ -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 };
}),
diff --git a/todo.md b/todo.md
index cf6b61d..310d785 100644
--- a/todo.md
+++ b/todo.md
@@ -537,3 +537,15 @@
## Correction affichage délai rappels post-formation
- [x] Corriger l.affichage de la colonne "Délai" pour afficher J+X pour les rappels post-formation
- [x] Tester l.affichage avec un rappel pré-formation et un rappel post-formation
+
+## Refonte système de rappels - Gestion par séquence
+- [x] Créer la table rappelSequences (relation many-to-many entre rappels et séquences)
+- [x] Appliquer la migration en base de données
+- [x] Créer un composant de sélection multi-séquences
+- [x] Modifier le formulaire AdminRappels pour intégrer le sélecteur de séquences
+- [x] Créer les fonctions DB pour gérer les associations rappel-séquence
+- [x] Mettre à jour les procédures tRPC create et update pour gérer les associations
+- [x] Modifier le scheduler pour ne traiter que les séquences associées à chaque rappel
+- [ ] Adapter l'affichage du tableau pour montrer les séquences associées
+- [ ] Tester la création d'un rappel avec sélection de séquences spécifiques
+- [ ] Tester l'envoi automatique avec le nouveau système