From f4ae023302f8a99b83a843052aef6390a2db0d6c Mon Sep 17 00:00:00 2001 From: louispaulb Date: Fri, 8 May 2026 10:29:59 -0400 Subject: [PATCH] fix(ops/dispatch): surface customer + service-location links from a job + fix bad coords MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related issues, one PR: 1. **Bad coords** on customer C-LPB4's "Wifi buggy" job (DJ-MNP8WIKT). Address on file is `691 rue des Hirondelles, Saint-Michel J0L2J0`, but the saved lat/lng (-73.677086, 45.159206) reverse-geocodes to `2336 rue René-Vinet, Sainte-Clotilde J0L1W0` — ~9 km away. The delta matches the Gigafibre HQ default fallback (-73.6756, 45.1599) pretty closely, suggesting the geocoder either failed silently at Service Location creation time or got pinned to the HQ centroid. Fixed live in DB (UPDATE on tabService Location LOC-0000000004 + tabDispatch Job DJ-MNP8WIKT to lng=-73.5792377, lat=45.2408452, verified via Nominatim against the typed address). The job pin should now show on the correct house. 2. **No way to jump from a job to the client** — the dispatcher had to memorize/type the customer ID. Now both the RightPanel and the job context-menu surface clickable shortcuts: • Client → `#/clients/` (opens ClientDetailPage in-app) • Lieu → `/desk/Service Location/` (opens ERPNext in a new tab; the ops SPA doesn't have a dedicated SL detail page) Required wiring `customer` + `serviceLocation` into the job map in `stores/dispatch.js` — the API (`fetchJobsFast` uses `["*"]`) was already returning the fields, the store just wasn't surfacing them. Note on the deeper bug: the SL lat/lng is the source of truth and the job currently *copies* it at creation time (rather than reading from the SL link dynamically). If a Service Location's coords are corrected after a job exists, the job retains stale coords. A follow-up could either (a) re-read on render, or (b) trigger a backfill when SL coords change. Out of scope for this fix — for now, the dispatcher who fixes an SL must also update any open jobs at that location. --- .../dispatch/components/RightPanel.vue | 22 +++++++++++++++++++ apps/ops/src/pages/DispatchPage.vue | 9 ++++++++ apps/ops/src/pages/dispatch-styles.scss | 4 ++++ apps/ops/src/stores/dispatch.js | 8 +++++++ 4 files changed, 43 insertions(+) diff --git a/apps/ops/src/modules/dispatch/components/RightPanel.vue b/apps/ops/src/modules/dispatch/components/RightPanel.vue index ff902c7..6969262 100644 --- a/apps/ops/src/modules/dispatch/components/RightPanel.vue +++ b/apps/ops/src/modules/dispatch/components/RightPanel.vue @@ -36,6 +36,28 @@ const onDeleteTag = inject('onDeleteTag')
Titre{{ panel.data?.job?.subject }}
+ + +
Adresse{{ panel.data?.job?.address || '—' }}
Durée{{ fmtDur(panel.data?.job?.duration) }}
Priorité diff --git a/apps/ops/src/pages/DispatchPage.vue b/apps/ops/src/pages/DispatchPage.vue index 034a7c8..59d8feb 100644 --- a/apps/ops/src/pages/DispatchPage.vue +++ b/apps/ops/src/pages/DispatchPage.vue @@ -1672,6 +1672,15 @@ onUnmounted(() => {
+ + 👤 Voir la fiche client ({{ ctxMenu.job.customer }}) + + + 🏠 Lieu de service ({{ ctxMenu.job.serviceLocation }}) + +
diff --git a/apps/ops/src/pages/dispatch-styles.scss b/apps/ops/src/pages/dispatch-styles.scss index da8891e..f3f1bdb 100644 --- a/apps/ops/src/pages/dispatch-styles.scss +++ b/apps/ops/src/pages/dispatch-styles.scss @@ -629,6 +629,10 @@ .sb-rp-urgent-tag { background:rgba(239,68,68,0.15); color:#ef4444; font-size:0.7rem; font-weight:700; padding:0.25rem 0.5rem; border-radius:6px; display:inline-block; margin-bottom:0.5rem; } .sb-rp-field { margin-bottom:0.45rem; color:var(--sb-text); } .sb-rp-lbl { display:block; font-size:0.58rem; font-weight:700; text-transform:uppercase; letter-spacing:0.05em; color:#7b80a0; margin-bottom:0.1rem; } +.sb-rp-link { color:#6366f1; text-decoration:none; font-size:0.78rem; font-weight:600; display:inline-flex; align-items:center; gap:4px; } +.sb-rp-link:hover { text-decoration:underline; } +.sb-rp-link-icon { font-size:0.65rem; opacity:0.7; } +.sb-rp-link code { background:rgba(99,102,241,0.08); padding:1px 5px; border-radius:3px; font-size:0.7rem; } .sb-rp-actions { padding:0.65rem 0.75rem; border-top:1px solid rgba(0,0,0,0.04); display:flex; flex-direction:column; gap:0.35rem; flex-shrink:0; } .sb-rp-primary { background:#6366f1; border:none; border-radius:7px; color:var(--sb-text); font-size:0.72rem; font-weight:700; padding:0.4rem 0.75rem; cursor:pointer; } .sb-rp-primary:hover { filter:brightness(1.12); } diff --git a/apps/ops/src/stores/dispatch.js b/apps/ops/src/stores/dispatch.js index 217da6c..d61b15e 100644 --- a/apps/ops/src/stores/dispatch.js +++ b/apps/ops/src/stores/dispatch.js @@ -30,6 +30,14 @@ export const useDispatchStore = defineStore('dispatch', () => { subject: j.subject || 'Job sans titre', address: j.address || 'Adresse inconnue', coords: [j.longitude || 0, j.latitude || 0], + // Persisted ERPNext links — surfaced as clickable shortcuts in + // the RightPanel so the dispatcher can jump to the client's + // full record (or the Service Location in ERPNext) without + // leaving the dispatch board. The Mapbox marker still uses + // the cached coords above; for source-of-truth verification + // (geocode mismatch) the rep clicks through to /clients/:id. + customer: j.customer || null, + serviceLocation: j.service_location || null, priority: j.priority || 'low', duration: j.duration_h || 1, status: j.status || 'open',