# TODO - Isolation complète des opérations

## 🎯 Objectif

Mettre en place une **isolation complète par opération** où chaque opération est totalement autonome et peut être supprimée indépendamment sans impacter les autres opérations ou la table centrale `users`.

## 📊 Architecture cible

```
operations (id: 850)
  ├── ope_users (id: 2500, fk_operation: 850, fk_user: 100)
  │   ├── ope_users_sectors (fk_user: 2500 ← ope_users.id, fk_sector: 5400)
  │   └── ope_pass (fk_user: 2500 ← ope_users.id, fk_sector: 5400)
  └── ope_sectors (id: 5400, fk_operation: 850)

users (id: 100) ← table centrale (conservée même si opération supprimée)
```

---

## ✅ Tâche 1 : Modification du schéma SQL

### 📁 Fichier : `scripts/orga/fix_fk_constraints.sql`

### Actions

- [ ] **1.1** Tester le script SQL sur **dva_geo** (DEV)
  ```bash
  incus exec dva-geo -- mysql rca_geo < /var/www/geosector/api/scripts/orga/fix_fk_constraints.sql
  ```

- [ ] **1.2** Vérifier les contraintes après exécution :
  ```sql
  SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
  FROM information_schema.KEY_COLUMN_USAGE
  WHERE TABLE_SCHEMA = 'rca_geo'
  AND TABLE_NAME IN ('ope_users_sectors', 'ope_pass')
  AND COLUMN_NAME = 'fk_user';
  ```
  Résultat attendu :
  - `ope_users_sectors.fk_user → ope_users.id`
  - `ope_pass.fk_user → ope_users.id`

- [ ] **1.3** Appliquer sur **rca_geo** (RECETTE) après validation sur dva_geo

- [ ] **1.4** Appliquer sur **pra_geo** (PRODUCTION) après validation sur rca_geo

### ⚠️ Important

- Les données existantes doivent être **nettoyées avant** d'appliquer le script
- Ou bien : recréer toutes les données avec la nouvelle migration
- Les FK `ON DELETE CASCADE` supprimeront automatiquement `ope_users_sectors` et `ope_pass` quand `ope_users` est supprimé

---

## ✅ Tâche 2 : Correction du script de migration2

### 📁 Fichiers concernés

1. `scripts/migration2/php/lib/SectorMigrator.php`
2. `scripts/migration2/php/lib/PassageMigrator.php`

### Actions

#### 2.1 SectorMigrator.php - Migration de ope_users_sectors

- [ ] **Ligne 253** : Changer de `users.id` vers `ope_users.id`

```php
// ❌ AVANT
':fk_user' => $us['fk_user'], // ID de users (table centrale)

// ✅ APRÈS
':fk_user' => $userMapping[$us['fk_user']], // ID de ope_users (mapping)
```

#### 2.2 PassageMigrator.php - Migration de ope_pass

- [ ] **Ligne 64-67** : Vérifier le mapping existe
- [ ] **Ligne 77** : Passer `ope_users.id` au lieu de `users.id`

```php
// ❌ AVANT (ligne 77)
$newPassId = $this->insertPassage($passage, $newOperationId, $newOpeSectorId, $passage['fk_user']);

// ✅ APRÈS
$newOpeUserId = $userMapping[$passage['fk_user']];
$newPassId = $this->insertPassage($passage, $newOperationId, $newOpeSectorId, $newOpeUserId);
```

- [ ] **Ligne 164** : Utiliser le paramètre `$userId` qui sera maintenant `ope_users.id`

```php
// ❌ AVANT
':fk_user' => $userId, // ID de users (table centrale)

// ✅ APRÈS (le paramètre $userId contiendra déjà ope_users.id)
':fk_user' => $userId, // ID de ope_users
```

- [ ] **Ligne 71** : Corriger `verifyUserSectorAssociation` pour vérifier avec `ope_users.id`

```php
// ❌ AVANT
if (!$this->verifyUserSectorAssociation($newOperationId, $passage['fk_user'], $newOpeSectorId)) {

// ✅ APRÈS
if (!$this->verifyUserSectorAssociation($newOperationId, $newOpeUserId, $newOpeSectorId)) {
```

