Files
geo/app/docs/TODO-ISOLATION-OPERATIONS.md
pierre 2f5946a184 feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles
- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD)
  * DEV: Clés TEST Pierre (mode test)
  * REC: Clés TEST Client (mode test)
  * PROD: Clés LIVE Client (mode live)
- Ajout de la gestion des bases de données immeubles/bâtiments
  * Configuration buildings_database pour DEV/REC/PROD
  * Service BuildingService pour enrichissement des adresses
- Optimisations pages et améliorations ergonomie
- Mises à jour des dépendances Composer
- Nettoyage des fichiers obsolètes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 18:26:27 +01:00

20 KiB
Raw Blame History

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)

    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 :

    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
// ❌ 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
// ❌ 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
// ❌ 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
// ❌ 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

    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)

    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

    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 :
      -- ❌ 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

    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 :

-- ✅ 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 :

{
  "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 :

// 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

  • 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
  • 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
  • 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)

  • 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
  • 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 :
      {
        "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)

  • 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
  • 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 :

    // 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 :

    // 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 :

    // 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 :

    // 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 iduserId (@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 : MembreModelUserSectorModel
    • 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 : userIdopeUserId dans les statistiques
  10. lib/presentation/pages/map_page.dart (lignes 792-795)

    • ERREUR CRITIQUE CORRIGÉE : us.idus.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

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

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

-- 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

-- 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
  • 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