Customers no longer authenticate with passwords. A POST to the hub's
/portal/request-link mints a 24h customer-scoped JWT and sends it via
email + SMS; the /#/login Vue page sits on top of this and a navigation
guard hydrates the Pinia store from the token on arrival.
Why now: legacy customer passwords are unsalted MD5 from the old PHP
system. Migrating hashes to PBKDF2 would still require a forced reset
for every customer, so it's simpler to drop passwords entirely. The
earlier Authentik forwardAuth attempt was already disabled on
client.gigafibre.ca; this removes the last vestige of ERPNext's
password form from the customer-facing path.
Hub changes:
- services/targo-hub/lib/portal-auth.js (new) — POST /portal/request-link
• 3-requests / 15-min per identifier rate limit (in-memory Map + timer)
• Lookup by email (email_id + email_billing), customer id (legacy +
direct name), or phone (cell + tel_home)
• Anti-enumeration: always 200 OK with redacted contact hint
• Email template with CTA button + raw URL fallback; SMS short form
- services/targo-hub/server.js — mount the new /portal/* router
Client changes:
- apps/client/src/pages/LoginPage.vue (new) — standalone full-page,
single identifier input, success chips, rate-limit banner
- apps/client/src/api/auth-portal.js (new) — thin fetch wrapper
- apps/client/src/stores/customer.js — hydrateFromToken() sync decoder,
stripTokenFromUrl (history.replaceState), init() silent Authentik
fallback preserved for staff impersonation
- apps/client/src/router/index.js — PUBLIC_ROUTES allowlist + guard
that hydrates from URL token before redirecting
- apps/client/src/api/auth.js — logout() clears store + bounces to
/#/login (no more Authentik redirect); 401 in authFetch is warn-only
- apps/client/src/composables/useMagicToken.js — thin read-through to
the store (no more independent decoding)
- PaymentSuccess/Cancel/CardAdded pages — goToLogin() uses router,
not window.location to id.gigafibre.ca
Infra:
- apps/portal/traefik-client-portal.yml — block /login and
/update-password on client.gigafibre.ca, redirect to /#/login.
Any stale bookmark or external link lands on the Vue page, not
ERPNext's password form.
Docs:
- docs/roadmap.md — Phase 4 checkbox flipped; MD5 migration item retired
- docs/features/billing-payments.md — replace MD5 reset note with
magic-link explainer
Online appointment booking (Plan B from the same discussion) is queued
for a follow-up session; this commit is Plan A only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Apr 22 refactor (41d9b5f) collapsed the tech scanner to Gemini-only
photo capture, dropping the live camera viewport and client-side multi-
barcode detection. Techs lost the fast point-and-scan flow that handles
90% of routine installs.
Restored as a hybrid: html5-qrcode as the primary path (instant, offline,
standard QR/barcode), Gemini kept as a second-chance fallback for hard
labels (damaged stickers, text-only serials, unusual symbologies). Offline
queue + scanEquipmentLabel() preserved unchanged.
Three tabs, defaulting to live camera:
- Caméra — continuous html5-qrcode stream, detection auto-beeps
- Photo — native camera; full-image + 3-strip local scan, Gemini fallback
- Manuel — plain text input
Both apps/field and apps/ops updated in lockstep so nothing drifts while
apps/field is being folded into apps/ops/j.
Run `npm install` in apps/ops/ to pull in html5-qrcode before the next build.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All docs moved with git mv so --follow preserves history. Flattens the
single-folder layout into goal-oriented folders and adds a README.md index
at every level.
- docs/README.md — new landing page with "I want to…" intent table
- docs/architecture/ — overview, data-model, app-design
- docs/features/ — billing-payments, cpe-management, vision-ocr, flow-editor
- docs/reference/ — erpnext-item-diff, legacy-wizard/
- docs/archive/ — HANDOFF-2026-04-18, MIGRATION, status-snapshots/
- docs/assets/ — pptx sources, build scripts (fixed hardcoded path)
- roadmap.md gains a "Modules in production" section with clickable
URLs for every ops/tech/portal route and admin surface
- Phase 4 (Customer Portal) flipped to "Largely Shipped" based on
audit of services/targo-hub/lib/payments.js (16 endpoints, webhook,
PPA cron, Klarna BNPL all live)
- Archive files get an "ARCHIVED" banner so stale links inside them
don't mislead readers
Code comments + nginx configs rewritten to use new doc paths. Root
README.md documentation table replaced with intent-oriented index.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Invoice OCR migrated from Ollama (GPU-bound, local) to Gemini 2.5
Flash via new targo-hub /vision/invoice endpoint with responseSchema
enforcement. Ops VM no longer needs a GPU.
- Ops /j/* now has full camera scanner (TechScanPage) ported from
apps/field with 8s timeout + offline queue + auto-link to Dispatch
Job context on serial/barcode/MAC 3-tier lookup.
- New TechDevicePage reached via /j/device/:serial showing every
ERPNext entity related to a scanned device: Service Equipment,
Customer, Service Location, active Subscription, open Issues,
upcoming Dispatch Jobs, OLT info.
- New docs/VISION_AND_OCR.md (full pipeline + §10 relationship graph
+ §8.1 secrets/rotation policy). Cross-linked from ARCHITECTURE,
ROADMAP, HANDOFF, README.
- Nginx /ollama/ proxy blocks removed from both ops + field.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
- New JobDetailPage: full-screen job view with editable fields (subject,
type, priority, time, duration, description), status transitions
(en route / terminer / rouvrir), GPS navigation to service location
- Equipment section: list equipment at location, add via scanner/search/create
- TasksPage: jobs now navigate to detail page instead of inline expand,
quick status buttons remain accessible from the list
- Offline support: all edits queued when offline, cached job data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Dispatch performance:
- Replace sequential batch fetches (batches of 15, one after another)
with full parallel Promise.all — all doc fetches fire simultaneously
- With 20 jobs: was ~3 sequential round-trips, now ~2 (1 list + 1 parallel)
Order traceability:
- Add sales_order (Link) and order_source (Select) fields to Dispatch Job
- checkout.js sets order_source='Online' + sales_order link on job creation
- acceptance.js sets order_source='Quotation' on quotation-sourced jobs
- Store maps new fields: salesOrder, orderSource
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- EquipmentDetail: collapsible node groups (clients grouped by mesh node)
- Signal strength as RSSI % (0-255 per 802.11-2020) with 10-tone color scale
- Management IP clickable link to device web GUI (/superadmin/)
- Fibre status compact top bar (status + Rx/Tx power when available)
- targo-hub: WAN IP detection across all VLAN interfaces
- targo-hub: full WiFi client count (direct + EasyMesh mesh repeaters)
- targo-hub: /devices/:id/hosts endpoint with client-to-node mapping
- ClientsPage: start empty, load only on search (no auto-load all)
- nginx: dynamic ollama resolver (won't crash if ollama is down)
- Cleanup: remove unused BillingKPIs.vue and TagInput.vue
- New docs and migration scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
targo-hub:
- Add /devices/* endpoints proxying GenieACS NBI API (port 7557)
- /devices/summary — fleet stats (online/offline by model)
- /devices/lookup?serial=X — find device by serial number
- /devices/:id — device detail with summarized parameters
- /devices/:id/tasks — send reboot, getParameterValues, refresh
- /devices/:id/faults — device fault history
- GENIEACS_NBI_URL configurable via env var
ops app:
- New useDeviceStatus composable for live ACS status
- Equipment chips show green/red online dot from GenieACS
- Enriched tooltips: firmware, WAN IP, Rx/Tx power, SSID, last inform
- Right-click context menu: Reboot device, Refresh parameters
- Signal quality color coding (Rx power dBm thresholds)
- 1-minute client-side cache to avoid hammering NBI API
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Tickets: load 10 initially, "Voir tous les tickets" expands to 500
- Inline editing for ticket status and priority (dblclick → select)
- Search: Enter key triggers immediate search and navigates to result
- Search: Arrow key navigation for result highlighting
- Reset expanded state on customer navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Customer IDs are now the raw legacy customer_id (bank payment reference):
LPB4, 114796350603272, DOMIL5149490230
New customers: C + 14 digits sequential (C10000000000001)
No collision with existing 15-digit bank references (gap at 10^13).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch Ops data source from Subscription to Service Subscription (source of truth)
- Reimport 39,630 native Subscriptions from Service Subscription data
- Rename 15,302 customers to CUST-{legacy_customer_id} (eliminates hex UUIDs)
- Rename all doctypes to zero-padded 10-digit numeric format:
SINV-0000001234, PE-0000001234, ISS-0000001234, LOC-0000001234,
EQP-0000001234, SUB-0000001234, ASUB-0000001234
- Fix subscription pricing: LPB4 now correctly shows 0$/month
- Update ASUB- prefix detection in useSubscriptionActions.js
- Add reconciliation, reimport, and rename migration scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- InlineField component + useInlineEdit composable for Odoo-style dblclick editing
- Client search by name, account ID, and legacy_customer_id (or_filters)
- SMS/Email notification panel on ContactCard via n8n webhooks
- Ticket reply thread via Communication docs
- All migration scripts (51 files) now tracked
- Client portal and field tech app added to monorepo
- README rewritten with full feature list, migration summary, architecture
- CHANGELOG updated with all recent work
- ROADMAP updated with current completion status
- Removed hardcoded tokens from docs (use $ERP_SERVICE_TOKEN)
- .gitignore updated (docker/, .claude/, exports/, .quasar/)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All /api/ and /ollama/ requests now go through the ops base path
(/ops/api/... and /ops/ollama/...) so Traefik routes them to
ops-frontend nginx, which injects the ERPNext token server-side.
Token is never exposed in the browser.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Ollama container running llama3.2-vision:11b on server
- OCR page in ops app: camera/upload → Ollama extracts vendor, date,
amounts, line items → editable form → create Purchase Invoice
- nginx proxies /ollama/ to Ollama API (both ops + field containers)
- Added createDoc to erp.js API layer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract data loading into loadCustomer() function and watch props.id
for changes. Previously only ran in onMounted — navigating between
clients showed stale data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move ERPNext API token from JS bundle to nginx proxy_set_header
(token only lives on server, never in client code)
- Switch ops + field apps from auth.targo.ca to id.gigafibre.ca SSO
- Fix "Aucun contenu" showing on tickets that have comments but no
description (check comments.length in v-if condition)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Integrates www.gigafibre.ca (React/Vite) into the monorepo.
Full git history accessible via `git log -- apps/website/`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integrates the Dispatch PWA (Vue/Quasar) into the gigafibre-fsm monorepo.
Full git history accessible via `git log -- apps/dispatch/`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>