gruene-seele/app/Http/Controllers/Admin/Inventory/StockDisposalController.php
Kevin Adametz 65f949dd7a Warenwirtschaft: OPT-1 Quick Wins + Entwicklungsplan V6.0
- N+1-Fix Ausschussliste: user.account eager laden (StockDisposalController@index)
- Composite-Indizes stock_entries(entry_type,status,ingredient_id/packaging_item_id) für InventoryService-Aggregationen
- Ungenutzten Eager-Load producedByUser aus ProductionRepository::listForIndex entfernt
- ExampleTest auf assertRedirect umgestellt (Suite 100% grün: 155 Tests)
- Query-Count-Regressionstest für die Ausschussliste
- Plan V6.0 (Code-Verifikation, OPT-Block, geklärte Kundenfragen)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:54:24 +00:00

126 lines
4.1 KiB
PHP

<?php
namespace App\Http\Controllers\Admin\Inventory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Inventory\StoreStockDisposalRequest;
use App\Models\DisposalReason;
use App\Models\Ingredient;
use App\Models\Location;
use App\Models\StockDisposal;
use App\Models\StockEntry;
use App\Services\InventoryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class StockDisposalController extends Controller
{
public function __construct(
protected InventoryService $inventoryService,
) {}
public function index(Request $request): View
{
$query = StockDisposal::query()
->with(['ingredient', 'packagingItem', 'location', 'stockEntry', 'user.account'])
->latest('disposed_at')
->latest('id');
if (in_array($request->query('type'), ['ingredient', 'packaging'], true)) {
$query->where('disposal_type', $request->query('type'));
}
return view('admin.inventory.stock-disposals.index', [
'values' => $query->limit(500)->get(),
'typeFilter' => $request->query('type'),
]);
}
public function create(Request $request): View|RedirectResponse
{
if (! auth()->user()->isAdmin()) {
return redirect()->route('home');
}
$prefill = [
'disposal_type' => 'ingredient',
'ingredient_id' => null,
'ingredient_label' => null,
];
$ingredientId = (int) $request->query('ingredient_id');
if ($ingredientId > 0 && ($ingredient = Ingredient::query()->find($ingredientId))) {
$prefill['ingredient_id'] = $ingredient->id;
$prefill['ingredient_label'] = $ingredient->inci ? $ingredient->name.' ('.$ingredient->inci.')' : $ingredient->name;
}
return view('admin.inventory.stock-disposals.create', [
'locations' => Location::query()->where('active', true)->orderBy('name')->get(),
'reasons' => $this->reasons(),
'prefill' => $prefill,
]);
}
public function store(StoreStockDisposalRequest $request): RedirectResponse
{
$data = $request->validatedPayload();
$data['user_id'] = (int) $request->user()->id;
StockDisposal::query()->create($data);
\Session::flash('alert-save', '1');
return redirect()->route('admin.inventory.stock-disposals.index');
}
public function ingredientCharges(Ingredient $ingredient): JsonResponse
{
$remainingByLocation = $this->inventoryService->remainingByLocationForIngredient($ingredient->id);
$charges = StockEntry::query()
->where('status', 'received')
->where('entry_type', 'ingredient')
->where('ingredient_id', $ingredient->id)
->with('location')
->orderBy('best_before')
->get(['id', 'location_id', 'batch_number', 'best_before', 'received_quantity']);
$results = $charges->map(function (StockEntry $charge) {
$label = $charge->batch_number ? __('Charge').' '.$charge->batch_number : __('Charge #:id', ['id' => $charge->id]);
if ($charge->location) {
$label .= ' · '.$charge->location->name;
}
if ($charge->best_before) {
$label .= ' · MHD '.$charge->best_before->format('d.m.Y');
}
return [
'id' => $charge->id,
'location_id' => $charge->location_id,
'text' => $label,
];
})->values()->all();
return response()->json([
'charges' => $results,
'remaining_by_location' => $remainingByLocation,
]);
}
/**
* Aktive Ausschuss-Gründe aus den Einstellungen (Warenwirtschaft → Allgemein).
*
* @return array<int, string>
*/
protected function reasons(): array
{
return DisposalReason::query()
->active()
->orderBy('pos')
->orderBy('label')
->pluck('label')
->all();
}
}