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:
46
src/routes/parts/new/+page.server.js
Normal file
46
src/routes/parts/new/+page.server.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import { createPart, getPartBySku, listCategories } from '$lib/server/parts.js';
|
||||
import { recordMovement } from '$lib/server/movements.js';
|
||||
|
||||
export function load() {
|
||||
return { categories: listCategories() };
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
default: async ({ request }) => {
|
||||
const form = await request.formData();
|
||||
const data = Object.fromEntries(form);
|
||||
const errors = validate(data);
|
||||
if (errors) return fail(400, { errors, values: data });
|
||||
|
||||
if (getPartBySku(data.sku.trim())) {
|
||||
return fail(400, { errors: { sku: 'parts.errors.sku_taken' }, values: data });
|
||||
}
|
||||
|
||||
// Save the part with quantity 0, then record an opening "in" movement
|
||||
// if the user supplied an initial quantity. This keeps quantity changes
|
||||
// funneled exclusively through stock_movements.
|
||||
const initialQty = Number(data.quantity_on_hand || 0);
|
||||
const id = createPart({ ...data, quantity_on_hand: 0 });
|
||||
if (initialQty > 0) {
|
||||
recordMovement({
|
||||
part_id: id,
|
||||
movement_type: 'in',
|
||||
quantity: initialQty,
|
||||
unit_price: data.cost_price,
|
||||
reference: 'OPENING'
|
||||
});
|
||||
}
|
||||
|
||||
throw redirect(303, `/parts/${id}`);
|
||||
}
|
||||
};
|
||||
|
||||
function validate(d) {
|
||||
const errors = {};
|
||||
if (!d.sku || !d.sku.trim()) errors.sku = 'parts.errors.sku_required';
|
||||
if ((!d.name_en || !d.name_en.trim()) && (!d.name_tg || !d.name_tg.trim())) {
|
||||
errors.name = 'parts.errors.name_required';
|
||||
}
|
||||
return Object.keys(errors).length ? errors : null;
|
||||
}
|
||||
Reference in New Issue
Block a user