|
|
|
|
@ -0,0 +1,114 @@
|
|
|
|
|
# HQ Console — Go-Live Cluster Spec (Due Dates, Import UI, Reminders/Documents Pages, Shell & Profile, Client Support Data)
|
|
|
|
|
|
|
|
|
|
> **STATUS: DRAFT — awaiting founder review.** Successor slice to the delivered D16 funnel
|
|
|
|
|
> (`2026-07-17-quote-to-close-funnel-design.md`). The Postgres switch (D15) is deliberately
|
|
|
|
|
> NOT in this slice — it is the dedicated task queued immediately after it, before the
|
|
|
|
|
> real-data import commit.
|
|
|
|
|
|
|
|
|
|
## 1. Overview & goals
|
|
|
|
|
|
|
|
|
|
Six workstreams that take the console from "feature-complete" to "safe to go live on":
|
|
|
|
|
|
|
|
|
|
| WS | What | Why now |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| **A** | Invoice **due dates** — dated default terms + per-document override | Overdue chase currently counts from issue date (A7); the client email should say the honest thing |
|
|
|
|
|
| **B** | **APEX import web UI** (owner-only) | The 300-client cutover is one-shot; CLI-only staging invites a mistake |
|
|
|
|
|
| **C** | Standalone **Reminders page** | Queue volume outgrows the dashboard card at 300 live clients |
|
|
|
|
|
| **D** | **Documents list page** + honest pagination on `GET /documents` | Documents are only reachable via client ledger/pipeline today; the endpoint returns ALL rows (rule-8 violation at scale) |
|
|
|
|
|
| **E** | **Shell & profile**: collapsible sidebar, self-service profile (name + own password), "My Day" dashboard mode, minimal employee extras (phone/title) | Self password change doesn't exist; the old app's most-used surface was the per-person workbench |
|
|
|
|
|
| **F** | **Client support data** from APEX: AnyDesk/OS/DB-password (encrypted), district + sector, WhatsApp + secretary contacts | AnyDesk was referenced 44× in the old app — support opens it daily; district/sector are the team's main list filters |
|
|
|
|
|
|
|
|
|
|
Hard rules apply throughout (paise, one billing engine, dated config, immutable documents,
|
|
|
|
|
portable repos, audited mutations in-txn, no silent caps, strict TS + green suite).
|
|
|
|
|
|
|
|
|
|
## 2. Assumptions & non-goals
|
|
|
|
|
|
|
|
|
|
- **A1** `billing.payment_terms_days` is a flat operational setting (like `quote.followup.policy`):
|
|
|
|
|
the *dated* fact is the due date **stamped on each document at issue**, so history never
|
|
|
|
|
rewrites when terms change.
|
|
|
|
|
- **A2** Due dates apply to INVOICE only (quotes/proformas are not payable paper).
|
|
|
|
|
- **A3** DB passwords encrypt with the same AES-256-GCM + `HQ_SECRET_KEY` used for the Gmail
|
|
|
|
|
refresh token (`crypto.ts`). No `HQ_SECRET_KEY` ⇒ the field refuses to save, loudly.
|
|
|
|
|
- **A4** Import UI accepts the same two CSVs the CLI does; parsing/verification/commit logic
|
|
|
|
|
is **reused, not reimplemented** (`import-apex.ts` functions get routes, not a rewrite).
|
|
|
|
|
- **Non-goals:** Postgres switch (next task); support ticket desk; WhatsApp *sending*;
|
|
|
|
|
HR fields (DOB/joining/leave/payroll — permanently out per D16); bulk actions;
|
|
|
|
|
kanban board.
|
|
|
|
|
|
|
|
|
|
## 3. Data model (all additive; no new tables)
|
|
|
|
|
|
|
|
|
|
| Change | Where | Notes |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| `document.due_date TEXT NULL` | SCHEMA + `migrate()` (PRAGMA-guarded) | A column, not payload JSON — the scan needs portable SQL |
|
|
|
|
|
| setting `billing.payment_terms_days` | seed `'15'` | Default terms; owner-editable |
|
|
|
|
|
| `staff_user.phone TEXT NULL`, `staff_user.title TEXT NULL` | additive | Shown on Employees + profile; nothing routes on them |
|
|
|
|
|
| `client.anydesk TEXT NULL`, `client.os TEXT NULL`, `client.db_password_enc TEXT NULL` | additive | `db_password_enc` stored encrypted; NEVER returned by list/get payloads |
|
|
|
|
|
| `client.district TEXT NULL`, `client.sector TEXT NULL` | additive | Filterable on the client book; mapped from the APEX CSV at import |
|
|
|
|
|
| contacts JSON | none | WhatsApp + secretary captured as typed contact rows (`role: 'whatsapp' \| 'secretary'`) — UI labels only |
|
|
|
|
|
|
|
|
|
|
## 4. API (`[N]` new · `[C]` changed)
|
|
|
|
|
|
|
|
|
|
| Route | Gate | Notes |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| `[C]` POST `/documents` (draft) | auth | accepts optional `dueDate` on INVOICE drafts |
|
|
|
|
|
| `[C]` `issueDocument` | — | stamps `due_date = doc_date + terms` when null and type is INVOICE (covers convert-and-send + recurring generation for free) |
|
|
|
|
|
| `[N]` PATCH `/me` | auth (self) | `{ displayName?, phone? }` — audited, role untouchable |
|
|
|
|
|
| `[N]` POST `/me/password` | auth (self) | requires `currentPassword`; purges the user's OTHER sessions (keeps the current one) |
|
|
|
|
|
| `[N]` POST `/import/stage` | owner | body: `{ clientsCsv, invoicesCsv }` text; runs existing staging; audited |
|
|
|
|
|
| `[N]` GET `/import/status` | owner | staging rows + per-row problems + verification report |
|
|
|
|
|
| `[N]` POST `/import/commit` | owner | refuses while ANY problem exists (existing guard); audited |
|
|
|
|
|
| `[C]` GET `/documents` | auth | gains `page/pageSize` + returns `total` (rule 8); keeps type/status/clientId filters |
|
|
|
|
|
| `[C]` PATCH `/clients/:id` | auth | accepts `anydesk/os/district/sector`; `dbPassword` (plaintext in, encrypted at rest) — owner/manager only for `dbPassword` |
|
|
|
|
|
| `[N]` POST `/clients/:id/reveal-db-password` | owner/manager | returns the decrypted value once; **every reveal writes an audit row** |
|
|
|
|
|
|
|
|
|
|
## 5. Screens
|
|
|
|
|
|
|
|
|
|
- **`/import`** (ADMIN, owner-only): two CSV pickers → staging tables with per-row problem
|
|
|
|
|
chips → verification summary → **Commit** enabled only at zero problems, behind a confirm
|
|
|
|
|
that states "APEX goes read-only after cutover". Re-stage freely.
|
|
|
|
|
- **`/reminders`** (WORK): FilterChips `Queued · Sent · Failed · Dismissed` + `Mine/All`
|
|
|
|
|
(managerial), table (kind · client · doc · due · status), Send/Preview/Dismiss reusing the
|
|
|
|
|
dashboard components, pager. Dashboard keeps its short queue and links here.
|
|
|
|
|
- **`/documents`** (WORK): type chips (All/QT/PI/INV/CN/RCT) + status filter, mono numbers,
|
|
|
|
|
amount column, pager; row → `/documents/:id`.
|
|
|
|
|
- **`/profile`** (user-chip menu): email + role read-only; edit display name/phone; change
|
|
|
|
|
own password (current password required).
|
|
|
|
|
- **Sidebar collapse**: footer ⟨/⟩ toggle to an icon rail, remembered in `localStorage`;
|
|
|
|
|
the <900px hamburger behavior is unchanged.
|
|
|
|
|
- **"My Day" dashboard**: staff land on *mine* (my pipeline due, my queue, my follow-ups);
|
|
|
|
|
owner/manager get a Mine/Everyone toggle. Composition of existing scoped queries only.
|
|
|
|
|
- **Client 360 support card**: AnyDesk (copy button) · OS · district · sector ·
|
|
|
|
|
DB password as `••••` with owner/manager **Reveal** (audited) · contacts list gains
|
|
|
|
|
WhatsApp/secretary typed rows.
|
|
|
|
|
- **Composer**: Due date field on INVOICE drafts, auto-filled from terms, editable.
|
|
|
|
|
- **Invoice PDF**: prints "Due date: <date>" when present.
|
|
|
|
|
|
|
|
|
|
## 6. Due-date mechanics (WS-A detail)
|
|
|
|
|
|
|
|
|
|
- Scan anchor becomes `COALESCE(due_date, doc_date)`; milestone math (d7/d15/d30, F7
|
|
|
|
|
highest-only) is unchanged.
|
|
|
|
|
- `reminderContext`: `ctx.dueDate` when present; template renders
|
|
|
|
|
*"was due on {dueDate} and is now {N} day(s) overdue"*, falling back to today's
|
|
|
|
|
*"past due"* wording for legacy invoices without one.
|
|
|
|
|
- Editing terms changes **future** stamps only — issued documents are immutable (rule 4).
|
|
|
|
|
|
|
|
|
|
## 7. Security notes (WS-E/F)
|
|
|
|
|
|
|
|
|
|
- `db_password_enc`: AES-256-GCM, never in `GET /clients`/`GET /clients/:id` payloads,
|
|
|
|
|
reveal-only endpoint, one audit row per reveal (`action='reveal_db_password'`).
|
|
|
|
|
- `POST /me/password` cannot touch role or other users; rate of change limited by the
|
|
|
|
|
current-password requirement.
|
|
|
|
|
- Import routes are owner-only and audited (`stage`/`commit` actions).
|
|
|
|
|
|
|
|
|
|
## 8. Build order (TDD; each phase independently shippable, suite green before landing)
|
|
|
|
|
|
|
|
|
|
1. **Due dates** — column + terms setting + issue-stamp + scan COALESCE + template + composer field + PDF line. *(Accept: stamp on all three issue paths; overdue math honors due_date; legacy invoices unaffected.)*
|
|
|
|
|
2. **Documents pagination + page** — API page/total, `/documents` screen + nav. *(Accept: total honest; filters compose; 300-row dataset paginates.)*
|
|
|
|
|
3. **Reminders page** — `/reminders` screen + nav; dashboard links to it. *(Accept: status chips map to `?status=`; staff see own only.)*
|
|
|
|
|
4. **Profile + self password** — `/me` routes + page + user-chip menu. *(Accept: wrong current password rejected; other sessions purged, current kept; role untouchable.)*
|
|
|
|
|
5. **Sidebar collapse + My Day** — shell toggle + dashboard mine/everyone. *(Accept: collapse persists; staff default to mine.)*
|
|
|
|
|
6. **Client support fields** — columns + encrypted password + reveal + Client 360 card + district/sector filters + contact types. *(Accept: reveal audited; password absent from all list payloads; saving without HQ_SECRET_KEY fails loudly.)*
|
|
|
|
|
7. **Import UI** — routes over existing importer + `/import` screen. *(Accept: commit refuses with problems; re-stage idempotent; series seeding preserved.)*
|
|
|
|
|
8. **Docs refresh** — STATUS/README/CLAUDE/D-number record.
|
|
|
|
|
|
|
|
|
|
**Total: 8 phases.** After this slice: the **Postgres switch**, then the real cutover.
|