Warenwirtschaft: AP-51 — Lagerplatz-Felder für Produkte & Verpackung

Lagerplatz-Zuordnung (Raum/Regal/Fach) gab es bisher nur bei Rohstoffen. Jetzt
dieselbe Storage-Hierarchie (StorageRoom/Shelf/Slot) auch für Produkte und
Verpackungsartikel, inkl. Anzeige an den passenden Stellen.

- Migration: storage_room_id/shelf/slot an products + packaging_items
  (nullable, FK constrained, nullOnDelete).
- Models Product + PackagingItem: fillable + storageRoom/Shelf/Slot-Relationen +
  storageLabel() („Raum 1 | Regal 3 | A").
- Produktformular (Inventar-Abschnitt) + Verpackungsformular: Auswahl-Zeile
  Raum/Regal/Fach; Optionen kommen aktiv/sortiert aus den zentral pflegbaren
  Bausteinen (Einstellungen → Allgemein).
- Speichern: ProductRepository normalisiert leere Auswahl -> null (FK);
  PackagingItem-Requests + Repository um die drei Felder erweitert.
- Anzeige: Produktbestand (Sub-Zeile mit Marker), Produktverpackung-Übersicht
  (neue Spalte „Lagerplatz").
- DeletionGuard: Lagerbaustein ist nicht löschbar, solange Produkte/Verpackung
  darauf verweisen (analog Rohstoffe).

Tests: 4 neue (Produkt speichern + leer->null, Verpackung speichern + Anzeige,
Löschsperre, freier Baustein löschbar). Migration auf Dev-/Live-DB gelaufen.
Suite 331 grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-08-28 09:46:40 +00:00
parent eb47c8a82d
commit 30d4e28696
16 changed files with 363 additions and 5 deletions

View file

@ -26,6 +26,9 @@ class StorePackagingItemRequest extends FormRequest
'min_stock_alert' => ['nullable', 'integer', 'min:0'],
'url' => ['nullable', 'string', 'url:http,https', 'max:2048'],
'product_id' => ['nullable', 'integer', 'exists:products,id'],
'storage_room_id' => ['nullable', 'integer', 'exists:storage_rooms,id'],
'storage_shelf_id' => ['nullable', 'integer', 'exists:storage_shelves,id'],
'storage_slot_id' => ['nullable', 'integer', 'exists:storage_slots,id'],
'active' => ['sometimes', 'boolean'],
];
}
@ -38,7 +41,7 @@ class StorePackagingItemRequest extends FormRequest
'weight_grams' => $this->filled('weight_grams') ? reFormatWawiNumber($this->input('weight_grams')) : null,
]);
foreach (['supplier_id', 'product_id', 'min_stock_alert'] as $key) {
foreach (['supplier_id', 'product_id', 'min_stock_alert', 'storage_room_id', 'storage_shelf_id', 'storage_slot_id'] as $key) {
if ($this->input($key) === '' || $this->input($key) === null) {
$this->merge([$key => null]);
}

View file

@ -26,6 +26,9 @@ class UpdatePackagingItemRequest extends FormRequest
'min_stock_alert' => ['nullable', 'integer', 'min:0'],
'url' => ['nullable', 'string', 'url:http,https', 'max:2048'],
'product_id' => ['nullable', 'integer', 'exists:products,id'],
'storage_room_id' => ['nullable', 'integer', 'exists:storage_rooms,id'],
'storage_shelf_id' => ['nullable', 'integer', 'exists:storage_shelves,id'],
'storage_slot_id' => ['nullable', 'integer', 'exists:storage_slots,id'],
'active' => ['sometimes', 'boolean'],
];
}
@ -38,7 +41,7 @@ class UpdatePackagingItemRequest extends FormRequest
'weight_grams' => $this->filled('weight_grams') ? reFormatWawiNumber($this->input('weight_grams')) : null,
]);
foreach (['supplier_id', 'product_id', 'min_stock_alert'] as $key) {
foreach (['supplier_id', 'product_id', 'min_stock_alert', 'storage_room_id', 'storage_shelf_id', 'storage_slot_id'] as $key) {
if ($this->input($key) === '' || $this->input($key) === null) {
$this->merge([$key => null]);
}