diff --git a/app/Http/Controllers/Admin/Inventory/NoticeController.php b/app/Http/Controllers/Admin/Inventory/NoticeController.php index 321e545..1e3c28b 100644 --- a/app/Http/Controllers/Admin/Inventory/NoticeController.php +++ b/app/Http/Controllers/Admin/Inventory/NoticeController.php @@ -37,7 +37,8 @@ class NoticeController extends Controller return view('admin.inventory.notices.index', [ 'title' => $title, 'subtitle' => $subtitle, - 'content' => Str::markdown($markdown), + // Audit N-15: Roh-HTML im Markdown strippen (Defense-in-Depth). + 'content' => Str::markdown($markdown, ['html_input' => 'strip', 'allow_unsafe_links' => false]), ]); } } diff --git a/app/Http/Controllers/Admin/Inventory/ProductionController.php b/app/Http/Controllers/Admin/Inventory/ProductionController.php index 19c5b14..a5a23dc 100644 --- a/app/Http/Controllers/Admin/Inventory/ProductionController.php +++ b/app/Http/Controllers/Admin/Inventory/ProductionController.php @@ -181,6 +181,11 @@ class ProductionController extends Controller 'outputs', ]); + // Audit N-8: Chargen-Nummern nicht in die Kopie übernehmen — das neue + // Produktionsdatum erzeugt sonst Duplikat-Chargen mit falschem Datum; + // leer ⇒ JS-Vorschlag (Präfix + ttmmjj) greift. + $production->outputs->each(fn ($output) => $output->setAttribute('batch_number', null)); + $defaultLocationId = $production->location_id; return view('admin.inventory.productions.create', [ diff --git a/app/Http/Controllers/Admin/Inventory/RawMaterialStockController.php b/app/Http/Controllers/Admin/Inventory/RawMaterialStockController.php index 57dee78..3d614fe 100644 --- a/app/Http/Controllers/Admin/Inventory/RawMaterialStockController.php +++ b/app/Http/Controllers/Admin/Inventory/RawMaterialStockController.php @@ -59,7 +59,8 @@ class RawMaterialStockController extends Controller return view('admin.inventory.raw-material-stock.index', [ 'rows' => $rows, - 'criticalCount' => $rows->where('status', 'critical')->count(), + // Audit N-4: gleiche Zählweise wie die Kacheln (kritisch inkl. bereits bestellt). + 'criticalCount' => $rows->whereIn('status', ['critical', 'critical_ordered'])->count(), 'horizonOptions' => $this->horizonOptions(), 'defaultHorizon' => 90, 'scope' => $scope, diff --git a/app/Http/Controllers/Admin/Inventory/StockDisposalController.php b/app/Http/Controllers/Admin/Inventory/StockDisposalController.php index ce09455..32f5169 100644 --- a/app/Http/Controllers/Admin/Inventory/StockDisposalController.php +++ b/app/Http/Controllers/Admin/Inventory/StockDisposalController.php @@ -32,9 +32,13 @@ class StockDisposalController extends Controller $query->where('disposal_type', $request->query('type')); } + // Audit N-5: Limit sichtbar machen statt still abzuschneiden (analog OPT-3). + $limit = $request->boolean('all') ? null : 500; + return view('admin.inventory.stock-disposals.index', [ - 'values' => $query->limit(500)->get(), + 'values' => $limit !== null ? $query->limit($limit)->get() : $query->get(), 'typeFilter' => $request->query('type'), + 'listLimit' => $limit, ]); } diff --git a/app/Repositories/StockEntryRepository.php b/app/Repositories/StockEntryRepository.php index 88103d5..627d5c5 100644 --- a/app/Repositories/StockEntryRepository.php +++ b/app/Repositories/StockEntryRepository.php @@ -130,6 +130,9 @@ class StockEntryRepository $ranked = StockEntry::query() ->selectRaw('supplier_id, price_per_kg, ROW_NUMBER() OVER (PARTITION BY supplier_id ORDER BY ordered_at DESC, id DESC) as rn') ->where('entry_type', 'ingredient') + // Audit N-2: nur tatsächliche Eingänge — offene, evtl. nie gelieferte + // Bestellungen bestimmen den letzten Einkaufspreis nicht mehr. + ->where('status', 'received') ->where('ingredient_id', $ingredientId) ->whereNotNull('supplier_id') ->whereNotNull('price_per_kg'); diff --git a/app/Services/InventoryService.php b/app/Services/InventoryService.php index e87849b..8d4ac48 100644 --- a/app/Services/InventoryService.php +++ b/app/Services/InventoryService.php @@ -241,8 +241,10 @@ class InventoryService */ public function dailyConsumptionByIngredient(?array $ingredientIds = null, int $windowDays = self::CONSUMPTION_WINDOW_DAYS): array { + // Audit N-3: exaktes Fenster — startOfDay() verlängerte den Zeitraum um + // den angebrochenen heutigen Tag (Tagesverbrauch bis ~1 % überschätzt). $windowDays = max(1, $windowDays); - $since = Carbon::now()->subDays($windowDays)->startOfDay(); + $since = Carbon::now()->subDays($windowDays); $rows = ProductionIngredient::query() ->join('productions', 'productions.id', '=', 'production_ingredients.production_id') diff --git a/app/Services/ProductStockService.php b/app/Services/ProductStockService.php index 959f8e9..80a6d8e 100644 --- a/app/Services/ProductStockService.php +++ b/app/Services/ProductStockService.php @@ -105,7 +105,10 @@ class ProductStockService return; } - $products = Product::query()->whereIn('id', $productIds)->get()->keyBy('id'); + // Audit N-9: withTrashed — Gegenbuchungen entfernter Output-Produkte + // dürfen nicht still übersprungen werden, wenn das Produkt inzwischen + // soft-gelöscht wurde. + $products = Product::withTrashed()->whereIn('id', $productIds)->get()->keyBy('id'); foreach ($productIds as $productId) { $alreadyBooked = (int) ($booked[$productId] ?? 0); diff --git a/app/Services/ProductionService.php b/app/Services/ProductionService.php index c17a22c..4cee7d2 100644 --- a/app/Services/ProductionService.php +++ b/app/Services/ProductionService.php @@ -629,12 +629,15 @@ class ProductionService if ($value === null || $value === '') { return 0.0; } - if (is_numeric($value)) { + // Audit N-7: Zahlen (interne Aufrufer) direkt; Strings konsequent im + // deutschen Format parsen — vorher wich „1.500" (Server: 1,5) von der + // UI-Ampel (1500) ab. + if (is_int($value) || is_float($value)) { return (float) $value; } $n = reFormatNumber((string) $value); - return $n !== null && $n !== '' ? (float) $n : 0.0; + return $n !== null && $n !== '' && is_numeric($n) ? (float) $n : 0.0; } } diff --git a/resources/views/admin/inventory/raw-material-stock/index.blade.php b/resources/views/admin/inventory/raw-material-stock/index.blade.php index c326fa8..905db29 100644 --- a/resources/views/admin/inventory/raw-material-stock/index.blade.php +++ b/resources/views/admin/inventory/raw-material-stock/index.blade.php @@ -2,7 +2,6 @@ @php $total = $rows->count(); - $criticalCount = $rows->whereIn('status', ['critical', 'critical_ordered'])->count(); $warningCount = $rows->where('status', 'warning')->count(); $okCount = $total - $criticalCount - $warningCount; @endphp diff --git a/resources/views/admin/inventory/stock-disposals/index.blade.php b/resources/views/admin/inventory/stock-disposals/index.blade.php index ae79c81..0d9e215 100644 --- a/resources/views/admin/inventory/stock-disposals/index.blade.php +++ b/resources/views/admin/inventory/stock-disposals/index.blade.php @@ -95,6 +95,12 @@ + @if(($listLimit ?? null) !== null && $values->count() >= $listLimit) +
+ {{ __('Es werden die neuesten :n Abgänge angezeigt.', ['n' => $listLimit]) }} + {{ __('Alle anzeigen') }} +
+ @endif @endsection