Lagerorte per 10-Zeichen-Code statt fortlaufender ID; iOS-Einlagern ohne Kamerazwang

Lagerort-IDs sind jetzt ein zufaelliger 10-Zeichen-Code (wie die Einzelstueck-UIDs) statt einer fortlaufenden Zahl - so kollidiert die Stammdaten-Sicherung zwischen zwei Instanzen praktisch nie mehr, und der Code ist zugleich der Inhalt des QR /l/<code>. Alle Fremdschluessel (lots, movements, items, Mindestbestaende, parent_id) ziehen mit; die Umstellung laeuft einmalig und transaktional beim Serverstart (_migrate_locations_to_code) und rollt bei Fehlern komplett zurueck. Vor dem Deploy ein DB-Backup machen.

iOS-Einlagern oeffnet nicht mehr sofort die Kamera, sondern ein Formular mit Artikelsuche; die Kamera kommt erst per Button. Im Formular laesst sich der Lagerort zusaetzlich per /l/-QR scannen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-27 12:15:25 +02:00
parent 6e48cd0a7a
commit d518b4aa23
23 changed files with 507 additions and 220 deletions

View File

@@ -100,7 +100,7 @@ export default function Einzelstuecke({ product, locations, shops, isAdmin, onCh
const cents = centsFrom(addForm.price);
const created = await api.createItems(product.id, {
count: Number(addForm.count) || 1,
location_id: addForm.location_id === "" ? null : Number(addForm.location_id),
location_id: addForm.location_id === "" ? null : addForm.location_id,
shop_id: shopId,
acquired_on: addForm.acquired_on || null,
warranty_until: addForm.warranty_until || null,
@@ -269,7 +269,7 @@ export default function Einzelstuecke({ product, locations, shops, isAdmin, onCh
<td data-label="Lagerort">
{isAdmin ? (
<select value={it.location_id ?? ""} style={{ marginTop: 0, minWidth: 120 }}
onChange={(e) => patchItem(it, { location_id: e.target.value === "" ? null : Number(e.target.value) })}>
onChange={(e) => patchItem(it, { location_id: e.target.value === "" ? null : e.target.value })}>
<option value=""> ohne </option>
{locations.map((l) => <option key={l.id} value={l.id}>{l.name}</option>)}
</select>

View File

@@ -35,7 +35,7 @@ export default function LocationMinStock({ locations, initial = [], unitLabel =
try {
const list = rows
.filter((r) => r.location_id !== "" && Number(r.min_stock) > 0)
.map((r) => ({ location_id: Number(r.location_id), min_stock: Number(r.min_stock) }));
.map((r) => ({ location_id: r.location_id, min_stock: Number(r.min_stock) }));
await onSave(list);
setOk(true);
} catch (err) {

View File

@@ -56,7 +56,7 @@ export default function ObjektBestand({ product, lots, locations, isAdmin, onCha
product_id: product.id,
quantity: menge,
unit: einheit,
location_id: addForm.location_id === "" ? null : Number(addForm.location_id),
location_id: addForm.location_id === "" ? null : addForm.location_id,
});
setAddForm({ location_id: addForm.location_id, quantity: "" });
await nachAktion();
@@ -93,8 +93,8 @@ export default function ObjektBestand({ product, lots, locations, isAdmin, onCha
await api.relocateStock({
product_id: product.id,
quantity: menge,
from_location_id: move.from === "" ? null : Number(move.from),
to_location_id: move.to === "" ? null : Number(move.to),
from_location_id: move.from === "" ? null : move.from,
to_location_id: move.to === "" ? null : move.to,
});
setMove(null);
toast("Umgelagert.");
@@ -110,7 +110,7 @@ export default function ObjektBestand({ product, lots, locations, isAdmin, onCha
await api.removeStock({
product_id: product.id,
quantity: menge,
location_id: remove.location_id === "" ? null : Number(remove.location_id),
location_id: remove.location_id === "" ? null : remove.location_id,
reason: remove.reason,
note: remove.note || null,
});