A full-stack inventory operations console for receiving, dispatching, transferring, and reconciling stock across locations without losing the audit trail.
Open Stockroom Ledger — a public sandbox with seeded inventory data. Its temporary database can reset when the Vercel container scales down; the Docker deployment below uses a persistent volume.
Inventory demos are often CRUD tables with no meaningful consistency rules. Stockroom Ledger focuses on the failure cases that matter when quantities represent real goods:
- atomic multi-location transfers—both balances change or neither does;
- idempotency keys so retried requests do not duplicate a movement;
- optimistic version checks that reject writes from stale browser state;
- audited cycle counts that preserve expected quantity, counted quantity, and signed variance;
- non-negative stock constraints and bounded request validation;
- an append-only movement ledger with actor, reason, route, and timestamp;
- integer currency and quantity storage to avoid rounding ambiguity;
- seeded data so the workflow can be evaluated immediately.
The interface makes these guarantees visible in the transaction panel and shows each location's current revision beside its balance.
docker compose up --build -d
curl -f http://127.0.0.1:8000/healthzOpen http://127.0.0.1:8000. The SQLite database is stored in the stockroom-data volume.
The container runs as UID 1001 with a read-only root filesystem, dropped Linux capabilities, no-new-privileges, a health check, and explicit CPU, memory, and process limits.
Interactive OpenAPI documentation is available at /docs.
| Method | Route | Purpose |
|---|---|---|
GET |
/api/dashboard |
Read metrics, balances, locations, movements, and cycle counts |
POST |
/api/movements |
Commit a receipt, dispatch, or transfer |
POST |
/api/cycle-counts |
Reconcile a physical count against a versioned balance |
GET |
/healthz |
Container and process health probe |
Every inventory mutation requires an Idempotency-Key header. A repeated key returns the original result with replayed: true and does not change inventory twice.
curl -X POST http://127.0.0.1:8000/api/movements \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: receiving-note-2048' \
-d '{
"kind": "transfer",
"productId": "prd-lamp",
"fromLocationId": "loc-north",
"toLocationId": "loc-retail",
"quantity": 5,
"reason": "Rebalance retail display stock",
"actor": "Hassan Ak",
"expectedSourceVersion": 1,
"expectedDestinationVersion": 1
}'The repository keeps UI, HTTP, and persistence concerns separate:
React + TypeScript
│ same-origin JSON
▼
FastAPI routes + Pydantic contracts
│
▼
transaction service ── idempotency / version / stock / count checks
│
▼
SQLite WAL ── product / location / balance / movement / cycle count
FastAPI serves the production frontend from the same origin, so the deployed application needs no permissive CORS policy. SQLite writes use BEGIN IMMEDIATE to serialize inventory mutations before validation and commit.
Requirements: Python 3.12+, Node.js 24+, and pnpm 11+.
# API
cd backend
python -m venv .venv
.venv/Scripts/python -m pip install -e ".[dev]" # Windows
.venv/Scripts/uvicorn stockroom.main:app --reload
# UI, in another terminal
cd frontend
pnpm install
pnpm devRun the quality gates:
cd backend && ruff check src tests && ruff format --check src tests && pytest
cd frontend && pnpm lint && pnpm test && pnpm build
docker build -t stockroom-ledger:test .Backend tests cover the seeded dashboard, atomic transfers, idempotent retries, cycle-count reconciliation, stale-count rollback, insufficient stock, validation, and security headers. Frontend tests cover rendering plus complete versioned movement and count requests.
Back up the stockroom-data volume before upgrading storage code. Releases are immutable tags, and the Compose file pins the application version.
To roll back, check out the previous release, rebuild, and verify the health endpoint before resuming writes:
git checkout v1.0.0
docker compose up --build -d --force-recreate
curl -f http://127.0.0.1:8000/healthzMIT © Hassan Ak
