Files
yann64 fbe184d2e6 Fix export type hint, dark mode import page, documentation
- Corrige le type de retour de UserController::export() (StreamedResponse)
- Ajoute les classes dark mode manquantes sur le bloc info de la page import
- Génère la documentation complète du projet dans docs/documentation.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 04:13:47 +02:00

692 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Documentation — MesRelevés
## Table des matières
1. [Présentation](#1-présentation)
2. [Stack technique](#2-stack-technique)
3. [Installation](#3-installation)
4. [Schéma de base de données](#4-schéma-de-base-de-données)
5. [Modèles Eloquent](#5-modèles-eloquent)
6. [Authentification et autorisations](#6-authentification-et-autorisations)
7. [Fonctionnalités](#7-fonctionnalités)
8. [Routes](#8-routes)
9. [Services](#9-services)
10. [Énumérations](#10-énumérations)
11. [Middlewares](#11-middlewares)
12. [Administration](#12-administration)
13. [Mises à jour](#13-mises-à-jour)
14. [Commandes Artisan](#14-commandes-artisan)
15. [Performance et indexation](#15-performance-et-indexation)
---
## 1. Présentation
**MesRelevés** est une application web dédiée aux associations de généalogie. Elle permet :
- la **saisie systématique** de relevés d'actes d'état civil (naissance, mariage, décès, etc.) ;
- la **recherche** plein texte dans ces relevés, avec filtres par type, lieu et période ;
- l'**export au format GEDCOM 5.5.1** pour import dans les logiciels de généalogie ;
- la **gestion collaborative** par sections locales, avec un workflow de validation par statut.
L'application supporte deux bases de données : **PostgreSQL 16** (recommandé, avec JSONB et recherche plein texte native) et MySQL/MariaDB.
---
## 2. Stack technique
| Composant | Technologie |
|-----------|-------------|
| Langage | PHP 8.2+ |
| Framework | Laravel 12 |
| Base de données | PostgreSQL 16 (recommandé) ou MySQL 8+ |
| Frontend | Blade + Alpine.js + Tailwind CSS |
| Assets | Vite |
| Cartographie | Leaflet.js + OpenStreetMap |
| Authentification | Laravel Breeze (sessions) + 2FA par code PIN |
| Cache | Redis (via Laravel Cache) |
---
## 3. Installation
### Prérequis
- PHP 8.2+ avec extensions : `pdo_pgsql` (ou `pdo_mysql`), `mbstring`, `json`, `xml`, `curl`
- Composer
- Node.js 18+ et npm
- PostgreSQL 16+ ou MySQL 8+
- Redis (optionnel, pour le cache)
### Installation manuelle
```bash
# Cloner le dépôt
git clone <url-du-depot> mesreleves
cd mesreleves
# Installer les dépendances
composer install --no-dev
npm install && npm run build
# Configurer l'environnement
cp .env.example .env
php artisan key:generate
# Éditer .env : APP_URL, DB_*, MAIL_*, CACHE_DRIVER
# Puis lancer les migrations
php artisan migrate
# Créer le lien de stockage public
php artisan storage:link
```
### Assistant d'installation (wizard)
L'application inclut un assistant graphique accessible à l'URL `/setup` lors du premier lancement (avant la création du fichier `storage/installed`). Il guide l'administrateur à travers :
1. **Connexion à la base de données** — saisie des paramètres et test de connexion
2. **Paramètres de l'application** — nom du site, URL, fuseau horaire
3. **Compte administrateur** — création du premier utilisateur
L'assistant exécute ensuite les migrations et crée le fichier `storage/installed`.
### Environnement de développement
```bash
php artisan serve # http://localhost:8000
npm run dev # Vite en watch (CSS/JS)
php artisan migrate:fresh --seed # Reset + données de test
```
---
## 4. Schéma de base de données
### Vue d'ensemble
```
lieu_types ──< lieux >── (auto-référentiel : lieu_parent_id)
├──< sections >──< section_user >── users
└──< sources >──< source_user >── users
├── source_types ──< source_type_fields
├── depots
└──< releves
```
### Tables
#### `users`
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | bigint PK | |
| `name` | string | Nom de l'utilisateur |
| `email` | string unique | Adresse e-mail (identifiant de connexion) |
| `password` | string | Hash bcrypt |
| `role` | string | `admin` \| `section_manager` \| `member` |
| `is_active` | boolean | Compte actif (défaut : true) |
| `email_verified_at` | timestamp | Date de vérification |
| `remember_token` | string | |
| `created_at`, `updated_at` | timestamp | |
#### `lieu_types`
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | bigint PK | |
| `nom` | string | Ex : « Commune », « Département », « Pays » |
#### `lieux`
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | bigint PK | |
| `lieu_type_id` | bigint FK nullable | → `lieu_types` |
| `nom` | string | Nom court |
| `code` | string nullable | Code INSEE, postal, etc. |
| `lieu_parent_id` | bigint FK nullable | Auto-référentiel (hiérarchie) |
| `nom_long` | string nullable | Calculé automatiquement (ex : « Bordeaux, Gironde, France ») |
| `latitude` | decimal(10,7) nullable | Coordonnées GPS |
| `longitude` | decimal(10,7) nullable | |
| `note` | text nullable | |
#### `sections`
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | bigint PK | |
| `nom` | string | |
| `lieu_id` | bigint FK nullable | → `lieux` |
| `adresse` | string nullable | |
| `email_contact` | string nullable | |
| `url` | string nullable | |
#### `section_user` (pivot)
| Colonne | Type | Description |
|---------|------|-------------|
| `section_id` | bigint FK | → `sections` (cascade delete) |
| `user_id` | bigint FK | → `users` (cascade delete) |
| `role_in_section` | string | `section_manager` \| `member` |
#### `depots`
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | bigint PK | |
| `nom` | string | |
| `description` | text nullable | |
| `adresse_postale` | string nullable | |
| `url` | string nullable | |
#### `source_types`
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | bigint PK | |
| `nom` | string | Ex : « Acte de naissance », « Registre paroissial » |
| `description` | text nullable | |
#### `source_type_fields`
Définit les champs dynamiques du formulaire de saisie pour chaque type de source.
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | bigint PK | |
| `source_type_id` | bigint FK | → `source_types` (cascade delete) |
| `name` | string | Clé JSON (ex : `nom_pere`) — unique par source_type |
| `label` | string | Libellé affiché dans le formulaire |
| `type` | string | `FieldType` : `text`, `date`, `boolean`, `select`, `textarea`, `number`, `place` |
| `required` | boolean | |
| `order` | smallint | Ordre d'affichage |
| `options` | json nullable | Pour `type=select` : liste des choix possibles |
#### `sources`
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | bigint PK | |
| `nom` | string | |
| `description` | text nullable | |
| `source_type_id` | bigint FK | → `source_types` |
| `depot_id` | bigint FK nullable | → `depots` (null si dépôt supprimé) |
| `section_id` | bigint FK nullable | → `sections` |
| `lieu_id` | bigint FK nullable | → `lieux` |
| `annee_debut` | smallint nullable | Période couverte |
| `annee_fin` | smallint nullable | |
| `cote` | string nullable | Cote d'archives |
| `auteur` | string nullable | |
| `status` | string | `SourceStatus` : `a_faire``en_cours``a_valider``termine` |
#### `source_user` (pivot)
| Colonne | Type | Description |
|---------|------|-------------|
| `source_id` | bigint FK | → `sources` (cascade delete) |
| `user_id` | bigint FK | → `users` (cascade delete) |
#### `releves`
| Colonne | Type | Description |
|---------|------|-------------|
| `id` | bigint PK | |
| `source_id` | bigint FK | → `sources` (cascade delete) |
| `data` | jsonb (pgsql) / json (mysql) | Champs variables selon le type de source |
| `created_by` | bigint FK | → `users` |
| `updated_by` | bigint FK | → `users` |
| `created_at`, `updated_at` | timestamp | |
| `nom` | text GENERATED | Extrait de `data->>'nom'` |
| `prenom` | text GENERATED | Extrait de `data->>'prenom'` |
| `date_evenement` | text GENERATED | Extrait de `data->'date_evenement'->>'valeur'` |
Les colonnes générées sont indexées (B-tree). Sur PostgreSQL, un index GIN couvre la colonne `data` entière.
#### `notifications`
Table standard Laravel pour les notifications en base de données (UUID comme clé primaire).
---
## 5. Modèles Eloquent
### User
**Relations :** `sections` (BelongsToMany via `section_user`), `sourcesAssignees` (BelongsToMany via `source_user`)
**Méthodes :**
| Méthode | Description |
|---------|-------------|
| `isAdmin()` | `true` si rôle `admin` |
| `isSectionManager()` | `true` si rôle `admin` ou `section_manager` |
| `isMemberOfSection(Section)` | Appartient à la section |
| `isManagerOfSection(Section)` | Est responsable de la section (ou admin) |
### Lieu
**Relations :** `lieuType`, `parent`, `enfants`, `sections`, `sources`, `releves` (HasManyThrough)
Le `nom_long` est recalculé automatiquement à la création et à la mise à jour (remonte la hiérarchie parente).
### Section
**Relations :** `lieu`, `membres` (BelongsToMany), `sources`, `responsables` (BelongsToMany filtré sur `role_in_section = section_manager`)
### Source
**Relations :** `sourceType`, `depot`, `section`, `lieu`, `membres` (BelongsToMany), `releves`
**Méthodes :**
| Méthode | Description |
|---------|-------------|
| `isVisibleBy(User)` | La source est visible par cet utilisateur |
| `canTransitionTo(SourceStatus, User)` | La transition de statut est autorisée |
### Releve
**Relations :** `source`, `createur` (User via `created_by`), `modificateur` (User via `updated_by`)
Le champ `data` est casté en tableau PHP (`'data' => 'array'`).
### SourceType / SourceTypeField
**Relations :** `SourceType``fields` (HasMany), `SourceTypeField``sourceType` (BelongsTo)
---
## 6. Authentification et autorisations
### Authentification
L'authentification repose sur Laravel Breeze (sessions). Le flux standard est :
1. Saisie de l'e-mail et du mot de passe
2. Si les credentials sont valides, un **code PIN à 6 chiffres** est envoyé par e-mail (2FA)
3. Saisie du code PIN (validité : 10 minutes, renvoi possible)
4. Accès à l'application
Le code PIN est stocké hashé en session (`2fa.pin_hash`) avec sa date d'expiration.
### Roles
| Valeur | Label | Description |
|--------|-------|-------------|
| `admin` | Administrateur | Accès complet à toute l'application |
| `section_manager` | Responsable de section | Gestion de sa section, validation des relevés |
| `member` | Membre | Saisie de relevés sur les sources assignées |
Un administrateur a **tous les droits** d'un responsable de section, et un responsable de section a tous les droits d'un membre.
### Tableau des autorisations
| Action | Admin | Resp. Section | Membre assigné | Membre |
|--------|:-----:|:-------------:|:--------------:|:------:|
| Voir liste des sources | ✓ | ✓ | ✓ | ✓ |
| Créer une source | ✓ | ✓ | | |
| Modifier une source | ✓ | ✓ | | |
| Supprimer une source | ✓ | | | |
| Assigner un membre | ✓ | ✓ | | |
| Voir relevés (source `terminée`) | ✓ | ✓ | ✓ | ✓ |
| Voir relevés (autres statuts) | ✓ | ✓ | ✓ | |
| Saisir / modifier un relevé | ✓ | ✓ | ✓ | |
| Supprimer un relevé | ✓ | ✓ | | |
| Passer status → `a_valider` | ✓ | ✓ | ✓ | |
| Passer status → `termine` | ✓ | ✓ | | |
| Gérer utilisateurs / sections | ✓ | | | |
### Workflow de statut d'une source
```
a_faire ──► en_cours ──► a_valider ──► termine
◄──────────── (rejet → retour en_cours)
```
Lors du passage en `a_valider`, une notification (e-mail + notification interne) est envoyée aux administrateurs et aux responsables de la section concernée.
### Policies
| Policy | Modèle | Actions couvertes |
|--------|--------|-------------------|
| `SourcePolicy` | `Source` | `viewAny`, `view`, `create`, `update`, `delete`, `assignMembre`, `transition` |
| `RelevePolicy` | `Releve` | `viewAny`, `view`, `create`, `update`, `delete` |
| `LieuPolicy` | `Lieu` | `create`, `update`, `delete` |
---
## 7. Fonctionnalités
### Gestion des lieux
- Hiérarchie libre de lieux (commune → département → pays, ou toute autre arborescence)
- Types de lieux configurables (admin)
- Calcul automatique du `nom_long` à partir de la hiérarchie parente
- Coordonnées GPS (latitude/longitude) pour la vue cartographique
- Import et export CSV
- Recherche AJAX par nom (endpoint `/lieux/search`)
### Gestion des sources
- Une source = un registre ou document à dépouiller
- Associée à un type de source, un dépôt d'archives, une section, un lieu et une période
- Membres assignés pour la saisie (table pivot `source_user`)
- Workflow de statut avec notifications automatiques
### Saisie des relevés
Le formulaire de saisie est **dynamique** : il est piloté par les champs définis dans `source_type_fields`. Chaque champ peut être de type :
| Type | Description |
|------|-------------|
| `text` | Champ texte simple |
| `textarea` | Zone de texte multiligne |
| `number` | Nombre entier ou décimal |
| `boolean` | Case à cocher |
| `select` | Liste déroulante (options définies dans `source_type_fields.options`) |
| `date` | Date avec choix du calendrier (grégorien, julien, républicain) |
| `place` | Sélecteur de lieu (recherche AJAX) |
Les champs de type `date` stockent : `{ "valeur": "YYYY-MM-DD", "calendrier": "gregorien|julien|republicain" }`.
### Recherche
La recherche plein texte porte sur les colonnes générées `nom`, `prenom`, `date_evenement` et sur le JSON complet. Elle supporte :
- recherche textuelle (LIKE ou FTS selon le SGBD)
- filtre par type de source
- filtre par lieu (avec descente récursive dans la hiérarchie via CTE)
- filtre par période (`annee_debut`/`annee_fin`)
Le nombre maximum de résultats est configurable par l'administrateur (défaut : 200). Un bandeau d'avertissement s'affiche lorsque la limite est atteinte.
### Carte
Vue cartographique (Leaflet.js + OpenStreetMap) des lieux géolocalisés ayant des relevés. Chaque marqueur affiche un popup avec les sources associées au lieu, leur statut et un lien vers la recherche.
### Export GEDCOM
Le service `GedcomExportService` génère un fichier `.ged` (GEDCOM 5.5.1) à partir d'une source ou d'une sélection de relevés. Les dates sont converties en grégorien ; le calendrier julien est annoté `@#DJULIAN@` et le calendrier républicain est converti via `DateConversionService`.
### Import de relevés CSV
Un import CSV est disponible pour les relevés (endpoint `/sources/{source}/import`). Le séparateur (`;` ou `,`) et le BOM UTF-8 sont détectés automatiquement.
---
## 8. Routes
### Routes publiques
| Méthode | URL | Action |
|---------|-----|--------|
| GET | `/` | Page d'accueil |
| GET/POST | `/setup/*` | Assistant d'installation |
| GET/POST | `/2fa` | Vérification 2FA |
| GET/POST | `/login` | Connexion |
| GET/POST | `/forgot-password` | Mot de passe oublié |
### Routes authentifiées (`/`)
| Méthode | URL | Action |
|---------|-----|--------|
| GET | `/dashboard` | Tableau de bord |
| GET/PATCH/DELETE | `/profile` | Profil utilisateur |
| GET | `/lieux` | Liste des lieux (avec filtres) |
| GET | `/lieux/search` | Recherche AJAX de lieux |
| GET/POST | `/lieux/import` | Import CSV de lieux |
| GET | `/lieux/export/csv` | Export CSV de lieux |
| GET/POST | `/lieux/{lieu}/edit` | CRUD lieux |
| GET | `/sources` | Liste des sources |
| GET/POST | `/sources/{source}/membres` | Gestion des membres assignés |
| POST | `/sources/{source}/transition` | Changer le statut |
| GET | `/sources/{source}/releves` | Liste des relevés d'une source |
| GET/POST | `/sources/{source}/releves/create` | Saisie d'un relevé |
| GET/PATCH | `/releves/{releve}/edit` | Modification d'un relevé |
| GET/POST | `/sources/{source}/import` | Import CSV de relevés |
| GET | `/export/source/{source}/csv` | Export CSV d'une source |
| GET | `/recherche` | Recherche plein texte |
| GET | `/carte` | Vue cartographique |
| GET | `/carte/data` | Données JSON pour la carte |
| GET | `/notifications` | Centre de notifications |
### Routes d'administration (`/admin`, middleware `role:admin`)
| Méthode | URL | Action |
|---------|-----|--------|
| GET | `/admin/dashboard` | Tableau de bord admin |
| GET/POST | `/admin/utilisateurs` | Liste + création d'utilisateurs |
| GET/PUT/DELETE | `/admin/utilisateurs/{id}` | Modification / suppression |
| GET/POST | `/admin/utilisateurs/export` | Export CSV utilisateurs |
| GET/POST | `/admin/utilisateurs/import` | Import CSV utilisateurs |
| POST | `/admin/utilisateurs/{id}/toggle-active` | Activer / désactiver un compte |
| GET/POST/PUT/DELETE | `/admin/lieu-types` | CRUD types de lieux |
| GET/POST/PUT/DELETE | `/admin/sections` | CRUD sections |
| GET/POST/DELETE | `/admin/sections/{id}/membres` | Membres d'une section |
| GET/POST/PUT/DELETE | `/admin/depots` | CRUD dépôts d'archives |
| GET/POST/PUT/DELETE | `/admin/source-types` | CRUD types de sources |
| POST/PUT/DELETE | `/admin/source-types/{id}/fields` | Gestion des champs dynamiques |
| POST | `/admin/source-types/{id}/fields/reorder` | Réordonner les champs |
| GET/POST | `/admin/parametres` | Paramètres du site |
| POST | `/admin/parametres/logo` | Upload du logo |
| POST | `/admin/parametres/smtp` | Configuration SMTP |
| POST | `/admin/parametres/smtp/test` | Test SMTP |
| POST | `/admin/parametres/storage-link` | Recréer le lien de stockage |
---
## 9. Services
### `DateConversionService`
Convertit les dates entre calendriers pour la génération GEDCOM.
| Méthode | Description |
|---------|-------------|
| `toGedcomDate(array)` | Convertit un champ date JSONB `{ valeur, calendrier }` en chaîne GEDCOM |
| `gregorianToGedcom(string)` | `YYYY-MM-DD``D MON YYYY` |
| `republicanToGregorian(string)` | `"15 Vendémiaire An III"``"1794-10-06"` |
Supporte les An I à XIV du calendrier républicain (17921806), avec les noms de mois accentués ou non.
### `GedcomExportService`
Génère un fichier GEDCOM 5.5.1 à partir de relevés.
| Méthode | Description |
|---------|-------------|
| `exportSource(Source)` | Exporte tous les relevés d'une source |
| `exportReleves(Collection, string)` | Exporte une sélection de relevés |
Détecte automatiquement le type d'événement (BIRT, MARR, DEAT) depuis le nom du type de source.
### `SiteSettingsService`
Persistance des paramètres du site dans `storage/app/site_settings.json`. Interface statique.
| Méthode | Description |
|---------|-------------|
| `get(key, default)` | Lire un paramètre |
| `set(key, value)` | Écrire un paramètre |
| `all()` | Tous les paramètres |
| `logoUrl()` | URL publique du logo (ou null) |
| `siteName()` | Nom du site |
| `smtpConfigured()` | SMTP configuré ? |
| `searchMaxResults()` | Limite de résultats de recherche (défaut : 200, min : 10) |
**Paramètres gérés :** `site_name`, `logo_path`, `allow_registration`, `search_max_results`, `smtp.*`, `update.*`
### `UpdateService`
Gère la vérification et l'application des mises à jour depuis le serveur de releases (Gitea). Utilisé par les commandes Artisan `app:check-update` et `app:update`.
### `DbCompat`
Classe utilitaire pour la compatibilité MySQL/PostgreSQL. Fournit des fragments SQL adaptés au SGBD actif.
| Méthode | Description |
|---------|-------------|
| `isPgsql()` | Détecte si le SGBD est PostgreSQL |
| `like()` | `ILIKE` (pgsql) ou `LIKE` (mysql) |
| `ftsRaw()` | Expression de recherche plein texte native |
| `generatedJsonCol(key)` | Expression SQL pour colonne générée JSON |
| `jsonRegexRaw(col)` | Expression regex sur JSON |
---
## 10. Énumérations
### `UserRole`
| Cas | Valeur | Label |
|-----|--------|-------|
| `Admin` | `admin` | Administrateur |
| `SectionManager` | `section_manager` | Responsable de section |
| `Member` | `member` | Membre |
### `SourceStatus`
| Cas | Valeur | Label | Transitions possibles |
|-----|--------|-------|-----------------------|
| `AFaire` | `a_faire` | À faire | → `en_cours` |
| `EnCours` | `en_cours` | En cours | → `a_valider` |
| `AValider` | `a_valider` | À valider | → `termine`, → `en_cours` |
| `Termine` | `termine` | Terminé | (aucune) |
### `CalendarType`
| Cas | Valeur | Label |
|-----|--------|-------|
| `Gregorien` | `gregorien` | Grégorien |
| `Julien` | `julien` | Julien |
| `Republicain` | `republicain` | Républicain |
### `FieldType`
`text`, `date`, `boolean`, `select`, `textarea`, `number`, `place`
---
## 11. Middlewares
### `CheckInstallation`
Redirige vers `/setup` si le fichier `storage/installed` est absent. Redirige vers `/` si l'installation est déjà effectuée et qu'on accède à `/setup`.
### `RoleMiddleware`
Usage : `role:admin` ou `role:section_manager`. Les administrateurs ont accès à toutes les routes quel que soit le rôle requis. Retourne HTTP 403 si le rôle est insuffisant.
### `EnsureUserIsActive`
Déconnecte automatiquement un utilisateur dont `is_active = false` et affiche un message d'erreur.
---
## 12. Administration
L'interface d'administration (`/admin`) est réservée aux utilisateurs de rôle `admin`.
### Gestion des utilisateurs
- Liste avec filtres (rôle, statut, recherche textuelle)
- Création manuelle (nom, e-mail, mot de passe, rôle)
- Modification (nom, e-mail, mot de passe optionnel, rôle)
- Suppression (protégée : impossible de supprimer le dernier admin ou son propre compte)
- Activation / désactivation (protégée : impossible de désactiver le dernier admin actif)
- Export CSV (avec les filtres actifs)
- Import CSV : colonnes `name`, `email`, `role`, `is_active` (optionnel) ; mots de passe temporaires générés et affichés une seule fois
### Paramètres du site
| Paramètre | Description |
|-----------|-------------|
| Nom du site | Affiché dans le titre et le header |
| Logo | Upload PNG/JPG/SVG (stocké dans `storage/app/public/site/`) |
| Inscriptions | Autoriser ou non les auto-inscriptions |
| Limite de résultats | Nombre maximum de résultats de recherche (105000, défaut 200) |
| SMTP | Serveur, port, chiffrement, identifiants (avec test d'envoi) |
| Mises à jour | Configuration de la vérification automatique |
| Lien de stockage | Recrée le symlink `public/storage → storage/app/public` |
### Tableau de bord admin
Statistiques globales : nombre d'utilisateurs, sections, sources par statut, relevés, lieux.
---
## 13. Mises à jour
Le système de mise à jour récupère les releases depuis un serveur Gitea et applique automatiquement :
1. Téléchargement de l'archive `.zip` de la release
2. Extraction et copie des fichiers
3. `composer install --no-dev`
4. `php artisan migrate --force`
5. `php artisan config:clear && php artisan view:clear`
### Commandes
```bash
php artisan app:check-update # Vérifie si une mise à jour est disponible
php artisan app:check-update --force # Ignore le cache
php artisan app:update # Télécharge et applique la mise à jour
php artisan app:update --check # Vérifie sans appliquer
php artisan app:update --force # Sans confirmation interactive
```
---
## 14. Commandes Artisan
| Commande | Description |
|----------|-------------|
| `app:check-update` | Vérifie la disponibilité d'une mise à jour |
| `app:update` | Applique la dernière mise à jour |
| `php artisan migrate` | Applique les migrations en attente |
| `php artisan migrate:fresh --seed` | Recrée la base avec données de test |
| `php artisan view:clear` | Vide le cache des vues compilées |
| `php artisan config:clear` | Vide le cache de configuration |
| `php artisan storage:link` | Crée le lien symbolique public/storage |
| `php artisan test` | Lance la suite de tests |
| `./vendor/bin/pint` | Formatage du code PHP (Laravel Pint) |
| `./vendor/bin/phpstan analyse` | Analyse statique |
---
## 15. Performance et indexation
### Index sur `releves`
| Index | Type | Colonne(s) |
|-------|------|-----------|
| `releves_nom_idx` | B-tree | `nom` (colonne générée) |
| `releves_prenom_idx` | B-tree | `prenom` (colonne générée) |
| `releves_date_evenement_idx` | B-tree | `date_evenement` (colonne générée) |
| `releves_data_gin_idx` | GIN (PostgreSQL) | `data` (JSONB complet) |
### Bonnes pratiques appliquées
- **Eager loading** systématique (`with()`) pour éviter les requêtes N+1
- **Pagination obligatoire** (25 par page par défaut, jamais de SELECT sans LIMIT)
- **Chargement différé des listes** : les pages Lieux et Sources n'affichent aucun résultat sans filtre actif (prévient les requêtes portant sur des dizaines de milliers d'enregistrements)
- **Cache Redis** (TTL 1h) sur les listes fréquemment consultées
- **Recherche par lieu** : CTE récursive pour descendre dans la hiérarchie des lieux sans jointures multiples
### Stockage des dates
Chaque champ de type `date` dans le JSONB stocke un objet :
```json
{
"valeur": "1792-09-22",
"calendrier": "gregorien"
}
```
La colonne générée `date_evenement` extrait `date_evenement.valeur` pour permettre les tris et filtres sans parsing JSON à chaque requête.