Warenwirtschaft: OPT-3 Index-Limits + OPT-4 show()-Refactoring

- Wareneingang-/Produktions-Uebersicht: Eingegangene auf 500 gedeckelt
  (offene Bestellungen immer vollstaendig), ?all=1 + Hinweis-Link;
  ungenutzte User-Eager-Loads entfernt
- RawMaterialStockController::show() entschlackt: Chargen-Restmengen (FEFO)
  und offene Bestellungen im InventoryService, letzter Netto-Preis je
  Lieferant als Window-Function-Query im StockEntryRepository

Tests: RawMaterialStockTest +2, volle Suite 186 gruen, 663 Assertions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-06 12:23:20 +00:00
parent 209ea467eb
commit e47d848d3d
10 changed files with 165 additions and 75 deletions

View file

@ -22,10 +22,13 @@ class ProductionController extends Controller
protected ProductionService $productionService
) {}
public function index(): View
public function index(Request $request): View
{
$limit = $request->boolean('all') ? null : 500;
return view('admin.inventory.productions.index', [
'values' => $this->productionRepository->listForIndex(),
'values' => $this->productionRepository->listForIndex($limit),
'listLimit' => $limit,
]);
}

View file

@ -7,8 +7,8 @@ use App\Models\Ingredient;
use App\Models\Location;
use App\Models\StockEntry;
use App\Models\TaxRate;
use App\Repositories\StockEntryRepository;
use App\Services\InventoryService;
use App\Services\ProductionService;
use App\Services\ProductStockService;
use Illuminate\Http\Request;
use Illuminate\View\View;
@ -17,8 +17,8 @@ class RawMaterialStockController extends Controller
{
public function __construct(
protected InventoryService $inventoryService,
protected ProductionService $productionService,
protected ProductStockService $productStockService,
protected StockEntryRepository $stockEntryRepository,
) {}
public function index(Request $request): View
@ -76,47 +76,19 @@ class RawMaterialStockController extends Controller
'products' => fn ($q) => $q->where('active', true),
]);
$entries = StockEntry::query()
->with(['supplier', 'location'])
->where('status', 'received')
->where('entry_type', 'ingredient')
->where('ingredient_id', $ingredient->id)
->orderByRaw('best_before is null, best_before asc')
->orderBy('id')
->get();
$consumed = $this->productionService->consumedByStockEntry($entries->pluck('id')->all());
$charges = $entries->map(function (StockEntry $entry) use ($consumed) {
$received = $entry->received_quantity !== null ? (float) $entry->received_quantity : 0.0;
$entry->setAttribute('remaining_quantity', round($received - ($consumed[(int) $entry->id] ?? 0.0), 2));
return $entry;
})->filter(fn (StockEntry $entry) => (float) $entry->getAttribute('remaining_quantity') > 0.0)
->values();
$remaining = array_sum(array_map(
fn (StockEntry $entry) => (float) $entry->getAttribute('remaining_quantity'),
$charges->all()
));
$charges = $this->inventoryService->chargesWithRemainingForIngredient($ingredient->id);
$remaining = round((float) $charges->sum(fn (StockEntry $entry) => (float) $entry->getAttribute('remaining_quantity')), 2);
$remainingByLocation = $this->inventoryService->remainingByLocationForIngredient($ingredient->id);
$openOrders = StockEntry::query()
->with(['supplier', 'location'])
->where('status', 'pending')
->where('entry_type', 'ingredient')
->where('ingredient_id', $ingredient->id)
->orderBy('ordered_at')
->orderBy('id')
->get();
$openOrders = $this->inventoryService->openOrdersForIngredient($ingredient->id);
$openTotal = round((float) $openOrders->sum('ordered_quantity'), 2);
$daily = ($this->inventoryService->dailyConsumptionByIngredient([$ingredient->id]))[$ingredient->id] ?? null;
$minAlert = $ingredient->min_stock_alert !== null ? (float) $ingredient->min_stock_alert : null;
$leadDays = $ingredient->delivery_time_days !== null ? (int) $ingredient->delivery_time_days : null;
$lastPriceBySupplier = $this->lastNetPricePerSupplier($ingredient->id);
$lastPriceBySupplier = $this->stockEntryRepository->lastNetPricePerSupplier($ingredient->id);
$productStock = $this->productStockService->currentStockByProduct($ingredient->products->pluck('id')->all());
@ -138,33 +110,6 @@ class RawMaterialStockController extends Controller
]);
}
/**
* Letzter Netto-kg-Preis je Lieferant für diesen Rohstoff (für die Bestell-/Lieferantenliste).
*
* @return array<int, float> [supplier_id => price_per_kg_net]
*/
protected function lastNetPricePerSupplier(int $ingredientId): array
{
$entries = StockEntry::query()
->where('entry_type', 'ingredient')
->where('ingredient_id', $ingredientId)
->whereNotNull('supplier_id')
->whereNotNull('price_per_kg')
->orderBy('ordered_at', 'desc')
->orderBy('id', 'desc')
->get(['supplier_id', 'price_per_kg']);
$result = [];
foreach ($entries as $entry) {
$supplierId = (int) $entry->supplier_id;
if (! isset($result[$supplierId])) {
$result[$supplierId] = (float) $entry->price_per_kg;
}
}
return $result;
}
/**
* @return array<int, string> [days => label]
*/

View file

@ -25,10 +25,13 @@ class StockEntryController extends Controller
protected StockEntryRepository $stockEntryRepository
) {}
public function index(): View
public function index(Request $request): View
{
$limit = $request->boolean('all') ? null : 500;
return view('admin.inventory.stock-entries.index', array_merge($this->formSharedData(), [
'values' => $this->stockEntryRepository->listForIndex(),
'values' => $this->stockEntryRepository->listForIndex($limit),
'listLimit' => $limit,
]));
}