# Gigafibre FSM — Status Snapshot (2026-04-18) — ARCHIVED

> **⚠️ ARCHIVED snapshot.** Links below point to the pre-reorganization layout and are stale. For the current state, see [../../roadmap.md](../../roadmap.md). For the live index, see [../../README.md](../../README.md).

> **Audience (original):** new contributor, stakeholder, or future-me opening this cold.
> **Goal (original):** be able to hold a coherent conversation about the system in 10 minutes.

---

## TL;DR

Gigafibre (consumer brand of TARGO Internet) is a Quebec fiber ISP. We are migrating a legacy PHP/MariaDB billing system (used daily since ~2012) to **ERPNext v16 on PostgreSQL**, wrapping it in Vue 3/Quasar PWAs for ops, dispatch, field, and customer self-service. The accounting, subscriptions, invoices, and payments are **already migrated and balanced**. We are now in the **"live ops" transition phase**: new quotes flow through ERPNext + DocuSeal e-signature, dispatch runs on the new PWA, and the legacy system is kept in read-only mode pending customer-portal cutover.

---

## Where we are (at a glance)

| Area | State | Notes |
|---|---|---|
| **Accounting migration** | ✅ Shipped | 629,935 invoices + 343,684 payments, GL balanced ($130M debit/credit) |
| **Master data migration** | ✅ Shipped | 15,303 customers · 833 items · 21,876 subscriptions · 7,241 equipment |
| **Staff SSO (auth.targo.ca)** | ✅ Live | Authentik forwardAuth, 6 groups |
| **Ops PWA (erp.gigafibre.ca/ops/)** | ✅ Live | ClientDetail, Tickets, Dispatch, Dashboard |
| **Dispatch PWA v1** | ✅ Live | Timeline + Mapbox + drag-drop |
| **Field tech PWA** | ✅ Live | Mobile via `/t/{token}` |
| **GenieACS TR-069** | ✅ Live | ~7,560 CPEs polled every 5 min |
| **targo-hub gateway** | ✅ Live | 11 modules (ERP, Twilio, SIP, AI, SSE, etc.) |
| **Quotation + DocuSeal flow** | 🆕 Just shipped (2026-04-18) | See "New this cycle" below |
| **Customer SSO (id.gigafibre.ca)** | ✅ Live | Authentik federated from staff |
| **Customer portal v2** | 🚧 In progress | Vue SPA spec ready; checkout + Stripe pending |
| **ERPNext scheduler** | ⏸ Paused | `pause_scheduler=1` — requires explicit approval to reactivate |
| **Invoice PDF "Facture TARGO"** | ✅ Live | Inline-SVG logo, QR to Stripe/portal |
| **Quote PDF "Soumission TARGO"** | ✅ Live | Same branding, QR to DocuSeal signing URL |
| **Legacy PHP/MariaDB** | 🗃 Read-only | Accessed from 10.100.80.100 for residual lookups |

---

## Architecture at a glance

```
                 Internet (Cloudflare DNS for gigafibre.ca)
                               │
          ┌────────────────────┴───────────────────┐
          │    Proxmox VM — 96.125.196.67          │
          │    Ubuntu 24.04 · Docker · Traefik v2.11│
          └────────────────────┬───────────────────┘
                               │
   ┌──────────┬───────────┬────┴─────┬──────────┬─────────────┐
   │          │           │          │          │             │
 ERPNext   Ops PWA    targo-hub    n8n     DocuSeal      Authentik
 erp.      /ops/      msg.         n8n.    sign.         auth.targo.ca
 giga.ca   (nginx)    giga.ca      giga.ca giga.ca       id.giga.ca
   │                     │
   │                     ├─── Twilio (SMS, +14382313838)
   │                     ├─── 3CX PBX (targopbx.3cx.ca)
   │                     ├─── Mailjet SMTP
   │                     ├─── Fonoster/Routr (SIP @ voice.giga.ca)
   │                     ├─── GenieACS NBI (10.5.2.115:7557)
   │                     ├─── Oktopus TR-369 (oss.giga.ca)
   │                     ├─── Gemini AI (agent/voice/OCR)
   │                     ├─── Stripe (payments)
   │                     └─── Traccar (tech GPS, tracker.targointernet.com)
   │
   └─── Legacy MariaDB (read-only, 10.100.80.100)
```

For the authoritative breakdown see [ARCHITECTURE.md](ARCHITECTURE.md).

---

## Features inventory (by surface)

