Commit Graph

83 Commits

Author SHA1 Message Date
louispaulb
2aee8f31df fix(contracts): create pending Service Subscription on signing + test templates
Root cause of CTR-00008: _createBuiltInInstallChain only created Issue +
Dispatch Jobs. It never created a pending Service Subscription, so when
the chain's terminal job Completed, activateSubscriptionForJob found
nothing matching customer+service_location+status='En attente' to flip.
Result: 4/4 tasks done, no sub activation, no prorated invoice.

Changes:
- contracts.js: after chain creation, create Service Subscription with
  status='En attente' (plan_name + service_category inferred from the
  contract). Back-link it on Service Contract.service_subscription (a
  new custom field — the stock 'subscription' field on Service Contract
  points at the built-in ERPNext Subscription doctype, not ours).
- project-templates.js: add test_single (1-step) and test_parallel
  (diamond: step0 → step1 ∥ step2) for faster lifecycle testing.
  Extract chooseTemplate(contract) with precedence:
    contract.install_template → contract_type mapping → fiber_install.
- contracts.js: chain builder now uses chooseTemplate instead of
  hardcoded fiber_install, logs the chosen template per contract.
- _inferServiceCategory/_inferPlanName helpers map contract metadata
  into the Service Subscription's required fields.

Companion changes on ERPNext (custom fields, no code):
  Service Contract.service_subscription  Link → Service Subscription
  Service Contract.install_template       Select (fiber_install,
    phone_service, move_service, repair_service, test_single,
    test_parallel)

Retroactive repair for CTR-00008 applied directly on prod:
  → SUB-0000100003 (Actif), SINV-2026-700014 (Draft, $9.32 prorata).

Smoke test of test_single path on prod (CTR-00010 synthetic, cleaned up):
  template=test_single ✓  sub created ✓  activated on completion ✓
  prorated invoice emitted ✓

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 10:03:49 -04:00
louispaulb
9fda9eb0b0 refactor(targo-hub): add types.js, migrate acceptance+payments, drop apps/field
- lib/types.js: single source of truth for Dispatch Job status + priority enums.
  Eliminates hard-coded 'In Progress'/'in_progress'/'Completed'/'done' checks
  scattered across tech-mobile, acceptance, dispatch. Includes CLIENT_TYPES_JS
  snippet for embedding in SSR <script> blocks (no require() needed).

- lib/tech-mobile.js: applies types.js predicates (isInProgress, isTerminal,
  isDone, isUrgent) both server-side and client-side via ${CLIENT_TYPES_JS}
  template injection. Single aliasing point for future status renames.

- lib/acceptance.js: migrated 7 erpFetch + 2 erpRequest sites to erp.js wrapper.
  Removed duplicate "Lien expiré" HTML (now ui.pageExpired()). Dispatch Job
  creation uses types.JOB_STATUS + types.JOB_PRIORITY.

- lib/payments.js: migrated 15 erpFetch + 9 erpRequest sites to erp.js wrapper.
  Live Stripe flows preserved exactly — frappe.client.submit calls kept as
  erp.raw passthroughs (fetch-full-doc-then-submit pattern intact). Includes
  refund → Return PE → Credit Note lifecycle, PPA cron, idempotency guard.

- apps/field/ deleted: transitional Quasar PWA fully retired in favor of
  SSR tech-mobile at /t/{jwt}. Saves 14k lines of JS, PWA icons, and
  infra config. Docs already marked it "retiring".

Smoke-tested on prod:
  /payments/balance/:customer (200, proper shape)
  /payments/methods/:customer (200, Stripe cards live-fetched)
  /dispatch/calendar/:tech.ics (200, VCALENDAR)
  /t/{jwt} (55KB render, no errors)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 23:18:25 -04:00
louispaulb
01bb99857f refactor(targo-hub): add erp.js wrapper + migrate 7 lib files to it
Replaces hand-rolled `erpFetch` + `encodeURIComponent(JSON.stringify(...))`
URL building with a structured wrapper: erp.get/list/listRaw/create/update/
remove/getMany/hydrateLabels/raw.

Key wins:
- erp.list auto-retries up to 5 times when v16 rejects a fetched/linked
  field with "Field not permitted in query" — the field is dropped and the
  call continues, so callers don't have to know which fields v16 allows.
