Deploy / deploy (push) Successful in 14s
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>
193 lines
7.5 KiB
PHP
193 lines
7.5 KiB
PHP
<?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(),
|
||
]);
|
||
}
|
||
}
|