Skip to main content

Agences, zones & GAB (données géographiques)

backend/src/controllers/agence.controller.js + zone.controller.js + gab.controller.js + frontend/src/views/backoffice/AgenciesView.vue + GabsView.vue + frontend/src/views/public/AgencesView.vue.

Dernière vérification contre le code

Vérifié le 2026-08-13. PostGIS est une extension PostgreSQL obligatoire (voir README backend) — sans elle, la création du schéma échoue.

1. Relations

zone 1—N agence (agence.id_zone, nullable) ; agence 1—N gab (gab.id_agence, nullable — un GAB peut exister sans agence de rattachement).

CHEF_ZONE filtre désormais réellement par zone — corrigé en deux temps le 2026-08-13

Le rôle backoffice CHEF_ZONE avait la permission view_all_agencies alors que users.id_zone n'était jamais lu comme filtre — il voyait donc toutes les agences de toutes les zones, comme un SUPER_ADMIN. Un module partagé a été introduit (backend/src/utils/agency-scope.js) :

  • agencyScopeWhere(user) — fragment where Prisma pour les listes : aucune restriction si vue globale, { agence: { id_zone: user.id_zone } } si CHEF_ZONE, sinon { id_agence: user.id_agence }.
  • canAccessAgency(user, agenceId) — même logique pour un accès à un enregistrement précis.

Ce premier correctif ne suffisait pas — testé et confirmé cassé en conditions réelles avant d'être vraiment corrigé :

  1. view_all_agencies court-circuitait tout le reste. agencyScopeWhere/canAccessAgency vérifient d'abord si l'utilisateur a cette permission (accès total, sans restriction) — or CHEF_ZONE l'avait dans seed/seed_roles.js, donc la branche "restreindre à la zone" n'était jamais atteinte. Corrigé en retirant view_all_agencies des permissions de CHEF_ZONE (permission déjà retirée en base sur l'environnement de dev, via node seed/seed_roles.js — pas le seed complet, qui aurait vidé la table agence).
  2. Une fois ce premier point corrigé, plus aucun dossier n'était visible pour CHEF_ZONE (régression inverse) : id_zone n'était jamais inclus dans le JWT émis par verify2fa (auth.controller.js) — seul id_agence l'était. agencyScopeWhere retombait donc sur { id_agence: user.id_agence }, et un CHEF_ZONE n'a justement pas de id_agence (null) — filtre qui ne matchait donc jamais rien. Corrigé en ajoutant id_zone au payload du JWT et à la réponse de verify2fa, ainsi qu'à la sélection Prisma de getMe.

Vérifié par un test de bout en bout (vrai login + 2FA + appels API) : un CHEF_ZONE de la zone Maritime voit les dossiers de son agence, et reçoit un 403 sur une agence d'une autre zone.

Appliqué à loan.controller.js (getLoanRequests, updateLoanStatus, previewStatusEmail, getLoanRequestById, getLoanRequestPdf) et account_requests.controller.js (getAll, getById). Appliqué aussi, un temps, à account.controller.js, qui s'est révélé être un doublon inutilisé et a été supprimé le même jour — voir Fiche détail d'une demande de compte.

2. Coordonnées géographiques : SQL brut, pas Prisma classique

La colonne PostGIS est Unsupported("geography") côté Prisma — impossible de créer/modifier via le client Prisma standard. agence.service.js/gab.service.js utilisent des requêtes SQL brutes (pool.query, driver pg) avec ST_SetSRID(ST_MakePoint($lng,$lat),4326)::geography pour créer/modifier, et relisent via ST_Y/ST_X pour reconvertir en lat/lng. Seules delete et les comptages passent par Prisma classique.

Piège pour un futur développeur

Toute évolution du CRUD agence/GAB touchant aux coordonnées doit passer par le SQL brut de agence.service.js/gab.service.js — ajouter un champ sans regarder ces fichiers risque de casser silencieusement la mise à jour des coordonnées (un prisma.agence.update() direct ignorerait la colonne geography).

Édition backoffice : deux champs numériques lat/lng simples (AgenciesView.vue, GabsView.vue) — pas de carte interactive pour la saisie.

3. Carte publique (AgencesView.vue)

Carte Leaflet (tuiles CartoDB Voyager), marqueurs distincts agence/siège/GAB, popup au clic, ajustement automatique de la vue sur les points valides, recherche texte (ville/désignation/zone) avec flyTo. Aucun calcul de distance ni "agence la plus proche" — ni géolocalisation navigateur ni ST_Distance ne sont utilisés nulle part dans le code actuel, malgré PostGIS qui le permettrait nativement.

4. Suppression d'agence : soft ou hard selon les dépendances

agence.remove fait un soft-delete (deleted_at) si l'agence est référencée par un dossier de prêt, une demande de compte, un GAB ou un utilisateur ; sinon un hard-delete avec suppression en cascade des numéros de téléphone associés (voir note ci-dessous). unsetOtherSieges garantit qu'une seule agence est marquée "siège" à la fois.

5. Note : sellphone_number

La table sellphone_number (numéros de téléphone de contact, rattachés à une agence ou à la fiche entreprise) n'a rien à voir avec de la vente malgré son nom — voir Contenu vitrine pour la clarification complète.

6. Pièges connus pour un futur développeur

  1. CHEF_ZONE ne filtrait pas réellement par zonecorrigé le 2026-08-13 (section 1). Si un nouveau contrôleur ajoute un filtre par agence, utiliser agencyScopeWhere/canAccessAgency (backend/src/utils/agency-scope.js) plutôt que de réécrire le pattern hasViewAll à la main.
  2. Toujours passer par agence.service.js/gab.service.js pour toute modification touchant lat/lng, jamais par Prisma direct.
  3. PostGIS reste une dépendance dure à l'installation — image Docker postgis/postgis:15-3.3 déjà utilisée dans docker-compose.yml.