feat(commercial) : géolocalisation des adresses Tiers (lat/lng + géocodage BAN + pin ajustable) (ERP-122)

Ajoute la géolocalisation aux adresses Client et Fournisseur, socle de la
tournée commerciale (M6 field-sales).

Back :
- migration : latitude/longitude NUMERIC(10,7), geo_manual BOOLEAN, geocoded_at
  TIMESTAMPTZ sur client_address et supplier_address (+ COMMENT ON COLUMN FR)
- GeolocatableAddressInterface (Shared/Domain/Contract) implémenté par les deux
  entités ; bornes WGS84 validées (Range -90/90, -180/180, messages FR)
- GeocoderInterface + BanGeocoder (api-adresse.data.gouv.fr), branché via
  AddressGeocoder dans les processors ; géocodage auto au create/update
- RG-6.08 : geo_manual=true fige les coordonnées (pas de réécriture auto)
- symfony/http-client passe en dépendance de production

Front :
- AddressGeoPin (Leaflet + OSM) : marqueur déplaçable -> PATCH lat/lng +
  geoManual=true, bouton Re-géocoder, badges « à géolocaliser » / « pin manuel »
- intégration dans les blocs adresse Client et Fournisseur

Tests : PHPUnit (géocodage create, non-réécriture RG-6.08, mapping BAN, bornes) +
Vitest (drag du pin, badges, re-géocodage).
This commit is contained in:
Matthieu
2026-06-11 14:31:35 +02:00
parent 431d831c8b
commit de4aaa1d64
34 changed files with 1966 additions and 204 deletions
@@ -243,6 +243,10 @@ final class ColumnCommentsCatalog
'billing_email' => 'Email de facturation — obligatoire si is_billing, null sinon (RG-1.11, chk_client_address_billing_email).',
'billing_email_secondary' => '2e email de facturation, optionnel (max 2). Interdit hors facturation, normalise en minuscules (RG-1.21).',
'position' => 'Ordre d affichage de l adresse dans la liste du client (croissant).',
'latitude' => 'Latitude WGS84 de l adresse (geocodage BAN ou pin manuel). NULL = non geolocalisee, exclue du calcul de tournee (RG-6.05).',
'longitude' => 'Longitude WGS84 de l adresse (geocodage BAN ou pin manuel). NULL = non geolocalisee.',
'geo_manual' => 'Pin positionne/corrige a la main : si vrai, le geocodage auto ne reecrit plus les coordonnees (RG-6.08). Faux par defaut.',
'geocoded_at' => 'Date du dernier geocodage automatique reussi (NULL si jamais geocode ou pin 100% manuel).',
] + self::timestampableBlamableComments(),
'client_address_site' => [
@@ -332,6 +336,10 @@ final class ColumnCommentsCatalog
'bennes' => 'Nombre de bennes sur le site fournisseur (entier nullable) — specifique fournisseur.',
'triage_provider' => 'Le fournisseur est prestataire de triage sur cette adresse. Faux par defaut.',
'position' => 'Ordre d affichage de l adresse dans la liste du fournisseur (croissant).',
'latitude' => 'Latitude WGS84 de l adresse (geocodage BAN ou pin manuel). NULL = non geolocalisee, exclue du calcul de tournee (RG-6.05).',
'longitude' => 'Longitude WGS84 de l adresse (geocodage BAN ou pin manuel). NULL = non geolocalisee.',
'geo_manual' => 'Pin positionne/corrige a la main : si vrai, le geocodage auto ne reecrit plus les coordonnees (RG-6.08). Faux par defaut.',
'geocoded_at' => 'Date du dernier geocodage automatique reussi (NULL si jamais geocode ou pin 100% manuel).',
] + self::timestampableBlamableComments(),
'supplier_address_site' => [