#### 2.3 Tester la migration complète

- [ ] **Sur dva_geo** : Vider les données d'une entité et relancer la migration
  ```bash
  php php/migrate_from_backup.php --mode=entity --entity-id=5
  ```

- [ ] **Vérifier** dans la base que :
  - `ope_users_sectors.fk_user` contient des IDs de `ope_users.id`
  - `ope_pass.fk_user` contient des IDs de `ope_users.id`
  - Les valeurs correspondent bien au mapping

- [ ] **Vérifier** qu'on peut supprimer une opération et que tout part avec (CASCADE)
  ```sql
  DELETE FROM operations WHERE id = 850;
  -- Doit supprimer automatiquement :
  -- - ope_users (ON DELETE CASCADE depuis operations)
  -- - ope_users_sectors (ON DELETE CASCADE depuis ope_users)
  -- - ope_pass (ON DELETE CASCADE depuis ope_users)
  -- - ope_sectors (ON DELETE CASCADE depuis operations)
  ```

---

## ✅ Tâche 3 : Vérifications API

### Impact sur les endpoints API

#### 3.1 Vérifier les requêtes utilisant `ope_pass.fk_user`

- [ ] **Rechercher** tous les endpoints qui lisent `ope_pass.fk_user`
  ```bash
  grep -r "ope_pass.*fk_user" src/Controllers/
  grep -r "fk_user.*ope_pass" src/Controllers/
  ```

- [ ] **Vérifier** que ces endpoints :
  - Font-ils des JOIN avec `users` via `ope_pass.fk_user` ?
  - Si OUI : Ajouter un JOIN via `ope_users` :
    ```sql
    -- ❌ AVANT
    SELECT op.*, u.encrypted_name
    FROM ope_pass op
    JOIN users u ON op.fk_user = u.id

    -- ✅ APRÈS
    SELECT op.*, u.encrypted_name
    FROM ope_pass op
    JOIN ope_users ou ON op.fk_user = ou.id
    JOIN users u ON ou.fk_user = u.id
    ```

#### 3.2 Vérifier les requêtes utilisant `ope_users_sectors.fk_user`

- [ ] **Rechercher** tous les endpoints qui lisent `ope_users_sectors.fk_user`
  ```bash
  grep -r "ope_users_sectors.*fk_user" src/Controllers/
  ```

- [ ] **Vérifier** la même chose : si JOIN avec `users`, ajouter passage par `ope_users`

#### 3.3 Endpoints probablement concernés

À vérifier :
- [ ] `OperationController` - Liste des utilisateurs d'une opération
- [ ] `PassageController` - Liste/détails des passages
- [ ] `SectorController` - Liste des secteurs avec utilisateurs affectés
- [ ] Tout endpoint retournant des statistiques par utilisateur

---

## ✅ Tâche 4 : Corrections API - Response JSON Login

### Impact sur la réponse JSON du login

#### 4.1 Groupe `users_sectors` - Ajouter `ope_user_id`

**Problème identifié** : Flutter reçoit `users_sectors` avec `id` (users.id) mais les `passages` ont `fk_user` (ope_users.id). Le mapping est impossible.

**Solution** : Modifier la requête dans `LoginController.php` (lignes 426 et 1181) pour retourner les deux IDs :

```sql
-- ✅ APRÈS
SELECT DISTINCT
    u.id as user_id,           -- users.id (table centrale, pour gestion membres)
    ou.id as ope_user_id,      -- ope_users.id (pour lier avec passages/sectors)
    ou.first_name,
    u.encrypted_name,
    u.sect_name,
    us.fk_sector
FROM users u
JOIN ope_users ou ON u.id = ou.fk_user
JOIN ope_users_sectors us ON ou.id = us.fk_user AND ou.fk_operation = us.fk_operation
WHERE us.fk_sector IN ($sectorIdsString)
AND us.fk_operation = ?
AND us.chk_active = 1
AND u.chk_active = 1
AND u.id != ?
```

