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/STATUS.md

204 lines
15 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Build Status — 2026-07-10 (post-slice)
**54 screens across 2 surfaces** (46 back office + 8 POS), on 8 tested logic packages
(89 tests green), and a store-server **with a real data layer** (SQLite behind portable
repositories; Oracle 12c adapter slots in when the Classic DDL arrives — D12).
Product form: **web app** served by the store-server (D2) — back office at `/`,
POS at `/pos/`, printing via `/api/print`.
**The vertical slice is LIVE and verified end-to-end**: PIN login (server-side scrypt +
lockout) → item cache from DB → bill commit in one transaction (GST series allocation,
bill + lines, stock movements, dormant outbox row, audit entry) with **server-side
engine re-verification** (client and server must agree to the paisa) → bill visible in
Back Office Bills, stock derived correctly, audit trail written. Verified by driving
both UIs in a real browser: bill `ST1C2/26-000001`, ₹600.00.
## The foundation (packages/ — real, tested code, not wireframe)
| Package | What it does | Tests |
|---|---|---|
| `@sims/domain` | Tenancy (tenant→store→counter→user), integer-paise money + ₹ Indian grouping, UUIDv7 ids, Day-Begin business day + FY, per-counter GST doc series (16-char rule), GSTIN checksum validation, immutable document types, dormant outbox (D14) | ✅ |
| `@sims/billing-engine` | Dated GST rates, inclusive/exclusive prices, CGST/SGST vs IGST, cess, pro-rata bill discounts, rupee round-off — golden-tested to the paisa | ✅ |
| `@sims/auth` | PIN policy + scrypt hashing, lockout reducer, role→action matrix with supervisor-PIN gates, POS session bound to business date | ✅ |
| `@sims/scanning` | EAN-13 validation, weighing-scale (2x) barcodes, scan-box grammar (`3*` qty, code, search), wedge scanner vs human-typing detector | ✅ |
| `@sims/search-core` | Catalog search (pure TS, no deps): Indic phonetic folding (chawal≈chaval, paneer≈panir, jeera≈zeera), in-memory index, tiered ranking (whole-prefix → token-prefix → phonetic → substring) | ✅ |
| `@sims/printing` | ESC/POS byte builder, receipt renderer (42/32 col), drawer kick; **A4 GST invoice HTML + amount-in-words, EAN-13 label sheets, GS v 0 Indic raster (S2)** | ✅ |
| `@sims/config` | Settings hierarchy resolver (system→tenant→store→counter→user, effective-dated), message catalog with language fallback | ✅ |
| `@sims/ui` | Design system: light/dark themes, 6 accents, Inter + JetBrains Mono bundled, wireframe component kit, ThemeSwitcher | — |
## POS — 7 screens (`/pos/`) — FUNCTIONAL
| # | Screen | State |
|---|--------|-------|
| 1 | Login — user tiles + PIN pad | Functional (seed users) |
| 2 | Shift Open — denomination count → opening float | Functional |
| 3 | **Billing screen** — scan box, lines grid, totals, quick keys, status bar | **Functional end-to-end**: real engine, barcode/multiplier/scale-barcode entry, hold/resume, per-counter GST series, one-key cash close (F12/Numpad+) |
| 4 | Payment overlay (F11) — Cash/UPI/Card/Khata | Functional (modes recorded; QR/split later) |
| 5 | Bill history (F10) | Functional (day log) |
| 6 | Day-End summary — totals by payment mode | Functional (variance calc later) |
| 7 | Settings (Ctrl+,) — printer IP/port/width, test print, drawer kick | Functional (prints via store-server) |
Also live: customer attach (F6); server-backed history (F10); commit failures keep the
cart; **live search suggestions** (visible-before-add, ↑/↓+Enter); **F2 qty / F3 price
inline cell edits** (price>MRP blocked); **unknown-code quick-add** (draft item created
server-side, billable immediately). Verified: bill ST1C2/26-000002.
**P1 pass done (verified)**: tendered-cash change flash (type `100` F12 → CHANGE ₹20),
Ctrl+B repeat-last-bill (current prices, never resurrected), instant `*` multiplier with
qty chip, F4 line discount (`4` = ₹4, `5%` = percent; Disc column appears on demand),
audio feedback (ok/warn/err beeps), ok-notices auto-clear, stock chips in suggestions
(live counts, out-of-stock warn-not-block). Bill ST1C2/26-000003 proves the chain.
Purchase Entry P1: crash-proof localStorage draft with resume banner; glance strip on
cell focus (stock · sale · MRP · live margin · last-3 costs via /purchases/cost-history).
Still pending: returns flow, supervisor PIN overlay (needs /auth/verify-pin),
purchase schema P1s (free-qty, disc%, interstate split — one migration pass together),
history-R repeat, customer display, offline queue.
**Catalog scale (S1 + S6) — done 2026-07-10.** New `@sims/search-core` (pure TS, no
deps) replaces BillingScreen's linear name filter: `phoneticKey` folds the Indic
spelling variants the spec names (aa→a, ee→i, oo→u, w→v, ph→f, bh→b, chh→ch, sh→s,
th→t, dh→d, z→j, drop doubled letters), and `buildIndex`/`querySearch` rank matches in
tiers (whole-prefix → token-prefix → phonetic → substring, shorter name breaks ties).
The POS memoizes the index over its live item cache (grows with quick-adds); the ≤8-row
dropdown and arrow/Enter behaviour are unchanged. S6/R13: the silent `LIMIT 2000` in
`listItems` is gone — `GET /api/items?all=1` returns the whole non-inactive catalog for
the POS cache (which now fetches with `?all=1`), every other path paginates
(limit/offset, default 5000); `listBills`/`listAudit` and their routes paginate too
(default 100), and the back-office Bills and Audit pages grew a "Load more" button.
Verified 2026-07-10: 66/66 tests green (11 new folding/tier cases), `npm run typecheck`
clean on root + all three apps, all three apps build, and the store-server was rebuilt
and restarted — `/api/health` ok, `/api/items?all=1` returns the full 12-item catalog
with no cap, limit/offset confirmed on items/bills/audit, and a phonetic query
`ashirwad` resolves to `Aashirvaad Atta 5kg`. Integer paise, existing tests, and the
zero-runtime-dep rule are untouched.
**Print coverage (S2) — done 2026-07-10.** Three new pure modules in `@sims/printing`:
`invoice-html.ts` (`renderInvoiceHtml` → a self-contained, printable **A4 GST tax
invoice**: seller name/GSTIN/address header, buyer block, HSN-wise line table, a
CGST/SGST **or** IGST + cess breakup grouped by HSN+rate, round-off, payable, and
**amount-in-words** via a new `amountInWordsINR(paise)` domain util — integer paise,
Indian lakh/crore numbering, singular "Paisa" — plus a bilingual English/Hindi footer);
`labels.ts` (`ean13Svg` — standards-correct EAN-13 with L/G/R element patterns and
first-digit parity, checksum via `@sims/scanning`'s `ean13CheckDigit`; `renderLabelSheetHtml`
— a printable barcode/MRP label grid); and `raster.ts` (pure `packRasterRow` /
`bitonalFromRGBA` / `rasterToEscPos` for **GS v 0** 1-bit bit-image receipts). Back
office: **Bills rows are clickable** → the A4 invoice opens in a print-ready tab (Blob
URL, own "Print A4" button); **Catalog → Label Printing is now LIVE** (prints labels for
current items, columns + label size configurable); Purchase Entry offers "Print labels"
for the received lines after a post. POS: a **"Receipt script: ASCII / Raster (Indic)"**
setting (persisted in the printer cfg) routes the commit-time receipt through a
canvas→GS v 0 raster path (`apps/pos/src/raster.ts`) so Devanagari/Malayalam item names
print, sharing one `receiptLines` content model with the ASCII path. Verified 2026-07-10:
**97 tests green** (20 new S2 cases: amountInWordsINR 0/paisa/1/lakh/crore, invoice fields
+ amount-in-words + IGST switch, EAN-13 structure + checksum, label sheet, raster
bit-packing), typecheck clean on root + all three apps, all three apps build, store-server
rebuilt and restarted. Drove the back office in a real browser (thomas/sims): Bills → click
`ST1C2/26-000001` → A4 invoice rendered with **"Rupees Six Hundred Only"**, CGST/SGST
breakup and place-of-supply Kerala; Catalog → Label Printing → sheet rendered **7 real
EAN-13 SVG symbols**; and the raster canvas pipeline was exercised in-browser (384-px 2"
head, Indic glyphs drew 1,575 ink pixels via the OS font, GS v 0 header bytes correct).
**Honest caveats:** raster receipts are unit- and canvas-verified but the physical thermal
output needs a real 80 mm printer; canvas Indic glyphs fall back to the shop PC's OS fonts
(Windows ships Nirmala UI) — bundled canvas webfonts are a follow-up; the demo seed
barcodes carry placeholder check digits, so `ean13Svg` encodes stored 13-digit codes
as-is (a strict `normalizeEan13` validator is exported for real catalog input).
**B2B at the counter (S3) — done 2026-07-10.** A registered buyer's GSTIN now flows from
attach → engine → document. New pure `deriveSupply(customer, storeState)` in `@sims/domain`
is the single place-of-supply rule both surfaces run: no GSTIN → B2C, place of supply = the
store (CGST/SGST); a GSTIN → B2B, place of supply = the buyer's state (its first two digits,
or an explicit stateCode), inter-state ⇒ IGST. **Parties gained B2B identity**: `createCustomer`
takes an optional GSTIN, **validated server-side** with `validateGstin` (bad checksum/shape
rejected with E-1401/E-1402), the state code derived from the GSTIN when absent; the back
office **Customers page has a GSTIN column** and the POS **F6 attach modal has an optional
"GSTIN (B2B)" field with live checksum/state feedback**. **Billing is now place-of-supply
aware**: the POS `computeBill` ctx uses the attached customer's state (falls back to the
store), and `commitBill` **does not trust the client** — it re-reads the customer row, derives
place of supply itself, recomputes, and the paisa-agreement check (R5) stays. The **immutable
bill snapshots the buyer** (`buyer_gstin`, `place_of_supply` — added to the CREATE TABLE and
back-filled on the running dev DB by a PRAGMA-guarded `ALTER TABLE` migrate step in `db.ts`);
`listBills` returns them. **Documents follow**: the thermal receipt prints a buyer GSTIN line
(IGST rows on the existing inter-state path), the A4 invoice buyer block fills GSTIN +
place-of-supply from the bill (closing the S2 caveat), and the POS totals panel shows an
**"IGST · inter-state"** badge plus a B2B chip on the customer strip. Verified 2026-07-10:
**110 tests pass** (4 new: `deriveSupply` no-GSTIN/intra/inter, B2B receipt buyer line),
typecheck clean on root + all three apps, all three apps build, store-server rebuilt and
restarted (`/api/health` ok, migration added both columns to the 3-bill dev DB). Drove both
UIs in a real browser: POS login (Ramesh/4728, Skip count) → F6 → created **Anand Traders**
(phone 9876543210) with Maharashtra GSTIN **`27ABCDE1234F1Z0`** (body `27ABCDE1234F1Z` +
`gstinCheckDigit` = `0`) → the modal showed "place of supply state 27 — GSTIN valid", the
strip showed the B2B chip, the totals showed **IGST · inter-state** → billed Surf Excel
(GST ₹22.12 as IGST) → committed **ST1C2/26-000004**. Server row confirms `buyer_gstin`,
`place_of_supply='27'`, `igst_paise=2212`, `cgst=sgst=0`, payable ₹145.00 (client/server
agreed to the paisa). Back office → Bills → the row opened its A4 **Tax Invoice** with
**Billed To Anand Traders / GSTIN 27ABCDE1234F1Z0 / State 27 — Maharashtra**, Place of Supply
**"27 — Maharashtra"**, and an **IGST** tax row (no CGST/SGST); Customers shows the GSTIN column.
**Honest caveats:** the buyer GSTIN receipt line is unit-tested only (no physical printer here);
the seed store's state 32 resolves as "Kerala" in the invoice state map though it is named
T.Nagar — a seed-data quirk unrelated to S3; the e-Invoice/IRN (GSP) hook remains P2 (S3 stops
at the document, per doc 17 §S3). Unrelated pre-existing failure: `apps/hq/test/gmail.test.ts`
imports a not-yet-added `../src/gmail` (HQ-console in-flight work, untouched by S3).
## Purchase Entry — LIVE (the second crown jewel)
`/purchases/entry`: supplier type-ahead → invoice no/date → scan-grammar line entry
(barcode / code / name / `48*surf`) → qty→cost cell flow (Enter-advances) → last-cost
prefill + cost-changed price-revision prompt → printed-total cross-check → F12 post.
Server: transactional commit (stock IN, price/MRP updates + audit, duplicate-invoice
rejection, totals re-verification). Purchase List page live too. Verified end-to-end:
HD/2651 ₹6,988.83 posted, duplicate rejected, stock 118→166. Full P1/P2 backlog:
docs/13-SPEC-PURCHASE-ENTRY.md.
## Back office — 46 screens (`/`) — WIREFRAME (sample data)
Login (1) + Dashboard (1, custom) + 44 module pages:
| Module | Pages | Count |
|--------|-------|------:|
| Catalog | Items · Categories · Price Lists · Schemes & Offers · Label Printing | 5 |
| Sales | Bills · Returns & Credit Notes · Customers · Khata & Collections · Loyalty | 5 |
| Purchases | Purchase List · Purchase Entry · **Purchase Inbox** (custom split-review) · Suppliers · GSTR-2B Reconciliation | 5 |
| Inventory | Stock · Adjustments · Transfers · Stock Take · Expiry Dashboard | 5 |
| Accounting | Vouchers · Ledgers · Day Book · P&L/Balance Sheet · Tally Export | 5 |
| GST | GSTR-1 · GSTR-3B · e-Invoice Queue · e-Way Bills · HSN Summary | 5 |
| Reports | Sales Reports · Margins · Stock Aging · Day-End Digest | 4 |
| Admin | **Users & Roles** (custom matrix) · Stores & Counters · **Settings** (custom hierarchy) · Print Templates · Integrations · Subscription & Plan · Audit Log · Sync & Devices · Backups · Data Import | 10 |
**6 pages are now LIVE on real data**: Items (list + search + New Item — billable at the
counter immediately), Customers, Bills (rows open the printable A4 GST invoice — S2),
Stock (derived from movements), Audit Log, and Label Printing (barcode/MRP sheets — S2).
Login is real (server-verified). 35 pages render from the data-driven registry; 4 are
custom wireframes (Dashboard, Purchase Inbox, Settings hierarchy, Users & Roles).
Edition-locked pages render as upsells; everything themed and role/edition-aware.
## Store-server (`apps/store-server`) — RUNNING SERVICE with DATA LAYER
Serves both web apps + `POST /api/print` + `GET /api/health`, and now the data API:
`/api/auth/pin`, `/api/auth/login` (scrypt + lockout), `/api/bootstrap`, `/api/items`
(GET/POST), `/api/parties` (GET/POST), `/api/bills` (GET/POST with engine
re-verification), `/api/stock`, `/api/audit`. SQLite (WAL) at `apps/store-server/data/`
behind portable repositories (`src/repos.ts`) — **the Oracle 12c adapter reimplements
those signatures when the Classic DDL arrives; nothing above them changes.**
Port 5181 (808085 are Windows-reserved).
## Not built yet (per roadmap)
- Store-server data layer: Oracle repository, real auth, item/bill APIs → needs the
**Classic Oracle DDL** (→ docs/12-DATA-MODEL.md, to be written once the DDL arrives)
- Owner mobile app (Phase 3) · Customer display · HQ console (doc 11): ops slice now spec'd to **start early** as `apps/hq` (docs/14-SPEC-HQ-CONSOLE.md, D15); fleet half still waits for the cloud tier (D14)
- Purchase Inbox backend (AI parsing) · e-Invoice/e-Way GSP · WhatsApp/UPI integrations
- Sync agent + cloud tier (D14: deliberately later) · Classic migration tool
- Onboarding wizard · i18n content (mechanism exists, Hindi/Malayalam strings pending)
## Decisions: resolved vs open
Resolved: D2 web-first · D5 DB-driven plans/prices · D8 Android later · D12 Oracle 12c
for now · D14 local-first-then-cloud · D15 HQ ops console early, in-monorepo.
**Open: D1 product name · D4 GSP vendor · D5 ₹ amounts · D11 builder headcount · D13 helpdesk.**
## How to run
```
cd apps/store-server && npm start # http://localhost:5181 (/ and /pos/)
npm test && npm run typecheck # from repo root
```