+ 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
document/payment types (quotation, proforma, credit note; NEFT/cheque/UPI modes) — do not
push those into the shared packages.
## Non-negotiable rules
@ -36,21 +36,21 @@ 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` |
| 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 (372 tests) |
| 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 (377 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
# 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
@ -60,19 +60,19 @@ 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.
| `reminder_schedule` | Dated reminder cadence + follow-up message text per rule kind —`effective_from`/`_to` window, CSV day offsets (a cadence change is a new dated row) |
| `reminder_schedule` | Dated reminder cadence + follow-up message text per rule kind —`effective_from`/`_to` window, CSV day offsets (a cadence change is a new dated row) |
| `/reports` | `Reports` | Dues aging, module revenue, client profitability, and an AWS cost bar chart by month |
| `/documents` | `Documents` | Register of every document, newest first: type chips + status filter, honest server pagination with true totals, one-click confirmed **Convert & send** on issued proformas |
| `/documents/new` | `NewDocument` | Quotation-in-minutes composer: client type-ahead, line editor, server-computed GST, due-date field on invoices (auto-filled from terms, editable), and a live PDF-fidelity preview (desktop split view / mobile sheet) |
| `/documents/:id` | `DocumentView` | Document page: PDF preview, state-driven action bar (issue / send / mark / convert / cancel / credit note / record payment), share & download group (copy link / WhatsApp / native share / revoke), event timeline and email log |