**Résultat JSON attendu** :
```json
{
  "user_id": 123,         // users.id (pour gestion des membres dans l'interface)
  "ope_user_id": 50,      // ope_users.id (pour lier avec passages.fk_user et sectors)
  "first_name": "Jane",
  "name": "Jane Smith",
  "sect_name": "Smith",
  "fk_sector": 456
}
```

**Usage Flutter** :
```dart
// Trouver les passages d'un utilisateur
passages.where((p) => p.fkUser == usersSectors[i].opeUserId)  // ✅ OK
```

- [ ] **Modifier** `LoginController.php` ligne 426 (méthode `login()`)
- [ ] **Modifier** `LoginController.php` ligne 1181 (méthode `checkSession()`)
- [ ] **Tester** la réponse JSON du login en mode admin

---

## ✅ Tâche 5 : Vérifications Flutter - Gestion des IDs

### Impact sur l'application mobile

#### 5.1 Modèles de données

- [x] **Vérifier** le modèle `UserSector` (ou équivalent)
  - Ajouter le champ `opeUserId` (int) pour stocker `ope_users.id`
  - Conserver `userId` (int) pour stocker `users.id`
  - ✅ **Fait** : `UserSectorModel` modifié avec les champs `userId` et `opeUserId`
  - ✅ **Fait** : Adaptateurs Hive régénérés avec `build_runner`

- [x] **Vérifier** le modèle `Passage` (ou équivalent)
  - Le champ `fkUser` pointe maintenant vers `ope_users.id`
  - ✅ **Fait** : `PassageModel.fkUser` pointe déjà vers `ope_users.id`

- [x] **Vérifier** le modèle `User`
  - Ajouter le champ `opeUserId` (int?) pour stocker l'ID de l'utilisateur dans `ope_users`
  - ✅ **Fait** : `UserModel` modifié avec `opeUserId` (@HiveField(20))
  - ✅ **Fait** : `CurrentUserService` expose `opeUserId` via getter

#### 5.2 Gestion des secteurs (Mode Admin)

- [x] **Création de secteur**
  - L'API crée dans `ope_sectors`
  - Attribution des users : utiliser `ope_user_id` (pas `user_id`)
  - Endpoint : `POST /api/sectors`
  - Body : `{ ..., users: [50, 51, 52] }` ← IDs de `ope_users`
  - ✅ **Fait** : `SectorDialog` utilise `userSector.opeUserId` pour l'attribution
  - ✅ **Fait** : Liste dédupliquée des membres depuis `UserSectorModel`

- [x] **Modification de secteur**
  - Attribution des users : utiliser `ope_user_id`
  - Endpoint : `PUT /api/sectors/:id`
  - Body : `{ ..., users: [50, 51, 52] }` ← IDs de `ope_users`
  - ✅ **Fait** : `SectorDialog` utilise `userSector.opeUserId` pour l'attribution

- [ ] **Suppression de secteur**
  - L'API supprime dans `ope_pass`, `ope_users_sectors` et `ope_sectors`
  - CASCADE gère automatiquement les dépendances
  - Endpoint : `DELETE /api/sectors/:id`

#### 5.3 Gestion des membres (Mode Admin)

- [ ] **Création de membre**
  - L'API crée dans `users` (table centrale)
  - L'API crée aussi dans `ope_users` pour l'opération active
  - **Réponse attendue** :
    ```json
    {
      "status": "success",
      "user": {
        "id": 123,              // users.id
        "ope_user_id": 50,      // ope_users.id (nouveau)
        "first_name": "John",
        "name": "John Doe",
        ...
      }
    }
    ```
  - Endpoint : `POST /api/users`
  - Flutter stocke les 2 IDs : `userId` et `opeUserId`

- [ ] **Modification de membre**
  - L'API met à jour `users` (table centrale)
  - L'API met à jour aussi `ope_users` pour l'opération active
  - Endpoint : `PUT /api/users/:id`

- [ ] **Suppression de membre**
  - L'API supprime de `ope_users` (opération active)
  - L'API supprime de `users` (table centrale)
  - CASCADE supprime automatiquement `ope_users_sectors` et `ope_pass`
  - Endpoint : `DELETE /api/users/:id?transfer_to=XX`