- erp.hydrateLabels batches link-label resolution (customer_name,
  service_location_name, …) in one query per link field — no N+1, no
  v16 breakage.
- Consistent {ok, error, status} shape for mutations.

Migrated call sites:
- otp.js: Customer email lookup + verifyOTP customer detail fetch +
  Contact email fallback + Service Location listing
- referral.js: Referral Credit fetch / update / generate
- tech-absence-sms.js: lookupTechByPhone, set/clear absence
- conversation.js: Issue archive create
- magic-link.js: Tech lookup for /refresh
- ical.js: Tech lookup + jobs listing for iCal feed
- tech-mobile.js: 13 erpFetch sites → erp wrapper

Remaining erpFetch callers (dispatch.js, acceptance.js, payments.js,
contracts.js, checkout.js, …) deliberately left untouched this pass —
they each have 10+ sites and need individual smoke-tests.

Live-tested against production ERPNext: tech-mobile page renders 54K
bytes, no runtime errors in targo-hub logs post-restart.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 23:01:27 -04:00
louispaulb
169426a6d8 refactor(targo-hub): extract ui/ kit, migrate tech-mobile to it
Introduces services/targo-hub/lib/ui/ as the shared kit for every magic-link
page served from the hub (tech mobile, acceptance, payments):

  design.css    tokens (--brand, --success, etc) + reset + all primitives
  components.js server-side HTML builders (badge/section/card/panel/statRow/
                tabBar) + shared date helpers (fmtTime/dateLabelFr/montrealDate)
                + canonical STATUS_META
  client.js     client-side api wrapper ($, toast, api.get/post, router.on/go)
                baked into every page — no more hand-rolled fetch+hashchange
  scanner.js    Gemini field-scan overlay (window.scanner.open(field,label,cb,ctx))
  shell.js      ui.page({title, body, bootVars, cfg, script, includeScanner})
                inlines everything into one self-contained HTML doc
  index.js      barrel

Migrates tech-mobile.js to the kit:
  - drops inline esc/toast/fmtTime/dlbl/STATUS_META/badge helpers
  - api.post('/status', {...}) instead of fetch(H+'/t/'+T+'/status', {...})
  - router.on('#job/:name', handler) instead of hand-rolled route()
  - scanner.open(field, label, cb, ctx) instead of ~60 lines of field-scan logic

Behavior preserved — rendered HTML keeps tabs, detail view, notes editor,
photo upload, per-field Gemini scans, Montreal-TZ date labels, v16 link-label
resolution. Verified live at msg.gigafibre.ca with a real TECH-4 token.

Sets up acceptance.js and payments.js to drop from ~700 → ~300 lines each
in the next commits by consuming the same primitives.
2026-04-22 22:47:19 -04:00
louispaulb
1d23aa7814 feat(tech-mobile): SPA redesign with tabs, detail view, notes, photos, field-scan
Rewrote msg.gigafibre.ca (tech magic-link page) from a today-only flat list
into a proper 4-tab SPA:
- Aujourd'hui: In Progress / En retard / Aujourd'hui / Sans date / À venir
- Calendrier: placeholder (phase 4)
- Historique: searchable + filter chips (Tous/Terminés/Manqués/Annulés)
- Profil: tech info, support line, refresh

