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>
This commit is contained in:
louispaulb 2026-04-03 21:29:05 -04:00
parent bfffed2b41
commit 838f8dcd8d

View File

@ -1,13 +1,218 @@
# Gigafibre FSM — Architecture # Gigafibre FSM — Architecture
## Overview ## Service Map
Field Service Management platform for Gigafibre ISP. ```
Inspired by Odoo OCA Field Service, Salesforce Field Service, and Zuper. ┌──────────────────┐
│ Authentik SSO │
│ id.gigafibre.ca │
└────────┬─────────┘
│ OIDC / Proxy Auth
┌──────────────────┐
│ Traefik │
│ Reverse Proxy │
│ + Let's Encrypt │
└──┬───┬───┬───┬───┘
│ │ │ │
┌───────────────────┘ │ │ └──────────────────┐
▼ ▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Ops App │ │ ERPNext v16 │ │ Targo-Hub │
│ erp.../ops/ │ │ erp.gigafibre.ca│ │ msg.gigafibre.ca │
│ (nginx+Quasar) │ │ (Frappe/Python) │ │ (Node.js) │
└───────┬─────────┘ └───────┬──────────┘ └──┬──────┬───────┘
│ │ │ │
│ /api/* proxy │ │ │
│ (token injected) │ │ │
└─────────────────────┘ │ │
│ │
┌──────────────────────────────────────┘ │
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ GenieACS NBI │ │ Twilio API │
│ 10.5.2.115:7557 │ │ SMS + Voice │
│ (TR-069 ACS) │ └──────────────────┘
└───────┬──────────┘
│ CWMP (TR-069)
┌──────────────────┐ ┌──────────────────┐
│ CPE / ONT │ │ Oktopus (USP) │
│ TP-Link XX230v │ │ oss.gigafibre.ca│
│ Raisecom HT803G │ │ TR-369 (future) │
└──────────────────┘ └──────────────────┘
```
> **Note**: For development standards and modularity rules, please refer to the [Design Guidelines](DESIGN_GUIDELINES.md). ## Host: 96.125.196.67 (hubdocker)
## Data Model All services run on a single Docker host. DNS records `erp.gigafibre.ca`,
`oss.gigafibre.ca`, `msg.gigafibre.ca` all resolve to this IP.
### Docker Containers
| Container | Image | Port | Network | Purpose |
|-----------|-------|------|---------|---------|
| ops-frontend | nginx:alpine | 80 | proxy | Ops SPA + ERPNext API proxy |
| targo-hub | node:20-alpine | 3300 | proxy, erpnext | SSE relay, SMS, GenieACS proxy |
| erpnext-frontend | frappe/erpnext | 8080 | erpnext | ERPNext web + API |
| erpnext-backend | frappe/erpnext | 8000 | erpnext | Frappe worker |
| erpnext-db-1 | postgres:16 | 5432 | erpnext | ERPNext database |
| oktopus-acs-1 | oktopusp/acs | 9292 | oktopus | USP/TR-369 controller |
| oktopus-mongo-1 | mongo:7 | 27017 | oktopus | Oktopus datastore |
| fn-routr | fonoster/routr-one | — | fonoster | VoIP SIP routing |
| fn-asterisk | fonoster/asterisk | — | fonoster | PBX media server |
| fn-postgres | postgres:16 | — | fonoster | Fonoster DB |
| apps-targo-db-1 | postgres | — | apps | Targo-hub database |
| authentik-* | goauthentik | — | authentik | SSO provider |
---
## Ops App (Quasar v2 + Vite)
**Served from:** `/opt/ops-app/` via `ops-frontend` (nginx)
**URL:** `https://erp.gigafibre.ca/ops/`
### Request Flow
```
Browser Traefik ops-frontend (nginx) ERPNext
│ │ │ │
│── GET /ops/... ──────▶ strip /ops ────────────▶ try_files ──▶ SPA │
│ │ │ │
│── GET /ops/api/... ──▶ strip /ops ────────────▶ /api/* proxy ─────▶
│ │ │ + Auth token │
│ │ │ injected │
```
### Directory Structure
```
apps/ops/src/
├── api/ # API clients
│ ├── auth.js # Token auth + session check
│ ├── erp.js # ERPNext CRUD (listDocs, getDoc, updateDoc...)
│ ├── dispatch.js # Dispatch jobs/techs/tags CRUD
│ ├── sms.js # SMS via n8n webhook
│ ├── traccar.js # GPS tracking
│ ├── ocr.js # Ollama Vision OCR
│ └── service-request.js # Service booking (future)
├── components/
│ ├── shared/
│ │ ├── DetailModal.vue # Right-panel detail viewer
│ │ ├── InlineField.vue # Odoo-style inline editing
│ │ ├── TagEditor.vue # Tag selector with levels
│ │ └── detail-sections/ # Per-doctype detail views
│ │ ├── EquipmentDetail # ONT diagnostics + mesh topology
│ │ ├── IssueDetail # Ticket view
│ │ ├── InvoiceDetail # Invoice + payment status
│ │ ├── PaymentDetail # Payment entry
│ │ └── SubscriptionDetail# Subscription management
│ └── customer/
│ ├── CustomerHeader.vue # Name, status, actions
│ ├── ContactCard.vue # Phone/email
│ ├── CustomerInfoCard.vue # Flags, notes, tax category
│ ├── ChatterPanel.vue # SMS/call thread
│ ├── ComposeBar.vue # Message input
│ ├── SmsThread.vue # SMS history
│ └── PhoneModal.vue # Twilio voice/SIP
├── composables/ # 24 Vue composables
│ ├── useDeviceStatus.js # GenieACS device lookup + cache
│ ├── useSSE.js # Server-sent events (targo-hub)
│ ├── useInlineEdit.js # Inline field save logic
│ ├── usePhone.js # 3CX/Twilio voice config
│ └── ... # Dispatch, map, formatting, etc.
├── modules/
│ └── dispatch/components/ # Dispatch-specific UI (timeline, calendar, map)
├── pages/ # 10 routed pages
│ ├── DashboardPage # KPI overview
│ ├── ClientsPage # Search-first customer list
│ ├── ClientDetailPage # Customer detail (subs, invoices, tickets)
│ ├── TicketsPage # Issue/ticket list
│ ├── DispatchPage # Scheduling + timeline (~1600 LOC)
│ ├── EquipePage # Equipment fleet
│ ├── OcrPage # Invoice OCR
│ ├── TelephonyPage # VoIP management
│ ├── RapportsPage # Reports (stub)
│ └── SettingsPage # Config: API, SMS, 3CX
├── stores/ # Pinia: dispatch, auth
├── config/ # erpnext.js, nav.js, ticket-config.js
└── router/index.js
```
---
## Targo-Hub (Node.js)
**Container:** `targo-hub` | **URL:** `msg.gigafibre.ca` | **Port:** 3300
### Endpoints
| Method | Path | Purpose |
|--------|------|---------|
| GET | `/sse?topics=customer:X` | Server-Sent Events stream |
| POST | `/broadcast` | Push event to SSE clients |
| POST | `/send/sms` | Send SMS via Twilio |
| POST | `/webhook/twilio/sms-incoming` | Receive inbound SMS |
| POST | `/webhook/twilio/sms-status` | SMS delivery status |
| GET | `/voice/token` | Twilio voice JWT |
| GET | `/devices/lookup?serial=X` | Find CPE in GenieACS |
| GET | `/devices/summary` | Fleet statistics |
| GET | `/devices/:id/hosts` | Connected clients + mesh mapping |
| POST | `/devices/:id/tasks` | Send task (reboot, refresh) |
| GET | `/health` | Health check |
### GenieACS Device Lookup (3 fallbacks)
```
serial = "TPLGC4160688"
1. Exact: DeviceID.SerialNumber._value == serial → match?
2. GPON: Device.Optical...GponSn._value =~ /C4160688$/ → match?
3. ID: _id =~ /TPLGC4160688/ → match?
```
### Hosts Endpoint Flow
```
GET /devices/:id/hosts?refresh
├── Task 1: getParameterValues → Device.Hosts.Host.{1-20}.*
│ (connection_request, timeout=15s)
├── Task 2: getParameterValues → Device.WiFi.MultiAP.APDevice.{1-3}
│ .Radio.{1-2}.AP.{1-4}.AssociatedDevice.{1-8}.*
│ (timeout=10s)
├── Read cached data from GenieACS MongoDB
├── Build clientNodeMap: MAC → {nodeName, band, signal, speed}
└── Return { total, hosts[{name, ip, mac, band, signal, attachedNode, lease}] }
```
---
## GenieACS Provision (XX230v)
**Provision:** `xx230v_inform` / `XX230v_inform_TpLink_fix`
TP-Link requires `clear()` + `commit()` before re-reading to avoid error 9805/9806:
```javascript
clear("Device.Hosts.Host", Date.now());
clear("Device.WiFi.MultiAP.APDevice.*.Radio.*.AP.*.AssociatedDevice", Date.now());
commit();
// Re-read in same provision cycle
declare("Device.Hosts.Host.*.HostName", {value: now});
declare("Device.WiFi.MultiAP.APDevice.*.Radio.*.AP.*.AssociatedDevice.*.MACAddress", {value: now});
```
Configures: TR-069 credentials, remote access (HTTPS on 443), VoIP digit map,
NTP, superadmin password. Reads ~100 diagnostic parameters per inform.
---
## Data Model (ERPNext Doctypes)
``` ```
Customer (ERPNext native) Customer (ERPNext native)
@ -15,123 +220,73 @@ Customer (ERPNext native)
├─ Address + GPS coordinates ├─ Address + GPS coordinates
├─ Connection type (FTTH/FTTB/Cable/DSL) ├─ Connection type (FTTH/FTTB/Cable/DSL)
├─ OLT port, VLAN, network config ├─ OLT port, VLAN, network config
├─ Access notes (door code, dog, etc.)
├─ Service Equipment (EQP-#####) ├─ Service Equipment (EQP-#####)
│ ├─ Type: ONT / Modem / Router / TV Box / IP Phone │ ├─ Type: ONT / Router / Switch / AP / Decodeur
│ ├─ Serial number + MAC address │ ├─ Serial number + MAC address
│ ├─ Status: inventory → active → defective → returned │ ├─ Status: Active / Inactive / En stock / Defectueux / Retourne
│ ├─ Network config (IP, firmware, credentials) │ ├─ Network config (IP, firmware, credentials)
│ ├─ Move history (Equipment Move Log) │ └─ Move history (Equipment Move Log)
│ └─ Linked to Subscription
└─ Service Subscription (SUB-#####) └─ Service Subscription (SUB-#####)
├─ Category: Internet / IPTV / VoIP / Bundle ├─ Plan: Internet / IPTV / VoIP / Bundle
├─ Plan: Fibre 50, Fibre 100, IPTV Essentiel... ├─ Billing: price, frequency, Stripe integration
├─ Speed up/down, monthly price, billing cycle
├─ Contract duration, promo end date
└─ Status: pending → active → suspended → cancelled └─ Status: pending → active → suspended → cancelled
Dispatch Job (existing, extended) Dispatch Job
├─ Customer + Service Location links ├─ Customer + Service Location
├─ Job type: Installation / Repair / Maintenance / Removal ├─ Assigned tech + assistants + tags
├─ Source: Helpdesk Issue or direct creation ├─ Schedule: date, time, duration
├─ Assigned tech + assistants ├─ Equipment Items / Materials Used
├─ Schedule: date, time, duration, route order └─ GPS position (Traccar)
├─ Equipment Items (installed/removed/replaced)
├─ Materials Used (from inventory)
├─ Checklist (configurable per job type)
├─ Time tracking (actual start/end, travel time)
├─ Photos + Customer signature
└─ GPS position (via Traccar WebSocket)
``` ```
## Tech Stack ---
| Component | Technology | Location | ## External Services
|-----------|-----------|----------|
| ERP Backend | ERPNext v16 (Frappe) | erp.gigafibre.ca |
| Dispatch PWA | Vue 3 / Quasar / Pinia | dispatch.gigafibre.ca |
| GPS Tracking | Traccar (WebSocket + REST) | tracker.targointernet.com |
| Maps & Routes | Mapbox GL JS + Directions API | Client-side |
| Auth / SSO | Authentik (forwardAuth) | auth.targo.ca |
| Reverse Proxy | Traefik v2.11 | Port 80/443 |
| Workflows | n8n | n8n.gigafibre.ca |
| Admin Hub | Custom Node.js | hub.gigafibre.ca |
## Authentication Flow | Service | URL | Used By | Purpose |
|---------|-----|---------|---------|
| ERPNext | erp.gigafibre.ca | Ops App, targo-hub | Business data |
| GenieACS | 10.5.2.115:7557 | targo-hub | CPE management (TR-069) |
| Twilio | api.twilio.com | targo-hub | SMS + Voice |
| Traccar | tracker.targointernet.com:8082 | Ops App | GPS fleet tracking |
| n8n | n8n.gigafibre.ca | Ops App | SMS workflow |
| Authentik | id.gigafibre.ca | Traefik | SSO (staff) |
| Authentik Client | auth.targo.ca | Portal | SSO (customers) |
| Mapbox | api.mapbox.com | Ops App | Maps + routing |
| 3CX PBX | targopbx.3cx.ca | targo-hub | Call logging |
---
## Data Flow: Customer → Device Diagnostics
``` ```
User → dispatch.gigafibre.ca User clicks equipment chip in ClientDetailPage
→ Traefik (authentik@file middleware)
→ Authentik forwardAuth check
→ Valid session? → App loads EquipmentDetail.vue
→ No session? → Redirect auth.targo.ca → Login → Callback → App
├── fetchStatus([{serial_number: "TPLGC4160688"}])
API calls: /api/* → ERPNext (service token, no CORS) │ → GET msg.gigafibre.ca/devices/lookup?serial=TPLGC4160688
GPS data: /traccar/* → Traccar proxy (Basic auth) │ → targo-hub → GenieACS NBI → summarizeDevice()
│ → {interfaces[], mesh[], wifi{}, opticalStatus, ethernet{}}
├── fetchHosts(serial, refresh=true)
│ → GET msg.gigafibre.ca/devices/:id/hosts?refresh
│ → 2 inline tasks to CPE → read cache → clientNodeMap
│ → {total, hosts[{name, ip, mac, band, signal, attachedNode}]}
UI renders:
┌─ Fibre [Up] Rx dBm (when available)
├─ IP Addresses (Internet, Gestion [clickable → /superadmin/], Service, LAN)
├─ WiFi (per-radio + total clients: direct + mesh)
├─ Ethernet ports
├─ General (firmware, SSID, uptime)
└─ Connected Clients (collapsible, grouped by mesh node)
├─ basement (5) — signal %, name, IP, MAC, band, lease
├─ hallway (2)
├─ living_room (4)
└─ Filaire / Autre (wired clients)
``` ```
## ERPNext Doctypes (module: Dispatch)
### Core
| Doctype | Autoname | Purpose |
|---------|----------|---------|
| Dispatch Technician | field:technician_id | Tech profile + GPS link |
| Dispatch Job | field:ticket_id | Work orders |
| Dispatch Tag | field:name | Job categorization |
### FSM Extension
| Doctype | Autoname | Purpose |
|---------|----------|---------|
| Service Location | LOC-.##### | Customer premises |
| Service Equipment | EQP-.##### | Deployed hardware |
| Service Subscription | SUB-.##### | Active service plans |
| Checklist Template | field:template_name | Reusable checklists |
### Child Tables
| Doctype | Parent |
|---------|--------|
| Equipment Move Log | Service Equipment |
| Job Equipment Item | Dispatch Job |
| Job Material Used | Dispatch Job |
| Job Checklist Item | Dispatch Job |
| Job Photo | Dispatch Job |
| Checklist Template Item | Checklist Template |
| Dispatch Job Assistant | Dispatch Job |
| Dispatch Tag Link | Dispatch Job / Technician |
## Workflows
### Helpdesk → Dispatch
1. Issue created (ERPNext HD) with type "Field Service"
2. Server script creates Dispatch Job, links back to Issue
3. Dispatcher assigns tech via PWA timeline
4. Tech completes job → Issue auto-resolved
### Equipment Lifecycle
1. Equipment purchased → status "En inventaire"
2. Installation job → status "Actif", linked to location + subscription
3. Repair/replacement → move log entry, status update
4. Return → status "Retourné", unlinked from location
### Subscription Lifecycle
1. Customer signs up → Subscription "En attente"
2. Installation job completed → Subscription "Actif"
3. Non-payment → Subscription "Suspendu"
4. Cancellation → removal job → equipment returned → Subscription "Annulé"
## Comparison with Industry Tools
| Feature | Gigafibre FSM | Odoo FS | Zuper | Salesforce FS |
|---------|---------------|---------|-------|---------------|
| Drag-drop dispatch | Yes (custom PWA) | Yes | Yes | Yes |
| GPS real-time | Yes (Traccar WS) | Limited | Yes | Yes |
| Route optimization | Yes (Mapbox) | Basic | Yes | Advanced |
| Equipment tracking | Yes (serial+MAC) | Yes | Yes | Yes |
| Barcode scanning | Planned | Yes | Yes | Yes |
| Offline mobile | Planned (PWA) | Limited | Yes | Yes |
| Subscriptions | Yes (custom) | Yes (native) | Yes | Yes |
| Helpdesk integration | Yes (ERPNext Issue) | Yes | Yes (Zendesk) | Yes (native) |
| Self-hosted | Yes | Yes | No | No |
| Cost | Free (OSS) | Free (CE) | ~$50/user/mo | ~$200/user/mo |