#### 5.4 Gestion des passages (Mode Admin & User)

- [x] **Création de passage**
  - Attribution automatique du `ope_sectors.id` le plus proche
  - Attribution du `ope_users.id` (utilisateur connecté ou sélectionné)
  - Endpoint : `POST /api/passages`
  - Body : `{ ..., fk_user: 50, fk_sector: 456 }` ← IDs de `ope_users` et `ope_sectors`
  - ✅ **Fait** : `PassageRepository.createPassage()` utilise `CurrentUserService.instance.opeUserId`

- [x] **Modification de passage**
  - Attribution du `ope_users.id` si changement d'utilisateur
  - Endpoint : `PUT /api/passages/:id`
  - Body : `{ ..., fk_user: 50 }` ← ID de `ope_users`
  - ✅ **Fait** : `PassageRepository.updatePassage()` utilise `CurrentUserService.instance.opeUserId`
  - ✅ **Fait** : Mode offline et online correctement implémentés

- [ ] **Suppression de passage**
  - L'API supprime dans `ope_pass`
  - Endpoint : `DELETE /api/passages/:id`

#### 5.5 Interface Flutter - Mapping des IDs

**Scénarios à gérer** :

1. **Affichage des secteurs avec utilisateurs affectés** :
   ```dart
   // Utiliser usersSectors[i].opeUserId pour lier avec passages
   final userPassages = passages.where((p) =>
     p.fkUser == usersSectors[i].opeUserId &&
     p.fkSector == sector.id
   ).toList();
   ```
   - ✅ **Fait** : `ActivityChart` filtre par secteurs assignés (pas par userId)
   - ✅ **Fait** : `MapPage` utilise `userSector.opeUserId` pour filtrer les secteurs

2. **Attribution d'un passage à un utilisateur** :
   ```dart
   // Envoyer ope_user_id dans la requête API
   await apiService.createPassage({
     ...passageData,
     'fk_user': userSector.opeUserId,  // ope_users.id
     'fk_sector': sector.id
   });
   ```
   - ✅ **Fait** : `PassageRepository` utilise `CurrentUserService.instance.opeUserId`

3. **Affichage du nom d'un utilisateur depuis un passage** :
   ```dart
   // Chercher dans usersSectors avec ope_user_id
   final userSector = usersSectors.firstWhere(
     (us) => us.opeUserId == passage.fkUser,
     orElse: () => null
   );
   final userName = userSector?.name ?? 'Inconnu';
   ```
   - ✅ **Fait** : `PaymentSummaryCard` utilise `opeUserId` pour filtrer les règlements
   - ✅ **Fait** : `HistoryPage` utilise `opeUserId` pour filtrer et vérifier les permissions
   - ✅ **Fait** : `UserFieldModePage` compare `passage.fkUser` avec `opeUserId`

4. **Gestion des membres** :
   ```dart
   // Conserver les 2 IDs lors de la création
   final newMember = await apiService.createUser(userData);
   membres.add(Member(
     userId: newMember['id'],           // users.id
     opeUserId: newMember['ope_user_id'], // ope_users.id
     ...
   ));
   ```
   - ⚠️ **À tester** : Vérifier la réception de `ope_user_id` dans les réponses API

5. **Statistiques par utilisateur** :
   - ✅ **Fait** : `PassageRepository.getStatisticsByUser()` utilise `passage.fkUser` (ope_users.id)
   - ✅ **Fait** : Variable renommée `opeUserId` pour clarté

6. **Services et chargement de données** :
   - ✅ **Fait** : `DataLoadingService` utilise `opeUserId` pour les clés Hive des secteurs
   - ✅ **Fait** : `CurrentUserService.opeUserId` accessible globalement

#### 5.6 Tests d'affichage

- [ ] Tester l'affichage des passages avec noms d'utilisateurs
- [ ] Tester l'affichage des secteurs avec utilisateurs affectés
- [ ] Tester la création d'un membre (vérifier que les 2 IDs sont reçus)
- [ ] Tester la suppression d'un membre (vérifier le transfert de passages)
- [ ] Tester la création d'un secteur avec attribution d'utilisateurs
- [ ] Tester la création d'un passage avec attribution d'utilisateur
- [ ] Tester la suppression d'une opération (doit tout nettoyer)

