query('all') ? null : self::INDEX_LIMIT; // Ohne payload (Review 20.07.2026): der Inhalt wird erst im Modal per show() // nachgeladen, statt für alle Zeilen mehrere MB JSON in die Seite zu rendern. $logs = ApiOrderLog::query() ->with(['shoppingUser:id,billing_firstname,billing_lastname']) ->select(['id', 'shopping_order_id', 'shopping_user_id', 'wp_order_number', 'positions_count', 'has_errors', 'error_summary', 'created_at']) ->orderByDesc('id') ->when($limit !== null, fn ($query) => $query->limit($limit)) ->get(); $errorCount = ApiOrderLog::query()->where('has_errors', true)->count(); $logTotal = ApiOrderLog::query()->count(); // SKU-Konsistenz (Plan §4): doppelte WP-Nummern brechen die Zuordnung, // aktive Produkte ohne WP-Nummer können nie per API zugeordnet werden. $duplicateNumbers = Product::query() ->where('active', true) ->whereNotNull('wp_number') ->where('wp_number', '!=', 0) ->selectRaw('wp_number, COUNT(*) as sku_count') ->groupBy('wp_number') ->havingRaw('COUNT(*) > 1') ->pluck('wp_number'); $duplicateSkuProducts = $duplicateNumbers->isEmpty() ? collect() : Product::query() ->where('active', true) ->whereIn('wp_number', $duplicateNumbers) ->orderBy('wp_number') ->orderBy('name') ->get(['id', 'name', 'wp_number']); $missingSkuProducts = Product::query() ->where('active', true) ->where(fn ($query) => $query->whereNull('wp_number')->orWhere('wp_number', 0)) ->orderBy('name') ->get(['id', 'name']); return view('admin.inventory.api-orders.index', [ 'logs' => $logs, 'listLimit' => $limit, 'listTotal' => $logTotal, 'errorCount' => $errorCount, 'duplicateSkuProducts' => $duplicateSkuProducts, 'missingSkuProducts' => $missingSkuProducts, ]); } /** * Inhalt einer Übertragung on-demand fürs Modal (Review 20.07.2026): liefert die * Positionen bzw. den Roh-Body als JSON, damit die Liste ohne payload auskommt. */ public function show(ApiOrderLog $apiOrderLog) { return response()->json([ 'lines' => $apiOrderLog->payload['lines'] ?? [], 'raw' => isset($apiOrderLog->payload['raw']) ? json_encode($apiOrderLog->payload['raw'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : null, ]); } }