Job detail view (hash-routed, #job/DJ-xxx):
- Customer + tap-to-call/navigate block
- Editable notes (textarea → PUT /api/resource/Dispatch Job)
- Photo upload (base64 → File doctype, is_private, proxied back via /photo-serve)
- Equipment section (inherited from overlay)
- Sticky action bar (Démarrer / Terminer)

Equipment overlay extended with per-field Gemini Vision scanners. Each
input (SN, MAC, GPON SN, Wi-Fi SSID, Wi-Fi PWD, model) has a 📷 that opens
a capture modal; Gemini is prompted to find THAT field specifically and
returns value+confidence. Tech confirms or retries before the value fills in.

Root cause of the "tech can't see his job" bug: page filtered
scheduled_date=today, so jobs on any other day were invisible even though
the token was tech-scoped. Now fetches a ±60d window and groups client-side.

vision.js: new extractField(base64, field, ctx) helper + handleFieldScan
route (used by new /t/:token/field-scan endpoint).

Also fixes discovered along the way:
- Frappe v16 blocks fetched/linked fields (customer_name, service_location_name)
  and phantom fields (scheduled_time — real one is start_time). Query now
  uses only own fields; names resolved in two batch follow-up queries.
- "Today" is Montreal-local, not UTC. Prevents evening jobs being mislabeled
  as "hier" when UTC has already rolled to the next day.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 22:19:00 -04:00
louispaulb
3db1dbae06 fix(contract): always run built-in chain + send ack SMS + default scheduled_date
Three bugs combined to make CTR-00008 (and likely others) land silently:

1. Fallback was count-based, not outcome-based.
   _fireFlowTrigger returned >0 when a broken Flow Template (FT-00005)
   "matched" on_contract_signed but did nothing. We took that as success
   and skipped the built-in install chain. Now we ALWAYS run the built-in
   chain; the idempotency check inside (look up existing Issue linked to
   contract) lets a healthy Flow Template short-circuit us naturally.

2. scheduled_date was null on all chained jobs.
   createDeferredJobs passed '' when no step.scheduled_date was set, and
   the fiber_install template doesn't set one. Jobs with null dates are
   filtered out of most dispatch board views, giving the user-visible
   "Aucune job disponible pour dispatch" symptom even after the chain was
   built. Default to today (via ctx.scheduled_date) so jobs appear on the
   board; dispatcher reschedules per capacity.

3. No post-sign acknowledgment to the customer.
   Previously the Flow Template was expected to send the confirmation SMS;
   since the template was broken, the customer got nothing after signing.
   Add _sendPostSignAcknowledgment that sends a "Bon de commande reçu"
   SMS with contract ref + service details + next steps. Fires only when
   the chain is actually created (not on idempotent skip) so we never
   double-notify.

Also:
- Resolve phone/email from cell_phone + email_billing (legacy-migrated
  Customer records use those fields, not Frappe defaults mobile_no /
  email_id) — otherwise we'd keep skipping SMS with "no phone on file".
- _createBuiltInInstallChain now returns { created, issue, jobs,
  scheduled_date, reason } so callers can branch on outcome.
- Export sendPostSignAcknowledgment so one-shot backfill scripts can
  re-notify customers whose contracts were signed during the broken
  window.
- Set order_source='Contract' (existing Select options patched separately
  to include 'Contract' alongside Manual/Online/Quotation).

Backfilled CTR-00008: ISS-0000250003 + 4 chained Dispatch Jobs all with
scheduled_date=2026-04-23, ack SMS delivered to Louis-Paul's cell.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:01:51 -04:00
louispaulb
aa5921481b feat: contract → chain → subscription → prorated invoice lifecycle + tech group claim
- contracts.js: built-in install chain fallback when no Flow Template matches
  on_contract_signed — every accepted contract now creates a master Issue +
  chained Dispatch Jobs (fiber_install template) so we never lose a signed
  contract to a missing flow config.
- acceptance.js: export createDeferredJobs + propagate assigned_group into
  Dispatch Job payload (was only in notes, not queryable).
- dispatch.js: chain-walk helpers (unblockDependents, _isChainTerminal,
  setJobStatusWithChain) + terminal-node detection that activates pending
  Service Subscriptions (En attente → Actif, start_date=tomorrow) and emits
  a prorated Sales Invoice covering tomorrow → EOM. Courtesy-day billing
  convention: activation day is free, first period starts next day.
- dispatch.js: fix Sales Invoice 417 by resolving company default income
  account (Ventes - T) and passing company + income_account on each item.
- dispatch.js: GET /dispatch/group-jobs + POST /dispatch/claim-job for tech
  self-assignment from the group queue; enriches with customer_name /
  service_location via per-job fetches since those fetch_from fields aren't
  queryable in list API.
- TechTasksPage.vue: redesigned mobile-first UI with progress arc, status
  chips, and new "Tâches du groupe" section showing claimable unassigned
  jobs with a "Prendre" CTA. Live updates via SSE job-claimed / job-unblocked.
- NetworkPage.vue + poller-control.js: poller toggle semantics flipped —
  green when enabled, red/gray when paused; explicit status chips for clarity.

E2E verified end-to-end: CTR-00007 → 4 chained jobs → claim → In Progress →
Completed walks chain → SUB-0000100002 activated (start=2026-04-24) →
SINV-2026-700012 prorata $9.32 (= 39.95 × 7/30).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 20:40:54 -04:00
louispaulb
07365d3b71 fix(tech-diag): warm-up fetch + no-redirect host so ping matches reality
Techs reported cloudflare.com showing 300+ms on the diagnostic page
while OS-level ICMP ping returned 5ms. The gap is entirely protocol
overhead:

- fetch() ≠ ICMP. Every call pays DNS + TCP + TLS + HTTP on top of
  the real RTT, which is easily 150–300ms cold on mobile LTE when the
  radio has to wake the RRC connection.
- Bare cloudflare.com redirects 301 → www.cloudflare.com, forcing a
  second DNS + TCP + TLS handshake for every "ping" and doubling
  the measured latency.
- TechDiagnosticPage.vue was also labeling the full 10MB download
  time as "Latence", so the number on the speed-test card was never
  a latency measurement at all.

Fixes, applied to both surfaces (Ops /j/diagnostic + Field /diagnostic):

- Swap cloudflare.com → 1.1.1.1/cdn-cgi/trace. 88-byte response, no
  redirect, no keepalive games — canonical "internet is up" endpoint.
- Warm-up fetch before every measurement. First call absorbs DNS +
  TCP + TLS + LTE wake; second call reports steady-state RTT. This
  applies to checkHosts() (ops) and resolveHost() (field composable).
- Split runSpeed() into separate ping + throughput measurements. Ping
  hits speed.cloudflare.com/cdn-cgi/trace (88 bytes on a warm
  connection); throughput hits /__down on the same origin so the TLS
  session is reused.

Deployed to production; smoke-verified:
- ops bundle TechDiagnosticPage.b925e02c.js contains
  '1.1.1.1/cdn-cgi/trace'
- field bundle DiagnosticPage.38a45f65.js contains the same
- zero bare 'cloudflare.com' hostname in either hosts array

Files:
- apps/ops/src/modules/tech/pages/TechDiagnosticPage.vue
- apps/field/src/composables/useSpeedTest.js
- apps/field/src/pages/DiagnosticPage.vue

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 16:08:24 -04:00
louispaulb
30a867a326 fix(tech): restore Gemini-native scanner + port equipment UX into ops
The ops tech module at /ops/#/j/* had drifted from the field app in two ways:

1. Scanner — a prior "restoration" re-added html5-qrcode, but the
   design has always been native <input capture="environment"> → Gemini
   2.5 Flash via targo-hub /vision/barcodes (up to 3 codes) and
   /vision/equipment (structured labels, up to 5). Revert useScanner.js
   + ScanPage.vue + TechScanPage.vue to commit e50ea88 and drop
   html5-qrcode from both package.json + lockfiles. No JS barcode
   library, no camera stream, no polyfills.

2. Equipment UX — TechJobDetailPage.vue was a 186-line stub missing the
   Ajouter bottom-sheet (Scanner / Rechercher / Créer), the debounced
   SN-then-MAC search, the 5-field create dialog, Type + Priority
   selects on the info card, and the location-detail contact expansion.
   Port the full UX from apps/field/src/pages/JobDetailPage.vue (526
   lines) into the ops module (458 lines after consolidation).

Rebuilt and deployed both apps. Remote smoke test confirms 0 bundles
reference html5-qrcode and the new TechJobDetailPage.1075b3b8.js chunk
(16.7 KB vs ~5 KB stub) ships the equipment bottom-sheet strings.

Docs:

- docs/features/tech-mobile.md — new. Documents all three delivery
  surfaces (legacy SSR /t/{jwt}, transitional apps/field/, unified
  /ops/#/j/*), Gemini-native scanner pipeline, equipment UX, magic-link
  JWT, cutover plan. Replaces an earlier stub that incorrectly
  referenced html5-qrcode.
- docs/features/dispatch.md — new. Dispatch board, scheduling, tags,
  travel-time optimization, magic-link SMS, SSE updates.
- docs/features/customer-portal.md — new. Plan A passwordless magic-link
  at portal.gigafibre.ca, Stripe self-service, file inventory.
- docs/architecture/module-interactions.md — new. One-page call graph
  with sequence diagrams for the hot paths.
- docs/README.md — expanded module index (§2) now lists every deployed
  surface with URL + primary doc + primary code locations (was missing
  dispatch, tickets, équipe, rapports, telephony, network, agent-flows,
  OCR, every customer-portal page). New cross-module edge map in §4.
- docs/features/README.md + docs/architecture/README.md — cross-link
  all new docs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:56:38 -04:00
louispaulb
7ac9a582c6 fix(portal): deploy Vue SPA to portal.gigafibre.ca, retire client.gigafibre.ca
Topology clarification:
- portal.gigafibre.ca = standalone nginx container serving /opt/client-app/
  (the actual Vue SPA). This is the real customer portal.
- client.gigafibre.ca = ERPNext frontend (exposes Frappe's password login
  form — dead-end UX, legacy MD5 attack surface).

Changes:
- apps/client/deploy.sh: target /opt/client-app/ directly with DEPLOY_BASE=/
  (was uploading into ERPNext's /assets/client-app/, which nothing serves).
  Atomic stage-and-swap + docker restart so the nginx bind-mount picks up
  the new inode.
- apps/portal/traefik-client-portal.yml: replace per-path /login and /desk
  blocks on client.gigafibre.ca with a catch-all 307 to portal.gigafibre.ca.
  Old bookmarks, old invoice links, and in-flight SMS all end up on the
  Vue SPA instead of Frappe's password page.
- apps/ops/package-lock.json: sync to include html5-qrcode transitive deps
  so `npm ci` in deploy.sh works from a clean checkout.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 15:02:31 -04:00
louispaulb
2b04e6bd86 feat(portal): passwordless magic-link login — retire ERPNext /login
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>
2026-04-22 13:25:28 -04:00
louispaulb
90f5f2eaa0 fix(field/ops): restore live camera + multi-barcode scanning at /j/scan
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>
2026-04-22 13:22:36 -04:00
louispaulb
beb6ddc5e5 docs: reorganize into architecture/features/reference/archive folders
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>
2026-04-22 11:51:33 -04:00
louispaulb
30bfe6175e docs: add Phase 2.7 — field ↔ ops unification at /j
Records what shipped in e50ea88 (scan + device pages, offline store,
Gemini vision pipeline) and lays out the remaining phases: PWA
hardening, auth unification, magic-link tech access, flow-runtime
integration, and final apps/field removal.

Fixes stale `/t/{token}` route reference in Phase 2 → `/j/`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 11:33:20 -04:00
louispaulb
e50ea88c08 feat: unify vision on Gemini + port field tech scan/device into /j
- 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>
2026-04-22 11:26:01 -04:00
louispaulb
41d9b5f316 feat: flow editor, Gemini QR scanner with offline queue, dispatch planning v2
Major additions accumulated over 9 days — single commit per request.

Flow editor (new):
- Generic visual editor for step trees, usable by project wizard + agent flows
- PROJECT_KINDS / AGENT_KINDS catalogs decouple UI from domain
- Drag-and-drop reorder via vuedraggable with scope isolation per peer group
- Chain-aware depends_on rewrite on reorder (sequential only — DAGs preserved)
- Variable picker with per-applies_to catalog (Customer / Quotation /
  Service Contract / Issue / Subscription), insert + copy-clipboard modes
- trigger_condition helper with domain-specific JSONLogic examples
- Global FlowEditorDialog mounted once in MainLayout, Odoo inline pattern
- Server: targo-hub flow-runtime.js, flow-api.js, flow-templates.js
- ERPNext: Flow Template/Run doctypes, scheduler, 5 seeded system templates
- depends_on chips resolve to step labels instead of opaque "s4" ids

QR/OCR scanner (field app):
- Camera capture → Gemini Vision via targo-hub with 8s timeout
- IndexedDB offline queue retries photos when signal returns
- Watcher merges late-arriving scan results into the live UI

Dispatch:
- Planning mode (draft → publish) with offer pool for unassigned jobs
- Shared presets, recurrence selector, suggested-slots dialog
- PublishScheduleModal, unassign confirmation

Ops app:
- ClientDetailPage composables extraction (useClientData, useDeviceStatus,
  useWifiDiagnostic, useModemDiagnostic)
- Project wizard: shared detail sections, wizard catalog/publish composables
- Address pricing composable + pricing-mock data
- Settings redesign hosting flow templates

Targo-hub:
- Contract acceptance (JWT residential + DocuSeal commercial tracks)
- Referral system
- Modem-bridge diagnostic normalizer
- Device extractors consolidated

Migration scripts:
- Invoice/quote print format setup, Jinja rendering
- Additional import + fix scripts (reversals, dates, customers, payments)

Docs:
- Consolidated: old scattered MDs → HANDOFF, ARCHITECTURE, DATA_AND_FLOWS,
  FLOW_EDITOR_ARCHITECTURE, BILLING_AND_PAYMENTS, CPE_MANAGEMENT,
  APP_DESIGN_GUIDELINES
- Archived legacy wizard PHP for reference
- STATUS snapshots for 2026-04-18/19

Cleanup:
- Removed ~40 generated PDFs/HTMLs (invoice_preview*, rendered_jinja*)
- .gitignore now covers invoice preview output + nested .DS_Store

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 10:44:17 -04:00
louispaulb
607ea54b5c 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
73691668d3 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
8fc722acdf feat(field): job detail page with equipment management and inline editing
- 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>
2026-04-09 07:21:38 -04:00
louispaulb
922572653a docs: comprehensive ecosystem overview for dev/sysadmin onboarding
Complete synthesis covering infrastructure, ERPNext data model, ops app
architecture (40 composables, 12 pages, dispatch features), targo-hub
modules, migration pipeline, integrations, and deployment procedures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 22:49:21 -04:00
louispaulb
0c77afdb3b 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
a9f8d0c7bf 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
fd326ac52e perf: parallelize dispatch API fetches + add sales_order/order_source fields
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>
2026-04-08 18:07:14 -04:00
louispaulb
c6b2dd1491 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
320655b0a0 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
838f8dcd8d docs: complete architecture — service map, dependencies, data flows
Full system documentation: Docker containers, request flows,
targo-hub endpoints, GenieACS integration, ops app structure,
external service dependencies, and device diagnostics data flow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 21:29:05 -04:00
louispaulb
bfffed2b41 feat: ONT diagnostics — grouped mesh topology, signal RSSI, management link
- 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>
2026-04-03 21:26:14 -04:00
louispaulb
fa37426f34 docs: data structure foundation for lead-to-service pipeline
ERPNext doctype relationships and field additions:
- Customer → Service Location → Subscriptions + Equipment
- Project with installation task templates
- Dispatch Job linked to project and location
- New custom fields: wifi_ssid/password, sip_username/password,
  gpon_serial, cwmp_serial, radius credentials on Service Equipment

Order wizard flow (website + agent):
- Address check → plan selection → customer info → dates → payment
- Atomic creation: Customer + Location + Subs + Equipment + Project
  + Tasks + Dispatch Job + fibre port reservation

Existing field tech app inventory:
- TasksPage (jobs + tickets), ScanPage (photo/live/manual),
  DevicePage (detail + customer link), DiagnosticPage (speed test)
- useScanner composable (3-strip photo scan for multi-barcode)
- Offline queue with replay

Migration gaps identified: WiFi/VoIP provisioning data,
RADIUS credentials, product catalog, fibre→RQA address matching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 09:37:38 -04:00
louispaulb
e9324b45bc docs: complete customer flow architecture (lead → live service)
End-to-end design covering:
- Address fuzzy search → fibre availability (16,056 entries, 30 OLTs)
- Plan selection + pricing engine (FTTH 25M-1.5G, TV, Phone, combos)
- Checkout → Stripe → n8n automation pipeline
- ERPNext: Customer → Service Location → Subscriptions → Equipment
- Project/task auto-creation for installation workflow
- Dispatch job with preferred dates and tag-based tech matching
- Field tech barcode scanning → Service Equipment creation
- ACS auto-provisioning on device bootstrap (WiFi, VoIP, firmware)
- Lead funnel for non-covered addresses (phone-only, business)
- Customer portal (invoices, WiFi management, tickets)
- Full product catalog mapped from legacy (Internet/TV/Phone/discounts)
- 5-sprint build order with dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 09:28:20 -04:00
louispaulb
0536e04c86 feat: extract GenieACS WiFi/VoIP provisioning data from MariaDB
Found GenieACS MariaDB at 10.100.80.100 (NOT 10.5.14.21 as
configured in ext scripts — that IP was stale/blocked).

Provisioning data:
- 1,713 WiFi entries (858 unique Deco MACs → SSID/password)
- 797 VoIP entries (469 unique RCMG ONT serials → SIP creds)
- WiFi keyed by Deco MAC (403F8C OUI), VoIP by ONT serial

Complete chain verified:
  ONT serial (RCMG) → fibre table (OLT/slot/port)
                     → device table (delivery_id)
                     → delivery (account_id → ERPNext customer)
                     → VoIP provisioning (SIP credentials)
                     → WiFi provisioning (via linked Deco MAC)

Reconciliation: 2,499 RCMG serials addressable, 2,003 have
full fibre+device chain, 282 have VoIP provisioning attached.
3,185 TPLG serials, 2,935 in both fibre and device tables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:49:26 -04:00
louispaulb
231bb6fbcc feat: complete device matching analysis (legacy ↔ GenieACS ↔ ERPNext)
Full data export and cross-reference analysis:
- 7,550 GenieACS devices with IPs, deviceId, tags
- 6,720 legacy devices (raisecom, tplink, onu categories)
- 16,056 fibre table entries (OLT frame/slot/port/ontid, VLANs)
- 8,434 legacy services linked to devices

Key finding: CWMP serial ≠ physical serial. Only 22/7,550 devices
are tagged with their physical serial (RCMG/TPLG). Raisecom MAC
is extractable from CWMP serial suffix. TP-Link CWMP serial = sticker
serial for ONT models.

Matching strategy documented: tag-based, MAC-based, OLT port-based.
Recommends bulk tagging via OLT query as first step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:08:57 -04:00
louispaulb
8ba73251f3 feat: full GenieACS config export (provisions, ext scripts, fleet data)
Complete backup of all GenieACS ACS configuration:
- 24 provision scripts (default, inform, bootstrap, firmware upgrades,
  per-model configs for HT803G, HT502, HT812, Deco, XX230v, XX430v, XX530v)
- 25 presets (trigger rules mapping events to provisions)
- 6 ext scripts (provisioning.js, wifi.js, voip.js — query MariaDB
  for per-device WiFi SSID/password and VoIP credentials)
- 12 firmware images catalogued (HT502, HG8245, HT803G-W/WS2, HT812, Deco)
- 7,550 device fleet snapshot (4,035 online, 53.4% online rate)
- GenieACS env config (MongoDB at 10.5.2.116, ext dir, JWT secret)

Fleet breakdown:
- Device2 (TP-Link Deco): 4,051 units (74% online) — bulk of fleet
- HT803G (Raisecom): 2,833 units (33% online) — legacy ONTs
- DISCOVERYSERVICE: 156 ghost entries (0% online)
- Grandstream phones: GXP2130/2160/1630, HT502/812

Key finding: ext scripts use MariaDB (10.5.14.21) for WiFi/VoIP
provisioning data (SSID, passwords, SIP credentials per serial).
This data must be migrated to ERPNext or a new provisioning DB
for Oktopus.

Custom fork: @genieacs/genieacs-targo v1.2.8-targo.3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 21:08:51 -04:00
louispaulb
56ad97bc71 feat: GenieACS config export + TR-069 to TR-369 migration plan
- Add /acs/export endpoint: dumps all provisions, presets, virtual
  params, files metadata in one call (insurance policy for migration)
- Add /acs/provisions, /acs/presets, /acs/virtual-parameters, /acs/files
- Shell script export_genieacs.sh for offline full backup
- TR069-TO-TR369-MIGRATION.md: phased migration plan from GenieACS
  to Oktopus with parallel run, provision mapping, CPE batching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 21:03:41 -04:00
louispaulb
ea71eec194 feat: GenieACS NBI integration for live CPE/ONT status
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>
2026-04-02 20:55:13 -04:00
louispaulb
a2c59d6528 feat: ticket lazy-load, inline editing, search improvements
- 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>
2026-04-02 14:43:25 -04:00
louispaulb
4693bcf60c feat: telephony UI, performance indexes, Twilio softphone, lazy-load invoices
- Add PostgreSQL performance indexes migration script (1000x faster queries)
  Sales Invoice: 1,248ms → 28ms, Payment Entry: 443ms → 31ms
  Indexes on customer/party columns for all major tables
- Disable 3CX poller (PBX_ENABLED flag, using Twilio instead)
- Add TelephonyPage: full CRUD UI for Routr/Fonoster resources
  (trunks, agents, credentials, numbers, domains, peers)
- Add PhoneModal + usePhone composable (Twilio WebRTC softphone)
- Lazy-load invoices/payments (initial 5, expand on demand)
- Parallelize all API calls in ClientDetailPage (no waterfall)
- Add targo-hub service (SSE relay, SMS, voice, telephony API)
- Customer portal: invoice detail, ticket detail, messages pages
- Remove dead Ollama nginx upstream

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 13:59:59 -04:00
louispaulb
413e15b16c refactor: strip CUST- prefix, use bank reference as customer ID
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>
2026-04-01 18:02:08 -04:00
louispaulb
4a8718f67c feat: subscription reimport, customer/doctype ID rename, zero-padded format
- 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>
2026-04-01 17:17:23 -04:00
louispaulb
7d7b4fdb06 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
101faa21f1 feat: inline editing, search, notifications + full repo cleanup
- 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>
2026-03-31 07:34:41 -04:00
louispaulb
26a0077015 fix: route API + Ollama calls through ops-frontend nginx proxy
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>
2026-03-31 00:00:14 -04:00
louispaulb
2453bc6ef2 feat: Ollama Vision OCR for bill/invoice scanning
- 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>
2026-03-30 23:57:21 -04:00
louispaulb
dc63462c0c fix: client detail page reloads when navigating between customers
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>
2026-03-30 23:51:34 -04:00
louispaulb
1ed86e37ad fix: server-side API token injection + ticket modal empty state
- 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>
2026-03-30 23:31:58 -04:00
louispaulb
11cd38f93c feat: add field tech app — barcode scanner, tasks, diagnostics, offline
Mobile-first Quasar PWA for field technicians at erp.gigafibre.ca/field/:
- Multi-barcode scanner (photo + live + manual) with device lookup
- Tasks page: today's Dispatch Jobs + assigned tickets
- Diagnostic: speed test, HTTP resolve, batch service check
- Device detail with customer linking
- Offline support: IndexedDB queue, API cache, auto-sync
- Standalone nginx container with Traefik StripPrefix + Authentik SSO

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 23:00:44 -04:00
louispaulb
13dcd4bf77 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
louispaulb
08cf1c94e3 feat: 29K customer memos imported as Comments with real dates
- 29,178 account_memo → Comment on Customer
- Timestamps converted from unix to datetime
- Author mapped from staff_id → User email
- Visible in Customer page comment section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:43:57 -04:00
louispaulb
c6b5aa8f61 feat: 99K payments imported with invoice references
- 99,839 Payment Entries (24 months, excl credits)
- 120,130 Payment-Invoice references (payment_item → Sales Invoice)
- Modes: Bank Draft (PPA/direct), Credit Card, Cheque, Cash
- legacy_payment_id custom field for traceability
- Creation dates set from legacy timestamps
- 0 errors

Full financial chain: Customer → Subscription → Invoice → Payment → Bank ref (PPA)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:33:35 -04:00
louispaulb
5640063bd0 fix: correct creation/modified dates from unix timestamps
- 129,078 Issues: creation = ticket.date_create, modified = ticket.last_update
- 115,721 Invoices: creation = invoice.date_orig
- 15,059 Customers: creation = account.date_orig, modified = account.date_last

All timestamps now show real legacy dates instead of import date.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:20:47 -04:00
louispaulb
4f74376412 feat: complete data mirror — all customers + 115K invoices
- 8,636 terminated customers imported (disabled=1, terminate reason/company/notes preserved)
- Total customers: 15,303 (100% of legacy)
- 33,131 Subscription.party links fixed (CUST-xxx)
- 115,721 Sales Invoices (24 months) + 658K line items
- Custom field: Sales Invoice.legacy_invoice_id
- All invoices as Draft (not submitted, not sent)

Customer lifecycle preserved:
  Active → services, subscriptions, invoices
  Terminated → disabled=1, customer_details has departure reason/competitor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:09:16 -04:00