# CLAUDE.md — HQ Ops Console This repo (`C:/SiMS/hq`) is the **internal console for running our own software business** — NOT software we ship to clients. It manages our ~300 existing Classic clients: client book, sellable modules with a dated price book, documents (quotation / proforma / invoice / credit note) on editable letterhead templates with PDF rendering and shareable public links, call/interaction tracking, AMC contracts, AWS cost recovery, payments + allocations, recurring plans, and reminders emailed via Gmail with bounce handling — plus reports, a dashboard, an append-only audit log, an APEX importer, and a scheduler. > **The Store/POS retail product is a SEPARATE repo.** It is local-first software installed > in shops. Do **not** add Store/POS/counter/retail features here, and do not reference them. > This app is vendor-side cloud only (the "HQ Console" of doc 11, started early per D15). ## Layout | Part | Where | Notes | |---|---|---| | Backend | `apps/hq/src` | Express over `better-sqlite3`, DB at `data/hq.db` (WAL) | | Frontend | `apps/hq-web/src` | React + Vite; pages Dashboard (My Day) / Pipeline / Reminders / Clients / ClientDetail / Documents / NewDocument / DocumentView / Modules / Reports / Employees (owner-only) / Profile / APEX Import (owner-only) / Settings | | Repos | `apps/hq/src/repos-*.ts` | one file per domain; plain functions over a `DB` handle | | Schema | `apps/hq/src/db.ts` + `apps/hq/schema.sql` | `CREATE TABLE IF NOT EXISTS` + additive `migrate()` | | Shared packages | `packages/*` | trimmed forks — see below | **Shared packages** (generic on purpose — keep them so): `@sims/domain` (`ids`/UUIDv7, `money`/paise, `business-day`, `doc-series`, `gstin`, `documents`), `@sims/auth` (scrypt PIN + lockout), `@sims/billing-engine` (`compute`, `tax`), `@sims/ui`. HQ owns its own document/payment types (quotation, proforma, credit note; NEFT/cheque/UPI modes) — do not push those into the shared packages. ## Non-negotiable rules These are the house hard rules (`docs/16-PROJECT-RULES.md`, `docs/07-DB-AND-CONFIG.md`) distilled to what applies to HQ. A change that breaks one does not land. | # | Rule | In this repo | |---|---|---| | Money is integer paise | Every amount is `Paise` (integer). Fractional-rupee floats never enter the domain; convert at the edge with `fromRupees`, format with `formatINR`. Every rounding step is explicit. | all `*_paise` columns; `@sims/domain/money` | | One billing engine, re-verified | GST is computed **only** through `@sims/billing-engine` `computeBill` — save and preview run the same path so they cannot drift. The server recomputes to the paisa; a mismatch is rejected. | `repos-documents.ts` | | Config over code, dated | Anything that varies — prices, tax rates/slabs, settings, doc-series prefixes, SAC codes, templates, message text — is a **DB row resolved by business date**, never a constant in code. A price or rate change is a new dated row, not a release. | `module_price_book`, `tax_class` (`effective_from`/`_to`), `doc_series`, `setting`, `template`, `reminder_schedule` (reminder cadence + follow-up text, resolved by `resolveSchedule`), `reminder-templates` | | Documents immutable once issued | Issuing assigns the number; after that the document is never edited or deleted. Corrections are **credit notes** (own per-FY series, links the invoice via `ref_doc_id`, CGST Act s.34). Cancel keeps the number consumed; only issued, unpaid, unallocated docs may cancel. QT→PI→INV convert by carrying lines forward into a new draft. | `repos-documents.ts` — `issueDocument`, `raiseCreditNote`, `cancelDocument`, `convertDocument` | | Portable-repo pattern | All data access is plain exported functions taking the `DB` handle first arg — no ORM, no repo classes, no ambient singletons. Multi-write operations wrap in `db.transaction(...)`. Keep SQL portable (SQLite today, **Postgres is the locked production engine** — D15); avoid engine-specific SQL. | every `repos-*.ts` | | Append-only audit | Every mutation calls `writeAudit(db, userId, action, entity, id, before, after)` in the **same transaction** as the change. The audit log is append-only — never update or delete rows. Previews and the public share route write nothing. | `audit.ts` | | Client id is the future tenant id | `client.id` is a client-generated UUIDv7 that becomes `tenant_id` when the cloud tier stands up — same rows, promoted once. Never re-key clients. | `repos-clients.ts` | | No silent caps | Every list that can grow paginates or warns; never silently truncate. | reports, directory queries | | Strict TS, green before landing | Strict TypeScript everywhere; `npm run typecheck` and `npm test` both clean before anything lands. Money math is locked by tests — a rounding change without a test change does not land. | `tsc`, vitest (346 tests) | ## Running it ``` npm install # workspace root, once # backend (serves API on :5182, and the built web UI + public /share links) # RUN FROM THE REPO ROOT — the DB path resolves to ./data/hq.db from CWD, so # `cd apps/hq && npm start` would boot a fresh empty DB in apps/hq/data instead. npm run build --workspace @sims/hq node apps/hq/dist/server.cjs # first boot on an empty data/ prints a one-time owner password # frontend dev (Vite on :5183, proxies /api and /share to :5182) cd apps/hq-web && npm run dev ``` - Owner login on first boot: `admin@tecnostac.com` + the one-time password printed in the server log (change it immediately). The "Gmail disconnected" banner is expected until Gmail is connected. - Env: `DATABASE_URL` (set → Postgres engine, absent → SQLite at `HQ_DATA_DIR`; D19), `SIMS_PIN_PEPPER` (set once, never change), `HQ_SECRET_KEY` (before Gmail), `GOOGLE_*` (Gmail), `AWS_*` (Cost Explorer), `HQ_PORT` (default 5182). - Postgres integration tests: `HQ_PG_TEST_URL=postgres://hq:hq_dev@localhost:5432/hq_test npm test` (wipes that database each run — never point it at real data). - Health check: `GET /api/health` → `{ ok: true }`. - Verified state: `npm install` + typecheck clean, 346 tests pass. Exercise real flows end to end before claiming done. ## Docs (kept in `docs/`) `14-SPEC-HQ-CONSOLE.md` (the HQ spec — read first), `11-ADMIN-SUPPORT-CONSOLE.md` (the console this grows into), `16-PROJECT-RULES.md` (hard rules), `07-DB-AND-CONFIG.md` (config-over-code + DB strategy), `06-DECISIONS.md` (every decision has a D-number — add one when you decide something), `DEPLOY-HQ.md` (go-live runbook). Follow **The Tables Rule**: anything that can be a table IS a table, in docs and UI.