### 👥 Staff — Ops PWA (`erp.gigafibre.ca/ops/`)

- **ClientDetailPage** — master client view, 8 parallel API calls (contacts, devices, subscriptions, invoices, payments, tickets, equipment, SMS history). Inline-editable fields Odoo-style.
- **TicketsPage** — issue triage, assignment, status, SLA indicators.
- **DispatchPage (v1)** — today's jobs, technician rows, drag-drop re-assign, Mapbox job map.
- **DashboardPage** — KPIs, outstanding AR, job throughput.
- **Wizard (ProjectWizard.vue)** — creates Quotation with `wizard_steps` JSON describing dispatch + subscription creation deferred until acceptance.
- **Theme** — light mode everywhere except Dispatch (dark by design).

### 🔧 Staff — Dispatch PWA v2 (`dispatch.gigafibre.ca`) *[in migration]*

- Larger Mapbox-driven timeline, undo support, technician lanes, planned features include Gantt view and auto-dispatch by skill tags.

### 📱 Field — Field tech PWA (mobile)

- Route at `/t/{token}` (no login — token-based).
- Offline-first IndexedDB; photo upload, checklist, materials used, customer signature.
- Barcode/serial scanner: native camera capture (`<input capture="environment">`) → Gemini 2.5 Flash Vision via `msg.gigafibre.ca/vision/barcodes` (extracts barcode / S/N / MAC / model from a single photo).

### 🧑‍💼 Customer — Client portal (`client.gigafibre.ca`) *[v2 in progress]*

**Live today:**
- Authentik client SSO at `id.gigafibre.ca`.
- Legacy password bridge: MD5 → PBKDF2 on first login (11,800 portal users provisioned).
- ERPNext Portal Server Script resolves `X-Authentik-Email` → Customer.

**In flight (Phase 4):**
- Vue SPA: see invoices, pay via Stripe Payment Element, view subscriptions, update contact info.
- Online appointment booking.
- Real-time tech arrival tracking via SMS.
- QR code on modem → subscriber dashboard.

### 🌐 Public — Marketing site (`www.gigafibre.ca`)

- React + Vite + Tailwind + shadcn/ui.
- RQA address eligibility check, AQ address normalization.
- Uses Supabase for pre-signup intake; Twilio for SMS OTP on checkout intent.

### ✍️ Quotation → Signature flow (just shipped)

1. Wizard creates Quotation w/ `wizard_steps` JSON (dispatch + subscriptions to execute on acceptance).
2. Wizard calls `POST /accept/generate` with `use_docuseal: true`.
3. Hub creates DocuSeal submission (template 1), writes `sign_url` to `Quotation.custom_docuseal_signing_url`.
4. PDF print format **Soumission TARGO** renders with QR + "SIGNER CETTE SOUMISSION" button.
5. Customer signs → DocuSeal webhook fires `form.completed` → hub runs `acceptQuotation()` → dispatch jobs + subscriptions materialized from `wizard_steps`.

See [project_docuseal_quote.md](~/.claude/projects/.../memory/project_docuseal_quote.md) for deep detail.

---

## ERPNext customisations

### Custom doctypes (FSM module)

| Doctype | ID | Purpose |
|---|---|---|
| Service Location | `LOC-#####` | Customer premises (address, GPS, OLT port, network config) |
| Service Equipment | `EQP-#####` | ONT / router / TV box — serial, MAC, IP, parent |
| Service Subscription | `SUB-#####` | Active plans — speed, price, billing, RADIUS creds |
| Service Contract | `CTR-#####` | Long-term agreement with benefits |
| Dispatch Job | `DJ-#####` | Work order — materials, checklist, photos, signature |
| Dispatch Technician | `DT-#####` | Tech profile — skills, Traccar GPS, color |
| Dispatch Tag | — | Skill tag with level (1 base → 5 expert) |
| Checklist Template | — | Re-usable job checklists |

### Custom fields (highlights)

| Doctype | Field | Purpose |
|---|---|---|
| Item | `custom_billing_frequency` | One-time / Monthly / Annual (source of truth) |
| Quotation Item · Sales Invoice Item · Sales Order Item | `custom_billing_frequency` | Inherited from Item via `fetch_from`, overridable per-line |
| Quotation | `custom_docuseal_signing_url`, `custom_docuseal_envelope_id` | Populated by hub after DocuSeal submit |
| Quotation | `custom_quote_type` | Résidentiel / Commercial |
| Quotation | `wizard_steps`, `wizard_context` | JSON blobs driving post-acceptance workflow |
| Customer | `legacy_account_id`, `legacy_customer_id`, `ppa_enabled`, `stripe_id` | Legacy mapping + Stripe link |
| Item | `legacy_product_id`, `download_speed`, `upload_speed`, `olt_profile` | Network profile |
| Subscription | `radius_user`, `radius_pwd`, `legacy_service_id` | Network auth |
| Issue | `legacy_ticket_id`, `assigned_staff`, `issue_type`, `is_important`, `service_location` | Ticket context |
| User | `legacy_password_md5` | Bridge for legacy portal passwords |

