$request->session()->get('graduation_bulk_upload_preview'), 'documentTypes' => [ 'skl' => 'SKL', 'ijazah' => 'Ijazah', 'transcript' => 'Transkrip Nilai', 'ukk-certificate' => 'Sertifikat UKK', 'lsp-certificate' => 'Sertifikat LSP', ], 'matchByOptions' => [ 'student_number' => 'NIS', 'nisn' => 'NISN', ], ]); } public function preview(Request $request): RedirectResponse { $preview = $this->buildPreviewPayload($request); return $preview; } public function previewAjax(Request $request): JsonResponse { $preview = $this->buildPreviewPayload($request); $request->session()->put('graduation_bulk_upload_preview', $preview); return response()->json([ 'success' => true, 'redirect_url' => route('graduated-students.bulk-upload.index'), 'summary' => $preview['summary'], 'message' => 'Preview bulk upload berhasil disiapkan.', ]); } protected function buildPreviewPayload(Request $request): array { $validated = $request->validate([ 'document_type' => ['required', 'in:skl,ijazah,transcript,ukk-certificate,lsp-certificate'], 'match_by' => ['required', 'in:student_number,nisn'], 'documents' => ['required', 'array', 'min:1', 'max:10'], 'documents.*' => ['required', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'], ], [ 'documents.required' => 'Pilih minimal satu file dokumen untuk dipreview.', 'documents.max' => 'Maksimal 10 file per batch upload.', 'documents.*.mimes' => 'Dokumen hanya boleh berupa PDF, JPG, JPEG, atau PNG.', 'documents.*.max' => 'Ukuran tiap dokumen maksimal 4 MB.', ]); $graduations = StudentGraduation::query()->get()->keyBy($validated['match_by']); $rows = []; $summary = [ 'total_files' => 0, 'matched' => 0, 'unmatched' => 0, 'will_replace' => 0, 'ready_to_upload' => 0, ]; foreach ($request->file('documents', []) as $file) { $summary['total_files']++; $originalName = $file->getClientOriginalName(); $baseName = pathinfo($originalName, PATHINFO_FILENAME); $normalizedKey = trim($baseName); $graduation = $graduations->get($normalizedKey); $existingPathField = [ 'skl' => 'skl_file_path', 'ijazah' => 'ijazah_file_path', 'transcript' => 'transcript_file_path', 'ukk-certificate' => 'ukk_certificate_file_path', 'lsp-certificate' => 'lsp_certificate_file_path', ][$validated['document_type']]; $storedTempPath = $file->store('temp/graduation-bulk-upload', 'public'); $row = [ 'original_name' => $originalName, 'stored_temp_path' => $storedTempPath, 'mime_type' => $file->getMimeType(), 'file_size' => $file->getSize(), 'match_key' => $normalizedKey, 'graduation_id' => $graduation?->id, 'student_name' => $graduation?->full_name, 'identifier_value' => $graduation?->{$validated['match_by']}, 'status' => $graduation ? 'matched' : 'unmatched', 'will_replace' => $graduation ? filled($graduation->{$existingPathField}) : false, ]; if ($graduation) { $summary['matched']++; $summary['ready_to_upload']++; if ($row['will_replace']) { $summary['will_replace']++; } } else { $summary['unmatched']++; } $rows[] = $row; } $preview = [ 'token' => (string) Str::uuid(), 'document_type' => $validated['document_type'], 'document_label' => [ 'skl' => 'SKL', 'ijazah' => 'Ijazah', 'transcript' => 'Transkrip Nilai', 'ukk-certificate' => 'Sertifikat UKK', 'lsp-certificate' => 'Sertifikat LSP', ][$validated['document_type']], 'match_by' => $validated['match_by'], 'match_by_label' => $validated['match_by'] === 'student_number' ? 'NIS' : 'NISN', 'rows' => $rows, 'summary' => $summary, ]; return $preview; } public function store(Request $request): RedirectResponse { $request->validate([ 'preview_token' => ['required', 'string'], ]); $preview = $request->session()->get('graduation_bulk_upload_preview'); if (! $preview || ($preview['token'] ?? null) !== $request->string('preview_token')->value()) { return back()->withErrors([ 'preview_token' => 'Preview bulk upload sudah tidak valid. Silakan upload ulang dokumen.', ]); } $documentType = $preview['document_type']; $documentConfig = [ 'skl' => ['name' => 'skl_file_name', 'path' => 'skl_file_path', 'mime' => 'skl_mime_type', 'size' => 'skl_file_size'], 'ijazah' => ['name' => 'ijazah_file_name', 'path' => 'ijazah_file_path', 'mime' => 'ijazah_mime_type', 'size' => 'ijazah_file_size'], 'transcript' => ['name' => 'transcript_file_name', 'path' => 'transcript_file_path', 'mime' => 'transcript_mime_type', 'size' => 'transcript_file_size'], 'ukk-certificate' => ['name' => 'ukk_certificate_file_name', 'path' => 'ukk_certificate_file_path', 'mime' => 'ukk_certificate_mime_type', 'size' => 'ukk_certificate_file_size'], 'lsp-certificate' => ['name' => 'lsp_certificate_file_name', 'path' => 'lsp_certificate_file_path', 'mime' => 'lsp_certificate_mime_type', 'size' => 'lsp_certificate_file_size'], ][$documentType]; $fileNameField = $documentConfig['name']; $filePathField = $documentConfig['path']; $mimeTypeField = $documentConfig['mime']; $fileSizeField = $documentConfig['size']; $processed = 0; $skipped = 0; DB::transaction(function () use ($preview, $documentType, $fileNameField, $filePathField, $mimeTypeField, $fileSizeField, &$processed, &$skipped) { foreach ($preview['rows'] as $row) { if (($row['status'] ?? null) !== 'matched' || empty($row['graduation_id'])) { $skipped++; continue; } $graduation = StudentGraduation::query()->find($row['graduation_id']); if (! $graduation) { $skipped++; continue; } if (! Storage::disk('public')->exists($row['stored_temp_path'])) { $skipped++; continue; } if ($graduation->{$filePathField} && Storage::disk('public')->exists($graduation->{$filePathField})) { Storage::disk('public')->delete($graduation->{$filePathField}); } $extension = pathinfo($row['original_name'], PATHINFO_EXTENSION); $finalPath = "student-graduation-documents/{$graduation->student_id}/{$documentType}/" . Str::uuid() . ($extension ? '.' . $extension : ''); Storage::disk('public')->move($row['stored_temp_path'], $finalPath); $graduation->update([ $fileNameField => $row['original_name'], $filePathField => $finalPath, $mimeTypeField => $row['mime_type'], $fileSizeField => $row['file_size'], ]); $processed++; } }); foreach ($preview['rows'] as $row) { if (! empty($row['stored_temp_path']) && Storage::disk('public')->exists($row['stored_temp_path'])) { Storage::disk('public')->delete($row['stored_temp_path']); } } $request->session()->forget('graduation_bulk_upload_preview'); Alert::success('Berhasil', "Bulk upload {$preview['document_label']} selesai. {$processed} dokumen diproses, {$skipped} dilewati."); return redirect() ->route('graduated-students.bulk-upload.index') ->with('success', "Bulk upload {$preview['document_label']} selesai. {$processed} dokumen diproses, {$skipped} dilewati."); } }