Pont (legacy-dispatch-sync.js) : - Import des coordonnées par job via cascade : table legacy `delivery` (point de service exact, JOIN ticket.delivery_id) > Service Location ERPNext > géocodage RQA > géocodage Mapbox. Validation bornes Québec (coord()). Couverture 153/172 (89%). - Géocodage RQA corrigé : retrait du générique de voie (Rue/Rang/Chemin absent de odonyme_recompose_normal) + code postal non accolé au terme (sinon ilike ne matche jamais). - Repli Mapbox geocoding pour rues trop récentes pour le RQA (MAPBOX_TOKEN). - Backfill + UPGRADE : coords delivery écrasent des coords SL moins précises (jamais l'inverse). - Comptabilité honnête : vérifie r.ok sur create/update (erp ne throw pas) → errors + error_samples. - Verrou de sérialisation sync() : tick + runs manuels ne se chevauchent plus (frappe_pg). - Subject tronqué à 140 (champ Data) → corrige CharacterLengthExceededError sur jobs sans SL. - Observabilité : coord_src tally + error_samples dans le résumé. Ops Planification (éditeur de journée) : - travelBetween() consulte une matrice Mapbox Matrix chargée à l'ouverture (loadDayRoute) → temps de trajet ROUTIERS RÉELS ; réordonnancement instantané sans nouvelle requête. Repli haversine si Mapbox indispo. Indicateur 🚗 réel vs 📏 vol d'oiseau. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.6 KiB
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 | Où |
|---|---|
| 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 |
Customer où legacy_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_dateseulement tant qu'il est encoreopen+ 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.js → legacyDeptColor) 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/preview— dry-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 :
deliverylegacy (point de service exact, viaticket.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.- Service Location ERPNext (coords du client matché) — repli.
- Géocodage RQA (
address-search/address-validate, Répertoire des adresses du Québec) — autoritaire en rural. ⚠️ Le générique de voie (« Rue »/« Rang »/« Chemin ») est retiré du terme (absent deodonyme_recompose_normal→ sinon l'ilike ne matche jamais) et le code postal n'est PAS accolé au terme (ses tokens seraient exigés dans le nom de rue). - Géocodage Mapbox (
MAPBOX_TOKEN, clé publique) — couvre les rues trop récentes pour le RQA. Contraint au Québec (country=ca+ proximity + bornescoord()).
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 mise en service :
153/172 jobs (89 %) — coord_src : delivery 26 · SL 38 · RQA 17 · Mapbox 29 · aucune 15.
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/updatene throw pas (renvoient{ok:false,error}) → le pont vérifier.ok(sinonerrors+++error_samplesdans 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
DataFrappe) : les jobs sans Service Location ajoutaient l'adresse au sujet →CharacterLengthExceededError. Détail complet conservé danslegacy_detail/coords. - Env :
MAPBOX_TOKENajouté à/opt/targo-hub/.env(clé publiquepk.) → 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).