Files
SpiceManagementSystem/app/Filament/User/Resources/BatchResource.php
T
Malek TellissiandClaude Sonnet 4.6 5c034ec895
Deploy / deploy (push) Successful in 14s
Add lot_prefix to products; compose LOT number from prefix + date suffix
Products now have a configurable lot_prefix (e.g. "112"). In the batch form
the prefix is shown read-only next to the suffix input (JJMMTT), and the full
lot_number (e.g. "112-260912") is composed automatically and stored.

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

257 lines
11 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;
use App\Filament\User\Resources\BatchResource\Pages;
use App\Models\Batch;
use App\Models\Farmer;
use App\Models\Product;
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\Resource;
use Illuminate\Support\HtmlString;
use Filament\Tables;
use Filament\Tables\Table;
class BatchResource extends Resource
{
protected static ?string $model = Batch::class;
protected static ?string $navigationIcon = 'heroicon-o-qr-code';
protected static ?string $navigationLabel = 'Chargen (LOT)';
protected static ?string $modelLabel = 'Charge';
protected static ?string $pluralModelLabel = 'Chargen';
protected static ?string $navigationGroup = 'Produkte';
protected static ?int $navigationSort = 3;
public static function form(Form $form): Form
{
return $form->schema([
Forms\Components\Section::make('Charge')->schema([
Forms\Components\Select::make('product_id')
->label('Produkt')
->relationship('product', 'title')
->searchable()
->preload()
->required()
->live()
->afterStateUpdated(function ($state, $set, $get) {
$set('size_id', null);
$set('recipe_id', null);
$set('origins', []);
if (! $state) {
return;
}
$product = Product::find($state);
if ($product?->recipes_id) {
$set('recipe_id', $product->recipes_id);
$recipe = Recipe::with('spices')->find($product->recipes_id);
if ($recipe) {
$set('origins', $recipe->spices->map(fn ($s) => [
'spice_id' => $s->id,
'entries' => [['supplier_id' => null, 'origin_country' => null, 'farmer_id' => null, 'notes' => null]],
])->toArray());
}
}
$suffix = $get('lot_suffix');
if ($suffix && $product?->lot_prefix) {
$set('lot_number', $product->lot_prefix . '-' . $suffix);
}
}),
Forms\Components\TextInput::make('lot_suffix')
->label('LOT-Nummer')
->prefix(fn (Get $get): ?string => Product::find($get('product_id'))?->lot_prefix
? Product::find($get('product_id'))->lot_prefix . ' -'
: null
)
->placeholder('JJMMTT')
->maxLength(50)
->required()
->dehydrated(false)
->live()
->afterStateHydrated(function ($component, $record) {
if (! $record?->lot_number) return;
$pos = strrpos($record->lot_number, '-');
$component->state($pos !== false
? substr($record->lot_number, $pos + 1)
: $record->lot_number
);
})
->afterStateUpdated(function ($state, $set, $get) {
$product = Product::find($get('product_id'));
$set('lot_number', $product?->lot_prefix
? $product->lot_prefix . '-' . $state
: $state
);
}),
Forms\Components\Hidden::make('lot_number'),
Forms\Components\Select::make('size_id')
->label('Variante (Verpackung)')
->options(fn (Get $get) => ProductSize::with('packing')
->where('product_id', $get('product_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 (Get $get) => Recipe::where('product_id', $get('product_id') ?? 0)
->orderByDesc('version')
->get()
->mapWithKeys(fn ($r) => [$r->id => 'v' . $r->version . ' · ' . $r->created_at->format('d.m.Y')])
)
->required()
->live()
->afterStateUpdated(function ($state, $set, $get) {
if (! $state) {
$set('origins', []);
return;
}
$recipe = Recipe::with('spices')->find($state);
if ($recipe) {
$existing = collect($get('origins') ?? [])->keyBy('spice_id');
$set('origins', $recipe->spices->map(fn ($s) => $existing->has($s->id)
? $existing[$s->id]
: ['spice_id' => $s->id, 'entries' => [['supplier_id' => null, 'origin_country' => null, 'farmer_id' => 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'),
])->columns(2),
Forms\Components\Section::make('Herkunft der Rohstoffe')->schema([
Forms\Components\Repeater::make('origins')
->label('')
->relationship()
->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 ?? '—'
)
->columnSpanFull(),
Forms\Components\Repeater::make('entries')
->label('Herkunftsangaben')
->relationship()
->schema([
Forms\Components\Select::make('supplier_id')
->label('Lieferant')
->options(fn () => \App\Models\Supplier::orderBy('name')->pluck('name', 'id'))
->searchable()
->createOptionForm([
Forms\Components\TextInput::make('name')->label('Name')->required(),
])
->createOptionUsing(fn ($data) => \App\Models\Supplier::create($data)->id)
->nullable(),
Forms\Components\TextInput::make('origin_country')
->label('Herkunftsland')
->maxLength(100),
Forms\Components\Select::make('farmer_id')
->label('Landwirt')
->options(fn () => Farmer::orderBy('name')->pluck('name', 'id'))
->searchable()
->createOptionForm([
Forms\Components\TextInput::make('name')->label('Name')->required(),
])
->createOptionUsing(fn ($data) => Farmer::create($data)->id)
->nullable(),
Forms\Components\Textarea::make('notes')
->label('Notizen')
->rows(2)
->columnSpanFull(),
])
->columns(3)
->addActionLabel('Eintrag hinzufügen')
->reorderable(false)
->columnSpanFull(),
])
->reorderable(false),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('lot_number')
->label('LOT-Nummer')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('product.title')
->label('Produkt')
->searchable(),
Tables\Columns\TextColumn::make('recipe.name')
->label('Rezeptur'),
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')
->filters([
Tables\Filters\SelectFilter::make('product')
->relationship('product', 'title')
->label('Produkt'),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ListBatches::route('/'),
'create' => Pages\CreateBatch::route('/create'),
'edit' => Pages\EditBatch::route('/{record}/edit'),
];
}
}