diff --git a/app/Filament/User/Resources/BatchResource.php b/app/Filament/User/Resources/BatchResource.php
index 5c5fff3..de742db 100644
--- a/app/Filament/User/Resources/BatchResource.php
+++ b/app/Filament/User/Resources/BatchResource.php
@@ -4,11 +4,13 @@ namespace App\Filament\User\Resources;
use App\Filament\User\Resources\BatchResource\Pages;
use App\Models\Batch;
+use App\Models\ProductSize;
use App\Models\Recipe;
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;
@@ -38,6 +40,17 @@ class BatchResource extends Resource
->searchable()
->preload()
->required()
+ ->live()
+ ->afterStateUpdated(fn ($set) => $set('size_id', null)),
+
+ 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')
@@ -48,6 +61,25 @@ class BatchResource extends Resource
->required()
->live(),
+ 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('—');
+ }
+ $size = ProductSize::with('packing')->find($sizeId);
+ $totalG = $units * ($size?->packing?->size ?? 0);
+ return new HtmlString("{$totalG} g ({$units} × {$size?->packing?->size}g)");
+ }),
+
Forms\Components\DatePicker::make('produced_at')
->label('Produktionsdatum'),
diff --git a/app/Filament/User/Resources/ProductResource/RelationManagers/BatchesRelationManager.php b/app/Filament/User/Resources/ProductResource/RelationManagers/BatchesRelationManager.php
index bf3726e..9d8dcc5 100644
--- a/app/Filament/User/Resources/ProductResource/RelationManagers/BatchesRelationManager.php
+++ b/app/Filament/User/Resources/ProductResource/RelationManagers/BatchesRelationManager.php
@@ -2,11 +2,14 @@
namespace App\Filament\User\Resources\ProductResource\RelationManagers;
+use App\Models\ProductSize;
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
{
@@ -24,6 +27,16 @@ class BatchesRelationManager extends RelationManager
->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')
->relationship('recipe', 'name')
@@ -31,6 +44,25 @@ class BatchesRelationManager extends RelationManager
->preload()
->required(),
+ 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('—');
+ }
+ $size = ProductSize::with('packing')->find($sizeId);
+ $totalG = $units * ($size?->packing?->size ?? 0);
+ return new HtmlString("{$totalG} g ({$units} × {$size?->packing?->size}g)");
+ }),
+
Forms\Components\DatePicker::make('produced_at')
->label('Produktionsdatum'),
@@ -84,6 +116,10 @@ class BatchesRelationManager extends RelationManager
->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.name')
->label('Rezeptur'),
diff --git a/app/Models/Batch.php b/app/Models/Batch.php
index a3a7fd6..0168302 100644
--- a/app/Models/Batch.php
+++ b/app/Models/Batch.php
@@ -6,7 +6,10 @@ use Illuminate\Database\Eloquent\Model;
class Batch extends Model
{
- protected $fillable = ['lot_number', 'product_id', 'recipe_id', 'produced_at', 'expires_at'];
+ protected $fillable = [
+ 'lot_number', 'product_id', 'size_id', 'recipe_id',
+ 'units_produced', 'quantity_g', 'produced_at', 'expires_at',
+ ];
protected $casts = [
'produced_at' => 'date',
@@ -18,6 +21,11 @@ class Batch extends Model
return $this->belongsTo(Product::class);
}
+ public function size()
+ {
+ return $this->belongsTo(ProductSize::class);
+ }
+
public function recipe()
{
return $this->belongsTo(Recipe::class);
@@ -27,4 +35,61 @@ class Batch extends Model
{
return $this->hasMany(BatchOrigin::class);
}
+
+ protected static function booted(): void
+ {
+ // Auto-calculate quantity_g before saving
+ static::saving(function (Batch $batch) {
+ if ($batch->units_produced && $batch->size_id) {
+ $gramsPerUnit = $batch->size?->packing?->size ?? 0;
+ $batch->quantity_g = $batch->units_produced * $gramsPerUnit;
+ }
+ });
+
+ static::created(function (Batch $batch) {
+ $batch->deductSpices();
+ });
+
+ static::updated(function (Batch $batch) {
+ if ($batch->wasChanged(['recipe_id', 'size_id', 'units_produced'])) {
+ $batch->restoreSpices(
+ $batch->getOriginal('recipe_id'),
+ $batch->getOriginal('quantity_g'),
+ );
+ $batch->deductSpices();
+ }
+ });
+
+ static::deleting(function (Batch $batch) {
+ $batch->restoreSpices($batch->recipe_id, $batch->quantity_g);
+ });
+ }
+
+ private function deductSpices(): void
+ {
+ if (! $this->quantity_g || ! $this->recipe_id) {
+ return;
+ }
+
+ $recipe = Recipe::with('spices')->find($this->recipe_id);
+
+ foreach ($recipe->spices as $spice) {
+ $grams = $this->quantity_g * $spice->pivot->amount_in_percent / 100;
+ $spice->decrement('quantity', $grams);
+ }
+ }
+
+ private function restoreSpices(int $recipeId, float $quantityG): void
+ {
+ if (! $quantityG || ! $recipeId) {
+ return;
+ }
+
+ $recipe = Recipe::with('spices')->find($recipeId);
+
+ foreach ($recipe->spices as $spice) {
+ $grams = $quantityG * $spice->pivot->amount_in_percent / 100;
+ $spice->increment('quantity', $grams);
+ }
+ }
}
diff --git a/database/migrations/2026_09_12_033131_add_production_fields_to_batches.php b/database/migrations/2026_09_12_033131_add_production_fields_to_batches.php
new file mode 100644
index 0000000..09c731d
--- /dev/null
+++ b/database/migrations/2026_09_12_033131_add_production_fields_to_batches.php
@@ -0,0 +1,29 @@
+foreignId('size_id')->nullable()->after('product_id')
+ ->constrained('products_sizes')->nullOnDelete();
+ $table->unsignedInteger('units_produced')->nullable()->after('size_id');
+ $table->decimal('quantity_g', 10, 2)->nullable()->after('units_produced');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('batches', function (Blueprint $table) {
+ $table->dropForeign(['size_id']);
+ $table->dropColumn(['size_id', 'units_produced', 'quantity_g']);
+ });
+ }
+};