303 lines
11 KiB
PHP
303 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Exams;
|
|
|
|
use App\Models\ClassSection;
|
|
use App\Models\ExamDraft;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class ExamDraftAdminService
|
|
{
|
|
public function __construct(
|
|
private ExamDraftConfigService $configService,
|
|
private ExamDraftFileService $fileService
|
|
) {
|
|
}
|
|
|
|
public function list(): array
|
|
{
|
|
$context = $this->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, \Illuminate\Http\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, ?\Illuminate\Http\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;
|
|
}
|
|
}
|