- 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>
15 KiB
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.idope_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 CASCADEsupprimeront automatiquementope_users_sectorsetope_passquandope_usersest supprimé
✅ Tâche 2 : Correction du script de migration2
📁 Fichiers concernés
scripts/migration2/php/lib/SectorMigrator.phpscripts/migration2/php/lib/PassageMigrator.php
Actions
2.1 SectorMigrator.php - Migration de ope_users_sectors
- Ligne 253 : Changer de
users.idversope_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.idau lieu deusers.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
$userIdqui sera maintenantope_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
verifyUserSectorAssociationpour vérifier avecope_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_usercontient des IDs deope_users.idope_pass.fk_usercontient des IDs deope_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_usergrep -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
usersviaope_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
- Font-ils des JOIN avec
3.2 Vérifier les requêtes utilisant ope_users_sectors.fk_user
-
Rechercher tous les endpoints qui lisent
ope_users_sectors.fk_usergrep -r "ope_users_sectors.*fk_user" src/Controllers/ -
Vérifier la même chose : si JOIN avec
users, ajouter passage parope_users
3.3 Endpoints probablement concernés
À vérifier :
OperationController- Liste des utilisateurs d'une opérationPassageController- Liste/détails des passagesSectorController- 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.phpligne 426 (méthodelogin()) - Modifier
LoginController.phpligne 1181 (méthodecheckSession()) - 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 stockerope_users.id - Conserver
userId(int) pour stockerusers.id
- Ajouter le champ
-
Vérifier le modèle
Passage(ou équivalent)- Le champ
fkUserpointe maintenant versope_users.id
- Le champ
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(pasuser_id) - Endpoint :
POST /api/sectors - Body :
{ ..., users: [50, 51, 52] }← IDs deope_users
- L'API crée dans
-
Modification de secteur
- Attribution des users : utiliser
ope_user_id - Endpoint :
PUT /api/sectors/:id - Body :
{ ..., users: [50, 51, 52] }← IDs deope_users
- Attribution des users : utiliser
-
Suppression de secteur
- L'API supprime dans
ope_pass,ope_users_sectorsetope_sectors - CASCADE gère automatiquement les dépendances
- Endpoint :
DELETE /api/sectors/:id
- L'API supprime dans
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_userspour 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 :
userIdetopeUserId
- L'API crée dans
-
Modification de membre
- L'API met à jour
users(table centrale) - L'API met à jour aussi
ope_userspour l'opération active - Endpoint :
PUT /api/users/:id
- L'API met à jour
-
Suppression de membre
- L'API supprime de
ope_users(opération active) - L'API supprime de
users(table centrale) - CASCADE supprime automatiquement
ope_users_sectorsetope_pass - Endpoint :
DELETE /api/users/:id?transfer_to=XX
- L'API supprime de
5.4 Gestion des passages (Mode Admin & User)
-
Création de passage
- Attribution automatique du
ope_sectors.idle plus proche - Attribution du
ope_users.id(utilisateur connecté ou sélectionné) - Endpoint :
POST /api/passages - Body :
{ ..., fk_user: 50, fk_sector: 456 }← IDs deope_usersetope_sectors
- Attribution automatique du
-
Modification de passage
- Attribution du
ope_users.idsi changement d'utilisateur - Endpoint :
PUT /api/passages/:id - Body :
{ ..., fk_user: 50 }← ID deope_users
- Attribution du
-
Suppression de passage
- L'API supprime dans
ope_pass - Endpoint :
DELETE /api/passages/:id
- L'API supprime dans
5.5 Interface Flutter - Mapping des IDs
Scénarios à gérer :
-
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(); -
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 }); -
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'; -
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 ... ));
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)
📋 Ordre d'exécution recommandé
- ✅ Corriger le code de migration2 (PHP)
- ✅ Tester sur dva_geo avec schéma modifié
- ✅ Vérifier l'API sur dva_geo
- ✅ Vérifier Flutter avec dva_geo
- 🚀 Déployer le schéma SQL sur rca_geo
- 🚀 Déployer le code sur rca_geo
- ✅ Tester en recette
- 🚀 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é
- Tests complets sur dva_geo
- Déploiement rca_geo
- Déploiement pra_geo