configService->context(); $teacherUserColumn = (string) ($context['teacher_user_column'] ?? ''); $adminUserColumn = (string) ($context['admin_user_column'] ?? ''); $draftsQuery = DB::table('exam_drafts as ed') ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ed.class_section_id') ->select( 'ed.*', 'cs.class_section_name', ); if ($teacherUserColumn !== '') { $draftsQuery ->leftJoin('users as u', 'u.id', '=', "ed.{$teacherUserColumn}") ->addSelect( 'u.firstname as teacher_first', 'u.lastname as teacher_last' ); } else { $draftsQuery->addSelect( DB::raw('NULL as teacher_first'), DB::raw('NULL as teacher_last') ); } if ($adminUserColumn !== '') { $draftsQuery ->leftJoin('users as a', 'a.id', '=', "ed.{$adminUserColumn}") ->addSelect( 'a.firstname as admin_first', 'a.lastname as admin_last' ); } else { $draftsQuery->addSelect( DB::raw('NULL as admin_first'), DB::raw('NULL as admin_last') ); } $drafts = $draftsQuery ->orderByDesc('ed.created_at') ->get() ->map(fn ($r) => (array) $r) ->all(); $classSections = ClassSection::query() ->select('class_section_id', 'class_section_name') ->whereExists(function ($query) use ($context) { $query->selectRaw('1') ->from('student_class as sc') ->join('students as s', 's.id', '=', 'sc.student_id') ->whereColumn('sc.class_section_id', 'classSection.class_section_id') ->where('s.is_active', 1); $schoolYear = (string) ($context['school_year'] ?? ''); if ($schoolYear !== '') { $query->where('sc.school_year', $schoolYear); } }) ->orderBy('class_section_name') ->get() ->toArray(); $legacyByClass = []; if ($context['has_legacy'] ?? false) { foreach ($drafts as $row) { $isLegacy = ! empty($row['is_legacy']); if (! $isLegacy) { continue; } $cid = (int) ($row['class_section_id'] ?? 0); if (! isset($legacyByClass[$cid])) { $legacyByClass[$cid] = [ 'class_section_id' => $cid, 'class_section_name' => $row['class_section_name'] ?? 'Class '.$cid, 'items' => [], ]; } $legacyByClass[$cid]['items'][] = $row; } } return [ 'drafts' => $drafts, 'classSections' => $classSections, 'legacyByClass' => $legacyByClass, 'examTypes' => ExamDraftConfigService::EXAM_TYPES, 'statusOptions' => ExamDraftConfigService::STATUS_OPTIONS, 'schoolYear' => (string) ($context['school_year'] ?? ''), 'semester' => (string) ($context['semester'] ?? ''), 'allowedExtensions' => ExamDraftConfigService::ADMIN_ALLOWED_EXTENSIONS, 'maxUploadBytes' => ExamDraftConfigService::MAX_UPLOAD_BYTES, ]; } public function uploadLegacy(int $adminId, array $payload, UploadedFile $file): array { $context = $this->configService->context(); $adminUserColumn = (string) ($context['admin_user_column'] ?? ''); $classSectionId = (int) ($payload['class_section_id'] ?? 0); $schoolYear = trim((string) ($payload['school_year'] ?? $context['school_year'] ?? '')); $semester = trim((string) ($payload['semester'] ?? $context['semester'] ?? '')); $examType = trim((string) ($payload['exam_type'] ?? '')); if ($classSectionId <= 0 || $schoolYear === '' || $semester === '') { return ['ok' => false, 'message' => 'Select class section, school year, and semester.']; } $stored = $this->fileService->storeUploadedFile( $file, ExamDraftConfigService::FINAL_UPLOAD_DIR, ExamDraftConfigService::ADMIN_ALLOWED_EXTENSIONS, ExamDraftConfigService::MAX_UPLOAD_BYTES ); if ($stored === null) { return ['ok' => false, 'message' => 'File type not allowed or upload failed.']; } $pdfName = $stored['extension'] === 'pdf' ? $stored['stored'] : $this->fileService->ensurePdfExists( $stored['stored'], $stored['extension'], ExamDraftConfigService::FINAL_UPLOAD_DIR ); if ($pdfName === null) { return ['ok' => false, 'message' => 'Unable to convert the uploaded exam to PDF.']; } $finalFilename = $stored['extension'] === 'pdf' ? $stored['original'] : (pathinfo($stored['original'], PATHINFO_FILENAME).'.pdf'); $payload = [ 'class_section_id' => $classSectionId, 'semester' => ucfirst(strtolower($semester)), 'school_year' => $schoolYear, 'exam_type' => $examType !== '' ? $examType : null, 'draft_title' => $examType !== '' ? $examType : 'Legacy Exam', 'final_file' => $pdfName, 'status' => 'finalized', 'version' => 1, ]; if ($context['has_teacher_id'] ?? false) { $payload['teacher_id'] = $adminId; } if ($context['has_author_id'] ?? false) { $payload['author_id'] = $adminId; } if ($context['has_final_filename'] ?? false) { $payload['final_filename'] = $finalFilename; } if ($context['has_admin_id'] ?? false) { $payload['admin_id'] = $adminId; } elseif ($adminUserColumn === 'reviewer_id') { $payload['reviewer_id'] = $adminId; } if ($context['has_reviewed_at'] ?? false) { $payload['reviewed_at'] = now(); } if ($context['has_legacy'] ?? false) { $payload['is_legacy'] = 1; } if ($context['has_final_pdf'] ?? false) { $payload['final_pdf_file'] = $pdfName; } $draft = ExamDraft::query()->create($payload); return ['ok' => true, 'draft' => $draft->toArray()]; } public function review(int $adminId, array $payload, ?UploadedFile $file): array { $draftId = (int) ($payload['draft_id'] ?? 0); if ($draftId <= 0) { return ['ok' => false, 'message' => 'Invalid submission selected.']; } $draft = ExamDraft::query()->find($draftId); if (! $draft) { return ['ok' => false, 'message' => 'Submission not found.']; } $comments = trim((string) ($payload['admin_comments'] ?? '')); $statusInput = trim((string) ($payload['review_status'] ?? '')); $currentStatus = (string) ($draft->status ?? 'draft'); $status = $this->normalizeStatus($statusInput !== '' ? $statusInput : $currentStatus, $currentStatus); if (! in_array($status, ExamDraftConfigService::STATUS_OPTIONS, true)) { $status = 'reviewed'; } $context = $this->configService->context(); $update = [ 'status' => $status, ]; if ($context['has_admin_comments'] ?? false) { $update['admin_comments'] = $comments !== '' ? $comments : null; } elseif ($context['has_reviewer_comments'] ?? false) { $update['reviewer_comments'] = $comments !== '' ? $comments : null; } if ($context['has_admin_id'] ?? false) { $update['admin_id'] = $adminId; } elseif (($context['admin_user_column'] ?? null) === 'reviewer_id') { $update['reviewer_id'] = $adminId; } if ($context['has_reviewed_at'] ?? false) { $update['reviewed_at'] = now(); } if ($file) { $stored = $this->fileService->storeUploadedFile( $file, ExamDraftConfigService::FINAL_UPLOAD_DIR, ExamDraftConfigService::ADMIN_ALLOWED_EXTENSIONS, ExamDraftConfigService::MAX_UPLOAD_BYTES ); if ($stored === null) { return ['ok' => false, 'message' => 'Unable to store the final draft.']; } $update['final_file'] = $stored['stored']; if ($context['has_final_filename'] ?? false) { $update['final_filename'] = $stored['original']; } $pdfName = $this->fileService->ensurePdfExists( $stored['stored'], $stored['extension'], ExamDraftConfigService::FINAL_UPLOAD_DIR ); if ($pdfName !== null && ($this->configService->context()['has_final_pdf'] ?? false)) { $update['final_pdf_file'] = $pdfName; } } elseif ($status === 'finalized' && ! empty($draft->teacher_file ?: $draft->author_file)) { $copied = $this->fileService->copyDraftToFinal( (string) ($draft->teacher_file ?? $draft->author_file), ExamDraftConfigService::TEACHER_UPLOAD_DIR, ExamDraftConfigService::FINAL_UPLOAD_DIR ); if ($copied !== null) { $update['final_file'] = $copied; if ($context['has_final_filename'] ?? false) { $update['final_filename'] = $draft->teacher_filename ?? $draft->author_filename ?? $draft->teacher_file ?? $draft->author_file; } $pdfName = $this->fileService->ensurePdfExists( $copied, pathinfo($copied, PATHINFO_EXTENSION), ExamDraftConfigService::FINAL_UPLOAD_DIR ); if ($pdfName !== null && ($context['has_final_pdf'] ?? false)) { $update['final_pdf_file'] = $pdfName; } } } if ($status === 'finalized' && ($context['has_legacy'] ?? false)) { $update['is_legacy'] = 1; } $draft->update($update); return ['ok' => true, 'draft' => $draft->fresh()->toArray()]; } private function normalizeStatus(string $input, string $default): string { $input = strtolower(trim($input)); $aliases = [ 'pending' => 'submitted', 'final' => 'finalized', 'approved' => 'finalized', ]; if (isset($aliases[$input])) { $input = $aliases[$input]; } return in_array($input, ExamDraftConfigService::STATUS_OPTIONS, true) ? $input : $default; } }