100 lines
3.7 KiB
PHP
100 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Grading\Validation;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class GradebookFinalizationValidator
|
|
{
|
|
/**
|
|
* Strong-mode validation only. Legacy mode deliberately remains displayable/lockable
|
|
* so historical records are not reinterpreted by the new policy.
|
|
*/
|
|
public function validateClassSectionBeforeLock(int $classSectionId, string $semester, string $schoolYear): array
|
|
{
|
|
$errors = [];
|
|
|
|
foreach ($this->scoreTables() as $table => $meta) {
|
|
if (! DB::getSchemaBuilder()->hasTable($table)) {
|
|
continue;
|
|
}
|
|
|
|
$query = DB::table($table)
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear);
|
|
|
|
if (DB::getSchemaBuilder()->hasColumn($table, 'status')) {
|
|
$pending = (clone $query)->where(function ($q) {
|
|
$q->whereNull('status')->orWhere('status', 'pending');
|
|
})->get();
|
|
|
|
foreach ($pending as $row) {
|
|
$errors[] = $this->error($table, $meta['category'], $row, 'pending_score', 'Score must be marked scored, missing, excused, or not_assigned before strong-mode lock.');
|
|
}
|
|
}
|
|
|
|
if (DB::getSchemaBuilder()->hasColumn($table, 'max_points')) {
|
|
$invalidMax = (clone $query)->where(function ($q) {
|
|
$q->whereNull('max_points')->orWhere('max_points', '<=', 0);
|
|
})->get();
|
|
|
|
foreach ($invalidMax as $row) {
|
|
$errors[] = $this->error($table, $meta['category'], $row, 'invalid_max_points', 'Max points must be greater than 0.');
|
|
}
|
|
|
|
$invalidScores = (clone $query)
|
|
->whereNotNull('score')
|
|
->where(function ($q) {
|
|
$q->where('score', '<', 0)->orWhereRaw('score > max_points');
|
|
})
|
|
->get();
|
|
|
|
foreach ($invalidScores as $row) {
|
|
$errors[] = $this->error($table, $meta['category'], $row, 'score_out_of_range', 'Score must be between 0 and max_points.');
|
|
}
|
|
} else {
|
|
$invalidScores = (clone $query)
|
|
->whereNotNull('score')
|
|
->where(function ($q) {
|
|
$q->where('score', '<', 0)->orWhere('score', '>', 100);
|
|
})
|
|
->get();
|
|
|
|
foreach ($invalidScores as $row) {
|
|
$errors[] = $this->error($table, $meta['category'], $row, 'score_out_of_range', 'Score must be between 0 and 100.');
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'valid' => empty($errors),
|
|
'errors' => $errors,
|
|
];
|
|
}
|
|
|
|
private function scoreTables(): array
|
|
{
|
|
return [
|
|
'homework' => ['category' => 'homework'],
|
|
'quiz' => ['category' => 'quiz'],
|
|
'project' => ['category' => 'project'],
|
|
'participation' => ['category' => 'participation'],
|
|
'midterm_exam' => ['category' => 'midterm_exam'],
|
|
'final_exam' => ['category' => 'final_exam'],
|
|
];
|
|
}
|
|
|
|
private function error(string $table, string $category, object $row, string $code, string $message): array
|
|
{
|
|
return [
|
|
'table' => $table,
|
|
'category' => $category,
|
|
'student_id' => isset($row->student_id) ? (int) $row->student_id : null,
|
|
'item_index' => $row->homework_index ?? $row->quiz_index ?? $row->project_index ?? null,
|
|
'code' => $code,
|
|
'message' => $message,
|
|
];
|
|
}
|
|
}
|