Deleting a part used to be impossible. Hard delete would cascade
stock_movements (FK ON DELETE CASCADE) and orphan invoice_lines, losing
the audit trail. Instead, the part detail page now has a Delete button
that flips active=0; listParts and categoriesWithParts filter on active,
but historical joins (recentMovements, linesFor, topSellingParts) stay
unfiltered so old movements and invoices still render the part name.
The existing active checkbox on the detail page doubles as a reactivate
switch.
SKU, location, and description fields are removed from every UI surface
(forms, /parts table, dashboard, movement/invoice pickers, invoice line
labels, top-sellers report). None were load-bearing — barcode + name +
category already cover lookup. The SKU column is kept in the DB
(NOT NULL UNIQUE) and auto-stamped server-side as `SKU-{id}` after
insert, so the change is reversible without a migration. updatePart no
longer writes SKU, freezing it after creation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
42 lines
1.3 KiB
JavaScript
42 lines
1.3 KiB
JavaScript
import { fail, redirect } from '@sveltejs/kit';
|
|
import { createPart, 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 });
|
|
|
|
// 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.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;
|
|
}
|