docs: complete project research for NetForge ISP Management SaaS
Files: - STACK.md - FEATURES.md - ARCHITECTURE.md - PITFALLS.md - SUMMARY.md Key findings: - Stack: Next.js 15 + TypeScript + PostgreSQL + Prisma monolith; BullMQ for billing cron jobs; MikroTik client needs library verification (LOW confidence) - Architecture: Modular monolith with shared-DB multi-tenancy; JournalEntryService as sole ledger gateway; derived financial state (no balance fields) - Critical pitfall: Cross-tenant data leakage + fake accounting + billing state machine must all be solved in Phase 1 before any feature work Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
527
.planning/research/ARCHITECTURE.md
Normal file
527
.planning/research/ARCHITECTURE.md
Normal file
@@ -0,0 +1,527 @@
|
|||||||
|
# Architecture Patterns
|
||||||
|
|
||||||
|
**Domain:** Multi-tenant ISP Operations Management SaaS
|
||||||
|
**Project:** NetForge
|
||||||
|
**Researched:** 2026-03-04
|
||||||
|
**Confidence:** HIGH (established patterns, well-documented domain — verified against training knowledge through August 2025; external verification tools unavailable, document from domain expertise)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended Architecture
|
||||||
|
|
||||||
|
NetForge is best structured as a **modular monolith with tenant-scoped data isolation**, not microservices. For a small-to-medium SaaS targeting ISPs with under 500-5,000 subscribers per tenant, microservices add operational complexity that outweighs their benefits. A monolith with clear internal module boundaries allows rapid iteration, is easier to reason about, and can be extracted later if one module scales differently.
|
||||||
|
|
||||||
|
The core architectural principle: **every database query is scoped to a tenant_id**. This is enforced at the repository/data-access layer, not at the application layer. No business logic should ever bypass tenant scoping.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Client Layer │
|
||||||
|
│ Admin Portal Staff Portal Collector App Client Portal │
|
||||||
|
│ (web, full) (web, full) (web, mobile) (web, limited)│
|
||||||
|
└──────────────────────────┬──────────────────────────────────────┘
|
||||||
|
│ HTTPS / REST or tRPC
|
||||||
|
┌──────────────────────────▼──────────────────────────────────────┐
|
||||||
|
│ API Gateway / Auth Layer │
|
||||||
|
│ - JWT validation │
|
||||||
|
│ - Tenant resolution (subdomain → tenant_id) │
|
||||||
|
│ - Role extraction │
|
||||||
|
│ - Route-level permission guards │
|
||||||
|
└──────────────────────────┬──────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────────────────────▼──────────────────────────────────────┐
|
||||||
|
│ Application Modules (Monolith) │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
|
||||||
|
│ │Subscriber│ │ Billing │ │ Payment │ │ Accounting │ │
|
||||||
|
│ │ Mgmt │ │ Engine │ │ Tracker │ │ (Ledger) │ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────┘ └───────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
|
||||||
|
│ │ Ticketing│ │ Job Order│ │Technician│ │ Inventory │ │
|
||||||
|
│ │ System │→ │ Workflow │ │ Mgmt │ │ (Assets) │ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────┘ └───────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
|
||||||
|
│ │ Expense │ │Dashboard │ │ Report │ │ Notification │ │
|
||||||
|
│ │ Tracking │ │& Metrics │ │ Engine │ │ (Internal) │ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────┘ └───────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Tenant Context Middleware │ │
|
||||||
|
│ │ (all modules receive tenant_id from request ctx) │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
└──────────────────────────┬──────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────────────────────▼──────────────────────────────────────┐
|
||||||
|
│ Data Layer │
|
||||||
|
│ PostgreSQL (primary) Redis (session/cache) │
|
||||||
|
│ - tenant_id on all rows - rate limiting │
|
||||||
|
│ - Row-Level Security - background job queues │
|
||||||
|
│ - Soft deletes - temp calculations │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Boundaries
|
||||||
|
|
||||||
|
### 1. Auth and Tenant Resolution
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Auth Module | JWT issuance, refresh, logout, session management | All modules (provides tenant_id + role to request context) |
|
||||||
|
| Tenant Resolver | Maps subdomain/slug to tenant_id, validates tenant exists and is active | Auth module, database |
|
||||||
|
| Permission Guard | Route-level RBAC enforcement based on role from JWT | All route handlers |
|
||||||
|
|
||||||
|
**Key rule:** The JWT payload carries `{ user_id, tenant_id, role }`. Every request arriving at the application layer already knows its tenant. No module may accept a request without tenant context.
|
||||||
|
|
||||||
|
**Role matrix summary:**
|
||||||
|
|
||||||
|
| Permission Area | Admin | Office Staff | Collector | Technician | Client |
|
||||||
|
|----------------|-------|-------------|-----------|------------|--------|
|
||||||
|
| Full system config | Yes | No | No | No | No |
|
||||||
|
| Billing & subscriber mgmt | Yes | Yes | No | No | No |
|
||||||
|
| Payment recording | Yes | Yes | Yes (own collections) | No | No |
|
||||||
|
| Ticket creation | Yes | Yes | No | No | Yes |
|
||||||
|
| Job order assignment | Yes | Yes | No | No | No |
|
||||||
|
| Job order completion | Yes | No | No | Yes (own) | No |
|
||||||
|
| Inventory adjustments | Yes | Yes | No | No | No |
|
||||||
|
| Accounting / journal entries | Yes | No | No | No | No |
|
||||||
|
| Own portal (view bills, pay) | No | No | No | No | Yes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Subscriber Management
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Subscriber Module | Registration, plan assignment, status (active/suspended/disconnected), contact info | Billing engine (triggers first bill), Inventory (asset assignment), Auth (creates client portal account) |
|
||||||
|
|
||||||
|
**Data owned:** `subscribers`, `service_plans`, `subscriber_plan_history`
|
||||||
|
|
||||||
|
**Key boundary:** Subscriber management does not know about accounting. When a subscriber is activated, it emits an event (or calls a service interface) to the Billing engine. The Billing engine then creates the appropriate ledger entries via the Accounting module. Subscriber module never writes to the journal directly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Billing Engine
|
||||||
|
|
||||||
|
This is the most complex module. It must handle prepaid and postpaid models with different logic.
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Billing Engine | Bill generation (scheduled + manual), billing cycle tracking, invoice creation, prepaid/postpaid logic, due date calculation, overdue detection | Subscriber module (read plan), Accounting module (write journal entries), Notification module (triggers bill-ready notices) |
|
||||||
|
|
||||||
|
**Prepaid logic:** Bill is generated before the service period begins. Subscriber must pay before activation or renewal. Overdue = service suspended.
|
||||||
|
|
||||||
|
**Postpaid logic:** Bill is generated at end of service period. Subscriber pays after consuming service. Overdue = follow-up by collector.
|
||||||
|
|
||||||
|
**Data owned:** `invoices`, `billing_cycles`, `billing_settings` (per tenant: cycle day, grace period, etc.)
|
||||||
|
|
||||||
|
**Key boundary:** The Billing engine does not record payments — that is the Payment Tracker's responsibility. An invoice has a status field (`unpaid`, `partial`, `paid`, `overdue`) that is updated by the Payment Tracker when a payment is recorded against it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Payment Tracker
|
||||||
|
|
||||||
|
Tracks all money received. Collector-aware — the system must know which collector received which payment for reconciliation.
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Payment Tracker | Recording cash/online payments, linking payment to invoice, updating invoice status, collector balance tracking, reconciliation | Billing engine (read invoice, update status), Accounting module (write journal entry: cash received → accounts receivable cleared), Collector module (update collector running balance) |
|
||||||
|
|
||||||
|
**Data owned:** `payments`, `collector_remittances`, `payment_methods`
|
||||||
|
|
||||||
|
**Key design decision:** Payments are immutable once recorded. Corrections happen via reversal entries, not edits. This is essential for audit integrity and aligns with double-entry accounting principles.
|
||||||
|
|
||||||
|
**Collector flow:**
|
||||||
|
```
|
||||||
|
Collector records cash payment from subscriber
|
||||||
|
→ Payment created (linked to collector + invoice)
|
||||||
|
→ Invoice status updated (partially or fully paid)
|
||||||
|
→ Collector running balance increases (they are holding the cash)
|
||||||
|
→ When collector remits to office: remittance event created
|
||||||
|
→ Collector balance decreases, office cash account increases
|
||||||
|
→ Accounting module writes journal entries for both events
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Accounting Module (Double-Entry Ledger)
|
||||||
|
|
||||||
|
The accounting module is a foundational module that every money-touching module writes to, but it does not initiate transactions itself.
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Chart of Accounts | Tenant-specific account structure (assets, liabilities, equity, income, expense) | All money modules (read accounts to post to) |
|
||||||
|
| Journal Entry Service | Creating balanced journal entries (debit = credit always), posting, voiding | Payment Tracker, Billing Engine, Expense Module, Inventory (asset capitalization) |
|
||||||
|
| Report Engine | Generating Trial Balance, Balance Sheet, Income Statement from journal entries | No writes — read-only from journal |
|
||||||
|
|
||||||
|
**Standard accounts (auto-provisioned per tenant at signup):**
|
||||||
|
```
|
||||||
|
Assets:
|
||||||
|
- Cash (office)
|
||||||
|
- Cash in Transit (collector-held)
|
||||||
|
- Accounts Receivable (subscriber balances)
|
||||||
|
- Inventory (network equipment)
|
||||||
|
- Fixed Assets (deployed equipment)
|
||||||
|
|
||||||
|
Liabilities:
|
||||||
|
- (minimal in v1 — can add later)
|
||||||
|
|
||||||
|
Equity:
|
||||||
|
- Owner's Equity
|
||||||
|
|
||||||
|
Income:
|
||||||
|
- Subscription Revenue
|
||||||
|
|
||||||
|
Expenses:
|
||||||
|
- Technician Labor
|
||||||
|
- Network Equipment (expense)
|
||||||
|
- Operating Expenses
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key rule:** Journal entries are never edited after posting. Corrections happen via reversing journal entries (a new entry that exactly offsets the original, then a correct entry). This is a non-negotiable accounting principle.
|
||||||
|
|
||||||
|
**Data owned:** `chart_of_accounts`, `journal_entries`, `journal_entry_lines`, `accounting_periods`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Ticketing and Job Order Workflow
|
||||||
|
|
||||||
|
These two are closely related but have distinct responsibilities.
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Ticket System | Receiving client issues (from client portal or staff manual entry), ticket lifecycle (open → assigned → resolved → closed), priority, category | Job Order module (a ticket can spawn one or more job orders), Subscriber module (read subscriber data), Notification module |
|
||||||
|
| Job Order Module | Technical work orders created from tickets, technician assignment, scheduling, completion recording, job type tracking | Ticket system (reads parent ticket), Technician module (assignment), Inventory module (parts used), Accounting module (record labor cost as journal entry) |
|
||||||
|
|
||||||
|
**Flow:**
|
||||||
|
```
|
||||||
|
Client submits ticket (portal) OR staff creates ticket (from call/text)
|
||||||
|
→ Ticket is open
|
||||||
|
→ Staff reviews → creates Job Order from ticket
|
||||||
|
→ Job Order assigned to technician
|
||||||
|
→ Technician completes job → marks completed (optionally with parts used)
|
||||||
|
→ If parts used → Inventory module decrements stock
|
||||||
|
→ Technician compensation calculated
|
||||||
|
→ Accounting: debit Labor Expense, credit Technician Payable
|
||||||
|
→ Ticket marked resolved
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Technician Management
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Technician Module | Technician profiles, compensation model (per-job vs salary), job rate lookup by job type, compensation calculation, payroll summary | Job Order module (receives completion events), Accounting module (writes compensation journal entries) |
|
||||||
|
|
||||||
|
**Data owned:** `technicians`, `job_types`, `job_rates`, `technician_compensation_records`
|
||||||
|
|
||||||
|
**Two compensation models must coexist:**
|
||||||
|
- Per-job: each job order completion calculates `job_rate * completion` and credits technician payable
|
||||||
|
- Monthly salary: fixed monthly amount, booked at period end regardless of job count
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Inventory Module
|
||||||
|
|
||||||
|
Manages two distinct concerns: stock on hand and deployed assets.
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Stock Management | Items in warehouse/storage, quantity tracking, purchase recording, low-stock alerts | Expense/Accounting module (purchase creates inventory asset entry), Job Order module (receives depletion events) |
|
||||||
|
| Asset Management | Equipment deployed to subscribers (router serial, ONU serial, location), asset lifecycle (deployed → returned → re-stocked or written off) | Subscriber module (asset linked to subscriber), Job Order module (technician records what was installed) |
|
||||||
|
|
||||||
|
**Data owned:** `inventory_items`, `inventory_categories`, `stock_transactions`, `deployed_assets`, `asset_assignments`
|
||||||
|
|
||||||
|
**Key distinction:**
|
||||||
|
- An item is "stock" until it is deployed to a subscriber address
|
||||||
|
- Once deployed, it becomes a "deployed asset" tracked per subscriber
|
||||||
|
- Return = asset moves back to stock (if functional) or written off (if damaged)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Expense Tracking
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Expense Module | Recording non-inventory operational expenses, category assignment, expense approval (optional), attachment of receipts | Accounting module (each expense creates a journal entry: debit Expense account, credit Cash or Payable) |
|
||||||
|
|
||||||
|
**Data owned:** `expenses`, `expense_categories`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. Dashboard and Reporting
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Dashboard Service | Aggregating KPIs: total subscribers, active/suspended, monthly revenue, outstanding receivables, collector balances, open tickets, pending job orders | Read-only from all modules |
|
||||||
|
| Report Engine | Formal financial reports (Balance Sheet, Income Statement, Trial Balance), operational reports (collector reconciliation, technician productivity, inventory valuation) | Read-only from journal entries and operational tables |
|
||||||
|
|
||||||
|
**Key rule:** The Dashboard and Report Engine are read-only. They never write. All their data comes from the operational modules. Complex reports should be pre-computed on a schedule (via background jobs) rather than computed on-demand to avoid slow queries on large datasets.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. Client Portal
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Client Portal | Subscriber-facing view: view current bill, payment history, submit support ticket, view service plan, view deployed equipment | Billing engine (read invoices), Payment Tracker (read payment history), Ticket system (create/read own tickets), Subscriber module (read own profile) |
|
||||||
|
|
||||||
|
**Key boundary:** The Client Portal is READ-HEAVY with one write action (submit ticket, initiate online payment). It is scoped strictly to the authenticated subscriber — a client JWT cannot access any other subscriber's data. This is enforced at both the permission guard layer AND the data access layer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 12. Notification Module (Internal)
|
||||||
|
|
||||||
|
| Component | Responsibility | Communicates With |
|
||||||
|
|-----------|---------------|-------------------|
|
||||||
|
| Notification Module | In-app notifications for staff (new ticket, job order assigned, overdue invoices), optionally email later | Receives events from Billing engine, Ticket system, Job Order module |
|
||||||
|
|
||||||
|
**Scope:** v1 should be in-app only (database-backed notifications, polled or WebSocket-pushed). Email/SMS integration is post-v1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
### Money Flow (Complete Picture)
|
||||||
|
|
||||||
|
```
|
||||||
|
[Service Activation]
|
||||||
|
Billing Engine generates Invoice
|
||||||
|
→ Invoice record created (status: unpaid)
|
||||||
|
→ Journal Entry: DR Accounts Receivable / CR Subscription Revenue
|
||||||
|
|
||||||
|
[Cash Payment via Collector]
|
||||||
|
Collector records payment
|
||||||
|
→ Payment record created (linked to invoice + collector)
|
||||||
|
→ Invoice status updated (partial/paid)
|
||||||
|
→ Journal Entry: DR Cash in Transit (collector) / CR Accounts Receivable
|
||||||
|
|
||||||
|
[Collector Remits to Office]
|
||||||
|
Remittance recorded
|
||||||
|
→ Journal Entry: DR Cash (office) / CR Cash in Transit (collector)
|
||||||
|
→ Collector balance zeroed
|
||||||
|
|
||||||
|
[Online Payment - future gateway]
|
||||||
|
Payment gateway webhook → Payment Tracker
|
||||||
|
→ Same flow as cash minus collector step
|
||||||
|
|
||||||
|
[Expense Recorded]
|
||||||
|
Expense Module creates expense
|
||||||
|
→ Journal Entry: DR Expense Account / CR Cash or Accounts Payable
|
||||||
|
|
||||||
|
[Inventory Purchase]
|
||||||
|
Stock purchase recorded
|
||||||
|
→ Journal Entry: DR Inventory Asset / CR Cash or Accounts Payable
|
||||||
|
|
||||||
|
[Equipment Deployed (Capitalized)]
|
||||||
|
Asset assigned to subscriber via Job Order
|
||||||
|
→ Journal Entry: DR Fixed Assets (deployed) / CR Inventory
|
||||||
|
|
||||||
|
[Technician Compensation (Per Job)]
|
||||||
|
Job Order completed
|
||||||
|
→ Journal Entry: DR Labor Expense / CR Technician Payable
|
||||||
|
|
||||||
|
[Technician Paid]
|
||||||
|
Payroll disbursement recorded
|
||||||
|
→ Journal Entry: DR Technician Payable / CR Cash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ticket-to-Resolution Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Client Portal or Staff Input
|
||||||
|
→ Ticket Created (open)
|
||||||
|
→ Staff Reviews
|
||||||
|
→ Job Order Created from Ticket
|
||||||
|
→ Technician Assigned (notification sent)
|
||||||
|
→ Technician Completes Work
|
||||||
|
→ Parts Used? → Inventory depleted
|
||||||
|
→ Job compensation calculated
|
||||||
|
→ Job Order Closed
|
||||||
|
→ Ticket Resolved
|
||||||
|
→ Client notified (in-app)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tenant Isolation Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Request arrives → DNS resolves subdomain (e.g., acme.netforge.app)
|
||||||
|
→ Tenant Resolver: subdomain → tenant_id (cached in Redis)
|
||||||
|
→ Auth validates JWT → extracts user_id, tenant_id, role
|
||||||
|
→ Request context carries { tenant_id, user_id, role }
|
||||||
|
→ Every repository method receives tenant_id as first parameter
|
||||||
|
→ All queries include WHERE tenant_id = ? as baseline
|
||||||
|
→ PostgreSQL Row Level Security as second defense layer
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Suggested Build Order (Phase Dependencies)
|
||||||
|
|
||||||
|
This ordering reflects hard dependencies — later components cannot function without earlier ones.
|
||||||
|
|
||||||
|
### Tier 1: Foundation (Must Build First)
|
||||||
|
|
||||||
|
These components have no dependencies on other domain modules.
|
||||||
|
|
||||||
|
1. **Tenant provisioning + Auth** — Without this, nothing is isolated or authenticated. All other modules receive tenant context from here.
|
||||||
|
2. **Chart of Accounts (Accounting Module — schema only)** — Every money event needs accounts to post to. Provision default accounts at tenant signup.
|
||||||
|
3. **Subscriber Management** — The core entity. Billing, inventory, and ticketing all reference subscribers.
|
||||||
|
|
||||||
|
### Tier 2: Core Operations (Depends on Tier 1)
|
||||||
|
|
||||||
|
4. **Billing Engine** — Depends on subscribers and service plans. Generates invoices.
|
||||||
|
5. **Payment Tracker** — Depends on invoices from Billing Engine. Records what comes in.
|
||||||
|
6. **Journal Entry Service** — Depends on Chart of Accounts. Now that billing and payments exist, we can post entries. Build this as a service that Billing and Payment Tracker call.
|
||||||
|
|
||||||
|
### Tier 3: Operational Modules (Depends on Tier 1 + 2)
|
||||||
|
|
||||||
|
7. **Inventory Module (Stock)** — Depends on Accounting (inventory purchases create journal entries).
|
||||||
|
8. **Expense Tracking** — Depends on Accounting (expense journal entries).
|
||||||
|
9. **Ticketing System** — Depends on Subscribers (tickets belong to subscribers).
|
||||||
|
10. **Job Order Module** — Depends on Tickets, Technician Module, Inventory (parts), Accounting (labor cost).
|
||||||
|
11. **Technician Management** — Can be built alongside Job Orders; they depend on each other.
|
||||||
|
|
||||||
|
### Tier 4: Visibility and Access (Depends on Tier 2 + 3)
|
||||||
|
|
||||||
|
12. **Dashboard and Metrics** — Depends on all operational modules being populated with data.
|
||||||
|
13. **Report Engine (Financial)** — Depends on Journal Entry Service having real data.
|
||||||
|
14. **Client Portal** — Depends on Billing Engine, Payment Tracker, Ticketing System.
|
||||||
|
|
||||||
|
### Tier 5: Enhancements
|
||||||
|
|
||||||
|
15. **Notification Module** — Can be added incrementally. Start with in-app, extend to email later.
|
||||||
|
16. **Asset Management** — Extends Inventory; tracks deployed equipment per subscriber.
|
||||||
|
17. **Collector Remittance Workflow** — Extends Payment Tracker.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Decisions
|
||||||
|
|
||||||
|
### Decision 1: Shared Database with Row-Level Security
|
||||||
|
|
||||||
|
**What:** All tenants in one PostgreSQL database. Every table has `tenant_id`. PostgreSQL Row Level Security (RLS) policies enforce isolation at the database level as a backstop.
|
||||||
|
|
||||||
|
**Why:** Simpler to operate than per-tenant databases. Schema migrations run once. Connection pooling works. For SMB SaaS targeting small ISPs (not enterprise), this is the standard and correct approach.
|
||||||
|
|
||||||
|
**Tradeoff:** A noisy tenant (one ISP with 10,000 subscribers generating lots of queries) can affect others. Mitigation: rate limiting at the API layer, background job queuing.
|
||||||
|
|
||||||
|
**Confidence:** HIGH — this is the established pattern for SMB SaaS (used by Shopify, Linear, many others at this scale tier).
|
||||||
|
|
||||||
|
### Decision 2: Event-Driven Internally, Not a Message Bus
|
||||||
|
|
||||||
|
**What:** Modules communicate through in-process function/service calls, not a message queue. When the Billing Engine generates an invoice, it calls `AccountingService.postEntry(...)` directly. No Kafka, no RabbitMQ.
|
||||||
|
|
||||||
|
**Why:** A message bus is justified when modules are deployed separately (microservices) or when processing is async by requirement. For a monolith targeting sub-second response times on a small-medium dataset, synchronous in-process calls are simpler and correct. Async background jobs (via a job queue like BullMQ or pg-based queue) handle scheduled billing runs and report generation.
|
||||||
|
|
||||||
|
**Tradeoff:** If one module's accounting write fails, the whole transaction fails. This is actually correct behavior for financial data — you don't want a payment recorded without its journal entry.
|
||||||
|
|
||||||
|
**Confidence:** HIGH — this is the correct tradeoff for this scale.
|
||||||
|
|
||||||
|
### Decision 3: Double-Entry Enforced at Service Layer
|
||||||
|
|
||||||
|
**What:** The `JournalEntryService` is the only way to write to `journal_entry_lines`. It validates that debits = credits before persisting. No module writes directly to accounting tables.
|
||||||
|
|
||||||
|
**Why:** Prevents imbalanced books. A bug in the Payment Tracker cannot create an unbalanced journal entry if all writes go through a single service that enforces balance.
|
||||||
|
|
||||||
|
**Confidence:** HIGH — standard accounting software pattern.
|
||||||
|
|
||||||
|
### Decision 4: Immutable Financial Records
|
||||||
|
|
||||||
|
**What:** Payments and journal entries cannot be edited after creation. Corrections use reversal entries.
|
||||||
|
|
||||||
|
**Why:** Audit trail integrity. Tax compliance. Debugging is possible. Financial regulators (even informal ones like business owners doing year-end) need to see the full history of what happened, not what it looks like after edits.
|
||||||
|
|
||||||
|
**Confidence:** HIGH — non-negotiable for any accounting system.
|
||||||
|
|
||||||
|
### Decision 5: Collector Balance as Derived State
|
||||||
|
|
||||||
|
**What:** A collector's current cash balance is not stored as a single field. It is computed from: `SUM(payments.amount WHERE collector_id = X AND NOT remitted) - SUM(remittances.amount WHERE collector_id = X)`. This can be cached in Redis for performance.
|
||||||
|
|
||||||
|
**Why:** A stored balance field is prone to drift (bugs, race conditions). The authoritative answer always comes from the transaction log.
|
||||||
|
|
||||||
|
**Tradeoff:** Slightly more expensive to compute. Cache with a short TTL (or invalidate on new payment/remittance) to handle at scale.
|
||||||
|
|
||||||
|
**Confidence:** HIGH — event sourcing / derived state principle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
### Anti-Pattern 1: Application-Layer-Only Tenant Isolation
|
||||||
|
|
||||||
|
**What:** Relying solely on `WHERE tenant_id = ?` in application code, without any database-level backstop.
|
||||||
|
|
||||||
|
**Why bad:** A single bug (missing WHERE clause) leaks data across tenants. This is a catastrophic security failure for a SaaS product.
|
||||||
|
|
||||||
|
**Instead:** Add PostgreSQL Row Level Security as a second layer. Even if the application omits the tenant filter, RLS prevents the query from returning another tenant's rows. Set RLS on every table that contains tenant data.
|
||||||
|
|
||||||
|
### Anti-Pattern 2: Billing Engine Writing Directly to Cash Tables
|
||||||
|
|
||||||
|
**What:** The Billing Engine or Payment Tracker directly incrementing/decrementing account balances in a "balances" table.
|
||||||
|
|
||||||
|
**Why bad:** Balance tables drift. Concurrent updates cause race conditions. You lose the transaction history needed for reconciliation.
|
||||||
|
|
||||||
|
**Instead:** No balance table exists. Balances are always derived from journal entry lines. `SUM(amount WHERE account_id = X AND type = 'debit') - SUM(amount WHERE account_id = X AND type = 'credit')` IS the balance. Cache for performance, but the source of truth is the ledger.
|
||||||
|
|
||||||
|
### Anti-Pattern 3: Merging Ticket and Job Order into One Entity
|
||||||
|
|
||||||
|
**What:** Treating "ticket" and "job order" as the same record.
|
||||||
|
|
||||||
|
**Why bad:** A single client issue might spawn multiple job orders (initial visit + follow-up). A job order might exist without a client ticket (proactive maintenance). They have different lifecycles, different owners (client-facing vs technician-facing), and different data shapes.
|
||||||
|
|
||||||
|
**Instead:** Keep them separate. Tickets have a `has_many :job_orders` relationship. Job orders have `belongs_to :ticket` (nullable for non-ticket-originating work).
|
||||||
|
|
||||||
|
### Anti-Pattern 4: Storing Computed Financial State
|
||||||
|
|
||||||
|
**What:** Storing "total revenue this month" or "outstanding balance per subscriber" as columns that get updated on every transaction.
|
||||||
|
|
||||||
|
**Why bad:** Race conditions. Bugs cause silent drift. Hard to audit. Hard to debug when numbers don't match.
|
||||||
|
|
||||||
|
**Instead:** Compute on demand from the ledger. Cache aggressively in Redis with appropriate invalidation. For reports, use materialized views or pre-computed report snapshots run on a schedule.
|
||||||
|
|
||||||
|
### Anti-Pattern 5: Granting Client Role DB-Level SELECT on All Tables
|
||||||
|
|
||||||
|
**What:** Building RBAC only at the API route level, without data-access-layer scoping for the client role.
|
||||||
|
|
||||||
|
**Why bad:** A client who discovers an API endpoint (through network inspection) can potentially access other subscribers' invoices if the data layer doesn't enforce subscriber-scoping on top of tenant-scoping.
|
||||||
|
|
||||||
|
**Instead:** Client Portal requests are scoped at TWO levels: tenant_id (from JWT) AND subscriber_id (from JWT). The data access layer for all client-facing queries adds `AND subscriber_id = ?` in addition to `AND tenant_id = ?`.
|
||||||
|
|
||||||
|
### Anti-Pattern 6: Soft Deletes Without Accounting for Them
|
||||||
|
|
||||||
|
**What:** Adding `deleted_at` soft deletes everywhere without ensuring all queries include `WHERE deleted_at IS NULL`.
|
||||||
|
|
||||||
|
**Why bad:** Deleted records silently appear in reports, counts, and joins if any query forgets the filter.
|
||||||
|
|
||||||
|
**Instead:** Use a database view or ORM default scope that filters deleted records. Alternatively, use PostgreSQL RLS to filter them. Never rely on developers remembering to add the filter manually.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scalability Considerations
|
||||||
|
|
||||||
|
| Concern | At 10 tenants (MVP) | At 100 tenants | At 1,000 tenants |
|
||||||
|
|---------|--------------------|-----------------|--------------------|
|
||||||
|
| Database size | Single PostgreSQL, no concerns | Same, add indexes | Consider read replicas for reports |
|
||||||
|
| Billing runs | Cron job, sequential per tenant | Background job queue with tenant workers | Distributed job queue (BullMQ), parallelized |
|
||||||
|
| Report generation | On-demand from ledger | Nightly pre-computed snapshots | Materialized views, async report jobs |
|
||||||
|
| Tenant isolation | RLS policies | Same | Same, plus connection pooling limits per tenant |
|
||||||
|
| API performance | No concerns | Cache hot reads in Redis | Horizontal API scaling, DB read replicas |
|
||||||
|
| Concurrent collectors | Not a concern | Optimistic locking on payment records | Same |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
All findings based on established SaaS architecture patterns, double-entry accounting principles, and multi-tenant database design patterns. External verification tools were unavailable during this research session.
|
||||||
|
|
||||||
|
**Confidence notes by section:**
|
||||||
|
- Multi-tenant shared DB with RLS: HIGH (industry standard, well-documented)
|
||||||
|
- Double-entry accounting patterns: HIGH (GAAP principles, stable for decades)
|
||||||
|
- Modular monolith over microservices for this scale: HIGH (well-established guidance from Martin Fowler, Sam Newman, others)
|
||||||
|
- Billing engine prepaid/postpaid model: HIGH (standard ISP billing concepts)
|
||||||
|
- Collector balance as derived state: HIGH (event sourcing principle)
|
||||||
|
- Specific framework/library choices: Deferred to STACK.md
|
||||||
206
.planning/research/FEATURES.md
Normal file
206
.planning/research/FEATURES.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
# Feature Landscape
|
||||||
|
|
||||||
|
**Domain:** ISP Management / Billing SaaS (Small-to-Medium ISPs, 50–2000 subscribers)
|
||||||
|
**Researched:** 2026-03-04
|
||||||
|
**Confidence note:** Web research tools were unavailable during this session. All findings are drawn from
|
||||||
|
training knowledge of Splynx, UISP (Ubiquiti), ISPApp, Wise-ISP, Sonar Software, HostBill, and
|
||||||
|
WHMCS-ISP setups. Confidence is MEDIUM overall; findings should be spot-checked against live product
|
||||||
|
feature pages before roadmap is locked.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table Stakes
|
||||||
|
|
||||||
|
Features users expect. Missing = product feels incomplete or ISP won't migrate to it.
|
||||||
|
|
||||||
|
| Feature | Why Expected | Complexity | Notes |
|
||||||
|
|---------|--------------|------------|-------|
|
||||||
|
| Client/subscriber database | Core of all ISP ops; every other module depends on it | Low | Name, address, plan, contact info, status |
|
||||||
|
| Plan/service tier management | ISPs sell multiple speed tiers; plans must map to billing amounts | Low | Monthly price, speed profile, data cap |
|
||||||
|
| Invoice auto-generation | Manual invoicing at scale is impossible; recurring billing is baseline | Medium | Date-based or cycle-based triggers |
|
||||||
|
| Payment logging (cash/bank) | Small ISPs in PH/developing markets are majority cash/bank-transfer | Low | Manual entry by staff or collector |
|
||||||
|
| Client status lifecycle | Active → Suspended → Cancelled is the daily operational loop | Low | Status drives network enforcement |
|
||||||
|
| MikroTik auto-suspend/activate | MikroTik dominates small ISP networks; auto-cut on overdue is expected | High | RouterOS API, PPPoE/hotspot profiles |
|
||||||
|
| Overdue/outstanding reports | Managers need to know who hasn't paid every single day | Low | Filter by due date, status, collector zone |
|
||||||
|
| SMS/notification reminders | Pre-due and overdue reminders reduce churn and collection effort | Medium | Gateway integration (Semaphore, Vonage, Twilio) |
|
||||||
|
| Basic ticketing / support log | Even basic ISPs track "client X reported outage" in some form | Medium | Create, assign, resolve, note |
|
||||||
|
| Multi-user roles with permissions | Staff, collectors, and techs need scoped access | Medium | At minimum: admin, office, field roles |
|
||||||
|
| Dashboard with key metrics | Owners demand daily revenue vs target, active vs suspended counts | Medium | Not vanity — operational necessity |
|
||||||
|
| Client payment history view | Collectors and clients need to verify "did I pay last month?" | Low | Ledger per client |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Differentiators
|
||||||
|
|
||||||
|
Features that set a product apart. Users don't always expect them, but they drive retention and referrals.
|
||||||
|
|
||||||
|
| Feature | Value Proposition | Complexity | Notes |
|
||||||
|
|---------|-------------------|------------|-------|
|
||||||
|
| Facebook Messenger / chatbot self-service | Clients in PH markets heavily use FB Messenger; zero-app friction for balance checks and ticket filing | High | Requires API-first design + n8n or equivalent automation layer |
|
||||||
|
| Technician mobile dispatch app | Field staff use phones, not laptops; native or PWA dispatch list with job status update | High | Offline-capable is ideal; GPS is a plus |
|
||||||
|
| Full double-entry accounting | Most ISP tools do "billing" not accounting; real P&L and balance sheet is rare at this price point | High | COA, journal entries, trial balance, income statement |
|
||||||
|
| Inventory / asset tracking per subscriber | Knowing "ONT #1234 is at Client X" prevents equipment theft and loss | Medium | Asset → status: In Stock / Deployed / With Technician |
|
||||||
|
| Collector zone management | ISPs with field collectors need geographic grouping and daily collection targets | Medium | Assign clients to collector, track daily collection totals |
|
||||||
|
| Payment collection receipting by collector | Collectors log cash collected in the field; system auto-credits client and tracks collector accountability | Medium | Differs from office payment logging; collector-specific audit trail |
|
||||||
|
| Expense and vendor tracking | Upstream bandwidth costs, tower rent, vehicle fuel — mapped to real P&L | Medium | Vendor ledger, bill entry, expense categories |
|
||||||
|
| Live MikroTik connection status query | See if a client is actively online right now, without logging into RouterOS | High | RouterOS API polling per subscriber |
|
||||||
|
| Multi-router / multi-zone router management | ISPs grow to 3–10 towers; each tower has its own router | Medium | Router registry, zone assignment, per-router client mapping |
|
||||||
|
| SaaS super-admin panel | Platform operator manages ISP tenants, monitors usage, controls subscriptions | High | Tenant CRUD, usage metering, billing ISPs |
|
||||||
|
| White-label client portal | ISP can brand the portal with their logo; premium tier feature | Medium | Per-tenant theme/logo/custom domain |
|
||||||
|
| Prepaid load / data voucher management | Some ISPs sell prepaid load cards or hotspot vouchers | Medium | Voucher code generation, redemption, expiry |
|
||||||
|
| Network map / topology view | Visual representation of which clients are on which tower/sector | High | Useful for troubleshooting but complex to build well |
|
||||||
|
| Automated PPPoE/hotspot profile sync | When plan is changed, speed profile on router updates automatically | High | MikroTik RouterOS API, profile name mapping |
|
||||||
|
| API-first with webhook support | Allows ISPs to integrate with their own tools or extend via n8n/Zapier | High | Full RESTful API, documented, versioned |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anti-Features
|
||||||
|
|
||||||
|
Features to explicitly NOT build in early phases. Common mistakes in this domain.
|
||||||
|
|
||||||
|
| Anti-Feature | Why Avoid | What to Do Instead |
|
||||||
|
|--------------|-----------|-------------------|
|
||||||
|
| Full RADIUS server | WISP RADIUS is a solved problem; building one is a multi-month distraction | Integrate with existing RADIUS (FreeRADIUS) via API or use MikroTik RouterOS directly |
|
||||||
|
| Network monitoring / SNMP polling (phase 1) | NMS is its own product category; premature complexity kills delivery | Defer; point users to UISP or LibreNMS for NOC needs |
|
||||||
|
| Built-in VoIP billing | VoIP ISPs are a separate niche with CDR complexity; out of scope | Not applicable to target (broadband ISPs) |
|
||||||
|
| Complex provisioning workflows (ZTP) | Zero-touch provisioning is enterprise-tier complexity | Manual install workflow + job order is sufficient for sub-2000 subscriber ISPs |
|
||||||
|
| Customer-facing mobile app (phase 1) | Native app development doubles scope; web portal serves the same need initially | Build responsive web client portal first; native app is phase 3 |
|
||||||
|
| Full ERP (HR, payroll, procurement) | Scope creep kills ISP billing products; non-core modules confuse users | Stick to ISP-specific accounting; integrate with external payroll tools if needed |
|
||||||
|
| Credit card processing as a primary flow | Small PH ISPs are cash/bank-transfer dominant; card processing adds compliance overhead for low ROI | Support it as optional later; GCash/PayMaya integrations are higher value for PH market |
|
||||||
|
| Automatic CAPEX depreciation schedules | Accounting purists want this; ISP operators don't use it | Log purchase cost; let accountant handle depreciation externally |
|
||||||
|
| Integrated email marketing / newsletter | Irrelevant to ISP ops; adds bloat | Use external tools (Mailchimp) if needed |
|
||||||
|
| Network usage graphs / bandwidth accounting per client | Useful but requires data source (RADIUS, router polling); high infra complexity | Scope to phase 2 or 3 if MikroTik traffic accounting is available |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Dependencies
|
||||||
|
|
||||||
|
Features that cannot be built without prerequisite features being in place first.
|
||||||
|
|
||||||
|
```
|
||||||
|
Client Database
|
||||||
|
└─> Plan/Service Tier Registry
|
||||||
|
└─> Invoice Auto-Generation
|
||||||
|
└─> Payment Logging
|
||||||
|
└─> Client Status Lifecycle (Active / Suspended / Cancelled)
|
||||||
|
└─> MikroTik Auto-Suspend / Auto-Activate
|
||||||
|
└─> Live Connection Status Query
|
||||||
|
|
||||||
|
Client Database
|
||||||
|
└─> Collector Zone Assignment
|
||||||
|
└─> Collector Payment Logging (field receipting)
|
||||||
|
└─> Collector Daily Summary / Accountability Report
|
||||||
|
|
||||||
|
Client Database
|
||||||
|
└─> Ticketing / Support Log
|
||||||
|
└─> Ticket → Job Order conversion
|
||||||
|
└─> Technician Dispatch List
|
||||||
|
└─> Technician Mobile View (PWA/app)
|
||||||
|
|
||||||
|
Invoice Auto-Generation + Payment Logging
|
||||||
|
└─> Double-Entry Journal Entries (automated)
|
||||||
|
└─> Chart of Accounts
|
||||||
|
└─> Ledger / Trial Balance
|
||||||
|
└─> Income Statement (P&L)
|
||||||
|
└─> Balance Sheet
|
||||||
|
|
||||||
|
Inventory Registry
|
||||||
|
└─> Asset Status Tracking (In Stock / Deployed / With Technician)
|
||||||
|
└─> Asset Assignment to Client or Technician
|
||||||
|
|
||||||
|
SMS Notifications
|
||||||
|
└─> SMS Gateway Integration (Semaphore / Twilio)
|
||||||
|
└─> Pre-due reminder
|
||||||
|
└─> Overdue alert
|
||||||
|
└─> Payment confirmation receipt
|
||||||
|
|
||||||
|
API-First Architecture
|
||||||
|
└─> n8n / Facebook Messenger integration
|
||||||
|
└─> Balance check chatbot
|
||||||
|
└─> Ticket creation via chat
|
||||||
|
|
||||||
|
SaaS Multi-Tenancy (tenant_id isolation)
|
||||||
|
└─> Super-Admin Panel
|
||||||
|
└─> ISP tenant management
|
||||||
|
└─> Usage metering / subscription billing of ISPs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MVP Recommendation
|
||||||
|
|
||||||
|
For an MVP targeting small ISPs (50–500 subscribers), prioritize these features first:
|
||||||
|
|
||||||
|
**Must-have (MVP blockers — without these no ISP will run on NetForge):**
|
||||||
|
1. Client database with status lifecycle
|
||||||
|
2. Service plan registry
|
||||||
|
3. Invoice auto-generation (monthly recurring)
|
||||||
|
4. Payment logging by office staff
|
||||||
|
5. Overdue / outstanding reports
|
||||||
|
6. MikroTik auto-suspend and auto-activate
|
||||||
|
7. Multi-user roles (Admin, Staff, Collector, Technician)
|
||||||
|
8. Basic ticketing → job order workflow
|
||||||
|
9. SMS reminders (pre-due + overdue + confirmation)
|
||||||
|
10. Executive dashboard (revenue collected, overdue count, active clients)
|
||||||
|
|
||||||
|
**High-value post-MVP (Phase 2):**
|
||||||
|
- Double-entry accounting with automated journal entries
|
||||||
|
- Inventory / asset tracking
|
||||||
|
- Collector zone management + field payment receipting
|
||||||
|
- Facebook Messenger chatbot via n8n API
|
||||||
|
- API documentation and webhook support
|
||||||
|
|
||||||
|
**Defer until Phase 3 or later:**
|
||||||
|
- Technician native mobile app
|
||||||
|
- White-label client portal
|
||||||
|
- Prepaid voucher / load management
|
||||||
|
- Live MikroTik connection status polling
|
||||||
|
- Automated PPPoE profile sync on plan change
|
||||||
|
- Super-admin / SaaS subscription management (platform meta-layer)
|
||||||
|
- Network usage graphs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Competitor Feature Coverage (Training Data — MEDIUM confidence)
|
||||||
|
|
||||||
|
Reference table showing which products cover which feature areas. Confidence is MEDIUM as this is
|
||||||
|
based on training data from products' public documentation up to mid-2025.
|
||||||
|
|
||||||
|
| Feature Area | Splynx | UISP CRM | ISPApp | Sonar | NetForge Target |
|
||||||
|
|--------------|--------|----------|--------|-------|-----------------|
|
||||||
|
| Recurring billing / invoicing | Yes | Yes | Yes | Yes | Yes |
|
||||||
|
| MikroTik integration | Deep | Deep (Ubiquiti-first) | Yes | Yes | Deep |
|
||||||
|
| Double-entry accounting | No (billing only) | No | Partial | Partial | Yes (differentiator) |
|
||||||
|
| Inventory management | Basic | Yes | Basic | Yes | Yes |
|
||||||
|
| Ticketing | Yes | Yes | Yes | Yes | Yes |
|
||||||
|
| Field collector workflow | No | No | Partial | No | Yes (differentiator) |
|
||||||
|
| SMS automation | Yes | Partial | Yes | Yes | Yes |
|
||||||
|
| Facebook Messenger chatbot | No | No | No | No | Yes (differentiator) |
|
||||||
|
| API / webhook | Yes | Yes | Partial | Yes | Yes |
|
||||||
|
| Prepaid vouchers | Partial | No | Yes | No | Phase 3 |
|
||||||
|
| Mobile technician app | No (web only) | Yes | Partial | Yes | Phase 3 |
|
||||||
|
| Multi-tenant SaaS platform | No (self-hosted) | No | No | No | Yes (architecture) |
|
||||||
|
| PH market-specific (GCash, local SMS) | No | No | Yes | No | Yes (differentiator) |
|
||||||
|
|
||||||
|
**Key insight:** No current product combines deep MikroTik integration + full double-entry accounting +
|
||||||
|
field collector workflow + Facebook Messenger self-service in a single multi-tenant SaaS. That is
|
||||||
|
NetForge's differentiated position.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- Training knowledge of Splynx (splynx.com), UISP (ui.com/uisp), ISPApp, Sonar Software (sonar.software),
|
||||||
|
Wise-ISP, HostBill ISP modules — confidence MEDIUM (training data up to ~mid-2025, not verified
|
||||||
|
against live product pages due to tool restrictions during this session)
|
||||||
|
- NetForge PRD (isp_system_prd.md) — the project's own requirements document, reviewed directly
|
||||||
|
- Domain knowledge of Philippine ISP market context: cash/bank-transfer dominance, GCash/PayMaya
|
||||||
|
prevalence, Facebook Messenger as primary customer communication channel — confidence MEDIUM (pattern
|
||||||
|
from multiple sources in training data, not validated with live market data in this session)
|
||||||
|
|
||||||
|
**Validation recommended before roadmap lock:**
|
||||||
|
- Verify Splynx feature list at splynx.com/features/
|
||||||
|
- Verify UISP CRM feature list at ui.com/uisp
|
||||||
|
- Verify Sonar feature list at sonar.software/features/
|
||||||
|
- Check ISPApp (ispapp.co) feature list
|
||||||
|
- Check Wise-ISP feature coverage
|
||||||
353
.planning/research/PITFALLS.md
Normal file
353
.planning/research/PITFALLS.md
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
# Domain Pitfalls: ISP Management SaaS
|
||||||
|
|
||||||
|
**Domain:** Multi-tenant ISP operations management (billing, accounting, inventory, ticketing)
|
||||||
|
**Researched:** 2026-03-04
|
||||||
|
**Confidence note:** Based on domain analysis of the PRD and known engineering patterns for billing systems, double-entry accounting, multi-tenant SaaS, and ISP-specific operational software. WebSearch was unavailable during this session; claims draw on well-established engineering knowledge. Flag LOW-confidence items for validation before implementation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical Pitfalls
|
||||||
|
|
||||||
|
Mistakes that cause rewrites or fundamental architectural breakage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 1: Leaking Tenant Data Due to Missing `tenant_id` on Every Query
|
||||||
|
|
||||||
|
**What goes wrong:** A developer writes a query without scoping it to the current tenant. One ISP's staff sees another ISP's clients, invoices, or payments. This is not just a bug — it is a compliance and trust catastrophe that can destroy the product.
|
||||||
|
|
||||||
|
**Why it happens:** Early in development, the app has only one tenant (the owner's own ISP), so queries work correctly without `tenant_id` filters. The pattern only breaks when a second tenant onboards. By then, dozens of queries may be unscoped.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
- Cross-tenant data exposure (data breach)
|
||||||
|
- Regulatory liability
|
||||||
|
- Loss of all tenants if discovered
|
||||||
|
- Complete rewrite of the data access layer
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Implement a Row-Level Security (RLS) policy at the database level (Postgres RLS is ideal) so queries that forget `tenant_id` return zero rows rather than wrong rows
|
||||||
|
- Create a base repository/service class that automatically appends `tenant_id` to every query; all data-access code must extend this class
|
||||||
|
- Never allow raw queries to bypass the scoping layer
|
||||||
|
- Write integration tests that spin up two tenants with overlapping data and assert zero cross-contamination
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- Any raw SQL query without a `WHERE tenant_id = ?` clause
|
||||||
|
- Queries using global IDs (e.g., `/invoices/42`) instead of tenant-scoped IDs
|
||||||
|
- No automated test that verifies cross-tenant isolation
|
||||||
|
|
||||||
|
**Phase:** Address in Phase 1 before any feature work. Multi-tenant scoping is infrastructure, not a feature.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 2: Treating Billing as Simple Invoice Generation
|
||||||
|
|
||||||
|
**What goes wrong:** The team builds a billing engine that generates invoices on a schedule — and stops there. The hard billing problems (proration, plan changes mid-cycle, backdated payments, credit memos, partial payments, grace periods, suspension/reinstatement timing) are discovered only when the first real ISP uses it and finds edge cases daily.
|
||||||
|
|
||||||
|
**Why it happens:** "Generate an invoice" looks simple. The complexity is in the state machine: what happens when a client pays half? What if they pay late and service was already suspended? What if a plan changes on day 15 of a 30-day cycle? These scenarios are invisible until production.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
- Incorrect account balances (ISP loses or double-charges money)
|
||||||
|
- Manual reconciliation work for every edge case
|
||||||
|
- Client disputes
|
||||||
|
- Accounting ledger entries become inconsistent (billing and accounting diverge)
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Model billing as a state machine from day one: `pending → active → overdue → suspended → reinstated | cancelled`
|
||||||
|
- Define explicit business rules for every transition before writing code (who designed this? write it down)
|
||||||
|
- Support partial payment application and carry-forward balances
|
||||||
|
- Support credit memos for overpayments
|
||||||
|
- Proration: define whether you support it in v1 or not — if not, lock plan changes to billing cycle boundaries and enforce this in the UI
|
||||||
|
- Keep billing events (invoice generated, payment applied, credit issued) as immutable ledger records; never mutate them
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- Billing logic that directly updates an "amount_due" column rather than computing it from an event log
|
||||||
|
- No concept of a payment application record (just marking an invoice "paid")
|
||||||
|
- No test coverage for partial payment scenarios
|
||||||
|
|
||||||
|
**Phase:** Phase 1. The state machine and ledger approach must be established before the first invoice is generated. Retrofitting this is a rewrite.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 3: Building a Fake Accounting System (Cash Tracking Disguised as Double-Entry)
|
||||||
|
|
||||||
|
**What goes wrong:** The developer implements revenue and expense tracking as simple balance fields or single-entry logs, then wraps it in accounting-sounding labels. The system cannot produce a valid Balance Sheet, Income Statement, or Trial Balance. When the owner tries to close the books or show financials to an accountant, the numbers are wrong or cannot be reconciled.
|
||||||
|
|
||||||
|
**Why it happens:** Double-entry bookkeeping is counterintuitive to developers who think in terms of CRUD. It is easier to build `payments` and `expenses` tables than a proper Journal → Ledger → Trial Balance → Financial Statements pipeline.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
- Financial reports that look right but fail when cross-checked
|
||||||
|
- Cannot reconcile cash collected by collectors against accounting records
|
||||||
|
- Tax/audit failures
|
||||||
|
- Complete rewrite of the financial layer — which also breaks all existing reports
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Implement a proper Journal Entry table as the single source of truth: every financial event creates one or more balanced journal entries (debits = credits, always)
|
||||||
|
- Chart of Accounts must be established before any financial data is recorded
|
||||||
|
- Every billing event, payment, expense, and payroll transaction must post to the journal; reports are derived from the journal, never from separate tables
|
||||||
|
- Validate that debits = credits on every journal entry at the database constraint level, not just application level
|
||||||
|
- Hire or consult with an accountant to validate the COA and journal entry logic before Phase 1 ships
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- A "revenue" column somewhere that gets incremented directly
|
||||||
|
- Separate `income` and `expense` tables that are never cross-validated
|
||||||
|
- No `journal_entries` table with debit/credit columns
|
||||||
|
- Reports that query `payments` directly rather than the ledger
|
||||||
|
|
||||||
|
**Phase:** Phase 1. The journal/ledger foundation must exist before any money moves in the system. You cannot add real accounting to an existing fake-accounting system without a full rewrite.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 4: Collector Cash Reconciliation Has No Audit Trail
|
||||||
|
|
||||||
|
**What goes wrong:** Collectors collect cash door-to-door. The system records that a payment was made, but there is no chain-of-custody record: who collected it, when, how much cash they were holding, when they remitted it to the office, and who verified receipt. Disputes between collectors and management cannot be resolved. Cash goes missing with no accountability.
|
||||||
|
|
||||||
|
**Why it happens:** Developers model this as "payment → done." The collector workflow (collect → hold → remit → verify) is an ISP-specific operational pattern that most billing system guides do not cover.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
- Cash theft with no detection mechanism
|
||||||
|
- Collector disputes with no resolution path
|
||||||
|
- Accounting entries that don't match physical cash
|
||||||
|
- ISP owner cannot trust their own collection data
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Model collector sessions: a collector "opens" a collection run, logs each payment during the run, then "closes" the run with a total remittance
|
||||||
|
- Record the collector ID, timestamp, amount, and client on each payment
|
||||||
|
- Require an office staff member to verify and sign off on each cash remittance; this creates a two-party audit trail
|
||||||
|
- The accounting entry for cash receipt should only post when the remittance is verified, not when the collector logs it
|
||||||
|
- Generate a daily collector summary report: payments collected vs. remitted vs. variance
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- Payments table has no `collector_id` column
|
||||||
|
- No concept of a "collection run" or "remittance session"
|
||||||
|
- Payments post to accounting immediately without a verification step
|
||||||
|
- No variance report between collected and remitted amounts
|
||||||
|
|
||||||
|
**Phase:** Phase 1 (billing and payment collection). This is core to the cash flow visibility goal, not an enhancement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 5: Inventory Tracking That Doesn't Survive Real-World Operations
|
||||||
|
|
||||||
|
**What goes wrong:** Inventory is modeled as a simple stock count. When technicians deploy equipment, swap units, return defective hardware, or take items from the warehouse without logging it, the inventory count diverges from reality within weeks. The system becomes "what we think we have" rather than "what we actually have."
|
||||||
|
|
||||||
|
**Why it happens:** Simple inventory is easy to build. The hard part is the movement model: equipment moves from warehouse → technician vehicle → client premises → (possibly back to warehouse or to another client). Each movement needs to be logged as an event, not just a count adjustment.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
- ISP cannot find hardware they own
|
||||||
|
- Cannot calculate accurate asset value for the balance sheet
|
||||||
|
- Cannot determine if technicians are losing or stealing equipment
|
||||||
|
- Inventory reports are ignored because they're known to be wrong
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Model inventory as an event ledger: every movement is an immutable `inventory_movement` record (from location, to location, quantity, actor, timestamp)
|
||||||
|
- Stock counts are computed from the movement log, never stored as a mutable field
|
||||||
|
- Locations are typed: Warehouse, TechnicianVehicle, ClientPremises, Repair, Written-off
|
||||||
|
- Require technicians to log item assignments before a job order can be marked complete
|
||||||
|
- Periodic physical count reconciliation workflow: compare physical count to system count, log variance as an adjustment event
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- An `inventory_items` table with a `quantity` column that gets directly incremented/decremented
|
||||||
|
- No movement log or audit trail
|
||||||
|
- No concept of item location beyond "in stock" or "deployed"
|
||||||
|
- Job orders completable without logging what hardware was used
|
||||||
|
|
||||||
|
**Phase:** Phase 2 or 3 (after billing is stable), but the event-ledger model must be decided in Phase 1 architecture even if implementation is deferred.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 6: Role-Based Access Without Row-Level Enforcement
|
||||||
|
|
||||||
|
**What goes wrong:** The app checks roles at the UI level ("hide this button for technicians") but not at the API level. A Technician who discovers the API endpoint can access or modify billing records, financial data, or other clients' information.
|
||||||
|
|
||||||
|
**Why it happens:** RBAC is implemented as frontend permission guards. Backend enforcement is added as an afterthought, often incompletely.
|
||||||
|
|
||||||
|
**Consequences:**
|
||||||
|
- Security vulnerability exploitable by any technically savvy user
|
||||||
|
- Collectors can see all clients' financial data, not just their assigned routes
|
||||||
|
- Technicians can mark their own job orders complete without office verification
|
||||||
|
- Clients can access other clients' accounts
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Enforce permissions at the API/service layer, not the UI layer — every endpoint checks the caller's role before returning data
|
||||||
|
- For row-level access (e.g., a Collector can only see their own payment records), enforce this in the query scope, not a post-fetch filter
|
||||||
|
- Define permission matrix explicitly before coding: for each role × action × resource, is it allowed?
|
||||||
|
- Write API-level tests that call endpoints as each role and assert correct 403 responses
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- Permission checks only in frontend components or middleware that can be bypassed
|
||||||
|
- No API test suite that tests unauthorized access scenarios
|
||||||
|
- "We'll tighten security later" as a deferred task
|
||||||
|
|
||||||
|
**Phase:** Phase 1. Every API endpoint written from day one must have role enforcement. Retrofitting security is incomplete by definition.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Moderate Pitfalls
|
||||||
|
|
||||||
|
Mistakes that cause delays, technical debt, or operational pain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 7: Prepaid vs. Postpaid Billing Not Modeled as Distinct State Machines
|
||||||
|
|
||||||
|
**What goes wrong:** Prepaid and postpaid are implemented as a single billing flow with an `if` statement. The logic becomes a maze of conditionals. Edge cases in one model break the other. Adding postpaid features breaks prepaid behavior.
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Treat prepaid and postpaid as two separate billing strategies that share a common interface
|
||||||
|
- Prepaid: service activation requires payment first; no invoice generated until funds are available or committed
|
||||||
|
- Postpaid: service activates on cycle start; invoice generated at cycle end; grace period before suspension
|
||||||
|
- Define each strategy's state machine separately; compose shared logic (payment application, journal entries) rather than duplicating it
|
||||||
|
|
||||||
|
**Phase:** Phase 1. The model must be chosen during billing engine design.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 8: Soft Deletes Without Considering Accounting Immutability
|
||||||
|
|
||||||
|
**What goes wrong:** The team implements soft deletes (`deleted_at` column) on clients, invoices, and payments. An accountant or auditor needs records that existed in a prior period. Soft-deleted records either pollute current reports or disappear from historical reports.
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Financial records (invoices, payments, journal entries) must never be deleted — only voided or reversed via a compensating journal entry
|
||||||
|
- Client records: soft delete is acceptable, but all financial history must remain linked and accessible
|
||||||
|
- "Void" an invoice by creating a credit memo journal entry; never set `deleted_at` on a posted journal entry
|
||||||
|
|
||||||
|
**Phase:** Phase 1. Define archival strategy before any financial data is created.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 9: Ticket → Job Order Workflow Without Status Synchronization
|
||||||
|
|
||||||
|
**What goes wrong:** Tickets and job orders are separate tables with no status sync. A technician marks a job order complete, but the originating ticket remains "open." Staff sees open tickets for resolved issues. Clients see their issue as unresolved.
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Define the canonical status flow: Ticket (open → assigned → resolved → closed) maps to Job Order (created → assigned → in-progress → completed → verified)
|
||||||
|
- Job Order completion should trigger a Ticket status update (or require explicit ticket closure)
|
||||||
|
- One ticket can spawn one or more job orders (e.g., initial visit + follow-up); the ticket closes only when all job orders are verified
|
||||||
|
- Avoid duplicating status fields; derive ticket status from its job orders where possible
|
||||||
|
|
||||||
|
**Phase:** Phase 2 (ticketing module).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 10: MikroTik Integration Built Directly Into Business Logic
|
||||||
|
|
||||||
|
**What goes wrong:** Router commands are called directly from billing code. When the MikroTik is offline, invoices cannot be generated. When the billing logic changes, router integration code is tangled throughout. Testing becomes impossible without a real router.
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Isolate MikroTik integration behind an adapter interface: `RouterAdapter.suspend(clientId)`, `RouterAdapter.activate(clientId)`
|
||||||
|
- Billing engine calls the adapter; the adapter handles the actual RouterOS API call
|
||||||
|
- Make the adapter async and queue-based: if the router is offline, the command is queued and retried, not blocking the billing operation
|
||||||
|
- Mock the adapter in tests — billing logic tests should never require a live router
|
||||||
|
|
||||||
|
**Phase:** Phase 1 (if MikroTik integration is in scope). Adapter pattern must be established from first integration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 11: Dashboard Metrics Computed at Query Time on Large Tables
|
||||||
|
|
||||||
|
**What goes wrong:** The executive dashboard runs `SUM(payments)`, `COUNT(overdue_clients)`, and similar aggregations live against full tables. With 500–2,000 subscribers per ISP and multiple ISPs, dashboard load times become unacceptable. Worse, some ISPs experience degraded billing performance because dashboard queries lock rows.
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Pre-aggregate key metrics on a schedule (daily totals, monthly summaries) into a `metrics_snapshots` table
|
||||||
|
- The dashboard queries snapshots, not raw tables
|
||||||
|
- Real-time counts (e.g., "currently overdue") use indexed, lightweight queries — not full-table scans
|
||||||
|
- Add database indexes on `(tenant_id, status)`, `(tenant_id, due_date)`, `(tenant_id, created_at)` from day one
|
||||||
|
|
||||||
|
**Phase:** Phase 1 architecture (indexes), Phase 2 or 3 (metrics snapshots if performance degrades).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 12: Technician Compensation Calculation Embedded in UI Layer
|
||||||
|
|
||||||
|
**What goes wrong:** Technician pay (per-job rates, monthly salary, deductions) is computed in the frontend or a report query rather than a dedicated payroll service. When business rules change (new job types, bonus structures), it requires UI changes and report rewrites rather than a single rule update.
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Build a `CompensationService` that encapsulates all pay calculation logic
|
||||||
|
- Job order completion triggers a compensation event record (not a direct pay update)
|
||||||
|
- Payroll period closing is a deliberate action that aggregates compensation events into a payroll record and posts journal entries
|
||||||
|
- Support both per-job and salary models as configurable per technician
|
||||||
|
|
||||||
|
**Phase:** Phase 2 (technician management).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minor Pitfalls
|
||||||
|
|
||||||
|
Mistakes that cause annoyance or rework but are recoverable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 13: Plan Tier Configuration Hardcoded in Application Code
|
||||||
|
|
||||||
|
**What goes wrong:** ISP plan tiers (e.g., "5Mbps - ₱500/month") are defined as constants in code. When an ISP wants to add a plan or change pricing, a code deployment is required.
|
||||||
|
|
||||||
|
**Prevention:** Plans are tenant-configurable records in the database with name, price, billing cycle, and data limits. Admins manage plans via UI. No code changes needed for plan management.
|
||||||
|
|
||||||
|
**Phase:** Phase 1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 14: Super-Admin Access Using Same Auth System as Tenant Users
|
||||||
|
|
||||||
|
**What goes wrong:** The platform owner (Super-Admin) authenticates through the same login system as ISP staff. A misconfiguration can accidentally scope the Super-Admin to a specific tenant, or a tenant admin can escalate privileges to Super-Admin.
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Super-Admin is a separate authentication context, ideally a separate login URL/subdomain
|
||||||
|
- Super-Admin role is not stored in the same `users` table as tenant users — separate table or a flag with stricter guards
|
||||||
|
- Super-Admin access bypasses tenant scoping explicitly and auditably, not by coincidence of missing `tenant_id` checks
|
||||||
|
|
||||||
|
**Phase:** Phase 1 (SaaS infrastructure).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 15: No Idempotency on Payment Logging
|
||||||
|
|
||||||
|
**What goes wrong:** A collector logs a payment and the request times out. They resubmit. The payment is recorded twice. The client's balance is double-credited. This is very hard to detect without an audit trail.
|
||||||
|
|
||||||
|
**Prevention:**
|
||||||
|
- Require an idempotency key on all payment creation requests (client-generated UUID)
|
||||||
|
- Server rejects duplicate idempotency keys within a time window
|
||||||
|
- Payment creation is a two-step operation: "draft" then "confirm" — timeout on confirm does not re-create
|
||||||
|
|
||||||
|
**Phase:** Phase 1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 16: Assuming One Router = One ISP
|
||||||
|
|
||||||
|
**What goes wrong:** Data model is built with one router per tenant. ISPs frequently have multiple MikroTik routers across different geographic zones. Hardcoding a single-router assumption requires schema migration when this is discovered.
|
||||||
|
|
||||||
|
**Prevention:** Router is a `network_nodes` table with `(tenant_id, router_id, zone_name, ip_address)`. Clients are assigned to a router, not to the tenant globally. Build multi-router from the start even if the first ISP has only one.
|
||||||
|
|
||||||
|
**Phase:** Phase 1 (data model).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase-Specific Warnings
|
||||||
|
|
||||||
|
| Phase Topic | Likely Pitfall | Mitigation |
|
||||||
|
|-------------|----------------|------------|
|
||||||
|
| Multi-tenant foundation | Cross-tenant data leakage | Postgres RLS + scoped repository pattern from day one |
|
||||||
|
| Billing engine design | State machine complexity, fake accounting | Define state machine and journal entry pattern before coding |
|
||||||
|
| Collector workflow | No audit trail, cash reconciliation gaps | Model collection sessions with two-party verification |
|
||||||
|
| Chart of Accounts | Built after features, causing retrofit | Design COA in Phase 1 even if reports come in Phase 2 |
|
||||||
|
| Inventory module | Mutable quantity counts | Event-ledger model; decide architecture even if deferred |
|
||||||
|
| Ticketing/job orders | Status desync between ticket and job order | One-to-many mapping with explicit sync rules |
|
||||||
|
| MikroTik integration | Tight coupling to billing logic | Adapter + async queue pattern from first integration |
|
||||||
|
| Dashboard | Live aggregation on large tables | Indexes in Phase 1; snapshots if needed later |
|
||||||
|
| Role enforcement | UI-only permission guards | API-layer enforcement tested for every role from day one |
|
||||||
|
| Technician payroll | Logic in reports/UI | CompensationService with compensation event records |
|
||||||
|
| Super-Admin | Privilege escalation or accidental tenant scoping | Separate auth context for platform owner |
|
||||||
|
| Payment logging | Duplicate payments on retry | Idempotency keys on all payment endpoints |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- Domain analysis based on PRD (`isp_system_prd.md`) and PROJECT.md, verified against well-established patterns in:
|
||||||
|
- Double-entry accounting system design (immutable journal, COA, ledger derivation)
|
||||||
|
- Multi-tenant SaaS security (Postgres RLS, tenant-scoped repositories)
|
||||||
|
- ISP billing state machines (prepaid/postpaid lifecycle, suspension/reinstatement)
|
||||||
|
- Inventory event-ledger patterns (movement log vs. mutable quantity)
|
||||||
|
- Collector cash-in-transit operational workflows (two-party reconciliation)
|
||||||
|
- Confidence: MEDIUM for architectural patterns (well-established), LOW for ISP-specific market research (WebSearch unavailable this session — recommend validating collector workflow and MikroTik integration specifics against real ISP operator feedback)
|
||||||
346
.planning/research/STACK.md
Normal file
346
.planning/research/STACK.md
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
# Technology Stack
|
||||||
|
|
||||||
|
**Project:** NetForge — Multi-Tenant ISP Management SaaS
|
||||||
|
**Researched:** 2026-03-04
|
||||||
|
**Research Confidence Note:** WebSearch, WebFetch, and Context7 tools were unavailable during this session. All version numbers and rationale below are derived from knowledge current as of August 2025 training data plus logical inference from the project's requirements. Treat version numbers as MEDIUM confidence — verify against official changelogs before pinning in package.json.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended Stack
|
||||||
|
|
||||||
|
### Core Framework
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| Next.js | 15.x | Full-stack React framework | App Router with server components reduces client JS bundle. API Routes serve as the API layer for n8n/MikroTik integrations. Edge middleware enables per-tenant routing. Deployed easily on Vercel or self-hosted. |
|
||||||
|
| React | 19.x | UI rendering | Bundled with Next.js 15. Server Components mean accounting tables and dashboards render server-side, improving cold-load performance for data-heavy views. |
|
||||||
|
| TypeScript | 5.x | Type safety | Accounting logic (double-entry journal entries, debit/credit balancing) must never silently pass wrong types. TypeScript catches ledger arithmetic errors at compile time, not runtime. |
|
||||||
|
|
||||||
|
**Why Next.js over alternatives:**
|
||||||
|
- SvelteKit: Smaller ecosystem for complex data tables and form libraries needed for accounting UI. Fewer multi-tenant SaaS templates to learn from.
|
||||||
|
- Remix: Good, but Next.js App Router has converged on similar patterns with broader adoption and tooling.
|
||||||
|
- Separate frontend + backend (React + Express): Overkill complexity for this team size. Next.js API Routes cover the API-first requirement adequately for v1; migrate to standalone API if load demands it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| PostgreSQL | 16.x | Primary relational database | Double-entry accounting requires ACID transactions — no eventual consistency. PostgreSQL's Row Level Security (RLS) is the right primitive for tenant isolation. JSON columns handle MikroTik config blobs without a separate document store. |
|
||||||
|
| Prisma ORM | 5.x | Database access layer | Type-safe queries align with TypeScript across the stack. Prisma Migrate handles schema evolution safely (critical when adding accounting tables incrementally). Prisma's `$transaction` API is correct for double-entry journal entry writes (debit + credit must be atomic). |
|
||||||
|
| Redis | 7.x | Session store, job queue backing, rate limiting | Session storage for web app auth, queue backing for billing engine cron jobs, rate limiting for public API endpoints used by n8n. |
|
||||||
|
|
||||||
|
**Multi-tenancy approach — `tenant_id` column strategy (not schema-per-tenant):**
|
||||||
|
|
||||||
|
Use a single shared schema where every table has a `tenant_id` column. Enforce isolation at the application layer (Prisma middleware that injects `WHERE tenant_id = ?` on every query) and optionally with PostgreSQL Row Level Security as a defense-in-depth second layer.
|
||||||
|
|
||||||
|
Why not schema-per-tenant: Managing 50+ ISP schemas becomes a migration nightmare. Prisma does not have first-class support for dynamic schema switching.
|
||||||
|
|
||||||
|
Why not database-per-tenant: Operational cost and connection pool explosion. Not warranted at this scale (target: under 500 subscribers per ISP, under ~200 ISPs on platform).
|
||||||
|
|
||||||
|
**Confidence:** HIGH for PostgreSQL and Prisma. MEDIUM for Redis version number.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Authentication & Authorization
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| NextAuth.js (Auth.js) | 5.x (v5 beta / stable) | Authentication | Handles session management, credential-based login, and JWT/session tokens. Supports the five-role model via custom session claims. Native Next.js App Router integration. No managed auth vendor lock-in. |
|
||||||
|
| CASL | 6.x | Fine-grained RBAC authorization | Five roles (Admin, Office Staff, Collector, Technician, Client) have overlapping permissions with context-dependent rules. CASL allows defining "Collector can read Payment where collectorId = self" without encoding every rule in if/else blocks. Separates auth logic from business logic. |
|
||||||
|
|
||||||
|
**Why not Clerk or Auth0:**
|
||||||
|
- Clerk/Auth0 are managed services. Multi-tenant SaaS with per-ISP user pools requires either expensive tiers or complex configuration. Auth.js + CASL gives full control at the cost of more initial setup — correct tradeoff for a product with complex role rules.
|
||||||
|
- Clerk is viable if team wants to move fast on auth and accept the vendor dependency. Note for roadmap: if Clerk is chosen, budget for migration away if ISP count scales significantly.
|
||||||
|
|
||||||
|
**Confidence:** HIGH for Auth.js architecture. MEDIUM for CASL v6 version number.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### UI & Component Layer
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| Tailwind CSS | 4.x | Utility-first styling | Fastest path to a consistent, responsive UI across the five role dashboards. No CSS module naming overhead. v4 introduces significant performance improvements in build time. |
|
||||||
|
| shadcn/ui | (copy-paste, no package version) | Base component library | Not an installed package — components are copied into the project. This means full control over component customization. Data tables (billing history, journal entries, inventory) and form components (payment entry, job order forms) are the primary shadcn components used. Built on Radix UI primitives for accessibility. |
|
||||||
|
| Recharts | 2.x | Dashboard charts | Revenue vs. collected charts, cash flow over time, subscriber growth. Recharts integrates cleanly with React and is the most common charting library in the Next.js/shadcn ecosystem. |
|
||||||
|
| React Hook Form | 7.x | Form management | Payment collection forms, ticket creation, inventory entries. RHF minimizes re-renders on large forms (client onboarding has many fields). |
|
||||||
|
| Zod | 3.x | Schema validation | Paired with React Hook Form for client-side validation and reused on the API layer for server-side validation. Single source of truth for data shapes shared between form and API. |
|
||||||
|
|
||||||
|
**Why not MUI or Ant Design:**
|
||||||
|
- MUI and Ant Design are full design systems that fight with custom branding. shadcn/ui with Tailwind gives a professional look without the override battle.
|
||||||
|
- For a vertical SaaS (ISP ops), the UI doesn't need to be generic — it should reflect the domain. shadcn gives building blocks; Tailwind gives control.
|
||||||
|
|
||||||
|
**Confidence:** HIGH for Tailwind and shadcn approach. MEDIUM for Recharts as chart choice (TanStack Table is also relevant for data-heavy tables).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Data Tables
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| TanStack Table | 8.x | Complex data table logic | Billing history, journal entry ledger, inventory list, subscriber list all require: sorting, filtering, pagination, and column visibility toggles. TanStack Table is headless — works with shadcn/Tailwind rendering. The most capable React table library available as of 2025. |
|
||||||
|
|
||||||
|
**Confidence:** HIGH. TanStack Table is the clear standard for complex React tables.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### API Layer
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| Next.js API Routes (App Router Route Handlers) | (bundled with Next.js) | REST API endpoints | API-first requirement means all operations are exposed via REST. Route Handlers in Next.js App Router handle this natively without a separate Express server. n8n connects to these endpoints. MikroTik integration uses these endpoints as webhooks. |
|
||||||
|
| Zod | 3.x | API request/response validation | Validate all incoming payloads on API routes. Reuse the same schemas from the frontend forms. |
|
||||||
|
|
||||||
|
**When to extract to a standalone API:**
|
||||||
|
If the platform reaches scale where Next.js server-side rendering and API processing compete for the same compute, extract the API to a standalone service (Fastify or Hono). For v1-v2, keeping them together in Next.js is the correct tradeoff.
|
||||||
|
|
||||||
|
**Confidence:** MEDIUM. This is defensible for v1 but the team should monitor API latency as billing engine cron jobs grow.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Background Jobs & Scheduling
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| BullMQ | 5.x | Job queue for billing engine and async tasks | Billing cron jobs (auto-generate invoices on billing cycle date, send SMS reminders) must be reliable and retryable. BullMQ on Redis is the standard for Node.js job queues. Supports delayed jobs (schedule SMS 3 days before due date), retries with backoff, and job priority. |
|
||||||
|
| node-cron or BullMQ Scheduler | (bundled) | Scheduling recurring billing runs | Trigger monthly invoice generation per tenant on their configured billing cycle date. |
|
||||||
|
|
||||||
|
**Why not Vercel Cron Jobs:**
|
||||||
|
Vercel cron jobs have execution time limits (10s on Hobby, 60s on Pro). Billing engine runs that process many subscribers in a single tenant will exceed this. BullMQ with a dedicated worker process is the correct architecture.
|
||||||
|
|
||||||
|
**Deployment note:** Background workers must run as a separate process, not as Vercel serverless functions. This means deployment on a VPS (Railway, Render, or a raw VPS) is required for the worker, even if the web app is on Vercel.
|
||||||
|
|
||||||
|
**Confidence:** HIGH for BullMQ. MEDIUM for version number.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Billing & Payments
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| Custom billing engine (in-house) | — | ISP billing logic | No off-the-shelf billing library covers ISP-specific patterns: prepaid vs. postpaid cycles per subscriber, collector cash tracking, partial payments, reconnection fees. Must be built custom on top of the accounting layer. |
|
||||||
|
| Stripe (for SaaS subscriptions only) | — | Charging ISPs for platform subscription | The SaaS owner charges ISPs monthly. Stripe handles this B2B subscription. Stripe is NOT used for ISP-to-subscriber payments in v1 (subscriber payments are tracked manually as cash/bank transfer). |
|
||||||
|
|
||||||
|
**Confidence:** HIGH for architecture decision. MEDIUM for Stripe as the SaaS billing choice (PayMongo is also relevant if targeting Philippines market, given the ISP context suggests Philippines-based operations).
|
||||||
|
|
||||||
|
**Philippines market note:** The PRD mentions SMS and door-to-door cash collection patterns common in Philippine ISPs. Consider PayMongo (local payment gateway) for online subscriber payments if that feature is added. GCash and Maya (PayMaya) are the dominant digital wallets.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Double-Entry Accounting Engine
|
||||||
|
|
||||||
|
This is the highest-complexity module. No third-party library fully covers it — must be built custom.
|
||||||
|
|
||||||
|
| Component | Approach | Why |
|
||||||
|
|-----------|----------|-----|
|
||||||
|
| Chart of Accounts | Database table: `accounts (id, tenant_id, code, name, type, normal_balance)` | Standard COA structure. Account types: Asset, Liability, Equity, Revenue, Expense. |
|
||||||
|
| Journal Entries | Database table: `journal_entries` (header) + `journal_entry_lines` (debit/credit lines). Prisma `$transaction` wraps every write. | Every financial event (invoice created, payment received, expense logged) writes an immutable journal entry. Double-entry constraint: sum(debits) must equal sum(credits) — enforced in application code and optionally as a PostgreSQL check constraint. |
|
||||||
|
| Ledger / Trial Balance | Computed from journal_entry_lines by account | Sum debits and credits per account. Trial balance = all accounts with net balance. |
|
||||||
|
| Reports | Derived queries: Balance Sheet = asset/liability/equity accounts at a point in time. P&L = revenue/expense accounts for a period. | Standard accounting report queries. |
|
||||||
|
|
||||||
|
**What to NOT use:** Do not use an external accounting API (QuickBooks, Xero API) as the accounting backend. The double-entry system must be internal to NetForge — ISPs need their books within the platform, not exported to another service. This is a core product differentiator.
|
||||||
|
|
||||||
|
**Confidence:** HIGH for the architectural approach. The patterns here (journal_entries + journal_entry_lines) are the industry-standard ledger schema used by every accounting system from QuickBooks to Odoo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### MikroTik Integration
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| RouterOS API client (Node.js) | `node-routeros` or `mikronode` | Connect to MikroTik routers via RouterOS API | MikroTik exposes a proprietary TCP API (port 8728/8729). Node.js libraries wrap this protocol. Commands: enable/disable PPPoE users, query connection status, update rate limits per plan change. |
|
||||||
|
|
||||||
|
**Confidence:** LOW — verify which Node.js RouterOS API client is actively maintained in 2025/2026. `node-routeros` was the most common as of training data but open-source maintenance may have shifted. This needs verification before Phase 1 implementation.
|
||||||
|
|
||||||
|
**Fallback:** If no well-maintained Node.js library exists, implement the RouterOS API protocol directly — it is a documented protocol (ASCII-based sentences). This is non-trivial but the protocol is stable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Infrastructure & Deployment
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| Railway or Render | — | Application hosting | Both support Next.js + PostgreSQL + Redis + background workers as separate services in one project. Railway is simpler to configure for a full-stack monorepo (web + worker + db + redis). Vercel for the Next.js web app is viable but creates split deployment (Vercel + Railway for worker). |
|
||||||
|
| Neon or Supabase (PostgreSQL) | — | Managed PostgreSQL | Neon offers serverless PostgreSQL with branching (useful for preview environments). Supabase includes PostgreSQL + Auth + Storage but the auth layer conflicts with Auth.js. Use Neon for managed Postgres if self-hosted Railway is not preferred. |
|
||||||
|
| Cloudflare (CDN/DNS) | — | CDN, DDoS protection, DNS | Free tier covers multi-tenant domain routing (each ISP subdomain: `isp-name.netforge.app`). |
|
||||||
|
| Docker | 24.x+ | Containerize worker process | Background worker runs as a Docker container alongside the web app in Railway/Render. Ensures consistent environment between dev and production. |
|
||||||
|
|
||||||
|
**Self-hosting note:** If the target market is Philippine ISPs with cost sensitivity, a single VPS (DigitalOcean/Linode/Hetzner) with Docker Compose may be more economical than managed services. This is a valid deployment strategy for v1.
|
||||||
|
|
||||||
|
**Confidence:** MEDIUM for Railway/Render recommendation. The self-hosted VPS path is equally valid and may be preferable given the market.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### SMS
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| Semaphore SMS (Philippines) | — | Billing reminders, payment confirmations | The dominant SMS API provider for the Philippines market. Handles pre-due reminders, overdue alerts, and payment receipt SMS. |
|
||||||
|
| Twilio | — | Alternative / fallback | International option. More expensive for Philippine SMS volume but better documented and more reliable for global coverage. |
|
||||||
|
|
||||||
|
**Recommendation:** Use Semaphore for initial Philippine market targeting. Abstract behind a `SmsService` interface so the provider can be swapped without touching business logic.
|
||||||
|
|
||||||
|
**Confidence:** MEDIUM for Semaphore. This is based on known Philippine ISP/SME tech patterns. Verify Semaphore API is still active and competitively priced in 2026.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why |
|
||||||
|
|------------|---------|---------|-----|
|
||||||
|
| Vitest | 2.x | Unit and integration tests | Accounting engine (journal entry creation, trial balance calculation) must be unit tested. Vitest is faster than Jest and has native TypeScript support. |
|
||||||
|
| Playwright | 1.x | End-to-end tests | Test billing cycle runs, payment collection flows, and role-based access restrictions end-to-end. |
|
||||||
|
|
||||||
|
**Confidence:** HIGH for Vitest. The accounting engine particularly demands unit tests — a bug in debit/credit logic corrupts financial records for all tenants.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Supporting Libraries
|
||||||
|
|
||||||
|
| Library | Version | Purpose | When to Use |
|
||||||
|
|---------|---------|---------|-------------|
|
||||||
|
| date-fns | 3.x | Date manipulation | Billing cycle calculations (next due date, days overdue, billing period start/end). Never use moment.js — deprecated. |
|
||||||
|
| decimal.js or dinero.js | — | Monetary arithmetic | Never use JavaScript floating-point for money calculations. All financial amounts (invoice totals, payments, account balances) must use decimal-safe arithmetic. |
|
||||||
|
| Papa Parse | 5.x | CSV import/export | Bulk subscriber import, payment export for reconciliation, accounting export. |
|
||||||
|
| pino | 9.x | Structured logging | Audit trail for financial operations. Every journal entry write, payment record, and billing cycle run should be logged with tenant context for debugging. |
|
||||||
|
|
||||||
|
**Confidence:** HIGH for decimal.js/dinero.js requirement — this is non-negotiable for an accounting system. MEDIUM for specific library versions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
| Category | Recommended | Alternative | Why Not |
|
||||||
|
|----------|-------------|-------------|---------|
|
||||||
|
| Framework | Next.js 15 | Remix | Remix is solid but Next.js has broader ecosystem for multi-tenant SaaS patterns, more shadcn examples, and App Router has now caught up on streaming/loading patterns. |
|
||||||
|
| ORM | Prisma | Drizzle ORM | Drizzle is lighter and faster, gaining popularity in 2025. For this project: Prisma's `$transaction` API and Middleware (for injecting tenant_id) are better suited for accounting-grade data integrity. Revisit Drizzle for v2 if Prisma query performance becomes a bottleneck. |
|
||||||
|
| ORM | Prisma | TypeORM | TypeORM is older, more complex, slower DX. Not recommended. |
|
||||||
|
| Auth | Auth.js | Clerk | Clerk accelerates auth setup but adds per-MAU cost that scales with ISP subscriber count. With 5 roles and complex per-tenant user pools, Auth.js custom implementation is worth the upfront cost. |
|
||||||
|
| Job Queue | BullMQ | Inngest | Inngest is a good managed option but adds vendor dependency for a billing-critical process. BullMQ on self-hosted Redis is more predictable for billing reliability. |
|
||||||
|
| Database | PostgreSQL | MySQL | PostgreSQL's Row Level Security is a material advantage for multi-tenancy. MySQL lacks this. |
|
||||||
|
| Database | PostgreSQL | MongoDB | Double-entry accounting requires relational joins and ACID transactions. MongoDB's eventual consistency is wrong for financial data. |
|
||||||
|
| Hosting | Railway | Vercel | Vercel doesn't support persistent workers without workarounds. Billing engine requires a persistent process. Railway supports web + worker + db as first-class services. |
|
||||||
|
| Charts | Recharts | Chart.js | Recharts is more idiomatic React (composable components). Chart.js requires imperative configuration. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Core Next.js project
|
||||||
|
npx create-next-app@latest netforge --typescript --tailwind --eslint --app --src-dir
|
||||||
|
|
||||||
|
# ORM and database
|
||||||
|
npm install prisma @prisma/client
|
||||||
|
npx prisma init
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
npm install next-auth@beta
|
||||||
|
npm install @auth/prisma-adapter
|
||||||
|
|
||||||
|
# Authorization
|
||||||
|
npm install @casl/ability @casl/react
|
||||||
|
|
||||||
|
# UI Components (shadcn — follow shadcn CLI, not npm install)
|
||||||
|
npx shadcn@latest init
|
||||||
|
|
||||||
|
# Forms and validation
|
||||||
|
npm install react-hook-form zod @hookform/resolvers
|
||||||
|
|
||||||
|
# Data tables
|
||||||
|
npm install @tanstack/react-table
|
||||||
|
|
||||||
|
# Charts
|
||||||
|
npm install recharts
|
||||||
|
|
||||||
|
# Job queue
|
||||||
|
npm install bullmq ioredis
|
||||||
|
|
||||||
|
# Date handling
|
||||||
|
npm install date-fns
|
||||||
|
|
||||||
|
# Money arithmetic
|
||||||
|
npm install decimal.js
|
||||||
|
|
||||||
|
# CSV handling
|
||||||
|
npm install papaparse
|
||||||
|
npm install -D @types/papaparse
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
npm install pino pino-pretty
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
npm install -D vitest @vitejs/plugin-react playwright @playwright/test
|
||||||
|
|
||||||
|
# Development
|
||||||
|
npm install -D @types/node tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical Architecture Decisions That Affect Every Phase
|
||||||
|
|
||||||
|
### 1. Tenant Context Middleware (Implement in Phase 1)
|
||||||
|
|
||||||
|
Every Prisma query must automatically scope to the current tenant. Implement a Prisma extension/middleware that injects `tenant_id` on every read and write:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// prisma/middleware/tenant.ts
|
||||||
|
prisma.$use(async (params, next) => {
|
||||||
|
const tenantId = getTenantContext(); // from request context
|
||||||
|
if (params.args.where) {
|
||||||
|
params.args.where.tenantId = tenantId;
|
||||||
|
} else {
|
||||||
|
params.args.where = { tenantId };
|
||||||
|
}
|
||||||
|
return next(params);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
If this is bolted on later, every query written before it needs auditing. Do it first.
|
||||||
|
|
||||||
|
### 2. Monetary Arithmetic (Non-Negotiable)
|
||||||
|
|
||||||
|
All financial values in the database are stored as integers in the smallest currency unit (centavos for PHP, cents for USD). Never store floats for money. The application layer converts to/from human-readable decimal using `decimal.js`. This prevents floating-point rounding errors in accounting reports.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// WRONG
|
||||||
|
const total = 100.1 + 200.2; // = 300.30000000000003
|
||||||
|
|
||||||
|
// RIGHT — store as integer centavos, compute with decimal.js
|
||||||
|
import Decimal from 'decimal.js';
|
||||||
|
const total = new Decimal(10010).plus(20020).toNumber(); // = 30030 centavos
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Journal Entry Immutability (Accounting Integrity)
|
||||||
|
|
||||||
|
Once a journal entry is written, it must never be deleted or modified. Corrections are made via reversal entries (new journal entry that negates the original). Enforce this at the database level:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- No DELETE allowed on journal_entries
|
||||||
|
-- Application code enforces; consider RLS policy as backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. API-First from Day One
|
||||||
|
|
||||||
|
All business operations must go through the API layer, even when called from the Next.js server-side. This ensures n8n and future mobile app can access every operation without a separate integration layer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
Note: Due to tool restrictions during this research session (WebSearch, WebFetch, Context7 unavailable), all recommendations are based on:
|
||||||
|
- Knowledge of the Next.js, React, Prisma, PostgreSQL ecosystem as of August 2025 training data
|
||||||
|
- Direct inference from the NetForge PRD requirements (double-entry accounting, multi-tenancy, Philippine ISP market)
|
||||||
|
- Standard patterns from the multi-tenant SaaS engineering community
|
||||||
|
|
||||||
|
**Confidence summary by area:**
|
||||||
|
- Core framework (Next.js + TypeScript + PostgreSQL + Prisma): HIGH — these are the dominant choices for this class of application with strong community validation
|
||||||
|
- Auth approach (Auth.js + CASL): MEDIUM-HIGH — correct architecture, version numbers need verification
|
||||||
|
- Accounting engine design (journal_entries schema): HIGH — industry-standard pattern independent of implementation stack
|
||||||
|
- MikroTik integration (Node.js client): LOW — needs verification of library maintenance status in 2026
|
||||||
|
- SMS (Semaphore): MEDIUM — verify pricing and API stability for Philippine market
|
||||||
|
- Infrastructure (Railway): MEDIUM — verify pricing tiers and feature parity for worker support
|
||||||
|
- Version numbers: MEDIUM — verify all package versions against npm before pinning
|
||||||
284
.planning/research/SUMMARY.md
Normal file
284
.planning/research/SUMMARY.md
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
# Project Research Summary
|
||||||
|
|
||||||
|
**Project:** NetForge — Multi-Tenant ISP Management SaaS
|
||||||
|
**Domain:** ISP Operations Management (billing, accounting, inventory, ticketing)
|
||||||
|
**Researched:** 2026-03-04
|
||||||
|
**Confidence:** MEDIUM (core architecture HIGH; library versions and market specifics MEDIUM-LOW)
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
NetForge is a vertical SaaS product targeting small-to-medium ISPs (50–2,000 subscribers per tenant) in the Philippine market and similar developing-market contexts where cash collection, MikroTik routers, and Facebook Messenger are operational norms. The dominant pattern for this class of application is a modular monolith with shared-database multi-tenancy — every table carries a `tenant_id`, enforced both at the application layer (Prisma middleware) and at the database layer (PostgreSQL Row Level Security). This is not a microservices problem; splitting too early would kill development velocity with no meaningful benefit at the target scale.
|
||||||
|
|
||||||
|
The recommended stack is Next.js 15 + TypeScript + PostgreSQL + Prisma, with BullMQ on Redis for background billing jobs, Auth.js for authentication, and CASL for fine-grained role-based access control across the five-role model (Admin, Office Staff, Collector, Technician, Client). The double-entry accounting engine is the most complex component and must be built custom — no off-the-shelf library covers the ISP-specific patterns (collector cash-in-transit, prepaid vs. postpaid cycles, field remittance workflows). This engine is also NetForge's primary competitive differentiator: no current competitor combines deep MikroTik integration, real double-entry accounting, field collector workflows, and Facebook Messenger self-service in a single multi-tenant SaaS.
|
||||||
|
|
||||||
|
The highest risks are architectural, not feature-level. Missing `tenant_id` on even one query leaks cross-tenant data catastrophically. Building billing as simple invoice generation without a proper state machine and journal-entry foundation leads to a rewrite. Collector cash tracking without an audit trail creates unresolvable disputes. All three of these must be addressed in Phase 1 before any user-facing features ship — they are infrastructure, not features.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Findings
|
||||||
|
|
||||||
|
### Recommended Stack
|
||||||
|
|
||||||
|
The core stack (Next.js 15, TypeScript, PostgreSQL 16, Prisma 5) is well-established for multi-tenant SaaS at this scale and carries HIGH confidence. Authentication is handled by Auth.js (NextAuth v5) with CASL for RBAC — this avoids managed-auth vendor lock-in while supporting the complex five-role permission matrix. UI is built with Tailwind CSS 4 + shadcn/ui components (copy-paste model for full control) + TanStack Table for the data-heavy views (billing history, ledger, inventory). Background billing jobs run on BullMQ backed by Redis — Vercel cron jobs are explicitly ruled out due to execution time limits. Deployment should target Railway or a self-hosted VPS with Docker, not Vercel alone, because the billing worker requires a persistent process.
|
||||||
|
|
||||||
|
**Core technologies:**
|
||||||
|
- **Next.js 15 + TypeScript 5:** Full-stack framework with App Router; API Routes serve as the REST API consumed by n8n and MikroTik webhooks. TypeScript catches ledger arithmetic errors at compile time.
|
||||||
|
- **PostgreSQL 16 + Prisma 5:** ACID transactions required for double-entry accounting; Prisma `$transaction` wraps every journal entry write. Row Level Security provides second-layer tenant isolation.
|
||||||
|
- **Auth.js (NextAuth v5) + CASL 6:** Credential-based auth with JWT session; CASL encodes per-role, per-context permission rules without sprawling if/else blocks.
|
||||||
|
- **BullMQ 5 on Redis 7:** Reliable, retryable job queue for billing cron runs, SMS reminders, and async operations. Persistent worker process required (not serverless).
|
||||||
|
- **decimal.js (non-negotiable):** All monetary values stored as integer centavos; decimal.js handles arithmetic. JavaScript floating-point must never touch financial calculations.
|
||||||
|
- **MikroTik RouterOS Node.js client:** `node-routeros` or `mikronode` — LOW confidence on maintenance status; verify before Phase 1 MikroTik work.
|
||||||
|
- **Semaphore SMS:** Primary SMS provider for the Philippine market; abstract behind a `SmsService` interface for swappability.
|
||||||
|
|
||||||
|
See `.planning/research/STACK.md` for full technology table and alternatives analysis.
|
||||||
|
|
||||||
|
### Expected Features
|
||||||
|
|
||||||
|
The ISP management market has a well-defined set of table stakes that no ISP will migrate without. NetForge's differentiated position combines features no single competitor currently offers together.
|
||||||
|
|
||||||
|
**Must have (table stakes — MVP blockers):**
|
||||||
|
- Client/subscriber database with status lifecycle (Active / Suspended / Cancelled)
|
||||||
|
- Service plan registry (configurable per tenant, not hardcoded)
|
||||||
|
- Invoice auto-generation with monthly recurring billing
|
||||||
|
- Payment logging by office staff (cash/bank transfer dominant)
|
||||||
|
- Overdue/outstanding reports (daily operational necessity)
|
||||||
|
- MikroTik auto-suspend and auto-activate via RouterOS API
|
||||||
|
- Multi-user roles (Admin, Office Staff, Collector, Technician, Client)
|
||||||
|
- Basic ticketing and job order workflow
|
||||||
|
- SMS reminders (pre-due, overdue, payment confirmation)
|
||||||
|
- Executive dashboard (revenue collected, overdue count, active clients)
|
||||||
|
|
||||||
|
**Should have (competitive differentiators — Phase 2):**
|
||||||
|
- Full double-entry accounting with automated journal entries (primary differentiator — competitors do billing, not accounting)
|
||||||
|
- Collector zone management + field cash collection with audit trail
|
||||||
|
- Inventory/asset tracking per subscriber (prevents equipment loss)
|
||||||
|
- Facebook Messenger chatbot via n8n API integration
|
||||||
|
- API documentation and webhook support
|
||||||
|
|
||||||
|
**Defer (v2+ / Phase 3+):**
|
||||||
|
- Technician native mobile app (responsive web PWA first)
|
||||||
|
- White-label client portal
|
||||||
|
- Prepaid voucher / hotspot load management
|
||||||
|
- Live MikroTik connection status polling per subscriber
|
||||||
|
- Automated PPPoE profile sync on plan changes
|
||||||
|
- Super-admin / SaaS platform subscription management (meta-layer)
|
||||||
|
- Network usage graphs
|
||||||
|
|
||||||
|
**Anti-features — do not build:**
|
||||||
|
- Full RADIUS server (integrate with FreeRADIUS instead)
|
||||||
|
- Built-in VoIP billing
|
||||||
|
- Zero-touch provisioning / complex provisioning workflows
|
||||||
|
- Full ERP (HR, payroll, procurement)
|
||||||
|
- Built-in email marketing
|
||||||
|
|
||||||
|
See `.planning/research/FEATURES.md` for full competitor comparison table and feature dependency graph.
|
||||||
|
|
||||||
|
### Architecture Approach
|
||||||
|
|
||||||
|
NetForge is a modular monolith with tenant-scoped data isolation. Twelve domain modules communicate through in-process service calls (not a message bus), with BullMQ handling the only genuinely async operations (scheduled billing runs, report pre-computation, SMS dispatch). The central invariant: every database query carries `tenant_id`, enforced at the Prisma middleware layer and backed by PostgreSQL RLS. No balance fields exist — all financial state is derived from immutable journal entry lines. The `JournalEntryService` is the sole gateway to the ledger; no module writes accounting records directly.
|
||||||
|
|
||||||
|
**Major components:**
|
||||||
|
1. **Auth + Tenant Resolution** — JWT carries `{user_id, tenant_id, role}`; subdomain resolves to `tenant_id`; CASL enforces permissions at every route handler.
|
||||||
|
2. **Subscriber Management** — Core entity. Billing, inventory, and ticketing all reference subscribers. Does not write to accounting directly — emits to Billing Engine.
|
||||||
|
3. **Billing Engine** — Prepaid and postpaid as distinct state machines. Generates invoices, tracks cycles, detects overdue. Calls Accounting module for journal entries.
|
||||||
|
4. **Payment Tracker** — Records cash/online payments, links to invoices, manages collector running balances. Immutable records — corrections via reversals only.
|
||||||
|
5. **Accounting Module (Double-Entry Ledger)** — Chart of Accounts, JournalEntryService (enforces debits = credits), Report Engine (read-only). Auto-provisioned default COA at tenant signup.
|
||||||
|
6. **Ticketing + Job Order Workflow** — Separate entities (one ticket spawns multiple job orders). Technician completion triggers inventory depletion and compensation journal entries.
|
||||||
|
7. **Technician Management** — Per-job and salary compensation models; CompensationService encapsulates all pay logic.
|
||||||
|
8. **Inventory Module** — Stock (warehouse) and deployed assets (at subscriber premises) tracked as an event ledger of movements, not mutable quantity columns.
|
||||||
|
9. **Expense Tracking** — Non-inventory operational expenses; each entry posts a journal entry.
|
||||||
|
10. **Dashboard + Report Engine** — Read-only aggregation. Pre-computed snapshots for performance; never live scans of full tables.
|
||||||
|
11. **Client Portal** — Subscriber-facing; scoped at tenant_id AND subscriber_id. Read-heavy with ticket submission as primary write.
|
||||||
|
12. **Notification Module** — In-app v1; SMS/email extendable via abstracted interfaces.
|
||||||
|
|
||||||
|
See `.planning/research/ARCHITECTURE.md` for full component diagrams, data flow, and anti-patterns.
|
||||||
|
|
||||||
|
### Critical Pitfalls
|
||||||
|
|
||||||
|
1. **Cross-tenant data leakage** — A single query missing `WHERE tenant_id = ?` exposes one ISP's data to another. Prevention: Prisma middleware injects `tenant_id` on every query; PostgreSQL RLS as backstop; integration tests with two tenants asserting zero cross-contamination. Must be implemented in Phase 1 before any feature work.
|
||||||
|
|
||||||
|
2. **Fake double-entry accounting** — Building `payments` and `expenses` tables and calling it accounting produces financial reports that cannot be reconciled or audited. Prevention: Implement `journal_entries` + `journal_entry_lines` as the single source of truth from Phase 1. All balances are derived from the ledger, never stored as mutable fields. Validate debits = credits at the database constraint level.
|
||||||
|
|
||||||
|
3. **Billing as simple invoice generation** — The complexity is in the state machine: partial payments, mid-cycle plan changes, grace periods, suspension/reinstatement timing, credit memos. Prevention: Model billing as an explicit state machine (`pending → active → overdue → suspended → reinstated | cancelled`) before writing any billing code. Define edge case rules in writing before implementation.
|
||||||
|
|
||||||
|
4. **Collector cash with no audit trail** — Door-to-door cash collection without chain-of-custody records (collector ID, collection run, remittance verification) enables unresolvable disputes and undetectable theft. Prevention: Model collection sessions with two-party remittance verification. Accounting entries post only on verified remittance, not on collection logging.
|
||||||
|
|
||||||
|
5. **UI-only RBAC** — Enforcing permissions only in frontend components means any technically savvy user can call API endpoints directly and access billing data as a Technician or other clients' data as a Collector. Prevention: Every API endpoint enforces role AND row-level access in the service layer. Write API tests for unauthorized access scenarios for each role from day one.
|
||||||
|
|
||||||
|
See `.planning/research/PITFALLS.md` for 12 additional moderate and minor pitfalls with phase-specific warnings.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implications for Roadmap
|
||||||
|
|
||||||
|
The feature dependency graph from FEATURES.md and the build-order tiers from ARCHITECTURE.md are in strong agreement. The roadmap must follow this dependency chain: multi-tenant foundation → subscriber + billing core → payment tracking + accounting → operational modules → visibility/reporting. There is no shortcut. Any attempt to build the accounting reports before the journal entry foundation, or to build MikroTik integration before the billing state machine, creates architectural debt requiring rewrites.
|
||||||
|
|
||||||
|
### Phase 1: Multi-Tenant Foundation and Billing Core
|
||||||
|
|
||||||
|
**Rationale:** Everything else depends on this. Tenant isolation, auth, subscriber management, billing engine, payment tracking, and the accounting ledger must all be established together. These are not separable — a billing engine without accounting posts is half-built. The journal entry schema must exist before the first invoice is generated.
|
||||||
|
|
||||||
|
**Delivers:** A working ISP can be onboarded. Subscribers managed, invoices generated on schedule, payments recorded, MikroTik suspensions automated, double-entry ledger posting correctly.
|
||||||
|
|
||||||
|
**Addresses features from FEATURES.md:**
|
||||||
|
- Client/subscriber database with status lifecycle
|
||||||
|
- Service plan registry (database-driven, per tenant)
|
||||||
|
- Invoice auto-generation (monthly recurring, prepaid and postpaid state machines)
|
||||||
|
- Payment logging by office staff
|
||||||
|
- MikroTik auto-suspend and auto-activate
|
||||||
|
- Multi-user roles with API-level enforcement
|
||||||
|
- Chart of Accounts + Journal Entry Service (accounting foundation)
|
||||||
|
- Executive dashboard (basic metrics — active, overdue, revenue collected)
|
||||||
|
|
||||||
|
**Avoids pitfalls:**
|
||||||
|
- Cross-tenant leakage (Prisma middleware + RLS from day one)
|
||||||
|
- Fake accounting (journal_entries schema established before first invoice)
|
||||||
|
- Billing state machine not modeled (prepaid/postpaid strategies built as distinct classes)
|
||||||
|
- UI-only RBAC (API-level enforcement from first endpoint)
|
||||||
|
- Idempotency missing on payments (idempotency keys on payment creation)
|
||||||
|
- One-router assumption (network_nodes table with router_id from day one)
|
||||||
|
|
||||||
|
**Needs research-phase:** YES — MikroTik RouterOS Node.js client library maintenance status is LOW confidence and must be verified before implementation commits to a specific library.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 2: Operational Modules and Accounting Completion
|
||||||
|
|
||||||
|
**Rationale:** Once the billing and accounting core is stable, the operational modules that depend on it (ticketing, job orders, inventory, expense tracking, collector workflows) can be built. These modules also complete the accounting picture — every expense, equipment purchase, and labor cost posts to the ledger.
|
||||||
|
|
||||||
|
**Delivers:** A complete operational platform. ISP staff can manage support tickets, dispatch technicians, track equipment from warehouse to subscriber, log expenses, and see collector accountability. The accounting system now has real data across all account types.
|
||||||
|
|
||||||
|
**Addresses features from FEATURES.md:**
|
||||||
|
- Basic ticketing and job order workflow
|
||||||
|
- Collector zone management and field payment receipting
|
||||||
|
- Inventory/asset tracking
|
||||||
|
- Expense and vendor tracking
|
||||||
|
- Overdue/outstanding reports (operational)
|
||||||
|
- SMS reminders (billing + payment confirmation)
|
||||||
|
- Financial reports (Trial Balance, Income Statement, Balance Sheet)
|
||||||
|
|
||||||
|
**Implements architecture:** Inventory Module (event-ledger model), Ticketing + Job Order Workflow (separate entities with explicit status sync), Collector Remittance Workflow (two-party verification), CompensationService.
|
||||||
|
|
||||||
|
**Avoids pitfalls:**
|
||||||
|
- Collector cash without audit trail (modeled as collection sessions in Phase 2)
|
||||||
|
- Inventory as mutable quantity (event-ledger model decided now even if partially deferred)
|
||||||
|
- Ticket/job order status desync (explicit one-to-many mapping with sync rules)
|
||||||
|
- Technician compensation in UI layer (CompensationService built as dedicated service)
|
||||||
|
|
||||||
|
**Needs research-phase:** MAYBE — Semaphore SMS API integration is MEDIUM confidence; verify current API documentation and pricing before SMS implementation work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 3: External Integrations and Self-Service
|
||||||
|
|
||||||
|
**Rationale:** With a stable operational core, external-facing integrations and self-service features become viable. The API-first architecture from Phase 1 makes n8n and Facebook Messenger integration straightforward. The client portal can now be built on top of the billing and payment history that exists.
|
||||||
|
|
||||||
|
**Delivers:** Reduced support overhead (client self-service), automated workflows (n8n / Facebook Messenger balance checks), and the platform's third key differentiator operational.
|
||||||
|
|
||||||
|
**Addresses features from FEATURES.md:**
|
||||||
|
- Facebook Messenger chatbot via n8n API
|
||||||
|
- Client portal (balance view, payment history, ticket submission)
|
||||||
|
- API documentation and webhook support
|
||||||
|
- Live MikroTik connection status query
|
||||||
|
|
||||||
|
**Uses stack:** Next.js API Routes (already built), n8n webhook endpoints, SMS gateway (Semaphore).
|
||||||
|
|
||||||
|
**Needs research-phase:** YES — n8n integration patterns and Facebook Messenger API webhook setup may need current documentation review.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 4: Platform Scale and Differentiation
|
||||||
|
|
||||||
|
**Rationale:** Super-admin panel, white-label support, and advanced MikroTik features (automated PPPoE profile sync, live status polling) require the earlier phases to be stable and load-tested. The super-admin auth isolation (separate context from tenant users) is a security requirement that must not be shortcut.
|
||||||
|
|
||||||
|
**Delivers:** The SaaS platform operator can manage ISP tenants, monitor usage, and bill them. Advanced ISPs can use white-label portals. Plan changes automatically propagate to router speed profiles.
|
||||||
|
|
||||||
|
**Addresses features from FEATURES.md:**
|
||||||
|
- Super-admin panel (ISP tenant management, usage metering, platform billing via Stripe)
|
||||||
|
- White-label client portal (per-tenant theme/logo/custom domain)
|
||||||
|
- Automated PPPoE profile sync on plan change
|
||||||
|
- Prepaid voucher / hotspot load management
|
||||||
|
|
||||||
|
**Avoids pitfalls:**
|
||||||
|
- Super-admin using same auth system as tenant users (separate auth context, separate table)
|
||||||
|
|
||||||
|
**Needs research-phase:** YES — Stripe integration for B2B SaaS subscriptions and PayMongo/GCash integration for Philippine online subscriber payments both need current API verification.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase Ordering Rationale
|
||||||
|
|
||||||
|
- **Dependency chains are strict:** Billing Engine requires Subscriber Management. Payment Tracker requires invoices from Billing Engine. Journal Entry Service requires Chart of Accounts. Dashboard requires all operational modules to have data. This ordering is not optional.
|
||||||
|
- **Accounting foundation cannot be retrofitted:** The journal_entries schema and JournalEntryService must exist before the first financial transaction is written anywhere in the system. Building billing first and adding accounting later is a rewrite path.
|
||||||
|
- **MikroTik is in Phase 1 because it's a table-stakes feature:** ISPs will not adopt NetForge without auto-suspend/activate. However, the RouterOS client library must be verified (LOW confidence) before committing to implementation.
|
||||||
|
- **Collector workflow is Phase 2 (not Phase 1):** Office payment logging covers the MVP. The full collector session / remittance workflow adds complexity that should follow the core billing foundation being proven.
|
||||||
|
- **External integrations (n8n, Facebook Messenger) are Phase 3:** They require the API layer to be stable. Building them before the API is finalized creates rework.
|
||||||
|
|
||||||
|
### Research Flags
|
||||||
|
|
||||||
|
Phases needing `/gsd:research-phase` during planning:
|
||||||
|
|
||||||
|
- **Phase 1 (MikroTik integration):** `node-routeros` and `mikronode` maintenance status is LOW confidence as of research date. Must verify which Node.js RouterOS API client is actively maintained before implementation.
|
||||||
|
- **Phase 2 (SMS):** Semaphore SMS API pricing and stability for 2026 is MEDIUM confidence. Verify current API documentation and rate limits before implementation.
|
||||||
|
- **Phase 3 (n8n + Facebook Messenger):** Integration patterns for n8n webhook → Facebook Messenger API need current documentation. Facebook Messenger API has a history of breaking changes.
|
||||||
|
- **Phase 4 (Payments):** Stripe B2B SaaS subscription integration is standard, but PayMongo/GCash integration for Philippine online payments needs current API research.
|
||||||
|
|
||||||
|
Phases with well-documented standard patterns (can skip research-phase):
|
||||||
|
|
||||||
|
- **Phase 1 (multi-tenant foundation):** Prisma middleware tenant injection, PostgreSQL RLS, Auth.js setup, and BullMQ job queue are all well-documented patterns with extensive community examples.
|
||||||
|
- **Phase 1 (double-entry accounting schema):** The `journal_entries` + `journal_entry_lines` ledger pattern is industry-standard and stable. No research needed — implement as specified in ARCHITECTURE.md.
|
||||||
|
- **Phase 2 (ticketing and job orders):** Standard CRUD with state machine patterns. No novel integration required.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Confidence Assessment
|
||||||
|
|
||||||
|
| Area | Confidence | Notes |
|
||||||
|
|------|------------|-------|
|
||||||
|
| Stack | MEDIUM-HIGH | Core framework/DB/ORM choices are HIGH confidence. Auth.js + CASL architecture is HIGH; version numbers are MEDIUM. MikroTik Node.js client is LOW — needs verification before Phase 1 implementation commits. |
|
||||||
|
| Features | MEDIUM | Based on training knowledge of Splynx, UISP, Sonar, ISPApp. No live verification of competitor feature pages possible during research. Competitor comparison table is directionally reliable but should be validated before roadmap lock. |
|
||||||
|
| Architecture | HIGH | Double-entry accounting patterns, modular monolith for this scale, shared-DB multi-tenancy, and collector cash-in-transit workflows are all well-established patterns. High confidence independent of tool availability. |
|
||||||
|
| Pitfalls | MEDIUM-HIGH | Critical pitfalls (tenant isolation, fake accounting, billing state machine) are well-established engineering patterns with HIGH confidence. ISP-specific operational pitfalls (collector audit trail, MikroTik coupling) are MEDIUM — derived from domain analysis, not validated with live ISP operators. |
|
||||||
|
|
||||||
|
**Overall confidence:** MEDIUM-HIGH
|
||||||
|
|
||||||
|
The architectural and accounting foundations are HIGH confidence. Stack choices are defensible and well-supported. The main uncertainties are market-specific (Semaphore SMS pricing, MikroTik library maintenance, Philippine payment gateway options) and should be validated with quick targeted research during Phase 1 and Phase 2 planning.
|
||||||
|
|
||||||
|
### Gaps to Address
|
||||||
|
|
||||||
|
- **MikroTik RouterOS Node.js client:** Verify `node-routeros` vs `mikronode` vs direct protocol implementation before Phase 1 commits to an approach. Check npm download trends and GitHub last-commit dates. This is LOW confidence and blocks Phase 1 MikroTik work.
|
||||||
|
|
||||||
|
- **Semaphore SMS:** Verify API is still active, competitively priced for Philippine market in 2026, and has adequate throughput for billing reminder volume. The `SmsService` abstraction from STACK.md means this can be swapped if Semaphore is unsuitable.
|
||||||
|
|
||||||
|
- **Collector workflow business rules:** The two-party remittance verification model is architecturally sound, but the exact operational flow (does the office staff verify before or after the collector remits? Is there a physical receipt?) should be validated with real ISP operators before Phase 2 implementation.
|
||||||
|
|
||||||
|
- **Philippine payment gateways (Phase 3+):** PayMongo, GCash, and Maya (PayMaya) integration options need current API research when online subscriber payments are in scope. The research notes this as relevant to the market but defers it from v1.
|
||||||
|
|
||||||
|
- **Competitor feature verification:** The FEATURES.md competitor table is based on training data (mid-2025). Verify Splynx, UISP, and Sonar current feature pages before finalizing the differentiator claims in product positioning.
|
||||||
|
|
||||||
|
- **Stripe vs alternative for ISP subscription billing:** Stripe is recommended for the SaaS owner billing ISPs (B2B subscription). Verify this is the right choice for the target market's legal entity types and banking setup. PayMongo may be more appropriate if the SaaS owner is Philippines-based.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Primary (HIGH confidence)
|
||||||
|
- NetForge PRD (`isp_system_prd.md`) — project requirements, reviewed directly
|
||||||
|
- Established SaaS architecture patterns — modular monolith, shared-DB multi-tenancy (Martin Fowler, Sam Newman), PostgreSQL RLS
|
||||||
|
- Double-entry accounting principles (GAAP) — COA, journal entry ledger, trial balance derivation
|
||||||
|
- BullMQ, Prisma, Auth.js, Next.js official documentation — from training data through August 2025
|
||||||
|
|
||||||
|
### Secondary (MEDIUM confidence)
|
||||||
|
- Training knowledge of Splynx, UISP CRM, ISPApp, Sonar Software feature sets (public documentation up to mid-2025)
|
||||||
|
- Philippine ISP market patterns (cash/bank-transfer dominance, GCash/Maya prevalence, Facebook Messenger as primary support channel)
|
||||||
|
- Semaphore SMS as dominant Philippine SMS gateway
|
||||||
|
- Railway vs Vercel deployment trade-offs
|
||||||
|
|
||||||
|
### Tertiary (LOW confidence — needs verification)
|
||||||
|
- `node-routeros` and `mikronode` Node.js MikroTik client maintenance status (open-source project activity not verifiable without WebSearch)
|
||||||
|
- Specific package version numbers across the stack — verify against npm before pinning in package.json
|
||||||
|
- Current Semaphore SMS API pricing and throughput limits
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Research completed: 2026-03-04*
|
||||||
|
*Ready for roadmap: yes*
|
||||||
Reference in New Issue
Block a user