### Print formats

| Format | Doctype | State |
|---|---|---|
| **Facture TARGO** | Sales Invoice | ✅ Live — used for monthly billing, QR to Stripe/portal |
| **Soumission TARGO** | Quotation | ✅ Live (2026-04-18) — QR to DocuSeal signing URL, split totals (Services récurrents + Frais uniques) |

### Known ERPNext v16 quirks

- **PostgreSQL GROUP BY / HAVING / double-quote bugs** — patches applied, bulk submit workflow documented.
- **Scheduler paused** (`pause_scheduler=1`) — do NOT reactivate without approval; Subscription → Invoice automation depends on it.
- **Frappe REST filter on Quotation.customer** rejected — use `party_name` when `quotation_to == "Customer"`.

---

## Integrations (live + credentialed)

| Integration | URL / endpoint | Auth | Used by |
|---|---|---|---|
| Authentik (staff) | `auth.targo.ca` | OAuth client + forwardAuth | ERPNext, ops, dispatch, n8n, hub |
| Authentik (client) | `id.gigafibre.ca` | forward_single proxy | client.gigafibre.ca |
| Twilio | `api.twilio.com` | Basic (SID/token) | targo-hub twilio.js, SMS, OTP |
| Cloudflare | `api.cloudflare.com` | X-Auth-Key + X-Auth-Email (Global) | DNS, ops app checks |
| GenieACS | `http://10.5.2.115:7557` | LAN no-auth | targo-hub devices.js (5-min poll) |
| Oktopus (TR-369) | `oss.gigafibre.ca` | user/pass | CPE next-gen mgmt |
| Stripe | `api.stripe.com` | secret key | Customer checkout, webhook |
| Fonoster / Routr | `voice.gigafibre.ca:5160/5163` | SIP creds | Voice calls via WSS |
| 3CX PBX | `targopbx.3cx.ca` | Basic | Voice polling, call logs |
| Mailjet SMTP | `in-v3.mailjet.com:587` | user/pass | DocuSeal + transactional email |
| Traccar | `tracker.targointernet.com:8082` | user/pass | Tech GPS |
| DocuSeal | `sign.gigafibre.ca` | API key | Quotation e-signature (just live) |
| n8n | `n8n.gigafibre.ca` | webhook (no auth) | SMS, OLT SSH commands, subscription automations |
| Gemini AI | `generativelanguage.googleapis.com` | Bearer | agent replies, OCR, voice |

All tokens/keys live in `/opt/targo-hub/.env` on the production host and are tracked in memory records (never in repo).

---

## 🆕 New this cycle (2026-04-08 → 2026-04-18)

- **DocuSeal e-signature for Quotations** — hub routes, Custom Fields, "Soumission TARGO" print format, signing URL writeback, webhook → `acceptQuotation`.
- **Billing frequency system** — `custom_billing_frequency` on Item + Quotation Item + Sales Invoice Item + Sales Order Item; print format splits Récurrent vs One-time with their own subtotals.
- **Print format fixes** — inline-SVG logo (was oversized PNG), `frappe.db.commit()` fix (was silently rolling back), correct `party_name` usage.
- **Hub** — `DOCUSEAL_DEFAULT_TEMPLATE_ID` env knob, writeback of `sign_url` to Quotation.
- **Custom app** — `gigafibre_utils.api.url_qr_base64(url)` helper for QR generation in print formats.

---

## 🚧 In flight (right now)

1. **Customer portal v2 (Vue SPA)** — invoice list, Stripe Payment Element, subscription view, contact update. Spec ready. No front-end code yet.
2. **Dispatch v2** — larger rewrite of dispatch PWA at `dispatch.gigafibre.ca`. Timeline + Mapbox + Gantt + auto-dispatch by tag/skill.
3. **ClientDetailPage refactor** — 1,512-line monolith → composables (`useCustomer`, `useSubscriptionGroups`, `useFormatters`, `useStatusClasses`).
4. **Service deactivation workflow** — n8n webhook ties Subscription cancellation to OLT port shutdown.
5. **Multi-email account cleanup** — ~30 customers have multiple emails; manual triage pending.

