Commit Graph

18 Commits

Author SHA1 Message Date
louispaulb
2631ed9199 feat(dispatch): couleur ticket = couleur skill + fil complet du ticket + tri pool (date/ville/priorité)
- Couleurs liées aux skills (éditable/cohérent) : hub deptToSkill() déduit une compétence du type legacy
  → /roster/unassigned-jobs renvoie required_skill ; PlanificationPage colore la carte par getTagColor(required_skill)
  (même couleur que le chip skill) ; bordure 5px
- Fil complet du ticket : hub /dispatch/legacy-sync/ticket-thread (ticket_msg + auteur staff, HTML nettoyé) ;
  api legacyTicketThread ; RightPanel bouton « 💬 Voir le fil / commentaires » (chargé au clic, messages+auteurs+dates)
- Order-by du pool dispatch : useBottomPanel.bottomSort (date|city|priority) + dropdown ⇅ dans BottomPanel
  (ville = 2e segment adresse / token sujet avant |)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:37:45 -04:00
louispaulb
f7343f6e0e roster(planif/dispatch): On-Hold bloqué, alerte hors-quart, deep-link Dispatch, aviser client (#58)
- On Hold : onCellDrop REFUSE d'assigner un job en attente d'un prérequis (notify), reste au panneau (≠ 🔒 visuel)
- Hors quart publié : marqueur ⚠ dans la cellule libre (offShiftJobs/rawCellJobs lit occByTechDay brut) +
  badge « hors quart » dans la timeline ressource — surface les jobs assignés un jour sans quart
- Deep-link : Planif gotoDispatch(tech) → /dispatch?tech=&date= ; DispatchPage lit route.query
  (goToDay(date+T12:00:00) anti-décalage tz + selectTechOnBoard)
- #58 : bouton « Désaffecter + aviser le client » dans le dialogue d'unassign Dispatch →
  roster.notifyReschedule (désassigne serveur + SMS lien /book au mobile du Customer)
- Doc docs/features/roster.md mise à jour (Fait récemment / TODO)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 09:13:17 -04:00
louispaulb
f507367ac4 fix(ops/client): cancelled subs no longer inflate monthly total + Lieu link in-app
Three connected dispatcher-facing issues from C-LPB4 audit:

1. **Monthly total was wrong on customer cards.** Section subtotal and
   `locSubsMonthlyTotal` summed `actual_price` for ALL subscriptions
   regardless of status, so cancelled rows (rendered with strikethrough)
   still pumped up the displayed billing figure. C-LPB4 showed
   "Total mensuel: 86,10$" computed as `196.05 - 109.95 = 86.10`,
   where 196.05 included 3 cancelled internet plans (Megafibre 80,
   TEST-E2E-FTTH, FTTH100 — all struck through in the UI). Real
   active monthly is 5.00$ (109.95 active + 5 frais réseau − 109.95
   loyalty rebate). Fixed both `sectionTotal` and `locSubsMonthlyTotal`
   /`locSubsAnnualTotal` to filter on `status === 'Active'`.

2. **"Lieu" link from a dispatch task pointed to ERPNext desk** which
   shows a raw doctype form (no abonnements, no totals, no contacts —
   just the bare fields). Now points in-app to
   `#/clients/<customer>?location=<SL>`. ClientDetailPage reads the
   query string on mount and:
     • scrolls the matching `loc-card` into view
     • pulses an indigo halo around it for ~2s so the rep finds it
       immediately even when the customer has many service locations.

3. **The shipping/billing distinction was invisible** on the customer
   page. Added an "Adresses de livraison" badge next to the "Lieux de
   service" section title — clarifies that this section IS the
   shipping address, distinct from the (future) billing address that
   will live on the Customer record. Cosmetic for now; the data
   migration to formalize that distinction is the next step.

These three round out the C-LPB4 audit triggered by the wrong
mapbox-pin location: now the customer card on the dispatcher's
screen shows correct totals, the dispatch link drops them right at
the spot they're trying to reach, and the role of each address-bearing
record is named explicitly.
2026-05-08 11:21:18 -04:00
louispaulb
4af25071be fix(ops/dispatch): /desk/<DocType>/ broken URL → /app/<slug>/ + add /address/validate hub
Two things ride together because the user noticed the URL bug while
testing the work-in-progress address validation:

1. **Broken Frappe URL pattern.** Three places in the dispatch UI
   were generating `/desk/Service Location/<id>` and
   `/desk/Dispatch Technician/<id>` links — both return "Page not
   found" on Frappe v14+ (= our v16) because the modern desk URL
   format is `/app/<slug>/<id>` where slug is lowercase + hyphens.
   Fixed in:
     • RightPanel.vue (Lieu link in the job details panel)
     • DispatchPage.vue (Lieu in the job ctx menu)
     • DispatchPage.vue (Ouvrir dans ERPNext in the tech ctx menu)

2. **`POST /address/validate` endpoint** on the hub. Wraps the
   existing RQA Supabase search (`address-search.js`) with a
   confidence-scored output:
     • exact_match (boolean) — score >= 0.7
     • best (the top RQA candidate with aq_address_id, lat, lng)
     • candidates[] (top 5 ranked)
     • confidence (0..1)
     • recommendation: validated | review | unmatched
   Score combines civic-number exact match, road-name fuzzy overlap,
   FSA+full postal-code bonuses, and city-name bonus. The endpoint
   is called from ops UI when adding/editing a Service Location to
   auto-populate aq_address_id + canonical lat/lng instead of
   trusting human typing or Mapbox geocode.

(Custom Fields aq_address_id, address_validation_status,
address_validated_at, linked_address have been added on Service
Location via the Frappe REST API in a separate operation — not in
this commit since they're DB-only.)
2026-05-08 11:01:32 -04:00
louispaulb
478d24859e fix(ops/dispatch): surface customer + service-location links from a job + fix bad coords
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/<id>` (opens ClientDetailPage in-app)
     • Lieu  → `/desk/Service Location/<id>` (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.
2026-05-08 10:29:59 -04:00
louispaulb
7a607817c0 refactor(ops/dispatch): single-color Lucide icons + tech-first resource filter
Two issues conflated in the same PR because they touch the same pixels:

1. **Resource filter no longer treats techs and materials as equals.**
   Was a 3-button inline toggle [Tous][👤 45][🔧 6] with all three
   visually similar — and the wrench glyph clashed with the wrench
   used for the filter-settings button. Now:
     • Default = 'human' (techs only). Materials are secondary
       resources; they don't deserve front-of-bar real estate.
     • Single chip [👥 45 ▾] in the toolbar. Click → dropdown:
         · Techs (45)         ← active by default
         · Matériel (6)       (only shown if materialCount > 0)
         · Tous (51)          (only shown if materialCount > 0)
     • Defaults to localStorage 'sbv2-filterResType' if previously
       persisted, otherwise 'human' instead of '' (was '').

2. **Mixed-style icons (emoji + Lucide SVG) replaced with consistent
   single-color Lucide-style strokes.**
   Each is a stroke-only inline <svg> with stroke="currentColor", so
   they inherit the surrounding text color (no green/red/yellow
   tinting). Added to the existing ICON set in useHelpers.js:
     user, users, package, sliders, chevDown, map, clipboard,
     sparkles, signal, rotateCw, alertTri, moreH, pause, play,
     externalLink, target, calendar
   Replaced in the dispatch top toolbar:
     ⚠️  → ICON.alertTri          (overload alert)
     📋  → ICON.clipboard         (unscheduled jobs)
     🗺  → ICON.map               (map toggle)
     🗓  → ICON.calendar          (planning toggle)
     👥  → ICON.users             (team-jobs button + Ressources menu)
     🔧  → ICON.sliders           (filter-settings — was wrench, which
                                   collided with the materials filter)
     👤/🔧 → ICON.users / .package (resource type dropdown)
     ↻  → ICON.rotateCw           (refresh in ⋯ menu)
       → ICON.sparkles           (AI in ⋯ menu)
     📡  → ICON.signal            (offers in ⋯ menu)
     ↗  → ICON.externalLink       (ERPNext link in ⋯ menu)
     ⋯  → ICON.moreH              (the ⋯ button itself)
   .sb-icon-svg gives them consistent sizing (14px in buttons, 15px
   in dropdown items, 16px in the ⋯ trigger) so they're crisp at all
   the spots they appear.

Emojis still in place elsewhere (job-tile chips, status badges, etc.)
will be migrated incrementally — out of scope for this pass which
only targeted the user's visible header.
2026-05-05 14:31:00 -04:00
louispaulb
9844e5ae54 fix(ops/dispatch): top bar polish — visible ⋯ menu, collapsed AI, fly-to tech, views dropdown
Four fixes around the dispatch header following dispatcher feedback:

1. **⋯ overflow menu was invisible**: .sb-header had `overflow:hidden`,
   which clipped the absolutely-positioned dropdown right at the
   header's bottom edge. Switched the header to `overflow:visible`
   (children all have flex-shrink:0 + a flex:1 center, so the layout
   doesn't actually overflow horizontally). Bumped z-index to 5000
   for safety on top of map/calendar layers.

2. **NLP/Assistant IA bar hidden by default**: was eagerly rendering
   on every page load, with the long French placeholder polluting
   the header below the toolbar. The user just wanted the icon. Now
   `nlpVisible` defaults to false, persisted in localStorage so power
   users who flip it on keep it open across sessions. Toggle still
   lives in the ⋯ menu.

3. **Click a tech in the resource list now flies the map to them**:
   selectTechOnBoard previously only opened the map panel. Now it
   also `map.flyTo({ center })` using `tech.gpsCoords ?? tech.coords`
   — live Traccar position wins when the tech is online; falls back
   to the saved home base. Animated, deferred a tick so map.resize()
   happens first, otherwise flyTo can land on garbage coords during
   the panel's open transition.

4. **Board view tabs collapsed into a "Vue principale ▾" dropdown**:
   was [Vue principale][Par région][+] inline. Now a single button
   showing the active view; click reveals the others + the future
   "+ Nouvelle vue" entry. Same dropdown component as the ⋯ menu
   (shared CSS, click-outside + ESC close).
2026-05-05 14:17:33 -04:00
louispaulb
2d38c79011 refactor(ops/dispatch): consolidate top toolbar with overflow ⋯ menu
The header right-side was getting noisy — 8 buttons + 2 indicators
all competing for screen width, with two visually-similar 📡 icons
(offer pool vs GPS settings) that confused dispatchers. On narrower
laptops the bar wrapped or icons overflowed.

New layout:

  [⚠ overload] [📋 unassigned + count] [🗺 Carte] [Publier + count] [+ WO] [⋯]

Everything else dropped into the ⋯ dropdown:
  • ↻ Actualiser
  •  Assistant IA
  • 📡 Offres aux techs (with green count badge)
  • 👥 Ressources & GPS  ← was 📡 in the bar; this is also where
                          the tech-management UI (rename, deactivate,
                          home location, Traccar device link) lives
  • ↗ Ouvrir ERPNext (with the inline status dot)

The ⋯ menu closes on Escape, on click outside, and after picking an
item. Same close-handler chain that already serves the job/tech
context menus.

The kept-up-front buttons all have either a status badge (counts,
overload alert) or are the primary CTAs (Publier, + WO) — so the
dispatcher's eye stays on workflow signal, not on chrome.
2026-05-05 14:07:04 -04:00
louispaulb
510d619b5e feat(ops/dispatch): right-click tech pin + click-on-map home picker + center map on HQ
Three connected UX changes:

1. **Map centered on Gigafibre HQ on first load** —
   Sainte-Clotilde (lng=-73.6756, lat=45.1599), zoom 10 — covers the
   service area (Sainte-Clotilde + Châteauguay + Napierville +
   Hemmingford). Was downtown Montréal.

2. **Right-click on a tech pin** opens the existing techCtx menu
   (already used from the calendar via @ctx-tech). New entries:
     • 📍 Adresse de départ…  → openTechHomeDialog
     • 🎯 Choisir sur la carte → startTechGeoFix (mirrors the existing
                                  geoFixJob flow used for jobs)

3. **The 📍 button in the GPS sidebar** now offers a 2-option chooser
   first: "Saisir une adresse" or "Cliquer sur la carte". Picking the
   map option drops the user into geoFixTech mode.

Implementation:

  • useMap.js: new geoFixTech ref + startTechGeoFix/cancelTechGeoFix
    + a contextmenu listener on each tech outer wrapper that calls
    openTechCtx(e, tech). The map's main click handler now branches:
    if geoFixTech is set, persist the lng/lat via saveTechHome (passed
    in via deps as a forward-bound arrow because saveTechHome is
    destructured below the useMap call in DispatchPage).
  • DispatchPage.vue: new banner shown while in pick mode (animated
    indigo bar at top, "Cliquez sur la carte pour {tech}", with a
    cancel button); ESC also cancels.
  • dispatch-styles.scss: .sb-geofix-banner styles + reusing the
    existing pulse keyframe.
2026-05-05 14:02:26 -04:00
louispaulb
ac52406e30 feat(ops/dispatch): editable tech home base + new default at Gigafibre HQ
Two changes around tech "departure point" coords (used for route
optimization when the tech has no live GPS yet):

1. New default fallback = 1867 chemin de la Rivière, Sainte-Clotilde
   (Gigafibre HQ, lng=-73.6756, lat=45.1599). Was downtown Montréal,
   which never made sense — every tech started the day with a 70 km
   imaginary commute.

2. Per-tech editable home base via a 📍 button on each row of the
   tech sidebar. Clicking it opens a dialog that accepts either:
     • a free-text address — geocoded via OpenStreetMap Nominatim
       (browser-side, sane User-Agent, no hub proxy needed)
     • or a literal "lat, lng" pair pasted directly
   On confirm: PUT to ERPNext (Dispatch Technician.latitude /
   .longitude), patch the local store row, and trigger a route
   recompute since the start point changed.

   The geocode hits Nominatim public — fine for a low-volume
   internal tool. If we ever exceed their fair-use limits, swap to
   the existing /address-search hub route which already has the
   AQ + RQA pipeline.
2026-05-05 13:53:14 -04:00
louispaulb
10d0854e51 refactor: reduce token count, DRY code, consolidate docs
Backend services:
- targo-hub: extract deepGetValue to helpers.js, DRY disconnect reasons
  lookup map, compact CAPABILITIES, consolidate vision.js prompts/schemas,
  extract dispatch scoring weights, trim section dividers across 9 files
- modem-bridge: extract getSession() helper (6 occurrences), resetIdleTimer(),
  consolidate DM query factory, fix duplicate username fill bug, trim headers
  (server.js -36%, tplink-session.js -47%, docker-compose.yml -57%)

Frontend:
- useWifiDiagnostic: extract THRESHOLDS const, split processDiagnostic into
  6 focused helpers (processOnlineStatus, processWanIPs, processRadios,
  processMeshNodes, processClients, checkRadioIssues)
- EquipmentDetail: merge duplicate ROLE_LABELS, remove verbose comments

Documentation (17 → 13 files, -1,400 lines):
- New consolidated README.md (architecture, services, dependencies, auth)
- Merge ECOSYSTEM-OVERVIEW into ARCHITECTURE.md
- Merge MIGRATION-PLAN + ARCHITECTURE-COMPARE + FIELD-GAP + CHANGELOG → MIGRATION.md
- Merge COMPETITIVE-ANALYSIS into PLATFORM-STRATEGY.md
- Update ROADMAP.md with current phase status
- Delete CONTEXT.md (absorbed into README)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 08:39:58 -04:00
louispaulb
6bb42a7b88 feat: tech mobile view integrated into ops app at /j, unassign confirmation
Tech mobile view (erp.gigafibre.ca/ops/#/j):
- TechLayout with bottom nav tabs (tasks, scanner, diagnostic, more)
- TechTasksPage: rich header with tech name/stats, job cards with
  priority dots, time, location, duration badges, bottom sheet detail
  with En route/Terminer buttons + scanner/detail access
- TechJobDetailPage: editable fields, equipment list, GPS navigation
- TechScanPage: device lookup by SN/MAC, create/link to job
- TechDiagnosticPage: speed test + host reachability checks
- Route /j replaces legacy dispatch-app tech view

Dispatch unassign confirmation:
- Dialog appears when unassigning published or in-progress jobs
- Warns that tech has already received the task
- Cancel/Confirm flow prevents accidental removal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 08:26:26 -04:00
louispaulb
9b9d307bdf feat: dispatch planning mode, offer pool, shared presets, recurrence selector
- Planning mode toggle: shift availability as background blocks on timeline
  (week view shows green=available, yellow=on-call; month view per-tech)
- On-call/guard shift editor with RRULE recurrence on tech schedules
- Uber-style job offer pool: broadcast/targeted/pool modes with pricing,
  SMS notifications, accept/decline flow, overload detection alerts
- Shared resource group presets via ERPNext Dispatch Preset doctype
  (replaces localStorage, shared between supervisors)
- Google Calendar-style RecurrenceSelector component with contextual
  quick options + custom RRULE editor, integrated in booking overlay
  and extra shift editor
- Remove default "Repos" ghost chips — only visible in planning mode
- Clean up debug console.logs across API, store, and page layers
- Add extra_shifts Custom Field on Dispatch Technician doctype

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 22:44:18 -04:00
louispaulb
91da480aeb perf: memoize dispatch timeline segments + load/capacity as computed Maps
Before: techDayJobsWithTravel(tech), periodLoadH(tech), techPeriodCapacityH(tech)
were called as functions in the template v-for — recalculated on EVERY render
for every tech (10 techs × 3 functions = 30 expensive recomputations per render).

After: Pre-computed as Vue computed Maps (segmentsMap, loadMap, capMap) that
only recompute when their reactive dependencies actually change. Template
reads from map[tech.id] — instant O(1) lookup, no recalculation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 18:24:16 -04:00
louispaulb
24b5644da4 refactor: extract composables from 5 largest files — net -1950 lines from main components
DispatchPage.vue: 1320→1217 lines
  - Extract SbModal.vue + SbContextMenu.vue reusable components
  - Extract useAbsenceResize composable
  - Extract dispatch constants to config/dispatch.js

ProjectWizard.vue: 1185→673 lines (-43%)
  - Extract useWizardPublish composable (270-line publish function)
  - Extract useWizardCatalog composable
  - Extract wizard-constants.js (step labels, options, categories)

SettingsPage.vue: 1172→850 lines (-27%)
  - Extract usePermissionMatrix composable
  - Extract useUserGroups composable
  - Extract useLegacySync composable

ClientDetailPage.vue: 1169→864 lines (-26%)
  - Extract useClientData composable (loadCustomer broken into sub-functions)
  - Extract useEquipmentActions composable
  - Extract client-constants.js + erp-pdf.js utility

checkout.js: 639→408 lines (-36%)
  - Extract address-search.js module
  - Extract otp.js module
  - Extract email-templates.js module
  - Extract project-templates.js module
  - Add erpQuery() helper to DRY repeated URL construction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 17:57:24 -04:00
louispaulb
a8eeb1b77d refactor: major cleanup — remove dead dispatch app, commit all backend code, extract client composables
- Remove apps/dispatch/ (100% replaced by ops dispatch module, unmaintained)
- Commit services/targo-hub/lib/ (24 modules, 6290 lines — was never tracked)
- Commit services/docuseal + services/legacy-db docker-compose configs
- Extract client app composables: useOTP, useAddressSearch, catalog data, format utils
- Refactor CartPage.vue 630→175 lines, CatalogPage.vue 375→95 lines
- Clean hardcoded credentials from config.js fallback values
- Add client portal: catalog, cart, checkout, OTP verification, address search
- Add ops: NetworkPage, AgentFlowsPage, ConversationPanel, UnifiedCreateModal
- Add ops composables: useBestTech, useConversations, usePermissions, useScanner
- Add field app: scanner composable, docker/nginx configs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 17:38:38 -04:00
louispaulb
43d5ca2e4e feat: nested tasks, project wizard, n8n webhooks, inline task editing
Major dispatch/task system overhaul:
- Project templates with 3-step wizard (choose template → edit steps → publish)
- 4 built-in templates: phone service, fiber install, move, repair
- Nested task tree with recursive TaskNode component (parent_job hierarchy)
- n8n webhook integration (on_open_webhook, on_close_webhook per task)
- Inline task editing: status, priority, type, tech assignment, tags, delete
- Tech assignment + tags from ticket modal → jobs appear on dispatch timeline
- ERPNext custom fields: parent_job, on_open_webhook, on_close_webhook, step_order
- Refactored ClientDetailPage, ChatterPanel, DetailModal, dispatch store
- CSS consolidation, dead code cleanup, composable extraction
- Dashboard KPIs with dispatch integration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 13:01:20 -04:00
louispaulb
35b7ea57d7 feat: add ops app + CONTEXT.md, simplify URL to /ops/
Ops app (Vue/Quasar PWA) with dispatch V2 integration, tag system,
customer 360, tickets, and dashboard. Served via standalone nginx
container at erp.gigafibre.ca/ops/ with Traefik StripPrefix + Authentik SSO.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 22:41:58 -04:00