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:
parent
bfffed2b41
commit
838f8dcd8d
|
|
@ -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 |
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user