---

## 🎯 Next priorities (ordered)

> Update this list at the end of each working session.

| # | Item | Why now | Owner | Blocked by |
|---|---|---|---|---|
| 1 | Register DocuSeal webhook in UI → `msg.gigafibre.ca/accept/docuseal-webhook` on `form.completed` | Without this, signed quotes don't materialise dispatch jobs | manual (UI) | — |
| 2 | Validate Subscription → Sales Invoice scheduler flow on a pilot subscription | Before reactivating scheduler for full run | eng | — |
| 3 | Reactivate `pause_scheduler=0` for April billing | Recurring invoices must generate | eng + ops approval | #2 |
| 4 | Customer portal v2 MVP (invoice list + Stripe Payment Element) | Drive self-serve payment, reduce AR calls | eng | — |
| 5 | Tag 46 techs with skills | Enable auto-dispatch | ops | — |
| 6 | Issue → Dispatch Job auto-creation wiring | Close ticket/dispatch loop | eng | — |
| 7 | Fix 15 invoices flagged "Credit Note Issued" with $3,695.77 overpaid | Accounting hygiene | accounting | — |
| 8 | 10DLC Twilio production upgrade | Avoid SMS rate throttling at scale | ops | Twilio approval |

---

## 🐛 Known issues & technical debt

- **Scheduler paused** — subscriptions won't produce invoices until reactivated.
- **Tech debt in ClientDetailPage.vue (1,512 LOC)** — slated for composable extraction.
- **Multiple `formatDate` / `formatMoney` implementations** across files — should consolidate.
- **6+ ad-hoc `statusClass` functions** (`subStatusClass`, `eqStatusClass`, `ticketStatusClass`...) — extract to `useStatusClasses`.
- **Dispatch v1 vs v2** live simultaneously — cutover plan not yet written.
- **15 "Credit Note Issued" invoices** with overpayment of $3,695.77.
- **~30 customers** share emails across accounts — migration chose one per; manual reconciliation TBD.
- **GenieACS CPE count ≠ ERPNext Service Equipment count** — TPLG serial vs real serial mismatch; MAC-based lookup required.

---

## 🚨 Gotchas (don't get burned)

- **Traefik HTTP → HTTPS global redirect breaks Let's Encrypt HTTP-01.** Add redirect per-route or use TLS-ALPN. See `feedback_traefik_ssl.md`.
- **Docker 29 + Traefik v3 don't play well** — stay on v2.11.
- **MongoDB 5+ needs AVX** — Proxmox CPU must be set to `host` type.
- **netplan overrides systemd-networkd** — remove for static networking.
- **Docker exec + nested SSH quoting is fragile** — always write `.py` to disk, `docker cp`, then exec. See `feedback_docker_exec.md`.
- **iptables + Docker** — avoid `iptables-multiport`, don't persist bridge rules, use the raw table. See `feedback_ddos_iptables.md`.
- **ERPNext Customer doctype has multi-address** — but legacy assumed 1:1. Device hierarchy is tied to **address**, not customer, intentionally — preserves manage IP / credentials / parent relationships across customer changes.
- **Ops app deploy path** — upload `dist/spa/*` (contents), not `dist/spa/` (dir), to `/opt/ops-app/`. See `feedback_deploy_path.md`.

---

## How to share this context

- **"I need someone on-board in 30 min":** have them read the README + this file.
- **"They need to work on dispatch":** add [DATA_AND_FLOWS.md](DATA_AND_FLOWS.md) + [APP_DESIGN_GUIDELINES.md](APP_DESIGN_GUIDELINES.md).
- **"They need to work on billing/invoicing":** add [BILLING_AND_PAYMENTS.md](BILLING_AND_PAYMENTS.md).
- **"They need to work on CPE / network gear":** add [CPE_MANAGEMENT.md](CPE_MANAGEMENT.md).
- **"They need server / infra access":** credentials live on the host in `/opt/targo-hub/.env` — share out-of-band. Memory records in `~/.claude/.../memory/` note the shape of each credential.

---

*Last updated: 2026-04-18 · Maintained alongside [ROADMAP.md](ROADMAP.md). When this file drifts more than a week out of date, create a new dated snapshot (`STATUS_YYYY-MM-DD.md`) and link it from the top of README.*
