Initial scaffold for AvtoAmbor parts inventory

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>
This commit is contained in:
David Beccue
2026-05-16 07:05:24 +05:00
commit 05be5b03aa
37 changed files with 4617 additions and 0 deletions

56
src/lib/server/schema.sql Normal file
View File

@ -0,0 +1,56 @@
-- AvtoAmbor schema. Money is stored as INTEGER dirams (1 TJS = 100 dirams).
-- Translated fields use _en / _tg suffixes.
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name_en TEXT NOT NULL,
name_tg TEXT NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS suppliers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT,
address TEXT,
notes TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS parts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sku TEXT NOT NULL UNIQUE,
name_en TEXT NOT NULL,
name_tg TEXT NOT NULL,
description_en TEXT,
description_tg TEXT,
category_id INTEGER REFERENCES categories(id) ON DELETE SET NULL,
unit TEXT NOT NULL DEFAULT 'pcs',
cost_price INTEGER NOT NULL DEFAULT 0, -- dirams
sale_price INTEGER NOT NULL DEFAULT 0, -- dirams
quantity_on_hand INTEGER NOT NULL DEFAULT 0,
reorder_level INTEGER NOT NULL DEFAULT 0,
location TEXT,
barcode TEXT,
active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS stock_movements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
part_id INTEGER NOT NULL REFERENCES parts(id) ON DELETE CASCADE,
movement_type TEXT NOT NULL CHECK(movement_type IN ('in','out','adjust')),
quantity INTEGER NOT NULL, -- positive for in/adjust-up, negative for out/adjust-down
unit_price INTEGER, -- dirams; nullable for adjustments
supplier_id INTEGER REFERENCES suppliers(id) ON DELETE SET NULL,
reference TEXT,
notes TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_parts_sku ON parts(sku);
CREATE INDEX IF NOT EXISTS idx_parts_barcode ON parts(barcode);
CREATE INDEX IF NOT EXISTS idx_parts_category ON parts(category_id);
CREATE INDEX IF NOT EXISTS idx_movements_part ON stock_movements(part_id);
CREATE INDEX IF NOT EXISTS idx_movements_created ON stock_movements(created_at);