diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0c8305c..a8c18ba 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,13 +1,218 @@ # 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) @@ -15,123 +220,73 @@ Customer (ERPNext native) ├─ Address + GPS coordinates ├─ Connection type (FTTH/FTTB/Cable/DSL) ├─ OLT port, VLAN, network config - ├─ Access notes (door code, dog, etc.) │ ├─ Service Equipment (EQP-#####) - │ ├─ Type: ONT / Modem / Router / TV Box / IP Phone + │ ├─ Type: ONT / Router / Switch / AP / Decodeur │ ├─ Serial number + MAC address - │ ├─ Status: inventory → active → defective → returned + │ ├─ Status: Active / Inactive / En stock / Defectueux / Retourne │ ├─ Network config (IP, firmware, credentials) - │ ├─ Move history (Equipment Move Log) - │ └─ Linked to Subscription + │ └─ Move history (Equipment Move Log) │ └─ Service Subscription (SUB-#####) - ├─ Category: Internet / IPTV / VoIP / Bundle - ├─ Plan: Fibre 50, Fibre 100, IPTV Essentiel... - ├─ Speed up/down, monthly price, billing cycle - ├─ Contract duration, promo end date + ├─ Plan: Internet / IPTV / VoIP / Bundle + ├─ Billing: price, frequency, Stripe integration └─ Status: pending → active → suspended → cancelled -Dispatch Job (existing, extended) - ├─ Customer + Service Location links - ├─ Job type: Installation / Repair / Maintenance / Removal - ├─ Source: Helpdesk Issue or direct creation - ├─ Assigned tech + assistants - ├─ Schedule: date, time, duration, route order - ├─ 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) +Dispatch Job + ├─ Customer + Service Location + ├─ Assigned tech + assistants + tags + ├─ Schedule: date, time, duration + ├─ Equipment Items / Materials Used + └─ GPS position (Traccar) ``` -## Tech Stack +--- -| Component | Technology | Location | -|-----------|-----------|----------| -| 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 | +## External Services -## 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 - → Traefik (authentik@file middleware) - → Authentik forwardAuth check - → Valid session? → App loads - → No session? → Redirect auth.targo.ca → Login → Callback → App - -API calls: /api/* → ERPNext (service token, no CORS) -GPS data: /traccar/* → Traccar proxy (Basic auth) +User clicks equipment chip in ClientDetailPage + │ + ▼ +EquipmentDetail.vue + │ + ├── fetchStatus([{serial_number: "TPLGC4160688"}]) + │ → GET msg.gigafibre.ca/devices/lookup?serial=TPLGC4160688 + │ → 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 |