['old' => …, 'new' => …]] mit Rohwerten (IDs, Y-m-d, Dezimal); * die lesbare Darstellung liefert describe(). */ class StockEntryChange extends Model { protected $fillable = [ 'stock_entry_id', 'user_id', 'status_before', 'reason', 'field_changes', ]; /** * Felder, die im Protokoll verfolgt werden (Reihenfolge = Anzeige). * * @var array */ public const TRACKED = [ 'entry_type', 'ingredient_id', 'packaging_item_id', 'supplier_id', 'location_id', 'ordered_at', 'ordered_quantity', 'purchase_unit', 'density_used', 'price_per_kg', 'price_per_kg_gross', 'tax_rate_id', 'price_total', 'received_at', 'received_quantity', 'batch_number', 'best_before', 'quality_id', ]; /** * @return array */ protected function casts(): array { return ['field_changes' => 'array']; } /** * @return BelongsTo */ public function stockEntry(): BelongsTo { return $this->belongsTo(StockEntry::class); } /** * @return BelongsTo */ public function user(): BelongsTo { return $this->belongsTo(User::class, 'user_id'); } /** * Geänderte Felder als lesbare Zeilen [label, alt, neu]. * * @return array */ public function describe(): array { $rows = []; foreach (self::TRACKED as $field) { if (! isset($this->field_changes[$field])) { continue; } $rows[] = [ 'label' => self::label($field), 'old' => $this->format($field, $this->field_changes[$field]['old'] ?? null), 'new' => $this->format($field, $this->field_changes[$field]['new'] ?? null), ]; } return $rows; } public static function label(string $field): string { return match ($field) { 'entry_type' => __('Art'), 'ingredient_id' => __('Inhaltsstoff'), 'packaging_item_id' => __('Verpackungsartikel'), 'supplier_id' => __('Lieferant'), 'location_id' => __('Lagerort'), 'ordered_at' => __('Bestelldatum'), 'ordered_quantity' => __('Bestellte Menge'), 'purchase_unit' => __('Einheit'), 'density_used' => __('Dichte (g/ml)'), 'price_per_kg' => __('Netto-Preis pro kg'), 'price_per_kg_gross' => __('Brutto-Preis pro kg'), 'tax_rate_id' => __('Umsatzsteuer'), 'price_total' => __('Gesamtpreis netto'), 'received_at' => __('Eingangsdatum'), 'received_quantity' => __('Eingegangene Menge'), 'batch_number' => __('Charge'), 'best_before' => __('Mindesthaltbarkeit'), 'quality_id' => __('Rohstoffqualität'), default => $field, }; } protected function format(string $field, mixed $value): string { if ($value === null || $value === '') { return '—'; } return match ($field) { 'entry_type' => match ($value) { 'ingredient' => __('Rohstoff'), 'packaging' => __('Verpackung'), 'shipping' => __('Versand'), default => (string) $value, }, 'ingredient_id' => Ingredient::withTrashed()->whereKey($value)->value('name') ?? '#'.$value, 'packaging_item_id' => PackagingItem::withTrashed()->whereKey($value)->value('name') ?? '#'.$value, 'supplier_id' => Supplier::withTrashed()->whereKey($value)->value('name') ?? '#'.$value, 'location_id' => Location::withTrashed()->whereKey($value)->value('name') ?? '#'.$value, 'quality_id' => MaterialQuality::withTrashed()->whereKey($value)->value('name') ?? '#'.$value, 'tax_rate_id' => TaxRate::withTrashed()->whereKey($value)->value('name') ?? '#'.$value, 'ordered_at', 'received_at', 'best_before' => Carbon::parse($value)->format('d.m.Y'), 'ordered_quantity', 'received_quantity' => formatWawiQuantity((float) $value).' '.($this->stockEntry?->unit === 'piece' ? __('Stk.') : 'g'), 'purchase_unit' => $value === 'ml' ? 'ml' : 'g', 'density_used' => Util::formatNumber($value), 'price_per_kg', 'price_per_kg_gross', 'price_total' => Util::formatNumber($value).' €', default => (string) $value, }; } }