Files
SpiceManagementSystem/app/Filament/User/Resources/ProductResource/RelationManagers/BatchesRelationManager.php
T
Malek TellissiandClaude Sonnet 4.6 62be3761a9
Deploy / deploy (push) Successful in 14s
Pre-fill batch origin rows from recipe spices automatically
When creating a new batch, the Herkunft-Repeater is automatically populated
with one row per Rohstoff from the product's current recipe. The Rohstoff
field is shown as a read-only label (Hidden spice_id + Placeholder name)
so the user only fills in supplier, origin country, farmer and notes.

Changing the recipe field updates the origin rows accordingly.
On edit, existing origin data is preserved unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-12 18:54:00 +02:00

193 lines
7.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Filament\User\Resources\ProductResource\RelationManagers;
use App\Models\ProductSize;
use App\Models\Recipe;
use App\Models\Spice;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Support\HtmlString;
class BatchesRelationManager extends RelationManager
{
protected static string $relationship = 'batches';
protected static ?string $title = 'Chargen (LOT)';
protected static ?string $modelLabel = 'Charge';
protected static ?string $pluralModelLabel = 'Chargen';
public function form(Form $form): Form
{
return $form->schema([
Forms\Components\TextInput::make('lot_number')
->label('LOT-Nummer')
->required()
->unique(ignoreRecord: true)
->maxLength(255),
Forms\Components\Select::make('size_id')
->label('Variante (Verpackung)')
->options(fn () => ProductSize::with('packing')
->where('product_id', $this->getOwnerRecord()->id)
->get()
->mapWithKeys(fn ($s) => [$s->id => $s->packing->name . ' ' . $s->packing->size . 'g'])
)
->required()
->live(),
Forms\Components\Select::make('recipe_id')
->label('Rezeptur')
->options(fn () => Recipe::where('product_id', $this->getOwnerRecord()->id)
->orderByDesc('version')
->get()
->mapWithKeys(fn ($r) => [$r->id => 'v' . $r->version . ' · ' . $r->created_at->format('d.m.Y')])
)
->default(fn () => $this->getOwnerRecord()->recipes_id)
->required()
->live()
->afterStateUpdated(function ($state, $set) {
if (! $state) {
$set('origins', []);
return;
}
$recipe = Recipe::with('spices')->find($state);
if (! $recipe) {
return;
}
$set('origins', $recipe->spices->map(fn ($s) => [
'spice_id' => $s->id,
'supplier_id' => null,
'origin_country' => null,
'farmer' => null,
'notes' => null,
])->toArray());
}),
Forms\Components\TextInput::make('units_produced')
->label('Anzahl Einheiten')
->numeric()
->required()
->live(debounce: 400),
Forms\Components\Placeholder::make('quantity_g_preview')
->label('Gesamtgewicht (berechnet)')
->content(function (Get $get): HtmlString {
$units = (int) $get('units_produced');
$sizeId = $get('size_id');
if (! $units || ! $sizeId) {
return new HtmlString('<span class="text-gray-400">—</span>');
}
$size = ProductSize::with('packing')->find($sizeId);
$totalG = $units * ($size?->packing?->size ?? 0);
return new HtmlString("<strong>{$totalG} g</strong> ({$units} × {$size?->packing?->size}g)");
}),
Forms\Components\DatePicker::make('produced_at')
->label('Produktionsdatum'),
Forms\Components\DatePicker::make('expires_at')
->label('Mindesthaltbarkeitsdatum'),
Forms\Components\Repeater::make('origins')
->label('Herkunft der Rohstoffe')
->relationship()
->afterStateHydrated(function ($component, $record, $livewire) {
// Pre-fill from product's current recipe only when creating a new batch
if ($record?->id) {
return;
}
$recipeId = $livewire->getOwnerRecord()->recipes_id;
if (! $recipeId) {
return;
}
$recipe = Recipe::with('spices')->find($recipeId);
if (! $recipe) {
return;
}
$component->state($recipe->spices->map(fn ($s) => [
'spice_id' => $s->id,
'supplier_id' => null,
'origin_country' => null,
'farmer' => null,
'notes' => null,
])->toArray());
})
->schema([
Forms\Components\Hidden::make('spice_id'),
Forms\Components\Placeholder::make('spice_name')
->label('Rohstoff')
->content(fn (Get $get): string =>
Spice::find($get('spice_id'))?->name ?? '—'
),
Forms\Components\Select::make('supplier_id')
->label('Lieferant')
->relationship('supplier', 'name')
->searchable()
->preload()
->nullable(),
Forms\Components\TextInput::make('origin_country')
->label('Herkunftsland')
->maxLength(100),
Forms\Components\TextInput::make('farmer')
->label('Landwirt / Betrieb')
->maxLength(255),
Forms\Components\Textarea::make('notes')
->label('Notizen')
->rows(2)
->columnSpanFull(),
])
->columns(2)
->addActionLabel('Rohstoff hinzufügen')
->columnSpanFull(),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('lot_number')
->columns([
Tables\Columns\TextColumn::make('lot_number')
->label('LOT-Nummer')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('units_produced')
->label('Einheiten')
->suffix(fn ($record) => $record->quantity_g
? ' (' . number_format($record->quantity_g, 0, ',', '.') . 'g)'
: ''),
Tables\Columns\TextColumn::make('recipe.version')
->label('Rezeptur')
->formatStateUsing(fn ($state) => $state ? 'v' . $state : '—'),
Tables\Columns\TextColumn::make('produced_at')
->label('Produktion')
->date('d.m.Y')
->sortable(),
Tables\Columns\TextColumn::make('expires_at')
->label('MHD')
->date('d.m.Y')
->sortable(),
])
->defaultSort('produced_at', 'desc')
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->headerActions([
Tables\Actions\CreateAction::make(),
]);
}
}