You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sims-hq/CLAUDE.md

73 lines
5.9 KiB
Markdown

# 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.