- 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>
25 KiB
GESTION-SECTORS.md
Vue d'ensemble
Ce document décrit le système de gestion des secteurs dans l'API Geosector, incluant la connexion aux bases de données d'adresses externes, la validation des limites départementales, et le processus complet de création de secteurs avec génération automatique des passages.
Évolutions récentes
Gestion des sessions
- La session stocke maintenant
entity_iddepuisfk_entitelors du login - Méthode
Session::getEntityId()disponible pour récupérer l'ID de l'entité - Utilisation cohérente de l'entity_id dans toutes les opérations
Gestion des passages orphelins
- Les passages avec
fk_sector = 0sont automatiquement intégrés au nouveau secteur - Évite les doublons pour les passages ayant déjà une
fk_adresse - Mise à jour atomique dans la transaction de création du secteur
Architecture multi-bases
Bases de données principales
-
Base principale (
geosector_app)- Contient toutes les tables de l'application
- Tables concernées :
ope_sectors,sectors_adresses,ope_pass,ope_users_sectors,x_departements_contours
-
Base adresses (dans conteneurs maria3/maria4)
- DVA : maria3 (13.23.33.4) - base
adresses- User :
adr_geo_user/d66,AdrGeoDev.User
- User :
- RCA : maria3 (13.23.33.4) - base
adresses- User :
adr_geo_user/d66,AdrGeoRec.User
- User :
- PROD : maria4 (13.23.33.4) - base
adresses- User :
adr_geo_user/d66,AdrGeoPrd.User
- User :
- Tables par département :
cp22,cp23, etc.
- DVA : maria3 (13.23.33.4) - base
-
Base bâtiments (dans conteneurs maria3/maria4)
- DVA : maria3 (13.23.33.4) - base
batiments- User :
adr_geo_user/d66,AdrGeoDev.User
- User :
- RCA : maria3 (13.23.33.4) - base
batiments- User :
adr_geo_user/d66,AdrGeoRec.User
- User :
- PROD : maria4 (13.23.33.4) - base
batiments- User :
adr_geo_user/d66,AdrGeoPrd.User
- User :
- Tables par département :
bat22,bat23, etc. - Colonnes principales :
batiment_groupe_id,cle_interop_adr,nb_log,nb_niveau,residence,altitude_sol_mean - Lien avec adresses :
bat{dept}.cle_interop_adr = cp{dept}.id
- DVA : maria3 (13.23.33.4) - base
Configuration
Dans src/Config/AppConfig.php :
// DÉVELOPPEMENT
'addresses_database' => [
'host' => '13.23.33.4', // Container maria3 sur IN3
'name' => 'adresses',
'username' => 'adr_geo_user',
'password' => 'd66,AdrGeoDev.User',
],
// RECETTE
'addresses_database' => [
'host' => '13.23.33.4', // Container maria3 sur IN3
'name' => 'adresses',
'username' => 'adr_geo_user',
'password' => 'd66,AdrGeoRec.User',
],
// PRODUCTION
'addresses_database' => [
'host' => '13.23.33.4', // Container maria4 sur IN4
'name' => 'adresses',
'username' => 'adr_geo_user',
'password' => 'd66,AdrGeoPrd.User',
],
// DÉVELOPPEMENT - Bâtiments
'buildings_database' => [
'host' => '13.23.33.4', // Container maria3 sur IN3
'name' => 'batiments',
'username' => 'adr_geo_user',
'password' => 'd66,AdrGeoDev.User',
],
// RECETTE - Bâtiments
'buildings_database' => [
'host' => '13.23.33.4', // Container maria3 sur IN3
'name' => 'batiments',
'username' => 'adr_geo_user',
'password' => 'd66,AdrGeoRec.User',
],
// PRODUCTION - Bâtiments
'buildings_database' => [
'host' => '13.23.33.4', // Container maria4 sur IN4
'name' => 'batiments',
'username' => 'adr_geo_user',
'password' => 'd66,AdrGeoPrd.User',
],
Gestion des contours départementaux
Table x_departements_contours
Création manuelle de la table (sans DROP permissions) :
CREATE TABLE IF NOT EXISTS `x_departements_contours` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code_dept` varchar(3) NOT NULL,
`nom_dept` varchar(100) NOT NULL,
`contour` GEOMETRY NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `idx_code_dept` (`code_dept`),
SPATIAL KEY `idx_contour` (`contour`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='Contours géographiques des départements français';
Import des contours
- Fichier source :
docs/contour-des-departements.geojson(depuis data.gouv.fr) - Import automatique : Uniquement lors de la connexion de l'admin
d6soft - Script :
scripts/init_departements_contours.php - Résultat : 96 départements importés avec support Polygon et MultiPolygon
Services principaux
AddressService
Gère la récupération des adresses depuis la base externe :
class AddressService {
// Récupère toutes les adresses dans un polygone
public function getAddressesInPolygon(array $coordinates, ?int $entityId = null): array
// Compte les adresses dans un polygone
public function countAddressesInPolygon(array $coordinates, ?int $entityId = null): int
}
Caractéristiques :
- Détection automatique des départements touchés par le secteur
- Interrogation de toutes les tables cp{dept} concernées
- Gestion des secteurs multi-départements
DepartmentBoundaryService
Vérifie les limites départementales des secteurs :
class DepartmentBoundaryService {
// Vérifie si un secteur est contenu dans un département
public function checkSectorInDepartment(array $sectorCoordinates, string $departmentCode): array
// Liste tous les départements touchés par un secteur
public function getDepartmentsForSector(array $sectorCoordinates): array
}
Retour type :
[
'is_contained' => bool,
'message' => string,
'intersecting_departments' => [
['code_dept' => '22', 'nom_dept' => 'Côtes-d\'Armor', 'percentage_overlap' => 75.5],
['code_dept' => '29', 'nom_dept' => 'Finistère', 'percentage_overlap' => 24.5]
]
]
BuildingService
Enrichit les adresses avec les données bâtiments :
namespace App\Services;
class BuildingService {
// Enrichit une liste d'adresses avec les métadonnées des bâtiments
public function enrichAddresses(array $addresses): array
}
Fonctionnement :
- Connexion à la base
batimentsexterne - Interrogation des tables
bat{dept}par département - JOIN sur
bat{dept}.cle_interop_adr = cp{dept}.id - Ajout des métadonnées :
fk_batiment,fk_habitat,nb_niveau,nb_log,residence,alt_sol - Fallback :
fk_habitat=1(maison individuelle) si pas de bâtiment trouvé
Données retournées :
[
'id' => 'cp22.123456',
'numero' => '10',
'voie' => 'Rue Victor Hugo',
'code_postal' => '22000',
'commune' => 'Saint-Brieuc',
'latitude' => 48.5149,
'longitude' => -2.7658,
// Données bâtiment enrichies :
'fk_batiment' => 'BAT_123456', // null si maison
'fk_habitat' => 2, // 1=individuel, 2=collectif
'nb_niveau' => 4, // null si maison
'nb_log' => 12, // null si maison
'residence' => 'Résidence Les Pins', // '' si maison
'alt_sol' => 25.5 // null si maison
]
Processus de création de secteur
1. Structure du payload
{
"user_id": 123,
"fk_entite": 45,
"operation_id": 789,
"sector": {
"id": 0,
"libelle": "Secteur Centre-Ville",
"color": "#FF5733",
"sector": "48.117266/-1.6777926#48.118500/-1.6750000#..."
},
"users": [12, 34, 56, 78]
}
2. Étapes de création
- Validation des données et de l'opération
- Vérification des limites départementales (warning si débordement)
- Début de transaction pour garantir la cohérence des données
- Insertion du secteur dans
ope_sectors - Affectation des utilisateurs dans
ope_users_sectorsavec :fk_operation,fk_user,fk_sectorcreated_at,fk_user_creat,chk_active = 1
- Intégration des passages orphelins :
- Recherche des passages avec
fk_sector = 0dans le polygone - Mise à jour de leur
fk_sectorvers le nouveau secteur - Exclusion des passages ayant déjà une
fk_adresse
- Recherche des passages avec
- Récupération des adresses via
AddressService::getAddressesInPolygon() - Enrichissement avec données bâtiments via
AddressService::enrichAddressesWithBuildings() - Stockage des adresses dans
sectors_adressesavec colonnes bâtiment :fk_batiment,fk_habitat,nb_niveau,nb_log,residence,alt_sol
- Création des passages dans
ope_pass:
- Maisons individuelles (fk_habitat=1) : 1 passage par adresse
- Immeubles (fk_habitat=2) : nb_log passages par adresse (1 par appartement)
- Champs ajoutés :
residence,appt(numéro 1 à nb_log),fk_habitat - Affectés au premier utilisateur de la liste
- Avec toutes les FK nécessaires (entité, opération, secteur, user)
- Données d'adresse complètes
- Commit de la transaction ou rollback en cas d'erreur
Processus de modification de secteur
1. Structure du payload UPDATE
{
"libelle": "Secteur Centre-Ville Modifié",
"color": "#00FF00",
"sector": "48.117266/-1.6777926#48.118500/-1.6750000#...",
"users": [12, 34],
"chk_adresses_change": 1
}
2. Paramètre chk_adresses_change
Valeurs :
0: Ne pas recalculer les adresses et passages (modification simple)1: Recalculer les adresses et passages (défaut)
Cas d'usage :
chk_adresses_change = 0
Modification rapide sans toucher aux adresses/passages :
- ✅ Modification du libellé
- ✅ Modification de la couleur
- ✅ Modification des coordonnées du polygone (visuel uniquement)
- ✅ Modification des membres affectés
- ❌ Pas de recalcul des adresses dans sectors_adresses
- ❌ Pas de mise à jour des passages (orphelins, créés, supprimés)
- ❌ Réponse sans passages_sector (tableau vide)
Utilité : Permet aux admins de corriger rapidement un libellé, une couleur, ou d'ajuster légèrement le périmètre visuel sans déclencher un recalcul complet qui pourrait prendre plusieurs secondes.
Réponse API :
{
"status": "success",
"message": "Secteur modifié avec succès",
"sector": { "id": 123, "libelle": "...", "color": "...", "sector": "..." },
"passages_sector": [], // Vide car chk_adresses_change = 0
"passages_orphaned": 0,
"passages_deleted": 0,
"passages_updated": 0,
"passages_created": 0,
"passages_total": 0,
"users_sectors": [...]
}
chk_adresses_change = 1 (défaut)
Modification complète avec recalcul :
- ✅ Modification du libellé/couleur/polygone
- ✅ Modification des membres
- ✅ Suppression et recréation de sectors_adresses
- ✅ Application des règles de gestion des bâtiments
- ✅ Mise en orphelin des passages hors périmètre
- ✅ Création de nouveaux passages pour nouvelles adresses
3. Réponse API pour CREATE
Format standardisé : Les données sont placées à la racine, sans groupe "data" intermédiaire.
{
"status": "success",
"message": "Secteur créé avec succès",
"sector": {
"id": 123,
"libelle": "Secteur Centre-Ville",
"color": "#FF5733",
"sector": "48.117266/-1.6777926#48.118500/-1.6750000#..."
},
"passages_sector": [
{
"id": 456,
"fk_operation": 789,
"fk_sector": 123,
"fk_user": 12,
"fk_type": 2,
"fk_adresse": "cp22.12345",
"passed_at": null,
"numero": "10",
"rue": "Rue de la Paix",
"rue_bis": "",
"ville": "Saint-Brieuc",
"residence": null,
"fk_habitat": null,
"appt": null,
"niveau": null,
"gps_lat": "48.117266",
"gps_lng": "-1.6777926",
"nom_recu": null,
"name": "", // Décrypté depuis encrypted_name
"remarque": null,
"email": "", // Décrypté depuis encrypted_email
"phone": "", // Décrypté depuis encrypted_phone
"montant": null,
"fk_type_reglement": null,
"email_erreur": null,
"nb_passages": null
}
],
"passages_integrated": 5, // Passages orphelins intégrés
"passages_created": 10, // Nouveaux passages créés
"users_sectors": [
{
"id": 12,
"first_name": "Jean",
"sect_name": "JDU",
"fk_sector": 123,
"name": "Dupont" // Décrypté depuis encrypted_name
}
]
}
4. Réponse API pour UPDATE
La réponse est identique à CREATE avec des compteurs supplémentaires :
{
"status": "success",
"message": "Secteur modifié avec succès",
"sector": {
"id": 123,
"libelle": "Secteur Centre-Ville Modifié",
"color": "#FF5733",
"sector": "48.117266/-1.6777926#48.118500/-1.6750000#..."
},
"passages_sector": [
// Liste complète de TOUS les passages actuels du secteur
],
"passages_orphaned": 3, // Passages mis en orphelin (hors polygone)
"passages_updated": 5, // Passages mis à jour avec fk_adresse
"passages_created": 10, // Nouveaux passages créés
"passages_total": 25, // Nombre total de passages dans le secteur
"users_sectors": [
// Liste des utilisateurs affectés
]
}
Notes importantes :
- Les champs sensibles (name, email, phone) sont stockés cryptés et décryptés à la volée
- La structure est identique entre CREATE et UPDATE pour faciliter l'intégration
- Tous les champs sont retournés, même s'ils sont null
- Code HTTP : 201 pour CREATE, 200 pour UPDATE
Gestion des secteurs multi-départements
Détection automatique
Le système détecte automatiquement quand un secteur touche plusieurs départements :
- Analyse spatiale : Utilisation de
ST_Intersectspour identifier tous les départements touchés - Calcul de pourcentage :
ST_Area(ST_Intersection)pour calculer le % de recouvrement - Interrogation multi-tables : Requête sur toutes les tables cp{dept} concernées
Exemple de secteur multi-départements
// Secteur à cheval sur 22 (Côtes-d'Armor) et 29 (Finistère)
$coordinates = [
[48.5778, -3.8280], // Morlaix (29)
[48.5778, -3.7280], // Vers l'est (22)
[48.4778, -3.7280],
[48.4778, -3.8280]
];
// Le système va automatiquement :
// 1. Détecter que le secteur touche 22 et 29
// 2. Interroger cp22 et cp29 pour les adresses
// 3. Créer les passages pour toutes les adresses trouvées
Tables de données
ope_sectors
id: Identifiant uniquelibelle: Nom du secteurcolor: Couleur d'affichagesector: Coordonnées (format lat/lng#lat/lng#...)fk_entite: Lien vers l'entité
sectors_adresses
fk_sector: Lien vers le secteurfk_adresse: ID de l'adresse dans la base externenumero,rue,rue_bis,cp,villegps_lat,gps_lng- Colonnes bâtiment :
fk_batiment: ID bâtiment (VARCHAR 50, null si maison)fk_habitat: 1=individuel, 2=collectif (TINYINT UNSIGNED)nb_niveau: Nombre d'étages (INT, null)nb_log: Nombre de logements (INT, null)residence: Nom résidence/copropriété (VARCHAR 75)alt_sol: Altitude sol en mètres (DECIMAL 10,2, null)
ope_pass (passages)
fk_operation,fk_sector,fk_user,fk_adressenumero,rue,rue_bis,villegps_lat,gps_lng- Colonnes bâtiment :
residence: Nom résidence (VARCHAR 75)appt: Numéro appartement (VARCHAR 10, saisie libre)niveau: Étage (VARCHAR 10, saisie libre)fk_habitat: 1=individuel, 2=collectif (TINYINT UNSIGNED)
fk_type: Type passage (2=à faire, autres valeurs pour fait/refus)encrypted_name,encrypted_email,encrypted_phone: Données cryptéescreated_at,fk_user_creat,chk_active
ope_users_sectors
fk_operation: Lien vers l'opérationfk_user: Lien vers l'utilisateur (ope_users)fk_sector: Lien vers le secteurcreated_at,fk_user_creat,chk_active
Règles de gestion des bâtiments lors de l'UPDATE
Principe général
Lors de la mise à jour d'un secteur, le système applique une logique intelligente pour gérer les passages en fonction du type d'habitat (maison/immeuble) et du nombre de logements.
Clé d'identification unique
Tous les passages sont identifiés par la clé : numero|rue|rue_bis|ville
Cette clé ne contient pas residence ni appt car ces champs sont en saisie libre par l'utilisateur.
Cas 1 : Maison individuelle (fk_habitat=1)
Si 0 passage existant :
→ INSERT 1 nouveau passage
- fk_habitat = 1
- residence = ''
- appt = ''
Si 1+ passages existants :
→ UPDATE le premier passage
- fk_habitat = 1
- residence = ''
→ Les autres passages restent INTACTS
(peuvent correspondre à plusieurs habitants saisis manuellement)
Cas 2 : Immeuble (fk_habitat=2)
Étape 1 : UPDATE systématique
→ UPDATE TOUS les passages existants à cette adresse
- fk_habitat = 2
- residence = sectors_adresses.residence (si non vide)
Étape 2a : Si nb_existants < nb_log (ex: 3 passages, nb_log=6)
→ INSERT (nb_log - nb_existants) nouveaux passages
- fk_habitat = 2
- residence = sectors_adresses.residence
- appt = '' (pas de numéro prédéfini)
- fk_type = 2 (à faire)
Résultat : 6 passages total (3 conservés + 3 créés)
Étape 2b : Si nb_existants > nb_log (ex: 10 passages, nb_log=6)
→ DELETE max (nb_existants - nb_log) passages
Conditions de suppression :
- fk_type = 2 (à faire)
- ET encrypted_name vide (non visité)
- Tri par created_at ASC (les plus anciens d'abord)
Résultat : Entre 6 et 10 passages (selon combien sont visités)
Points importants
✅ Préservation des données utilisateur :
apptetniveaune sont JAMAIS modifiés (saisie libre conservée)- Les passages visités (encrypted_name rempli) ne sont JAMAIS supprimés
✅ Mise à jour conditionnelle :
residenceest mis à jour uniquement si non vide dans sectors_adresses- Permet de conserver une saisie manuelle si la base bâtiments n'a pas l'info
✅ Gestion des transitions :
- Une adresse peut passer de maison (fk_habitat=1) à immeuble (fk_habitat=2) ou inversement
- La logique s'adapte automatiquement au nouveau type d'habitat
✅ Uniformisation GPS :
- Tous les passages d'une même adresse partagent les mêmes coordonnées GPS (gps_lat, gps_lng)
- Ces coordonnées proviennent de
sectors_adresses(enrichies depuis la base externeadresses) - Cette règle s'applique lors de la création et de la mise à jour avec
chk_adresses_change=1 - Garantit la cohérence géographique pour tous les passages d'un même immeuble
Exemple concret
Situation initiale :
- Adresse : "10 rue Victor Hugo, 22000 Saint-Brieuc"
- 8 passages existants (dont 3 visités)
- nb_log passe de 8 à 5
Actions :
- UPDATE les 8 passages → fk_habitat=2, residence="Les Chênes"
- Tentative suppression de (8-5) = 3 passages
- Recherche des passages avec fk_type=2 ET encrypted_name vide
- Suppose 5 passages non visités trouvés
- Suppression des 3 plus anciens non visités
- Résultat : 5 passages restants (3 visités + 2 non visités)
Logs et monitoring
Le système génère des logs détaillés pour :
- Nombre d'adresses trouvées par département
- Secteurs hors limites départementales
- Passages créés avec succès
- Erreurs de connexion aux bases d'adresses
- Performance des requêtes spatiales
Scripts de test
test_sector_departments.php: Test des limites départementalestest_addresses_connection.php: Test de connexion à la base d'adresses
Notes importantes
- Fail-safe : La création de secteur continue même si la base d'adresses est inaccessible
- Transactions :
- Toute la création est dans une transaction pour garantir la cohérence
- Toujours vérifier
inTransaction()avant d'appelerrollBack() - Gestion correcte des erreurs PDO avec try/catch
- Performance : Les requêtes spatiales utilisent des index spatiaux pour optimiser les performances
- Modification de secteur : Plus complexe car nécessite de gérer les passages existants (non implémenté)
- Paramètres SQL : Utiliser des noms uniques pour éviter l'erreur "Invalid parameter number"
- Jointures : Les données utilisateur viennent de la table
users, pasope_users(qui n'a pas nom/prenom)
Bilan de la gestion des adresses et passages
Vue d'ensemble du cycle de vie
Base Adresses (cp22, cp23...) → sectors_adresses → ope_pass
1. CRÉATION D'UN SECTEUR
Flux des données :
- Récupération des adresses depuis la base externe (
AddressService) - Intégration des passages orphelins (
fk_sector = NULL) situés dans le polygone - Stockage dans
sectors_adressesde toutes les adresses du polygone - Création automatique de passages (
ope_pass) pour chaque adresse SAUF celles déjà utilisées par les passages orphelins
Détails :
- Passages créés :
fk_type = 2,encrypted_name = ''(vide), affectés au premier utilisateur - Passages orphelins : mis à jour avec le nouveau
fk_sector - Évite les doublons : les adresses déjà utilisées par des passages orphelins ne génèrent pas de nouveau passage
2. MISE À JOUR D'UN SECTEUR
Processus de mise à jour :
- Mise à jour des attributs (libelle, color, sector)
- Mise à jour des membres affectés
- Suppression/recréation des adresses dans
sectors_adresses - Gestion intelligente des passages via
updatePassagesForSector:
Gestion des passages lors de l'UPDATE :
a) Vérification géographique des passages existants
- Pour chaque passage du secteur, vérification si ses coordonnées GPS sont dans le nouveau polygone
- Si DANS le polygone : Conservation du passage
- Si HORS du polygone : Mise en orphelin (
fk_sector = NULL)
b) Traitement des nouvelles adresses
Pour chaque adresse dans sectors_adresses :
- Vérification primaire : Recherche par
fk_adresse - Vérification secondaire : Si pas trouvé, recherche par
numero,rue_bis,rue,ville- Si trouvé → Mise à jour du
fk_adressedans le(s) passage(s)
- Si trouvé → Mise à jour du
- Création : Si aucun passage existant, création avec :
fk_type = 2,encrypted_name = ''- Affecté au premier utilisateur du secteur
- Toutes les données de l'adresse
3. SUPPRESSION D'UN SECTEUR
Traitement différencié des passages :
-
Passages "non visités" (
fk_type = 2ETencrypted_namevide) :- Suppression définitive de la base
- Ces passages correspondent aux adresses non visitées
-
Passages "visités" (tous les autres) :
- Mise à jour :
fk_sector = NULL - Deviennent des passages orphelins
- Conservent toutes leurs données (contact, montant, etc.)
- Mise à jour :
Autres suppressions :
- Suppression des affectations membres (
ope_users_sectors) - Suppression des adresses (
sectors_adresses) - Suppression du secteur lui-même
Tableau récapitulatif
| Action | sectors_adresses | ope_pass dans polygone | ope_pass hors polygone | Nouvelles adresses |
|---|---|---|---|---|
| CREATE | Insertion depuis base externe | - | Intégration si orphelins | Création automatique de passages |
| UPDATE | Suppression/recréation | Conservation | Mise en orphelin | Création si pas de passage existant |
| DELETE | Suppression totale | Suppression si non visités / Orphelin si visités | - | - |
Points d'attention
- Cohérence géographique : Lors d'un UPDATE, le système vérifie automatiquement et met en orphelin les passages hors du nouveau périmètre
- Passages orphelins : Peuvent être réintégrés lors de la création d'un nouveau secteur englobant
- Mise à jour du fk_adresse : Lors d'un UPDATE, les passages existants peuvent recevoir leur
fk_adresses'ils correspondent à une adresse - Performance : La création/mise à jour génère potentiellement des milliers de passages selon la densité d'adresses
Erreurs communes et solutions
"There is no active transaction"
- Cause : Appel à
rollBack()sans transaction active - Solution : Vérifier
$db->inTransaction()avant rollback
"Column not found: fk_address"
- Cause : La colonne s'appelle
fk_adresse(avec 'e') - Solution : Corriger les noms de colonnes dans les requêtes
"Invalid parameter number"
- Cause : Réutilisation du même nom de paramètre dans une requête
- Solution : Utiliser des noms uniques (
:param1,:param2, etc.)
"Unknown column 'ou.nom'"
- Cause : La table
ope_usersn'a pas de colonnes nom/prenom - Solution : Joindre avec la table
usersqui contientencrypted_nameetfirst_name
"Class 'ApiService' not found"
- Cause : Import manquant dans le controller
- Solution : Ajouter
use App\Services\ApiService;etrequire_once