diff --git a/app/Filament/User/Resources/SpiceResource.php b/app/Filament/User/Resources/SpiceResource.php index 194679a..93a624d 100644 --- a/app/Filament/User/Resources/SpiceResource.php +++ b/app/Filament/User/Resources/SpiceResource.php @@ -78,7 +78,7 @@ class SpiceResource extends Resource public static function getRelations(): array { return [ - // + RelationManagers\StockMovementsRelationManager::class, ]; } diff --git a/app/Filament/User/Resources/SpiceResource/RelationManagers/StockMovementsRelationManager.php b/app/Filament/User/Resources/SpiceResource/RelationManagers/StockMovementsRelationManager.php new file mode 100644 index 0000000..a56ea04 --- /dev/null +++ b/app/Filament/User/Resources/SpiceResource/RelationManagers/StockMovementsRelationManager.php @@ -0,0 +1,34 @@ +columns([ + Tables\Columns\TextColumn::make('created_at') + ->label('Datum') + ->dateTime('d.m.Y H:i') + ->sortable(), + + Tables\Columns\TextColumn::make('description') + ->label('Beschreibung'), + + Tables\Columns\TextColumn::make('amount') + ->label('Menge') + ->formatStateUsing(fn ($state) => ($state > 0 ? '+' : '') . number_format($state, 2, ',', '.') . 'g') + ->color(fn ($state) => $state > 0 ? 'success' : 'danger'), + ]) + ->defaultSort('created_at', 'desc') + ->paginated([25, 50]); + } +} diff --git a/app/Filament/User/Resources/SpiceSupplierResource/Pages/CreateSpiceSupplier.php b/app/Filament/User/Resources/SpiceSupplierResource/Pages/CreateSpiceSupplier.php index 10967d0..50d42a4 100644 --- a/app/Filament/User/Resources/SpiceSupplierResource/Pages/CreateSpiceSupplier.php +++ b/app/Filament/User/Resources/SpiceSupplierResource/Pages/CreateSpiceSupplier.php @@ -4,17 +4,8 @@ namespace App\Filament\User\Resources\SpiceSupplierResource\Pages; use App\Filament\User\Resources\SpiceSupplierResource; use Filament\Resources\Pages\CreateRecord; -use Illuminate\Database\Eloquent\Model; -use App\Models\Spice; class CreateSpiceSupplier extends CreateRecord { protected static string $resource = SpiceSupplierResource::class; - - protected function handleRecordCreation(array $data): Model { - $record = static::getModel()::create($data); - - Spice::findOrFail($data['spice_id'])->increment('quantity', $data['amount']); - return $record; - } } diff --git a/app/Filament/User/Resources/SpiceSupplierResource/Pages/EditSpiceSupplier.php b/app/Filament/User/Resources/SpiceSupplierResource/Pages/EditSpiceSupplier.php index 9b23c48..a93ef3c 100644 --- a/app/Filament/User/Resources/SpiceSupplierResource/Pages/EditSpiceSupplier.php +++ b/app/Filament/User/Resources/SpiceSupplierResource/Pages/EditSpiceSupplier.php @@ -16,14 +16,9 @@ class EditSpiceSupplier extends EditRecord ->requiresConfirmation() ->modalHeading('Lieferung löschen') ->modalDescription(fn ($record) => $record->spice && $record->spice->quantity < $record->amount - ? 'Warnung: Nicht mehr genügend ' . $record->spice->name . ' im Lager. Der Bestand wird auf 0 gesetzt. Trotzdem löschen?' + ? 'Warnung: Nach dem Löschen wird der Bestand von ' . $record->spice->name . ' negativ.' : 'Soll diese Lieferung wirklich gelöscht werden?' - ) - ->before(function ($record) { - if ($record->spice) { - $record->spice->decrement('quantity', $record->amount); - } - }), + ), ]; } } diff --git a/app/Models/Batch.php b/app/Models/Batch.php index 0168302..484b6fe 100644 --- a/app/Models/Batch.php +++ b/app/Models/Batch.php @@ -38,58 +38,62 @@ class Batch extends Model 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; + $batch->quantity_g = $batch->units_produced * ($batch->size?->packing?->size ?? 0); } }); static::created(function (Batch $batch) { - $batch->deductSpices(); + $batch->writeSpiceMovements(); }); 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(); + $batch->deleteSpiceMovements(); + $batch->writeSpiceMovements(); } }); static::deleting(function (Batch $batch) { - $batch->restoreSpices($batch->recipe_id, $batch->quantity_g); + $batch->deleteSpiceMovements(); }); } - private function deductSpices(): void + private function writeSpiceMovements(): 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) { + if (! $recipe) { return; } - $recipe = Recipe::with('spices')->find($recipeId); - foreach ($recipe->spices as $spice) { - $grams = $quantityG * $spice->pivot->amount_in_percent / 100; - $spice->increment('quantity', $grams); + $grams = $this->quantity_g * $spice->pivot->amount_in_percent / 100; + SpiceStockMovement::create([ + 'spice_id' => $spice->id, + 'amount' => -$grams, + 'source_type' => 'batch', + 'source_id' => $this->id, + 'description' => 'Charge ' . $this->lot_number . ' (' . $spice->pivot->amount_in_percent . '%)', + ]); + $spice->recalculateQuantity(); } } + + private function deleteSpiceMovements(): void + { + $spiceIds = SpiceStockMovement::where('source_type', 'batch') + ->where('source_id', $this->id) + ->pluck('spice_id'); + + SpiceStockMovement::where('source_type', 'batch') + ->where('source_id', $this->id) + ->delete(); + + Spice::whereIn('id', $spiceIds)->get()->each->recalculateQuantity(); + } } diff --git a/app/Models/Spice.php b/app/Models/Spice.php index 40cbc2a..90c4783 100644 --- a/app/Models/Spice.php +++ b/app/Models/Spice.php @@ -14,4 +14,14 @@ class Spice extends Model ->withPivot('amount_in_percent') ->withTimestamps(); } + + public function stockMovements() + { + return $this->hasMany(SpiceStockMovement::class); + } + + public function recalculateQuantity(): void + { + $this->update(['quantity' => $this->stockMovements()->sum('amount')]); + } } diff --git a/app/Models/SpiceStockMovement.php b/app/Models/SpiceStockMovement.php new file mode 100644 index 0000000..fb1f273 --- /dev/null +++ b/app/Models/SpiceStockMovement.php @@ -0,0 +1,15 @@ +belongsTo(Spice::class); + } +} diff --git a/app/Models/SpiceSupplier.php b/app/Models/SpiceSupplier.php index 6ddd955..bf3567a 100644 --- a/app/Models/SpiceSupplier.php +++ b/app/Models/SpiceSupplier.php @@ -18,7 +18,6 @@ class SpiceSupplier extends Model 'supplied_at', ]; - // Beziehungen public function spice() { return $this->belongsTo(Spice::class); @@ -28,4 +27,25 @@ class SpiceSupplier extends Model { return $this->belongsTo(Supplier::class); } + + protected static function booted(): void + { + static::created(function (SpiceSupplier $ss) { + SpiceStockMovement::create([ + 'spice_id' => $ss->spice_id, + 'amount' => $ss->amount, + 'source_type' => 'delivery', + 'source_id' => $ss->id, + 'description' => 'Lieferung' . ($ss->supplier ? ' von ' . $ss->supplier->name : ''), + ]); + $ss->spice->recalculateQuantity(); + }); + + static::deleted(function (SpiceSupplier $ss) { + SpiceStockMovement::where('source_type', 'delivery') + ->where('source_id', $ss->id) + ->delete(); + $ss->spice->recalculateQuantity(); + }); + } } diff --git a/database/migrations/2026_09_12_250000_create_spice_stock_movements.php b/database/migrations/2026_09_12_250000_create_spice_stock_movements.php new file mode 100644 index 0000000..92e8027 --- /dev/null +++ b/database/migrations/2026_09_12_250000_create_spice_stock_movements.php @@ -0,0 +1,67 @@ +id(); + $table->foreignId('spice_id')->constrained('spices')->cascadeOnDelete(); + $table->decimal('amount', 10, 2); // positiv = Zugang, negativ = Abgang + $table->enum('source_type', ['delivery', 'batch']); + $table->unsignedBigInteger('source_id'); + $table->string('description'); + $table->timestamps(); + + $table->index(['spice_id', 'created_at']); + }); + + // Bestehende Lieferungen als Bewegungen einlesen + DB::table('spices_suppliers')->orderBy('id')->each(function ($row) { + DB::table('spice_stock_movements')->insert([ + 'spice_id' => $row->spice_id, + 'amount' => $row->amount, + 'source_type' => 'delivery', + 'source_id' => $row->id, + 'description' => 'Lieferung', + 'created_at' => $row->created_at, + 'updated_at' => $row->updated_at, + ]); + }); + + // Bestehende Chargen als Bewegungen einlesen + DB::table('batches')->orderBy('id')->each(function ($batch) { + $spices = DB::table('recipes_spices')->where('recipe_id', $batch->recipe_id)->get(); + foreach ($spices as $pivot) { + $grams = $batch->quantity_g * $pivot->amount_in_percent / 100; + DB::table('spice_stock_movements')->insert([ + 'spice_id' => $pivot->spice_id, + 'amount' => -$grams, + 'source_type' => 'batch', + 'source_id' => $batch->id, + 'description' => 'Charge ' . $batch->lot_number, + 'created_at' => $batch->created_at, + 'updated_at' => $batch->updated_at, + ]); + } + }); + + // Bestand aller Rohstoffe aus den Bewegungen neu berechnen + DB::table('spices')->orderBy('id')->each(function ($spice) { + $balance = DB::table('spice_stock_movements') + ->where('spice_id', $spice->id) + ->sum('amount'); + DB::table('spices')->where('id', $spice->id)->update(['quantity' => $balance]); + }); + } + + public function down(): void + { + Schema::dropIfExists('spice_stock_movements'); + } +};