gigafibre-fsm/docs/features/legacy-dispatch-bridge.md
louispaulb b6831a1e48 Pont legacy : géocodage RQA via la recherche TRIGRAM (RPC search_addresses) + garde-fou anti-faux-positif
- address-search.js : expose searchAddressesRpc() → RPC Postgres `search_addresses` (pg_trgm), la MÊME
  recherche que l'autocomplete de disponibilité fibre. Trouve les rues que l'ilike manquait (générique géré
  par la colonne odonyme_recompose_long + phase 2 trigram), priorise les CP J0L/J0S (territoire).
- geocodeRQA() (bridge) bascule de l'ilike vers la RPC. Garde-fou : la phase 2 trigram dérive quand le
  civique est absent du RQA (« 2245 René-Vinet » → « Rue Grenet, Montréal »). On n'accepte un résultat que si
  le civique concorde + un token de nom de rue correspond + (territoire J0L/J0S OU CP/ville legacy concordants).
  Vérifié sur les données réelles : accepte 494 Av Curry / 3055 Routhier / 228 Principale / 61 Jean-François ;
  rejette René-Vinet→Grenet/Panet, chemin Ridge→Ferme, rue West→Perras (bons faux positifs écartés).
- Le faible compte RQA (8) = haute précision (l'ilike comptait 17 dont des faux positifs). Mapbox couvre le
  reste (rues neuves/civiques absents) ; ~109/125 (87 %) coordonnés ; les « aucune » = campings/villes mal écrites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:05:14 -04:00

8.3 KiB
Raw Blame History

Pont legacy → Dispatch (osTicket → Dispatch Job) — Handoff dev

Tire régulièrement les tickets ouverts assignés au compte « Tech Targo » dans la DB legacy (osTicket/MariaDB gestionclient) et crée/maj un Dispatch Job ERPNext pour les répartir (grille Planification / tableau Dispatch).

Pourquoi « Tech Targo » = staff id 3301

Dans le legacy, le travail terrain à dispatcher est assigné au compte générique « Tech Targo » (staff.id = 3301, username tech) — c'est le default_staff des départements techniciens (Installation/Réparation/Fibre). Filtre du pont : ticket.status='open' AND ticket.assign_to=3301. (~70 tickets au démarrage.) Override possible via LEGACY_TARGO_STAFF_ID.

Surfaces

Quoi
Module services/targo-hub/lib/legacy-dispatch-sync.js
Routage + scheduler services/targo-hub/server.js (/dispatch/legacy-sync, startSync() au boot)
Champ idempotence Custom Field Dispatch Job.legacy_ticket_id (dispatch-app/frappe-setup/setup_dispatch_custom_fields.py)
Conso côté UI Pool « à assigner » du tableau Dispatch + panneau « Jobs à assigner » de la Planification

Mapping ticket legacy → Dispatch Job

Dispatch Job Source legacy Notes
legacy_ticket_id ticket.id clé d'idempotence (lookup avant create) ; affiché dans le panneau détail
legacy_dept ticket_dept.name département granulaire → coloriage des cartes « comme legacy »
ticket_id 'LEG-' + ticket.id nom lisible du DJ
subject ticket.subject + adresse ajoutée si pas de Service Location
customer Customerlegacy_account_id = ticket.account_id 61/70 matchés ; sinon laissé vide
service_location + latitude/longitude Service Location du customer (ville qui matche) → pin carte
job_type ticket.dept_id → {Installation, Réparation, Retrait, Autre} valeurs valides du Select
scheduled_date ticket.due_date (epoch) converti America/Toronto (anti-décalage UTC)
start_time ticket.due_time HH:MM tel quel · am→08:00 · pm→13:00 · day→aucune
priority ticket.priority (1/2/≥3) → low / medium / high
duration_h défaut par type Install 2h · Répar 1.5h · autre 1h (le legacy n'en a pas)
status toujours open (pool ; PAS auto-assigné à un tech précis)

Comportement

  • Idempotent : 1 ticket legacy = 1 Dispatch Job (clé legacy_ticket_id). Re-run ⇒ 0 doublon.
  • Ne clobbe PAS le répartiteur : un DJ déjà assigné/déplacé n'est plus touché ; maj de scheduled_date seulement tant qu'il est encore open + non assigné.
  • SÉQUENTIEL (frappe_pg ne supporte pas la concurrence) — pas de Promise.all.

Coloriage des cartes (comme legacy)

jobColor() (apps/ops/src/composables/useHelpers.jslegacyDeptColor) colore par legacyDept : Installation(Fibre)/Monteur/Fusionneur → vert #46992f · Réparation(Fibre) → or #f1c84b · Télé (install/réparation) → rose #ec5fb0 · Téléphonie → vert pâle #8fce93 · Désinstallation → rouge foncé #c0392b. (Tech en pause → rouge vif, priorité.) Le store (_mapJob) expose legacyDept/legacyTicketId ; le pool « non-assigné » est déjà trié par scheduledDate.

Endpoints

  • GET /dispatch/legacy-sync/previewdry-run, 0 écriture : ce qui serait créé + matching client/SL + non-matchés.
  • POST /dispatch/legacy-sync/run — exécute la synchro (création/maj). Retourne {tickets, created, updated, skipped, errors, unmatched_customer}.

Récurrence

startSync() (server.js, au boot) — opt-in via env :

LEGACY_DISPATCH_SYNC=on        # active la récurrence (sinon preview/run manuels seulement)
LEGACY_DISPATCH_SYNC_MIN=15    # période en minutes (défaut 15)

Posé dans /opt/targo-hub/.env. ⚠️ Après modif de .env, recréer le conteneur (cd /opt/targo-hub && docker compose up -d) — docker restart ne relit pas l'env_file. 1er passage différé de 90 s après le boot, puis toutes les MIN minutes.

État (mise en service 2026-06-06)

70 tickets importés (0 erreur, 9 clients non matchés = comptes post-migration + 2 tickets internes « FORMATION EN HAUTEUR »). Récurrence active (15 min).

Coordonnées GPS & routage routier réel (2026-06-06 f)

Le pont importe des coordonnées fiables par job (pour le routage routier réel dans l'éditeur de tournée). Cascade de sources, de la plus précise à la plus large :

  1. delivery legacy (point de service exact, via ticket.delivery_id → delivery.latitude/longitude) — JOIN ajouté à fetchTargoTickets. Source de référence ; on préfère aussi l'adresse de service (delivery.address1/city/zip) à l'adresse de facturation du compte.
  2. Service Location ERPNext (coords du client matché) — repli.
  3. Géocodage RQA via recherche TRIGRAM (address-search.searchAddressesRpc → RPC Postgres search_addresses, pg_trgm) — la même recherche que l'autocomplete de disponibilité fibre (phase 1 = numéro civique + mots de rue sur odonyme normal/court/long[avec générique]/municipalité/CP ; phase 2 = trigram complet ; priorise les CP J0L/J0S = territoire). Bien plus robuste que l'ancien ilike (qui manquait « René-Vinet », générique absent de odonyme_recompose_normal). Garde-fou anti-faux-positif (la phase 2 trigram dérive quand le civique est absent du RQA, ex. « 2245 René-Vinet » → « Rue Grenet, Montréal ») : on n'accepte un résultat que si le civique concorde + au moins un token de nom de rue correspond + (territoire J0L/J0S OU CP/ville legacy concordants).
  4. Géocodage Mapbox (MAPBOX_TOKEN, clé publique) — couvre ce que le RQA n'a pas (rues neuves, civiques absents). Contraint au Québec (country=ca + proximity + bornes coord()).

Validation coord() : bornes Québec (lat 44→63, lon 80→57) → rejette 0/0 et placeholders. Backfill + UPGRADE : sur un job existant, on remplit les coords absentes ET on écrase des coords Service Location moins précises par les coords delivery (point exact) — jamais l'inverse. Caches géocodage au niveau module (1 appel par adresse / vie du hub ; échecs mémorisés). Couverture : ~109/125 tickets (87 %)coord_src (run courant) : delivery 26 · SL 38 · RQA-trigram 8 · Mapbox 37 · aucune 16. Le faible compte RQA = haute précision (l'ancien ilike comptait 17 mais avec des faux positifs hors-rue/hors-ville) ; les « aucune » = adresses réellement absentes (campings « Lac des Pins », villes mal orthographiées « Franlkin »).

Routage routier réel (Ops → Planification → éditeur de journée) : loadDayRoute() appelle l'API Mapbox Matrix une fois à l'ouverture (toutes les durées routières d'un coup) → travelBetween() retourne le temps RÉEL ; le réordonnancement réutilise la matrice sans nouvelle requête. Repli haversine (40 km/h) si Mapbox indispo. Indicateur 🚗 (réel) vs 📏 (vol d'oiseau).

Robustesse (2026-06-06 f)

  • Comptabilité honnête : erp.create/update ne throw pas (renvoient {ok:false,error}) → le pont vérifie r.ok (sinon errors++ + error_samples dans le résumé). Avant : creates échoués comptés réussis.
  • Verrou de sérialisation sur sync() : tick récurrent + runs manuels ne se chevauchent JAMAIS (frappe_pg sans concurrence → sinon « socket hang up » + écritures perdues dans un rollback).
  • Subject tronqué à 140 (champ Data Frappe) : les jobs sans Service Location ajoutaient l'adresse au sujet → CharacterLengthExceededError. Détail complet conservé dans legacy_detail/coords.
  • Env : MAPBOX_TOKEN ajouté à /opt/targo-hub/.env (clé publique pk.) → recréer le conteneur.

TODO / idées

  • Matcher les clients non matchés (créer le Customer / enrichir legacy_account_id) → réduit les 15 « aucune coord ».
  • Géoloc live du tech (Capacitor transistorsoft/capacitor-background-geolocation) → 1er point de la tournée.
  • Filtrer les départements non-terrain (ToDo, Support Informatique, Conception…) si bruit.
  • Écrire en retour le tech assigné / la date vers le legacy (bidirectionnel) — non fait (lecture seule legacy).