with(['tenant', 'actor']) ->orderByDesc('created_at'); // 대상 타입 필터 (기본: Stock) $targetType = $request->input('target_type', 'Stock'); if ($targetType) { $query->where('target_type', $targetType); } // 액션 필터 if ($request->filled('action')) { $query->where('action', $request->action); } // 테넌트 필터 if ($request->filled('tenant_id')) { $query->where('tenant_id', $request->tenant_id); } // 날짜 범위 필터 if ($request->filled('from')) { $query->where('created_at', '>=', $request->from.' 00:00:00'); } if ($request->filled('to')) { $query->where('created_at', '<=', $request->to.' 23:59:59'); } // 검색 (LOT 번호, 참조 ID) if ($request->filled('search')) { $search = $request->search; $query->where(function ($q) use ($search) { $q->whereRaw("JSON_EXTRACT(after, '$.lot_no') LIKE ?", ["%{$search}%"]) ->orWhereRaw("JSON_EXTRACT(after, '$.reference_id') = ?", [$search]); }); } // 통계 $stats = $this->getStats($targetType); // 페이지네이션 $logs = $query->paginate(50)->withQueryString(); // 테넌트 목록 (필터용) $tenants = Tenant::orderBy('company_name')->get(['id', 'company_name as name']); // 액션 목록 $actions = AuditLog::STOCK_ACTIONS; return view('audit-logs.index', compact('logs', 'stats', 'tenants', 'actions', 'targetType')); } /** * 감사 로그 상세 */ public function show(int $id): View { $log = AuditLog::with(['tenant', 'actor'])->findOrFail($id); return view('audit-logs.show', compact('log')); } /** * 통계 조회 */ private function getStats(?string $targetType): array { $baseQuery = AuditLog::query(); if ($targetType) { $baseQuery->where('target_type', $targetType); } $total = (clone $baseQuery)->count(); $byAction = (clone $baseQuery) ->selectRaw('action, COUNT(*) as count') ->groupBy('action') ->pluck('count', 'action') ->toArray(); // 오늘 기록 수 $today = (clone $baseQuery) ->whereDate('created_at', today()) ->count(); return [ 'total' => $total, 'today' => $today, 'by_action' => $byAction, ]; } }