gigafibre-fsm/docs/features/dispatch.md
louispaulb 0f8d2b0565 docs: bring all docs in sync with the May 2026 reality
Mass refresh — the docs were last touched 2026-04-22, two weeks behind
shipped reality. This commit updates 9 files to reflect current truth.

WHAT CHANGED IN THE PRODUCT (since 22 Apr) THAT THE DOCS NOW REFLECT:

  • Oktopus CE / TR-369 stack decommissioned (containers + volumes +
    images all removed; broker had filled /dev/sdb with 75 GB of debug
    logs and took ERPNext down for 4 days). Hub gates the integration
    behind OKTOPUS_DISABLED=1 — modules retained, no-op'd at runtime.
  • dispatch.gigafibre.ca (legacy PHP SPA) replaced by an nginx 301
    redirect to /ops/#/dispatch.
  • Top toolbar of the dispatch module: collapsed to single-color
    Lucide icons + ⋯ overflow menu + "Vue principale ▾" + "[👥 N ▾]"
    resource type chip (defaults to techs, materials in the dropdown
    only when relevant).
  • Tech home base / departure point: editable per-tech via 📍 button,
    address geocode (Nominatim) or click-on-map picker, right-click
    on tech pin opens the same actions. Map defaults centered on
    Gigafibre HQ (1867 chemin de la Rivière, Sainte-Clotilde) instead
    of downtown Montreal.
  • POST /auth/users invite flow on the hub: creates the Authentik
    user, sets a temp password, mails it via Mailjet (Authentik's
    own recovery flow isn't configured), creates the matching ERPNext
    System User. Surfaced in ops Settings → Utilisateurs → Inviter.
  • Two Authentik instances clarified as parallel-and-permanent (not
    a migration): auth.targo.ca for staff, id.gigafibre.ca for clients.

FILES TOUCHED:

  README.md — service table refreshed, arch diagram redrawn (no
    Oktopus row), auth section explains the invite flow + two
    parallel instances.
  docs/architecture/overview.md — new "Decommissioned" section,
    correct retirement status for dispatch-app + apps/field, two
    Authentik instances explicitly distinguished, dev-gotchas list
    rewritten (drops MongoDB AVX, adds log-rotation hard-learned
    lesson, adds note about Authentik recovery flow).
  docs/architecture/data-model.md — Step 5 hardware provisioning
    now describes the GenieACS path (TR-069 Inform → preset push)
    instead of the dead TR-369 path.
  docs/architecture/module-interactions.md — oktopus.js and
    oktopus-mqtt.js entries marked as gated, provision.js note
    updated, GenieACS row in external-integrations updated, MQTT
    row removed from real-time channels, interaction matrix loses
    the Oktopus column and gains an Authentik admin REST cell.
  docs/features/dispatch.md — Top bar section completely rewritten
    to match the current chrome (left/center/right regions,
    single-color Lucide, dropdowns); new Tech home base section
    documenting the 📍 + map-pick + right-click flows; retirement
    note now reads as a status, not a plan.
  docs/features/cpe-management.md — full rewrite. Oktopus migration
    plan replaced by a "decommissioned" note + the existing GenieACS
    + modem-bridge architecture as the steady state. TP-Link XX230v
    deep-dive sections preserved (still accurate).
  docs/README.md, docs/features/README.md, docs/roadmap.md —
    intent-table descriptions and live-URLs table corrected.

The docs/archive/ snapshots (2026-04-18, 2026-04-19) are untouched —
they're historical and should remain that way.
2026-05-05 20:10:40 -04:00

27 KiB
Raw Permalink Blame History

Dispatch

Real-time technician scheduling, work-order creation, and field publishing for the Ops PWA. Largest module in apps/ops/ — the single page where CSRs turn incoming service requests into scheduled work on a technician's calendar.

1. Goals & Principles

Dispatch is the live control surface for every field intervention. It replaces the legacy PHP dispatch-app (slated for retirement per ../architecture/overview.md) with a Vue 3 / Pinia timeline bound directly to ERPNext Dispatch Job and Dispatch Technician DocTypes.

Principles enforced in the code:

  • Single source of truth is ERPNext. Every drag, resize, tag toggle issues a PUT /api/resource/Dispatch Job/{name} through src/api/dispatch.js. The Pinia store (src/stores/dispatch.js) only holds a mapped view; on reload it rehydrates from Frappe.
  • Optimistic UI + undo stack. useUndo snapshots mutations so keyboard Cmd+Z can roll back a mistake before ERPNext ever replies.
  • Match skill, not cost. Per feedback_dispatch_tags.md convention, tags carry a level 1-5 and auto-dispatch picks the lowest adequate match — experts are preserved for jobs that require them.
  • Push, don't poll. Timeline stays in sync via SSE topics dispatch and network from targo-hub — tech-absence SMS, outage alerts, OLT up/down events all land as toasts without a refresh.
  • Human and material resources share one grid. A Dispatch Technician row is either a human (with a phone / user) or a material asset (Véhicule, Nacelle, OTDR, Fusionneuse…) — the same drag-drop logic applies.
  • Work is not visible to techs until it is published. The published flag on a Dispatch Job gates both the mobile page (/t/{token}) and the iCal feed.

2. File Inventory

Front-end (Ops PWA)

Path Responsibility
apps/ops/src/pages/DispatchPage.vue Top-level page, composes all composables, owns SSE connection, renders timeline / week / month switcher plus inline map panel.
apps/ops/src/modules/dispatch/components/TimelineRow.vue One row per resource — renders shift, absence, travel, assist, ghost and job segments; emits drag / drop / resize events.
apps/ops/src/modules/dispatch/components/WeekCalendar.vue 7-day grid view with absence chips, ghost recurrences and day-load bars; handles planning mode shifts and on-call bands.
apps/ops/src/modules/dispatch/components/MonthCalendar.vue Month overview with per-day tech-count badges; shows selected tech's availability when in planning mode.
apps/ops/src/modules/dispatch/components/BottomPanel.vue Unassigned jobs tray with lasso selection, batch assign and auto-distribute buttons.
apps/ops/src/modules/dispatch/components/RightPanel.vue Details side-panel for a selected job — edit, move, geofix, unassign, set end date, remove assistant, assign pending, update tags.
apps/ops/src/modules/dispatch/components/JobEditModal.vue Inline edit of title, address, note, duration, priority and tags.
apps/ops/src/modules/dispatch/components/WoCreateModal.vue "Nouveau work order" modal — runs rankTechs + refineWithDrivingTimes on the top 3 candidates.
apps/ops/src/modules/dispatch/components/CreateOfferModal.vue Creates a job offer in broadcast / targeted / pool mode with a pricing preset.
apps/ops/src/modules/dispatch/components/OfferPoolPanel.vue Live offer feed with status chips (open, pending, accepted, expired, cancelled).
apps/ops/src/modules/dispatch/components/PublishScheduleModal.vue "Publier & envoyer l'horaire par SMS" — publishes jobs, builds per-tech magic link + webcal iCal URL, sends via Twilio.
apps/ops/src/modules/dispatch/components/SbModal.vue Generic overlay modal with header / body / footer slots.
apps/ops/src/modules/dispatch/components/SbContextMenu.vue Positioned context-menu wrapper used for right-click menus on techs and jobs.
apps/ops/src/modules/dispatch/components/MapPanel.vue Standalone map panel component — present but not imported by DispatchPage.vue, which uses an inline Mapbox block instead.
apps/ops/src/modules/dispatch/components/SuggestSlotsDialog.vue Client for /dispatch/suggest-slots (hub endpoint). Present but not imported by DispatchPage.vue — kept for future "Find me a time" entry point.
apps/ops/src/stores/dispatch.js Pinia store — maps ERPNext rows to UI-friendly shape, rebuilds tech queues, triggers GPS polling.
apps/ops/src/config/dispatch.js Row height constant, resource icon map (Véhicule, Nacelle, Fusionneuse…), re-exports HUB_SSE_URL.
apps/ops/src/api/dispatch.js All Frappe REST calls — fetchTechnicians, fetchJobsFast, updateJob, publishJobs, createJob, createTech.
apps/ops/src/composables/useScheduler.js Drives period navigation, busy lanes, ghost materialization.
apps/ops/src/composables/useDragDrop.js Drag sources / drop targets for jobs, techs and assistants.
apps/ops/src/composables/useAutoDispatch.js Client-side candidate ranking mirroring the hub algorithm.
apps/ops/src/composables/useJobOffers.js Offer-pool state + PRICING_PRESETS.
apps/ops/src/composables/useAbsenceResize.js Drag-to-resize absence bands.
apps/ops/src/composables/useTechManagement.js Create / delete / reassign technicians.
apps/ops/src/composables/useTagManagement.js Tag editor bindings for techs and jobs.
apps/ops/src/composables/useContextMenus.js Tech and job right-click menus.
apps/ops/src/composables/useSelection.js Lasso selection in the bottom panel.
apps/ops/src/composables/useBottomPanel.js Collapsed / pinned state of the unassigned tray.
apps/ops/src/composables/usePeriodNavigation.js 3-period buffer and infinite horizontal scroll.
apps/ops/src/composables/useResourceFilter.js Human / material / group filtering.
apps/ops/src/composables/useMap.js Mapbox GL layer and marker lifecycle.
apps/ops/src/composables/useUndo.js Per-mutation snapshots for Cmd+Z.
apps/ops/src/composables/useAddressSearch.js Mapbox geocoder wrapper used by Wo and Job modals.
apps/ops/src/components/dispatch/NlpInput.vue Natural-language "type-to-dispatch" shortcut.

Back-end (targo-hub)

Path Responsibility
services/targo-hub/server.js Routes /dispatch/* to dispatch.js, /dispatch/ical-token/:id and /dispatch/calendar/:id.ics to ical.js, /t/* to tech-mobile.js, /traccar/* to traccar.js, /magic-link/* to magic-link.js.
services/targo-hub/lib/dispatch.js POST /dispatch/best-tech, POST /dispatch/suggest-slots, POST /dispatch/create-job. Implements rankTechs, getTechsWithLoad, enrichWithGps, suggestSlots.
services/targo-hub/lib/ical.js HMAC-SHA256 token signing, VCALENDAR builder with America/Toronto VTIMEZONE, status + priority mapping, RRULE passthrough.
services/targo-hub/lib/traccar.js Bearer-auth proxy to the Traccar GPS server with a 60s device cache.
services/targo-hub/lib/magic-link.js POST /magic-link/generate, POST /magic-link/verify, POST /magic-link/refresh — JWT tokens, default 72h TTL, job-level and all-jobs-for-tech variants.
services/targo-hub/lib/tech-mobile.js Server-rendered mobile page at /t/{token} with status update, scan, vision, equipment and catalog endpoints.
services/targo-hub/lib/tech-absence-sms.js Inbound Twilio SMS → Gemini Flash NLU (regex fallback) → setTechAbsence / clearTechAbsence → SSE tech-absence broadcast.
services/targo-hub/lib/sse.js Topic fan-out used by the dispatch and network channels.

ERPNext (Frappe custom DocTypes)

Path Responsibility
erpnext/setup_fsm_doctypes.py Creates / patches custom fields on Dispatch Job and Dispatch Technician.

3. Data Model

Dispatch Job

Core Frappe fields plus these custom fields (from erpnext/setup_fsm_doctypes.py):

Field Type Purpose
customer Link → Customer Billing-side link.
service_location Link → Service Location Physical address + geocoded coordinates.
job_type Select Installation / Réparation / Maintenance / Retrait / Dépannage / Autre.
source_issue Link → Issue Origin ticket, when the job came from Support.
depends_on Link → Dispatch Job Blocks scheduling until the dependency is done.
parent_job Link → Dispatch Job Multi-step workflow (with step_order).
on_open_webhook, on_close_webhook Data Flow-editor callbacks — see flow-editor.md.
equipment_items, materials_used, checklist, photos Table Child tables fed by the mobile tech page.
actual_start, actual_end, travel_time_min Datetime / Int Field-recorded metrics.
completion_notes, customer_signature Text / Attach End-of-job capture.
published Check Gates visibility on the mobile page and iCal feed.
is_recurring, recurrence_rule, recurrence_end, pause_periods, template_id Mixed RRULE-based repeating jobs (materialized on demand).
continuous Check Emergency jobs that may span weekends / off-days.
assistants (child table) Table tech_id, tech_name, duration_h, note, pinned.
tags (child table) Table tag, level, required.

Dispatch Technician

Field Type Purpose
resource_type Select human or material.
resource_category Select Véhicule / Outil / Salle / Équipement / Nacelle / Grue / Fusionneuse / OTDR.
weekly_schedule Text / JSON Parsed by parseWeeklySchedule.
extra_shifts JSON (hidden) Per-date overrides.
absence_reason, absence_from, absence_until, absence_start_time, absence_end_time Mixed Current absence band.
traccar_device_id Data Links to a Traccar GPS device.
tags (child table) Table tag, level.

Full field definitions live in ../architecture/data-model.md.

4. UI Surfaces

All French strings below are pulled directly from DispatchPage.vue.

Top bar

The header is split in three flex regions (left / center / right), all icons are single-color Lucide-style strokes pulled from ICON.* in useHelpers.js — no emojis or multi-color glyphs.

Left region (filters + view selector):

  • Search — type a tech name or saved-preset chip to scope the grid.
  • Resource type chip [👥 N ▾] — single dropdown anchored to the current selection. Default is human (techs only). Materials are secondary; the Matériel and Tous entries appear only if there's at least one material resource. Persisted in localStorage.
  • Board view dropdown [Vue principale ▾] — replaces the inline tabs. Click → list of saved board views + a future "+ Nouvelle vue" entry.
  • Filters/settings (sliders icon) — opens the filter panel (status / group / tags / hide-absent). The icon is sliders, not wrench — wrench is reserved for the materials filter so the two concerns don't share a glyph.
  • Projets — visible only when there are team-jobs (assistants[] populated); shows the project count badge.

Center region (period + view + planning):

  • Aujourd'hui jump-to-today, prev/next period.
  • Jour / Semaine / Mois view toggle.
  • Planning toggle (calendar icon, label Planning) — switches the grid to availability/shift editing mode.

Right region (signal + CTAs + overflow):

  • Surchargé alert (triangle icon) — appears only when at least one tech is over capacity for the selected day; tooltip lists the techs and their %.
  • Jobs non assignées (clipboard icon) with a badge for the unscheduled count.
  • Carte (map icon) — toggles the inline Mapbox panel (Day view only). The map defaults to centered on Gigafibre HQ (lng=-73.6756, lat=45.1599, zoom=10) covering Sainte-Clotilde + Châteauguay + Napierville + Hemmingford. Clicking a tech in the resource list flies the map to their position (live Traccar fix if online, else saved home base).
  • Publier (purple CTA) with a draft-count badge.
  • + WO (indigo CTA) — opens WoCreateModal.
  • ⋯ overflow menu — hosts the secondary actions: Actualiser, Assistant IA (collapses the NLP input bar by default), Offres aux techs (with green count badge), Ressources & GPS (opens the Traccar / tech management modal — see §4.x), and "Ouvrir ERPNext" (with inline status dot for the API connection).

Two clarifications worth knowing:

  • The .sb-header container uses overflow:visible so dropdowns can spill below it. Earlier overflow:hidden clipped the ⋯ menu — fixed in commit 16343b6.
  • All dropdowns close on Escape, on click outside, and after picking an item. The handler chain is shared with the existing ctxMenu / techCtx / assistCtx close logic.

Timeline view (TimelineRow.vue)

One row per resource. Each row renders stacked segments:

Segment type Source Notes
shift weeklySchedule + extraShifts Active working window; drop targets enabled.
absence absence_from..absence_until or weekly "off" days Resizable via useAbsenceResize.
travel Computed between consecutive jobs Mapbox Directions API; non-draggable.
assist assistants[] on another tech's job Pinned assists cannot be dragged away.
ghost Unmaterialized recurrence Click → materialize concrete Dispatch Job.
job Assigned, scheduled Dispatch Job Primary drag source.

Drag sources: tech avatar (to split shifts), job blocks (to move / reassign), pinned assist blocks (to nudge helper). Emits select-tech, ctx-tech, job-dragstart, timeline-drop, block-move, block-resize, absence-resize, ghost-click, ghost-materialize.

Week view (WeekCalendar.vue)

Seven-day grid per resource. Distinguishes isExplicitAbsent (a typed absence record) from isScheduleOff (non-working weekday). In planning mode it draws availability bands and on-call shift overlays on top.

Month view (MonthCalendar.vue)

Month grid with per-day badges showing how many techs are available. In planning mode a sidebar surfaces the selected tech's weekly schedule and extra shifts.

Bottom panel (BottomPanel.vue)

Unassigned jobs tray. Supports:

  • Lasso selection (click-drag to select multiple)
  • Batch assign to a tech
  • Auto-distribute across available techs using useAutoDispatch
  • Collapse / pin state via useBottomPanel

Right panel (RightPanel.vue)

Details + pending-request slide-in. Emits edit, move, geofix, unassign, set-end-date, remove-assistant, assign-pending, update-tags.

Inline map

DispatchPage renders its own Mapbox panel using useMap and MAPBOX_TOKEN from config/erpnext.js. MapPanel.vue exists in the module but is not currently imported — kept for a possible future extraction.

Offer pool (OfferPoolPanel.vue, CreateOfferModal.vue)

Three offer modes surface as icon toggles in CreateOfferModal:

Mode Behaviour
broadcast Blast to every matching tech; first accepter wins.
targeted Single named recipient.
pool A named candidate list; first to accept wins.

Pricing is composed from PRICING_PRESETS (in useJobOffers) as displacement$ + hourlyRate$/h × duration. Offer status chips: open, pending, accepted, expired, cancelled.

Context menu (SbContextMenu.vue)

Right-click on a tech row, a tech pin on the map, or a job block opens a context menu wired through useContextMenus — quick reassign, cancel, open in ERP, copy magic link, copy iCal URL.

Tech-specific menu (techCtx) entries:

  • 🗺 Voir sur la carte
  • 🔀 Optimiser la route
  • 🏷 Skills / Tags
  • 📅 Copier le lien iCal
  • 📍 Adresse de départ… — opens the home-base dialog (see below)
  • 🎯 Choisir sur la carte — enters the geoFixTech pick mode
  • ↗ Ouvrir dans ERPNext

Tech home base (departure point)

Each tech has a latitude / longitude on Dispatch Technician that serves as their start-of-day coordinate when no live Traccar fix is available. The dispatch route optimizer uses these as the origin when computing the optimal job sequence.

Editing it — three paths converge on saveTechHome(tech, lng, lat) in useTechManagement.js:

  1. 📍 button next to a tech in the GPS sidebar — opens a 2-option chooser: "Saisir une adresse" (free-text geocoded via OpenStreetMap Nominatim) or "Cliquer sur la carte" (entry into geoFixTech mode).
  2. Right-click on the tech's pin on the map — same context menu entries as above.
  3. Quick paste — the address dialog also accepts a literal lat, lng pair (e.g. 45.16, -73.68) for power users.

Pick mode (geoFixTech) shows an indigo banner at the top of the screen with the tech's name and an Annuler button. Cursor is set to crosshair on the map. Next click captures lng/lat → PUTs to ERPNext → recomputes routes. ESC cancels.

Default fallback for techs without saved coords is Gigafibre HQ — 1867 chemin de la Rivière, Sainte-Clotilde QC (lng=-73.6756177, lat=45.1599145). Set in apps/ops/src/stores/dispatch.js.

Drag-drop rules (enforced in useDragDrop)

  • Drop on an active shift / within absence band = allowed (toggle absence).
  • Drop on a non-working weekday = blocked unless continuous=1.
  • Drop on a tech whose tags don't meet tags.required levels = warning toast but still allowed (CSR override).
  • Escape cancels any in-flight drag.
  • Delete while a job is selected opens the unassign confirmation.
  • Cmd+Z walks back the useUndo snapshot stack.

5. Key Flows

5.1 Create a work order

  1. CSR clicks + WOWoCreateModal.vue opens.
  2. Address input auto-geocodes via useAddressSearch (Mapbox geocoder).
  3. On submit, WoCreateModal calls findBestTech() which locally runs the ranking, then refines the top 3 with live driving times.
  4. createJob (in src/api/dispatch.js) POSTs to ERPNext.
  5. The store prepends the new job; SSE fan-out notifies other open Ops tabs.

5.2 Suggest the best tech (server side)

POST /dispatch/best-tech on targo-hub (lib/dispatch.js):

  • rankTechs computes a score per candidate using SCORE_WEIGHTS — proximityMultiplier=4, proximityMax=100 km, loadMultiplier=30, overloadPenalty=500, gpsFreshnessBonus=20.
  • Proximity uses a Euclidean km approximation at Montreal latitude (fast enough for ranking).
  • getTechsWithLoad tallies today's scheduled hours; anything above the configured cap triggers the overload penalty.
  • enrichWithGps adds a freshness bonus when a recent Traccar fix exists.

5.3 Suggest time slots

POST /dispatch/suggest-slots (lib/dispatch.js) returns up to N best windows across all techs:

  • 7-day horizon, capped at 2 slots per tech to diversify suggestions.
  • Default shift 08:00-17:00 (overridable per tech).
  • 15-minute travel buffer inserted before/after existing jobs.
  • SuggestSlotsDialog.vue is the front-end client — present in the repo but not yet wired into DispatchPage.vue; the current path is tech selection via WoCreateModal or manual drag.

5.4 Offer pool

  1. CSR opens CreateOfferModal, picks mode + pricing preset.
  2. Offer is persisted through useJobOffers.
  3. OfferPoolPanel subscribes to offer events and updates chip status.
  4. Accepting techs fire a state transition that converts the offer into an assigned Dispatch Job.

5.5 Publish & SMS schedule

PublishScheduleModal.vue — title "Publier & envoyer l'horaire par SMS":

  1. Operator selects a tech + date range.
  2. publishJobs(jobNames) from src/api/dispatch.js PUTs published: 1 on each job in parallel.
  3. For each tech, the modal fetches:
    • A magic link via POST /magic-link/generate (targo-hub, default 72h TTL) → produces the /t/{token} URL that opens the mobile tech page.
    • An iCal token via GET /dispatch/ical-token/{techId} → builds a webcal:// URL pointing at /dispatch/calendar/{techId}.ics?token=....
  4. Twilio sends two SMS:
    • "Mes tâches: {link}" — the magic link.
    • "Ajouter à mon calendrier: {webcal url}" — the iCal subscription URL.
  5. sendTestSms is invoked with reference_doctype = Dispatch Technician so every send is logged against the tech in ERPNext.

5.6 Absence via SMS (inbound)

services/targo-hub/lib/tech-absence-sms.js:

  1. Twilio inbound webhook hits handleAbsenceSms.
  2. lookupTechByPhone matches the last 10 digits of the sender against Dispatch Technicians.
  3. Gemini 2.5 Flash (OpenAI-compatible endpoint) parses intent + dates; a regex fallback catches "absent / malade / sick / formation / vacances" with simple date ranges if the LLM is down.
  4. setTechAbsence or clearTechAbsence PUTs to Dispatch Technician and broadcasts SSE tech-absence on the dispatch topic.
  5. Ops timelines update in-place; the operator sees a toast. A French confirmation SMS is returned to the tech.

5.7 Recurring jobs (ghosts → materialized)

  • A template job carries is_recurring=1 and an RFC 5545 recurrence_rule (e.g. FREQ=WEEKLY;BYDAY=SA,SU).
  • WeekCalendar and TimelineRow render upcoming occurrences as ghost segments computed client-side.
  • Clicking a ghost fires ghost-materialize, creating a concrete Dispatch Job with template_id set — subsequent edits diverge from the template.
  • pause_periods carves out vacation windows without deleting the template.

6. External Integrations

Traccar (GPS)

services/targo-hub/lib/traccar.js proxies /traccar/devices, /traccar/positions and a generic GET fallback with Traccar bearer-token auth preferred over Basic. Device metadata is cached for 60 seconds. The store (useGpsTracking) polls positions and feeds live gpsCoords, gpsSpeed, gpsOnline into each tech row — these in turn power the freshness bonus in the server-side ranker. Traccar limitation noted in ../architecture/overview.md #6.4: only one deviceId per request — poll in parallel.

Twilio

  • OutboundPublishScheduleModal calls sendTestSms to deliver the magic link and webcal URL per tech.
  • Inboundtech-absence-sms.js owns Twilio's incoming SMS webhook and drives the absence flow above.
  • Number +14382313838 per reference_twilio.md.

iCal / webcal

services/targo-hub/lib/ical.js:

  • Token is HMAC-SHA256 over {techId} with ICAL_SECRET, truncated to 16 hex chars.
  • Fetches jobs from past 7 days to future 60 days for the tech.
  • Emits RFC 5545 with a full America/Toronto VTIMEZONE (DST rules included).
  • Status: open → TENTATIVE, cancelled / completed → CANCELLED.
  • Priority: urgent → 1, medium → 5, everything else → 9.
  • Honors recurrence_rule — passes the RRULE through untouched.

Subscription URL handed to techs is webcal://msg.gigafibre.ca/dispatch/calendar/{techId}.ics?token=... — Apple Calendar and Google Calendar both handle this.

Mobile tech page (/t/{token})

services/targo-hub/lib/tech-mobile.js is a lightweight server-rendered page — not the Ops PWA. It authenticates the tech with a JWT magic link (verifyJwt), lists their published jobs for the day, and offers POST endpoints:

Route Purpose
GET /t/{token} Day view for the tech.
POST /t/{token}/status Flip status (In Progress, Completed).
POST /t/{token}/scan Record a barcode scan.
POST /t/{token}/vision Send a photo to /vision/barcodes for OCR.
POST /t/{token}/equip Install a Service Equipment row.
POST /t/{token}/equip-remove Decommission equipment.
GET /t/{token}/catalog Fetch the equipment catalog.
GET /t/{token}/equip-list Fetch equipment already on site.

The Ops PWA never opens this page — it lives alongside Dispatch to replace the retiring apps/field app. See ../architecture/overview.md §1.

7. Cross-module References

  • ../architecture/overview.md — ecosystem map, retirement plan for the legacy dispatch-app and field app.
  • ../architecture/data-model.md — canonical Dispatch Job / Dispatch Technician field list.
  • flow-editor.md — wires on_open_webhook / on_close_webhook on Dispatch Job into workflow runs.
  • vision-ocr.md — barcode / equipment recognition consumed by the tech mobile page during a dispatch.
  • cpe-management.md — "Diagnostiquer" deep-dives started from a customer card flow into the same tech assignment surface.
  • billing-payments.md — Sales Orders that trigger installation Dispatch Jobs (order_source, sales_order fields on the job).

8. Failure Modes

Symptom Likely cause Where to look
Timeline loads but all rows are empty ERPNext token revoked or Administrator "Generate Keys" clicked src/api/auth.js + note in ../architecture/overview.md §6.3
Drops appear to work then revert on refresh ERPNext exc swallowed — check console for [API] PUT Dispatch Job/... failed apps/ops/src/api/dispatch.js
iCal subscription returns 401 Wrong token or ICAL_SECRET changed — ask operator to re-copy the webcal URL services/targo-hub/lib/ical.js
Tech never receives magic link SMS Twilio rejected the number or published=0 on the selected jobs PublishScheduleModal.vue + Twilio console
Absence SMS ignored Phone number not matched in Dispatch Technician (last-10-digits LIKE) services/targo-hub/lib/tech-absence-sms.jslookupTechByPhone
GPS dot stale / missing Traccar token expired, cache 60s, or no traccar_device_id on the tech services/targo-hub/lib/traccar.js
Suggestion always picks same tech Overload penalty not firing — check getTechsWithLoad result for load cap services/targo-hub/lib/dispatch.js
SSE toasts stop arriving ForwardAuth dropped the session; reload to re-auth with Authentik apps/ops/src/pages/DispatchPage.vue SSE setup
Ghost occurrences missing RRULE parse failed client-side — malformed recurrence_rule apps/ops/src/stores/dispatch.js _mapJob
Tech mobile page shows "not found" JWT expired (default 72h) — operator must re-publish to regenerate services/targo-hub/lib/magic-link.js

9. Retirement Status (May 2026)

Both legacy frontends are now decommissioned:

  • dispatch-app (PHP) at dispatch.gigafibre.ca — the nginx serving /opt/dispatch-app/ was repointed to a single return 301 https://erp.gigafibre.ca/ops/#/dispatch rule. Anyone hitting an old bookmarked URL bounces through the redirect into the ops dispatch module without re-authentication noise (the authentik@file middleware was removed from the dispatch router so the redirect fires immediately).
  • apps/field — the lightweight mobile tech page at /t/{token} is the replacement. See §6 of this doc.

The replacement pairing is:

  • Ops SPA Dispatch module — this document — for all scheduling, ranking, publishing.
  • Tech mobile page at /t/{token} for the field-side experience.

Do not add new features to dispatch-app or apps/field. Any feature request should land in apps/ops/src/modules/dispatch/ or the tech-mobile handler in services/targo-hub/lib/tech-mobile.js.


Back to docs/README.md · roadmap.md