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

@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* AP-51: Lagerplatz-Zuordnung (Raum/Regal/Fach) auch für Produkte und
* Verpackungsartikel — gleiche Storage-Hierarchie wie bei Rohstoffen
* (StorageRoom/Shelf/Slot). Referenzen werden beim Löschen eines
* Lagerbausteins genullt (nullOnDelete).
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->foreignId('storage_room_id')->nullable()->after('weight')
->constrained('storage_rooms')->nullOnDelete();
$table->foreignId('storage_shelf_id')->nullable()->after('storage_room_id')
->constrained('storage_shelves')->nullOnDelete();
$table->foreignId('storage_slot_id')->nullable()->after('storage_shelf_id')
->constrained('storage_slots')->nullOnDelete();
});
Schema::table('packaging_items', function (Blueprint $table) {
$table->foreignId('storage_room_id')->nullable()->after('product_id')
->constrained('storage_rooms')->nullOnDelete();
$table->foreignId('storage_shelf_id')->nullable()->after('storage_room_id')
->constrained('storage_shelves')->nullOnDelete();
$table->foreignId('storage_slot_id')->nullable()->after('storage_shelf_id')
->constrained('storage_slots')->nullOnDelete();
});
}
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->dropConstrainedForeignId('storage_room_id');
$table->dropConstrainedForeignId('storage_shelf_id');
$table->dropConstrainedForeignId('storage_slot_id');
});
Schema::table('packaging_items', function (Blueprint $table) {
$table->dropConstrainedForeignId('storage_room_id');
$table->dropConstrainedForeignId('storage_shelf_id');
$table->dropConstrainedForeignId('storage_slot_id');
});
}
};