163 lines
10 KiB
Markdown
163 lines
10 KiB
Markdown
# Accounting, User Management & Customer Portal — Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
|
||
|
||
**Goal:** Extend Zappier with three surfaces: (a) company accounting & system management, (b) internal user management, and (c) an end-user customer portal — with full documentation and screenshot walkthroughs for each.
|
||
|
||
**Architecture:** Same ports-and-adapters style as the existing codebase. New domain modules (`src/invoicing.ts`, `src/reports.ts`, `src/accounts.ts`) expose pure logic + repo interfaces; SQLite adapters live in `src/db/`; HTTP wiring goes into `src/admin.ts` (company side) and a new `src/portal.ts` (customer side). The admin SPA gains tabs; the portal is a second dependency-free SPA under `portal/`. No new runtime deps in Phase 1–2; Phase 3 adds `otplib`-style TOTP only if unavoidable — prefer `node:crypto` scrypt for passwords and a minimal TOTP implementation per RFC 6238.
|
||
|
||
**Tech Stack:** Node 20 · TS strict · Express 4 · better-sqlite3 · jest/supertest.
|
||
|
||
## Global Constraints
|
||
|
||
- Money is integer cents everywhere; multipliers applied once, `Math.round`.
|
||
- Money/date logic must be pure functions, unit-tested without HTTP or SQLite.
|
||
- All SQL in `src/db/` repos only; all tables `CREATE TABLE IF NOT EXISTS`; schema changes for existing tables use idempotent `ALTER TABLE ... ADD COLUMN` guarded by `PRAGMA table_info` checks.
|
||
- Existing 87 jest tests + 4 mocha tests stay green; `npx tsc --noEmit` clean after every task.
|
||
- No servers left running after any verification; walkthrough screenshots use a throwaway `ZAPPIER_DB`.
|
||
- PDFs: Phase 1–2 deliver **print-ready HTML report/invoice pages** (browser Print → PDF, zero deps) plus CSV; binary PDF generation is deferred unless the owner asks.
|
||
- The demo password `$$$Adm1n###` and `admin-dev-key` remain dev defaults, overridable by env.
|
||
|
||
## Surface map
|
||
|
||
| Surface | URL | Auth | Audience |
|
||
|---|---|---|---|
|
||
| Admin SPA (extended) | `/admin` | login token / `x-admin-key` | Company ops & accounting |
|
||
| Customer portal SPA (new) | `/portal` | customer session token (+TOTP 2FA) | End-user customers |
|
||
| Public API | `/v1` | `x-api-key` | Unchanged |
|
||
|
||
---
|
||
|
||
## Phase 1 — Accounting backend
|
||
|
||
### Task 1: Schema + customer billing identity
|
||
|
||
**Files:**
|
||
- Modify: `src/auth.ts` — `Customer` gains `billingType: 'stripe' | 'purchase_order'` (default `'stripe'`), `email?: string`
|
||
- Modify: `src/db/customer-repo.ts` — idempotent `ALTER TABLE customers ADD COLUMN billing_type TEXT NOT NULL DEFAULT 'stripe'`; same for `email TEXT`; map in `toCustomer`
|
||
- Modify: `src/admin.ts` — `PUT /customers/:id` accepts `billingType` (validated) and `email`
|
||
- Test: `tests/db-customer.test.ts`, `tests/admin.test.ts` (extend)
|
||
|
||
**Interfaces:**
|
||
- Consumes: existing `CustomerRepo`
|
||
- Produces: `Customer.billingType`, `Customer.email` used by all later tasks
|
||
|
||
- [x] Extend `tests/db-customer.test.ts`: migration on existing DB adds columns with `'stripe'` default; round-trip `billingType`/`email`
|
||
- [x] Extend `tests/admin.test.ts`: `PUT /customers/:id` sets `billingType: 'purchase_order'`; invalid value → 400
|
||
- [x] Implement; run suites; commit `feat(accounting): customer billing types and email`
|
||
|
||
### Task 2: Invoice engine
|
||
|
||
**Files:**
|
||
- Create: `src/invoicing.ts` — types + pure generation logic + repo interface
|
||
- Create: `src/db/invoice-repo.ts` — SQLite adapter
|
||
- Test: `tests/invoicing.test.ts`, `tests/db-invoice.test.ts`
|
||
|
||
**Interfaces:**
|
||
```ts
|
||
export interface Invoice {
|
||
id: string; // INV-2026-07-<customerSeq>
|
||
customerId: string;
|
||
period: string; // YYYY-MM
|
||
status: 'draft' | 'issued' | 'paid';
|
||
lines: InvoiceLine[]; // { endpointId, calls, cents }
|
||
totalCents: number; // sum of lines (gross usage)
|
||
creditCents: number; // monthly credit applied
|
||
billableCents: number; // totalCents - creditCents, floored at 0
|
||
billingType: 'stripe' | 'purchase_order';
|
||
poNumber?: string; // PO billing only
|
||
issuedAtMs?: number; dueAtMs?: number; paidAtMs?: number;
|
||
}
|
||
export interface InvoiceRepo {
|
||
save(invoice: Invoice): void;
|
||
get(id: string): Invoice | undefined;
|
||
list(filter: { customerId?: string; period?: string; status?: Invoice['status'] }): Invoice[];
|
||
nextSequence(period: string): number;
|
||
}
|
||
export function buildInvoice(args: {
|
||
customer: Customer; period: string; sequence: number;
|
||
entries: UsageEntry[]; tier: TierConfig; poNumber?: string;
|
||
}): Invoice; // groups entries by endpoint; status 'draft'
|
||
```
|
||
|
||
- [x] Tests: line grouping, credit math (partial/zero/excess), id format, PO fields
|
||
- [x] Repo tests: save/get/list filters, sequence increments per period
|
||
- [x] Implement; commit `feat(accounting): invoice engine`
|
||
|
||
### Task 3: Reports service (billing + trends, CSV/JSON)
|
||
|
||
**Files:**
|
||
- Create: `src/reports.ts` — `billingRows(entries, customers, tiers, range)` → rows `{customerId, name, billingType, calls, totalCents, creditCents, billableCents}`; `usageTrend(entries, bucket: 'day'|'week')` → `[{bucket, calls, cents}]`; `toCsv(rows)` with RFC-4180 escaping
|
||
- Test: `tests/reports.test.ts`
|
||
|
||
- [x] Tests: date-range filtering (inclusive from, exclusive to), per-customer vs all, per-billingType filter, trend bucketing across month boundary, CSV quoting of commas/quotes/newlines
|
||
- [x] Implement; commit `feat(accounting): reports service`
|
||
|
||
### Task 4: Admin accounting API
|
||
|
||
**Files:**
|
||
- Modify: `src/admin.ts` — new routes (all behind existing admin auth):
|
||
- `POST /invoices/generate { period, customerId?, poNumber? }` → builds draft invoices for the period (all customers or one; idempotent per customer+period — regenerating replaces the draft)
|
||
- `POST /invoices/:id/issue`, `POST /invoices/:id/paid`
|
||
- `GET /invoices?customerId&period&status`
|
||
- `GET /invoices/:id` (+ `?format=html` print-ready invoice page)
|
||
- `GET /reports/billing?from&to&customerId&billingType&format=json|csv`
|
||
- `GET /reports/usage-trend?from&to&bucket&customerId`
|
||
- `GET /zapier/status` → `{ published: boolean, triggerCount, actionCount, baseUrl }` read from `zapier-app/` files (static inspection, no network)
|
||
- Test: `tests/admin-accounting.test.ts`
|
||
|
||
- [x] Tests per route incl. CSV content-type, filter combos, invoice lifecycle transitions (draft→issued→paid; illegal transitions → 409)
|
||
- [x] Implement; commit `feat(accounting): admin accounting API`
|
||
|
||
---
|
||
|
||
## Phase 2 — Admin UI: Accounting, Reports, Users tabs
|
||
|
||
### Task 5: Invoices tab (generate, filter, lifecycle, print page)
|
||
### Task 6: Reports tab (date-range pickers, customer + billing-type filters, CSV download, trend charts as inline SVG)
|
||
### Task 7: Users tab (admin account management: list/create/deactivate admin users backed by a new `admin_users` table replacing the static two-account map; login endpoint reads the table; env seed preserved)
|
||
### Task 8: System tab (Zapier connection status, billing job last-run info from `billing_reports`/`job_locks`)
|
||
|
||
Each: admin UI section + `tests/` coverage for any new API + screenshot verification. Commit per task.
|
||
|
||
---
|
||
|
||
## Phase 3 — Customer portal (`/portal`)
|
||
|
||
### Task 9: Customer identity
|
||
- `customers` += `password_hash`, `totp_secret`, `totp_enabled`, `email_verified`
|
||
- `src/accounts.ts`: scrypt hash/verify (`node:crypto`), session tokens (new `portal_sessions` table), signup `POST /portal/api/signup` (creates customer on `free` tier + issues API key), login `POST /portal/api/login`
|
||
- Tests: hash round-trip, signup/login flows, session expiry
|
||
|
||
### Task 10: TOTP 2FA
|
||
- RFC 6238 TOTP (HMAC-SHA1, 30 s step, 6 digits) implemented in `src/accounts.ts` (no dep): `generateTotpSecret`, `totpUri(secret, email)`, `verifyTotp(secret, code, window=1)`
|
||
- Routes: `POST /portal/api/2fa/setup` (returns secret + otpauth URI; QR rendered client-side via a tiny inline QR lib or Google-Charts-free canvas QR — decision: render otpauth URI as text + QR via `qrcode` npm dep, portal-side only), `POST /portal/api/2fa/enable`, `POST /portal/api/2fa/verify` (login second step), `POST /portal/api/2fa/disable`
|
||
- Tests: known RFC vectors, window tolerance, login requires second factor when enabled
|
||
|
||
### Task 11: Portal dashboard API
|
||
- `GET /portal/api/me` (profile, tier, apiKey, regenerate key `POST /portal/api/api-key`)
|
||
- `GET /portal/api/usage` (month-to-date + credit)
|
||
- `GET /portal/api/invoices` (own invoices only, scoped by session customer)
|
||
- `POST /portal/api/reload { amountCents }` — prepaid balance: `customers` += `balance_cents`; Stripe PaymentIntent via existing SDK (test mode); balance drawn down at invoice issue before metered reporting
|
||
- `PUT /portal/api/email-invoicing { enabled, email }` — stored prefs; billing job emails PO invoices (send via SMTP env config; dev: log-only transport)
|
||
- Tests: session scoping (cannot read other customers' invoices), reload math, key regeneration invalidates old key
|
||
|
||
### Task 12: Portal SPA
|
||
- `portal/index.html` + `portal/app.js` in the same design language as the new admin: login/signup/2FA screens, dashboard (usage + balance), invoices (history + print), billing (reload, email invoicing), API docs + pricing info pages (rendered from `openapi.yaml` + live rate card)
|
||
- Screenshot verification of every screen
|
||
|
||
---
|
||
|
||
## Phase 4 — Documentation
|
||
|
||
### Task 13: `docs/ACCOUNTING.md` + PDF — company accounting walkthrough (invoices, PO billing, reports, trends, CSV export) with screenshots
|
||
### Task 14: `docs/USER-MANAGEMENT.md` + PDF — admin users walkthrough
|
||
### Task 15: `docs/CUSTOMER-PORTAL.md` + PDF — end-user walkthrough (signup, 2FA, reload, invoices, API docs)
|
||
### Task 16: Refresh `USER-MANUAL.md`, `DEVELOPER.md`, `README.md`; re-export all PDFs
|
||
|
||
---
|
||
|
||
## Self-review notes
|
||
|
||
- Spec coverage: Stripe **and** PO billing (Tasks 1–2, 11), invoicing histories (2, 4, 5), CSV/PDF + date range + all/individual/by-type (3, 4, 6), usage/billing trends (3, 6), management reports (4, 6, 8), Zapier connections (4, 8), user management (7), customer login/setup/2FA/invoices/reload/email invoicing/API docs/pricing (9–12), docs for all three surfaces (13–16).
|
||
- Type consistency: `Invoice`, `InvoiceRepo`, `billingRows`, `usageTrend`, `toCsv` signatures are defined once above and reused in every later task.
|