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>
118 lines
2.8 KiB
PHP
118 lines
2.8 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Database\Factories\PackagingItemFactory;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
class PackagingItem extends Model
|
|
{
|
|
/** @use HasFactory<PackagingItemFactory> */
|
|
use HasFactory, SoftDeletes;
|
|
|
|
protected $fillable = [
|
|
'packaging_material_id',
|
|
'supplier_id',
|
|
'name',
|
|
'category',
|
|
'weight_grams',
|
|
'min_stock_alert',
|
|
'url',
|
|
'product_id',
|
|
'storage_room_id',
|
|
'storage_shelf_id',
|
|
'storage_slot_id',
|
|
'active',
|
|
];
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'active' => 'boolean',
|
|
'weight_grams' => 'decimal:2',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return BelongsTo<PackagingMaterial, $this>
|
|
*/
|
|
public function packagingMaterial(): BelongsTo
|
|
{
|
|
return $this->belongsTo(PackagingMaterial::class);
|
|
}
|
|
|
|
/**
|
|
* @return BelongsTo<Supplier, $this>
|
|
*/
|
|
public function supplier(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Supplier::class);
|
|
}
|
|
|
|
/**
|
|
* @return BelongsTo<Product, $this>
|
|
*/
|
|
public function product(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Product::class);
|
|
}
|
|
|
|
/**
|
|
* Produkte, die diesen Verpackungsartikel in der Stückliste führen (BOM).
|
|
*
|
|
* @return BelongsToMany<Product, $this>
|
|
*/
|
|
public function products(): BelongsToMany
|
|
{
|
|
return $this->belongsToMany(Product::class, 'product_packagings')
|
|
->withPivot('quantity', 'pos')
|
|
->withTimestamps();
|
|
}
|
|
|
|
/**
|
|
* AP-51: Lagerplatz (Raum/Regal/Fach) — gleiche Hierarchie wie beim Rohstoff.
|
|
*
|
|
* @return BelongsTo<StorageRoom, $this>
|
|
*/
|
|
public function storageRoom(): BelongsTo
|
|
{
|
|
return $this->belongsTo(StorageRoom::class);
|
|
}
|
|
|
|
/**
|
|
* @return BelongsTo<StorageShelf, $this>
|
|
*/
|
|
public function storageShelf(): BelongsTo
|
|
{
|
|
return $this->belongsTo(StorageShelf::class);
|
|
}
|
|
|
|
/**
|
|
* @return BelongsTo<StorageSlot, $this>
|
|
*/
|
|
public function storageSlot(): BelongsTo
|
|
{
|
|
return $this->belongsTo(StorageSlot::class);
|
|
}
|
|
|
|
/**
|
|
* Lagerplatz-Anzeige im Schema „Raum 1 | Regal 3 | A" (nur gepflegte Teile).
|
|
*/
|
|
public function storageLabel(): ?string
|
|
{
|
|
$parts = array_filter([
|
|
$this->storageRoom?->label,
|
|
$this->storageShelf?->label,
|
|
$this->storageSlot?->label,
|
|
]);
|
|
|
|
return $parts === [] ? null : implode(' | ', $parts);
|
|
}
|
|
}
|