SvelteKit 2 + Svelte 4 + adapter-node, SQLite via better-sqlite3 (WAL, foreign keys on). Bilingual EN/Тоҷикӣ throughout, locale persisted in localStorage. Pages: dashboard (totals, low stock, recent movements), parts list with search and sort, part create/edit, record movement (in/out/adjust with smart unit-price and adjust-quantity prefill), suppliers list with inline add. Schema: categories, suppliers, parts (with _en/_tg name+description columns, dirams for money), stock_movements with check on movement_type. On-hand updates are done in JS inside a transaction with the movement insert. Dockerized dev: docker compose, named project, bind-mounted data/ for DB persistence. Seed contains 6 categories, 4 suppliers, 31 realistic parts (Lada / Nexia / Opel / Toyota bias). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
168 lines
6.2 KiB
Plaintext
168 lines
6.2 KiB
Plaintext
|
||
# AvtoAmbor — Auto Parts Inventory System (v1 scaffold)
|
||
|
||
## Context
|
||
Build a simple, single-user inventory system named "AvtoAmbor" for an auto parts
|
||
store in Tajikistan. Development is on Linux inside Docker; the production target
|
||
is a Windows machine where the owner will access it via browser on localhost.
|
||
v1 is PARTS ONLY — no service jobs, customers, or invoicing.
|
||
|
||
Do not write tests, lint config, or CI in this pass. Keep the code idiomatic,
|
||
lightly commented, and not over-engineered.
|
||
|
||
## Tech stack (strict)
|
||
- Node.js 20 LTS, run via Docker
|
||
- SQLite via `better-sqlite3`
|
||
- SvelteKit 2.x pinned to **Svelte 4** — `"svelte": "^4.2.0"` in package.json.
|
||
Use Svelte 4 syntax only: no runes, no `$state`/`$derived`/`$effect`.
|
||
- `@sveltejs/adapter-node` for production (so deployment to Windows is just
|
||
`node build/index.js`)
|
||
- Plain CSS. No Tailwind, no component library.
|
||
|
||
## Repo layout
|
||
avtoambor/
|
||
Makefile
|
||
Dockerfile
|
||
docker-compose.yml
|
||
.dockerignore
|
||
.gitignore
|
||
README.md
|
||
package.json
|
||
svelte.config.js
|
||
vite.config.js
|
||
src/
|
||
app.html
|
||
hooks.server.js # opens db on startup
|
||
lib/
|
||
server/
|
||
db.js # better-sqlite3, WAL mode, foreign keys on
|
||
schema.sql
|
||
seed.sql
|
||
parts.js # CRUD helpers
|
||
movements.js
|
||
suppliers.js
|
||
i18n/
|
||
en.json
|
||
tg.json
|
||
store.js # locale store + t(key) helper
|
||
components/
|
||
Header.svelte # "AvtoAmbor" wordmark + EN/Тоҷ toggle
|
||
routes/
|
||
+layout.svelte # renders <Header/>
|
||
+page.svelte # dashboard
|
||
parts/
|
||
+page.svelte # list, search, sort
|
||
+page.server.js
|
||
new/
|
||
+page.svelte
|
||
+page.server.js
|
||
[id]/
|
||
+page.svelte # edit + recent movements
|
||
+page.server.js
|
||
movements/
|
||
new/
|
||
+page.svelte
|
||
+page.server.js
|
||
suppliers/
|
||
+page.svelte
|
||
+page.server.js
|
||
data/ # gitignored; holds avtoambor.db
|
||
scripts/
|
||
init-db.js # reads schema.sql + seed.sql, writes data/avtoambor.db
|
||
|
||
## Database schema (initial guess — we will iterate)
|
||
- All translated fields use `_en` and `_tg` suffixes.
|
||
- Money stored as INTEGER dirams (1 TJS = 100 dirams).
|
||
- Timestamps as ISO 8601 TEXT (`datetime('now')`).
|
||
|
||
Tables:
|
||
- categories(id PK, name_en, name_tg, sort_order)
|
||
- suppliers(id PK, name, phone, address, notes, created_at)
|
||
- parts(id PK, sku UNIQUE NOT NULL, name_en, name_tg,
|
||
description_en, description_tg, category_id FK,
|
||
unit TEXT, cost_price INT, sale_price INT,
|
||
quantity_on_hand INT DEFAULT 0, reorder_level INT DEFAULT 0,
|
||
location TEXT, barcode TEXT, active INT DEFAULT 1,
|
||
created_at, updated_at)
|
||
- stock_movements(id PK, part_id FK, movement_type
|
||
CHECK(movement_type IN ('in','out','adjust')),
|
||
quantity INT, unit_price INT,
|
||
supplier_id FK NULL, reference TEXT, notes TEXT,
|
||
created_at)
|
||
|
||
Indexes on parts.sku, parts.barcode, parts.category_id, stock_movements.part_id.
|
||
|
||
Update `parts.quantity_on_hand` in application code (in a transaction with
|
||
the movement insert), not via trigger — clearer for future-me.
|
||
|
||
## Seed data
|
||
- 5–6 categories: Filters, Brakes, Engine, Electrical, Fluids, Belts & Hoses
|
||
- 3–4 suppliers with realistic names
|
||
- 25–30 realistic auto parts with EN and Tajik (Cyrillic) names.
|
||
Bias toward parts common for Lada, Daewoo Nexia, Opel, and Toyota,
|
||
which are common in Tajikistan. Use realistic somoni prices.
|
||
|
||
## UI
|
||
- `Header.svelte` (in `+layout.svelte`, every page):
|
||
- Left: "AvtoAmbor" wordmark
|
||
- Right: language toggle showing the *other* language (click EN → switches to
|
||
Tajik). Persist choice to `localStorage` under key `avtoambor.locale`.
|
||
- Pages for v1:
|
||
- `/` dashboard: total SKUs, count of parts at/below reorder level,
|
||
total inventory value at cost
|
||
- `/parts` searchable + sortable list
|
||
- `/parts/new` create
|
||
- `/parts/[id]` edit, with recent movements panel
|
||
- `/movements/new` record in/out/adjust
|
||
- `/suppliers` list + add inline
|
||
- Every visible string goes through the i18n helper. Missing keys fall back to
|
||
English and log a `console.warn` once per missing key.
|
||
GIve it a Tajik look & feel, if that's even possible for such a simple app
|
||
|
||
## i18n
|
||
- `en.json` and `tg.json` with nested keys (e.g. `nav.parts`, `parts.sku`)
|
||
- `store.js` exports a writable `locale` store and a derived `t` function:
|
||
`$t('parts.sku')` in templates
|
||
- Default locale is `'tg'`. On mount in the layout, read `localStorage` and
|
||
hydrate if present.
|
||
|
||
## Makefile
|
||
Targets (use `docker compose`). First target = `help`.
|
||
- `help` — print a friendly banner + list of targets with descriptions
|
||
- `install` — `docker compose run --rm app npm install`
|
||
- `run` — `docker compose up` (dev server on 5173)
|
||
- `build` — production build via adapter-node into `build/`
|
||
- `db-init` — run `scripts/init-db.js`, skip if `data/avtoambor.db` exists
|
||
- `db-reset` — confirm prompt, then delete and recreate the db
|
||
- `docker-build` — build the image
|
||
- `docker-shell` — interactive bash in the container
|
||
- `clean` — remove `node_modules`, `build/`, but keep `data/`
|
||
- `clean-all` — also wipe `data/`
|
||
|
||
Use `@` to suppress command echo where it would be noise.
|
||
|
||
## Dockerfile
|
||
- `node:20-bookworm-slim`
|
||
- Install `python3 make g++` for the `better-sqlite3` native build
|
||
- Non-root user
|
||
- WORKDIR `/app`
|
||
- EXPOSE 5173 and 3000
|
||
|
||
## docker-compose.yml
|
||
- One service `app`
|
||
- Bind-mount the repo to `/app`
|
||
- Named volume for `node_modules` so it doesn't shadow the host
|
||
- Bind-mount `./data` so the SQLite file persists on the host
|
||
- Map 5173:5173 and 3000:3000
|
||
|
||
## README.md
|
||
Short: what it is, prerequisites (Docker), quickstart
|
||
(`make install && make db-init && make run`), and a one-liner on production:
|
||
`make build` then `node build/index.js` on the Windows host.
|
||
|
||
## Deliverables for this pass
|
||
1. Generate every file above, working out of the box.
|
||
2. Print the resulting file tree.
|
||
3. Print the exact command sequence to bring it up from a fresh clone.
|
||
4. Call out anything you guessed at that I should review before we move on.
|