# review.md — Journal des décisions EME > Ce fichier trace chaque étape réalisée et chaque choix technique, avec sa justification. > Il est mis à jour au fil de l'avancement. Lecture du plus ancien (haut) au plus récent (bas). > > Règles métier -> `docs/regles-metier/regles-gestion.md` · Contexte -> `CONTEXT.md` · Plan -> `TODO.md` --- ## Conventions et choix transverses Ces décisions s'appliquent à tout le projet, sauf mention contraire. | Décision | Pourquoi | |---|---| | Statuts métier en `String` côté Prisma (pas d'enum natif) | SQL Server ne supporte pas les enums Prisma. Les valeurs sont validées côté TypeScript via `src/models/enums.ts`. | | Timestamps automatiques : `createdAt @default(now())`, `updatedAt @updatedAt` | Évite d'avoir à fournir ces valeurs manuellement ; `updatedAt` est géré par Prisma au runtime. | | `actif Boolean @default(true)` | Une entité créée est active par défaut. | | `@unique` sur les identifiants fonctionnels | Intégrité : un identifiant en double casse l'authentification / l'unicité métier. | | Champs nullable selon les cardinalités `0..1` du diagramme de classes | Un champ non connu à la création (ex. date de retour) ou optionnel doit être `NULL`, sinon le workflow métier est bloqué. | | `onDelete: NoAction, onUpdate: NoAction` sur les données de traçabilité (Emprunt, Checklist, ChecklistElement, Anomalie, Historique) | Ces données ne doivent jamais être supprimées/altérées automatiquement en cascade. Toute suppression sera explicite côté service. Évite aussi l'erreur SQL Server "multiple cascade paths". | | ERD tenu à jour en miroir du schéma (`docs/conceptions/uml/ERD.md`) | La source de vérité doit refléter le code réel (nullable, `UK`, etc.). | | Workflow Git : commits sur `develop` uniquement | `master` est figé ; `develop` reste toujours à jour de `master` (aucune divergence puisque master ne bouge plus). | | Process avant chaque migration : `prisma format` -> `prisma validate` -> `npm run build` -> migration | Détecter toute erreur de schéma/typage avant de toucher la base. | ### Note outillage — migrations en environnement non-interactif `prisma migrate dev` se bloque quand il doit demander une confirmation (ex. ajout de contrainte `@unique` sur table existante) car le terminal est non-interactif. Contournement utilisé : générer le SQL avec `prisma migrate diff --from-config-datasource --to-schema ... --script`, écrire le fichier de migration, puis appliquer avec `prisma migrate deploy`. Résultat strictement identique à `migrate dev`, sans perte de données. En terminal interactif (VS Code), `migrate dev` fonctionne normalement (taper `y`). --- ## Journal chronologique ### Étape 0 — Audit initial - Constat : Block 0 (bootstrap) terminé mais non coché ; schéma à 4/16 entités ; aucune migration ; aucun commit Git. - Décision : compléter le schéma par petits blocs (et non d'un coup) pour valider/committer au fur et à mesure. ### Étape 1 — Configuration des migrations Prisma - Correction `DATABASE_URL` (mot de passe désaligné avec `DB_PASSWORD`). - Découverte : Prisma 7 interdit `url` dans `schema.prisma` -> la connexion vit dans `prisma.config.ts`. Ligne `url` retirée du datasource. ### Étape 2 — Modèles de référence (Campus, Role, Utilisateur, CategorieMateriel) - Defaults + timestamps auto ajoutés sur Campus, Utilisateur, CategorieMateriel (Role n'a pas de timestamps -> non modifié). - `@unique` sur `Role.code`, `Utilisateur.microsoftId`, `Utilisateur.email`. - `Utilisateur.classe` -> nullable : un `RESPONSABLE` n'a pas de classe (seuls les étudiants en ont). - Ajout dans l'ERD des annotations `UK` (sur `code`, `microsoft_id`, `email`) et `nullable` (sur `classe`). - Choix laissé de côté : tailles `@db.NVarChar(n)` (colonnes restées en `NVARCHAR(1000)` par défaut). À reconsidérer plus tard. ### Étape 3 — Bloc A : Infrastructure (SallePret, PosteEmprunt) — commit `d026b75` - `SallePret` rattachée à `Campus` (FK `campus_id`) ; `PosteEmprunt` rattaché à `SallePret` (FK `salle_pret_id`). ### Étape 4 — Bloc B : CarteEtudiante — commit `dfa73cd` - Rattachée à `Utilisateur`. - Dette notée : `qr_code` devrait être `@unique` (sert à l'identification, RG02) — non posé pour rester fidèle à l'ERD. ### Étape 5 — Bloc C : Matériel (Materiel, Accessoire, MaterielAccessoire) — commit `0774ee3` - Table de liaison `MaterielAccessoire` entre `Materiel` et `Accessoire`, sans colonnes `created_at`/`updated_at`. - Dette notée : `@@unique([materielId, accessoireId])` et `Materiel.statut @default("DISPONIBLE")`. ### Étape 6 — Bloc D : Transactions (Emprunt, Checklist, ChecklistElement) — commit `6908a2b` - Champs de retour/optionnels nullable : `dateRetourReelle`, `modeIdentificationRetour`, `commentaireDepart`, `commentaireRetour`, `Checklist.commentaire`, `ChecklistElement.commentaire`. - Pourquoi : RG12 crée l'emprunt au départ ; les infos de retour n'existent pas encore. En `NOT NULL`, la création serait impossible. - `ChecklistElement.accessoireId` rendu nullable : un élément de checklist peut être un libellé libre, sans accessoire catalogué associé (cardinalité 0..1). - Les 9 FK en `NoAction` : traçabilité + évitement des cascade paths. - Annotation `nullable` ajoutée dans l'ERD sur les champs de retour/commentaires d'`Emprunt` et `accessoire_id`/`commentaire` de checklist. ### Étape 7 — Stratégie Git : branche `develop` - Création de `develop` depuis `master`. Désormais tous les commits vont sur `develop` ; `master` figé au Bloc C. ### Étape 8 — Bloc E : Suivi (Anomalie, Notification, Historique) — commit `0fc9f9e` - Schéma 16/16 complet. - Nullable selon `0..1` : `Anomalie.traiteeParId` (null tant que non pris en charge), `observation`, `dateResolution` ; `Notification.anomalieId` ; les 5 FK optionnelles d'`Historique` (un événement n'a pas toujours d'emprunt/matériel/etc.). - Double relation `Anomalie` vers `Utilisateur` nommée : `@relation("AnomalieEtudiant")` (étudiant concerné) et `@relation("AnomalieResponsable")` (responsable qui traite). - Defaults : `Anomalie.detecteeAutomatiquement @default(true)`, `Notification.lu @default(false)`, `Notification.dateCreation @default(now())`. - Les 12 FK en `NoAction` (traçabilité). - `Notification` et `Historique` créées sans colonne `updatedAt` (seulement `date_creation`/`created_at`). - Annotation `nullable` ajoutée dans l'ERD sur `traitee_par_id`, `observation`, `date_resolution`, `anomalie_id` et les 5 FK optionnelles d'`Historique`. ### Étape 9 — Seeds de référence — commit `2c3a5db` - Fichier `prisma/seed.ts` autonome (adapter MSSQL, lecture directe des variables DB), lancé via `npm run seed` (script ajouté au `package.json`). - Choix `npm run seed` (ts-node) plutôt que `prisma db seed` : l'API de configuration du seed en Prisma 7 (`prisma.config.ts`) n'étant pas confirmée, un script direct est plus fiable. - Données : 2 rôles (`ETUDIANT`, `RESPONSABLE`), 1 campus (`Campus Saint-Christophe`, Cergy, 95800, adresse provisoire), 1 salle (`Salle de prêt principale`), 1 poste (`POSTE-01`), 6 catégories (Ordinateur portable, Vidéoprojecteur, Tablette, Caméra, Périphérique audio, Câble / Adaptateur). - Idempotence : `upsert` sur `Role.code` ; `findFirst` avant `create` pour les entités sans clé unique. Relance vérifiée (aucun doublon). - Incident : le client Prisma était obsolète (ne connaissait que les 4 modèles de référence). Régénéré avec `npx prisma generate` avant le seed. Leçon : après des migrations, régénérer/vérifier le client avant d'exécuter du code qui l'utilise. ### Étape 10 — Fixtures de test - Fichier `prisma/fixtures.ts` + script `npm run fixtures` (séparé du seed : données de dev/test, non installées en production). - Non destructif : détection via un email témoin (`marie.dupont@ensitech.eu`) ; si présent, le script s'arrête (cohérent avec le principe "pas de suppression automatique"). - Contenu : 4 utilisateurs (3 étudiants + 1 responsable), 3 cartes, 6 matériels (statuts variés), 3 accessoires + liaisons, 4 emprunts couvrant `EN_COURS` / `EN_RETARD` / `CLOTURE` / `RETOUR_NON_CONFORME`, 6 checklists, 1 anomalie + 1 notification, 2 entrées d'historique. - Idempotence vérifiée (2e exécution = skip). #### Contexte métier clarifié — structure ENSUP (impacte la modélisation) - ENSUP est un groupe **multi-campus** (Saint-Christophe/Cergy, Saint-Quentin, Nantes, Marseille...). - Chaque campus héberge **deux écoles** : ENSUP (filières généralistes) et Ensitech (informatique). Bâtiments et salles **partagés**. - La distinction d'école se fait par le **domaine email** : `ensitech.eu` (info) et `ensup.eu` (reste). La validation de domaine (RG, à venir) doit accepter les **deux**. - Le modèle n'a pas d'entité "École" : seule `Campus` existe. Un campus = un lieu physique partagé par les deux écoles (et non une école). - MVP : on démarre avec le **seul campus Saint-Christophe (Cergy)** ; les autres campus viendront ensuite. ### Étape 11 — Block 1 : middlewares backend - Structure en couches : `utils/logger.ts`, `errors/app-error.ts`, `middlewares/` (request-logger, not-found, error-handler). - Gestionnaire d'erreurs global : une `AppError` renvoie son statut + message ; toute autre erreur devient une 500 (stack exposée uniquement en `development`) et est tracée. - Logger de requêtes (méthode, URL, statut, durée) + logger applicatif horodaté. - CORS restreint à `env.frontendUrl` (était ouvert à toutes les origines). `SIGTERM` géré en plus de `SIGINT` (arrêt propre du container Docker). - Express 5 propage nativement les erreurs des handlers async : pas de wrapper `asyncHandler` nécessaire. - Vérifié au runtime : `/health` 200, route inconnue 404 en JSON, requêtes journalisées. ### Étape 12 — Block 1 : ESLint + Prettier - ESLint 10 (flat config `eslint.config.mjs`) + typescript-eslint 8 ; Prettier 3 (`.prettierrc.json`). - Règles de qualité strictes encodées : `no-explicit-any`, `no-non-null-assertion`, `explicit-module-boundary-types`, `no-unused-vars` (ignore le préfixe `_`), `prefer-const`. `eslint-config-prettier` désactive les règles en conflit avec Prettier. - Prettier : quotes simples, point-virgule, 100 colonnes, 2 espaces, trailing commas. - Scripts : `lint`, `lint:fix`, `format`, `format:check`. - État : lint vert sur tout le code existant ; formatage appliqué (3 fichiers) ; build OK. ### Étape 13 — Correctif tooling : type-check des scripts Prisma - Problème : `prisma/seed.ts` et `fixtures.ts` étaient hors du `include` du tsconfig ; l'IDE ne chargeait pas les types Node (`process` non reconnu, 2 erreurs). - Correctif : `tsconfig.json` élargi à `src` + `prisma` en `noEmit` (IDE et `tsc --noEmit`) ; nouveau `tsconfig.build.json` dédié à la compilation `src` -> `dist` (`npm run build`). - Bénéfice : les scripts Prisma sont désormais type-checkés (angle mort comblé). ### Étape 14 — Block 4 : architecture en couches + 1er endpoint (catalogue matériel) - Mise en place de l'architecture en couches : `routes/` -> `controllers/` -> `services/` -> `repositories/` (un dossier par couche). - Auth simulée temporaire : middleware `current-user` qui résout l'utilisateur via l'en-tête `x-user-email` et pose `req.user` (typé via `types/authenticated-user.ts` + augmentation `Express.Request`). À remplacer par la validation JWT au Block 3. - Endpoint `GET /api/materiels` (catalogue) : applique RG10 (matériels `DISPONIBLE` du campus de l'étudiant), filtres `categorieId` et `q` (recherche nom/marque/modèle/référence). - Format de réponse standardisé : `{ "data": ... }` (cohérent avec `{ "error": ... }`). - Vérifié au runtime : 401 sans en-tête / utilisateur inconnu, 200 avec les disponibles du campus, filtres OK, 400 sur `categorieId` invalide. ### Étape 15 — Block 4 : endpoint "mes emprunts en cours" - Endpoint `GET /api/mes-emprunts` : retourne les emprunts non restitués (`EN_COURS`, `EN_RETARD`) de l'utilisateur courant, triés par date de retour prévue, avec le matériel et sa catégorie inclus. - RG15 : filtrage par `utilisateurId` (chacun ne voit que ses propres emprunts). - Réutilise l'architecture en couches et l'auth simulée déjà en place. - Vérifié au runtime : Marie 1 (`EN_COURS`, son `CLOTURE` exclu), Lucas 1 (`EN_RETARD`), Sofia 0 (son `RETOUR_NON_CONFORME` exclu), 401 sans en-tête. ### Étape 16 — Block 4 : profil et identification - `GET /api/auth/me` (protégé) : renvoie le profil complet de l'utilisateur courant (role + campus). - `GET /api/auth/carte/:qr` (public) : identification par QR code (RG02/RG03) ; contrôles carte active, non expirée et compte actif ; renvoie le profil. - Refactor du routage : `currentUser` appliqué par sous-routeur (au lieu d'un middleware global) pour laisser la route d'identification publique. - Vérifié au runtime : `/me` 200 (profil Marie) et 401 sans en-tête ; `/carte` QR valide 200 sans en-tête (route publique), QR inconnu 404. ### Étape 17 — DTO de sortie sur les endpoints existants - Dossier `src/dtos/` : une interface DTO + un mapper pur `toXxxDto` par ressource (utilisateur, matériel, emprunt). - Mapping effectué dans le controller (couche présentation) ; les services continuent de renvoyer les entités (réutilisables). - Champs internes désormais masqués : `microsoftId`, timestamps, FK brutes, `numeroInventaire`/`numeroSerie`. - Vérifié au runtime sur `/me`, `/materiels`, `/mes-emprunts`. --- ## Dette technique en attente À traiter ensemble plus tard (avec mise à jour ERD en miroir) : 1. `CarteEtudiante.qr_code` -> `@unique` (identification, RG02) 2. `MaterielAccessoire` -> `@@unique([materielId, accessoireId])` (un accessoire une seule fois par matériel) 3. `Materiel.statut` -> `@default("DISPONIBLE")` (un matériel neuf est disponible, RG07) 4. Tailles de colonnes `@db.NVarChar(n)` (actuellement `NVARCHAR(1000)` partout) 5. Swagger/OpenAPI (Block 1, reporté après les endpoints) 6. Auth réelle Azure AD (Block 3) — remplacera l'auth simulée par en-tête --- *Dernière mise à jour : 2026-06-22 — DTO de sortie en place sur les 3 endpoints de lecture.*