---

### 📝 Modifications Flutter effectuées (2025-01-23)

#### Fichiers modifiés

1. **`lib/core/data/models/user_sector_model.dart`** (lignes 11-50)
   - Ajout du champ `opeUserId` (@HiveField(5))
   - Renommage du champ `id` → `userId` (@HiveField(0))
   - Mise à jour de `fromJson()` pour parser les deux IDs
   - Mise à jour de `toJson()` et `copyWith()`

2. **`lib/core/data/models/user_model.dart`** (ligne 122)
   - Ajout du champ `opeUserId` (@HiveField(20))
   - Mise à jour de `fromJson()`, `toJson()` et `copyWith()`

3. **`lib/core/services/current_user_service.dart`** (ligne 24)
   - Ajout du getter `opeUserId` pour accès global

4. **`lib/core/services/data_loading_service.dart`** (ligne 451)
   - Utilisation de `userSector.opeUserId` pour les clés Hive

5. **`lib/presentation/dialogs/sector_dialog.dart`** (lignes 86, 538-542, 999-1007)
   - Changement de source : `MembreModel` → `UserSectorModel`
   - Utilisation de `opeUserId` pour la sélection des membres
   - Déduplication des membres (un user peut être sur plusieurs secteurs)

6. **`lib/presentation/pages/history_page.dart`** (lignes 62-63, 126, 997-1009, 1023, 1608, 1629)
   - Ajout de `currentOpeUserId` (ligne 62)
   - Utilisation de `UserSectorModel` avec déduplication
   - Comparaison `passage.fkUser == currentOpeUserId` pour filtrage et permissions

7. **`lib/presentation/user/user_field_mode_page.dart`** (ligne 999-1002)
   - Correction : `passage.fkUser == userRepository.getCurrentUser()?.opeUserId`

8. **`lib/presentation/widgets/charts/payment_summary_card.dart`** (ligne 425, 428-430)
   - Utilisation de `currentUser?.opeUserId` pour filtrer les règlements utilisateur

9. **`lib/core/repositories/passage_repository.dart`** (lignes 105, 384-388, 422-426, 577)
   - Renommage du paramètre : `getPassagesByUser(int opeUserId)`
   - Utilisation de `CurrentUserService.instance.opeUserId` pour création/modification
   - Renommage de variable : `userId` → `opeUserId` dans les statistiques

10. **`lib/presentation/pages/map_page.dart`** (lignes 792-795)
    - **ERREUR CRITIQUE CORRIGÉE** : `us.id` → `us.opeUserId`
    - Utilisation de `CurrentUserService.instance.opeUserId`

11. **`lib/presentation/pages/home_page.dart`** (lignes 22-32)
    - Suppression de variable inutilisée : `currentUserId`

#### Adaptateurs générés

- **`lib/core/data/models/user_sector_model.g.dart`** - Régénéré avec `build_runner`
- **`lib/core/data/models/user_model.g.dart`** - Régénéré avec `build_runner`

#### Widgets vérifiés (pas de modification nécessaire)

- `PassageSummaryCard` - Affiche tous les passages déjà filtrés par l'API
- `ActivityChart` - Filtre par secteurs assignés, pas par userId
- `SectorDistributionCard` - Affiche tous les secteurs sans filtrage utilisateur
- `MembersBoardPassages` - `membre.id` représente déjà `ope_users.id`

#### Analyse statique

```bash
flutter analyze
```
- ✅ **0 erreur de compilation**
- ℹ️ 33 avertissements de style (info uniquement)

---

## 📋 Ordre d'exécution recommandé

1. ✅ **Corriger le code de migration2** (PHP)
2. ✅ **Tester sur dva_geo** avec schéma modifié
3. ✅ **Vérifier l'API** sur dva_geo
4. ✅ **Vérifier Flutter** avec dva_geo
5. 🚀 **Déployer le schéma SQL** sur rca_geo
6. 🚀 **Déployer le code** sur rca_geo
7. ✅ **Tester en recette**
8. 🚀 **Déployer en production** (pra_geo)

