5.9 KiB
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 / Clients / ClientDetail / Documents / Modules / Reports |
| 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-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 (177 tests) |
Running it
npm install # workspace root, once
# backend (serves API on :5182, and the built web UI + public /share links)
cd apps/hq && npm start # build-server.mjs → dist/server.cjs; first boot 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:
HQ_DATA_DIR(DB location),SIMS_PIN_PEPPER(set once, never change),HQ_SECRET_KEY(before Gmail),GOOGLE_*(Gmail),AWS_*(Cost Explorer),HQ_PORT(default 5182). - Health check:
GET /api/health→{ ok: true }. - Verified state:
npm install+ typecheck clean, 177 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.