withCount('versions'); if ($request->filled('category_id')) { $query->where('category_id', $request->category_id); } if ($request->filled('status')) { $query->where('status', $request->status); } if ($request->filled('search')) { $query->where('title', 'like', '%' . $request->search . '%'); } $documents = $query->latest()->paginate(15)->withQueryString(); $categories = Category::active()->ordered()->get(); return view('admin.documents.index', compact('documents', 'categories')); } public function create(): View { $categories = Category::active()->ordered()->get(); return view('admin.documents.create', compact('categories')); } public function store(StoreDocumentRequest $request, CreateDocumentAction $action): RedirectResponse { try { $document = $action->execute( $request->validated(), $request->file('file') ); return redirect() ->route('admin.documents.show', $document) ->with('success', "Dokumen '{$document->title}' berjaya diupload dan sedang diproses."); } catch (RuntimeException $e) { return back() ->withInput() ->with('error', $e->getMessage()); } } public function show(Document $document): View { $document->load([ 'category', 'versions.uploader', 'currentVersion.chunks' => fn($q) => $q->limit(20), ]); return view('admin.documents.show', compact('document')); } public function edit(Document $document): View { $categories = Category::active()->ordered()->get(); return view('admin.documents.edit', compact('document', 'categories')); } public function update(Request $request, Document $document): RedirectResponse { $validated = $request->validate([ 'title' => ['required', 'string', 'max:255'], 'description' => ['nullable', 'string', 'max:1000'], 'category_id' => ['required', 'exists:categories,id'], 'effective_date' => ['nullable', 'date'], 'expiry_date' => ['nullable', 'date'], 'tags' => ['nullable', 'array'], 'language' => ['nullable', 'in:ms,en'], ]); $validated['updated_by'] = auth()->id(); $document->update($validated); return redirect() ->route('admin.documents.show', $document) ->with('success', 'Maklumat dokumen berjaya dikemaskini.'); } /** * Upload versi baru untuk dokumen yang sedia ada. */ public function uploadVersion( Request $request, Document $document, UploadNewVersionAction $action ): RedirectResponse { $request->validate([ 'file' => ['required', 'file', 'mimes:pdf', 'max:' . config('knowledgebase.upload.max_file_size', 20480)], 'change_notes' => ['nullable', 'string', 'max:500'], ]); try { $action->execute($document, $request->file('file'), $request->only('change_notes')); return redirect() ->route('admin.documents.show', $document) ->with('success', 'Versi baru berjaya diupload dan sedang diproses.'); } catch (RuntimeException $e) { return back()->with('error', $e->getMessage()); } } /** * Toggle status aktif/tidak aktif dokumen. * Apabila dinyahaktifkan, chunk dalam Qdrant juga dimatikan. */ public function toggleStatus(Document $document): RedirectResponse { $newStatus = !$document->is_active; if ($newStatus) { // Aktifkan — versi semasa mesti sudah indexed $currentVersion = $document->currentVersion; if (!$currentVersion || !$currentVersion->isProcessed()) { return back()->with('error', 'Dokumen tidak boleh diaktifkan kerana pemprosesan belum selesai.' ); } $document->update([ 'is_active' => true, 'status' => Document::STATUS_ACTIVE, ]); $this->auditService->documentActivated($document); } else { // Deactivate — matikan juga dalam Qdrant $document->update([ 'is_active' => false, 'status' => Document::STATUS_INACTIVE, ]); if ($currentVersion = $document->currentVersion) { $this->ingestionService->deactivateVersionInQdrant($currentVersion); } $this->auditService->documentDeactivated($document); } $status = $newStatus ? 'diaktifkan' : 'dinyahaktifkan'; return back()->with('success', "Dokumen '{$document->title}' telah {$status}."); } /** * Trigger reindex untuk versi tertentu. */ public function reindex(Document $document): RedirectResponse { $currentVersion = $document->currentVersion; if (!$currentVersion) { return back()->with('error', 'Tiada versi semasa untuk diindeks semula.'); } ReindexDocumentJob::dispatch($currentVersion->id); $this->auditService->documentReindexed($document, $currentVersion); return back()->with('success', 'Reindeks telah dimulakan. Sila semak semula sebentar lagi.'); } /** * Download PDF asal. */ public function download(Document $document, DocumentVersion $version) { abort_if($version->document_id !== $document->id, 404); $disk = config('knowledgebase.upload.storage_disk', 'local'); if (!Storage::disk($disk)->exists($version->stored_path)) { abort(404, 'Fail tidak dijumpai.'); } return Storage::disk($disk)->download( $version->stored_path, $version->original_filename ); } }