---

## 🔍 Requêtes SQL utiles pour vérification

### Vérifier les contraintes FK actuelles

```sql
SELECT
    TABLE_NAME,
    COLUMN_NAME,
    CONSTRAINT_NAME,
    REFERENCED_TABLE_NAME,
    REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND (TABLE_NAME = 'ope_pass' OR TABLE_NAME = 'ope_users_sectors')
AND COLUMN_NAME = 'fk_user';
```

### Vérifier l'intégrité des données après migration

```sql
-- Vérifier que tous les fk_user de ope_pass existent dans ope_users
SELECT COUNT(*) as orphans
FROM ope_pass op
LEFT JOIN ope_users ou ON op.fk_user = ou.id
WHERE ou.id IS NULL;
-- Résultat attendu : 0

-- Vérifier que tous les fk_user de ope_users_sectors existent dans ope_users
SELECT COUNT(*) as orphans
FROM ope_users_sectors ous
LEFT JOIN ope_users ou ON ous.fk_user = ou.id
WHERE ou.id IS NULL;
-- Résultat attendu : 0
```

### Tester la suppression en cascade

```sql
-- Compter avant suppression
SELECT
    (SELECT COUNT(*) FROM ope_users WHERE fk_operation = 850) as ope_users_count,
    (SELECT COUNT(*) FROM ope_users_sectors WHERE fk_operation = 850) as ope_users_sectors_count,
    (SELECT COUNT(*) FROM ope_pass WHERE fk_operation = 850) as ope_pass_count,
    (SELECT COUNT(*) FROM ope_sectors WHERE fk_operation = 850) as ope_sectors_count;

-- Supprimer l'opération
DELETE FROM operations WHERE id = 850;

-- Vérifier que tout a été supprimé (doit retourner 0 partout)
SELECT
    (SELECT COUNT(*) FROM ope_users WHERE fk_operation = 850) as ope_users_count,
    (SELECT COUNT(*) FROM ope_users_sectors WHERE fk_operation = 850) as ope_users_sectors_count,
    (SELECT COUNT(*) FROM ope_pass WHERE fk_operation = 850) as ope_pass_count,
    (SELECT COUNT(*) FROM ope_sectors WHERE fk_operation = 850) as ope_sectors_count;
```

---

## 📝 Notes importantes

### Avantages de cette architecture

✅ **Isolation complète** : Supprimer une opération supprime tout (ope_users, secteurs, passages)
✅ **Performance** : Pas de jointures complexes avec la table centrale `users`
✅ **Historique** : Les données d'une opération sont figées dans le temps
✅ **Simplicité** : Requêtes plus simples, moins de risques d'incohérences

### Implications

⚠️ **Duplication** : Un utilisateur travaillant sur 3 opérations aura 3 entrées dans `ope_users`
⚠️ **Taille** : La table `ope_users` sera plus volumineuse
⚠️ **Jointures** : Pour remonter aux infos de la table `users`, il faut passer par `ope_users.fk_user`

### Rétrocompatibilité

❌ Ce changement **CASSE** la compatibilité avec les données existantes
✅ Nécessite une **re-migration complète** de toutes les entités après modification du schéma
✅ Ou bien : script de transformation des données existantes (plus complexe)

---

## 🎯 Statut

- [ ] Schéma SQL modifié sur dva_geo
- [ ] Code migration2 corrigé
- [ ] API vérifiée et corrigée
- [x] **Flutter vérifié et corrigé** ✅ (2025-01-23)
  - Modèles de données mis à jour (UserSectorModel, UserModel)
  - Services mis à jour (CurrentUserService, DataLoadingService)
  - Repositories mis à jour (PassageRepository)
  - Pages et widgets mis à jour (11 fichiers)
  - Adaptateurs Hive régénérés
  - Analyse statique : 0 erreur
- [ ] Tests complets sur dva_geo (en attente API)
- [ ] Déploiement rca_geo
- [ ] Déploiement pra_geo
