376 lines
13 KiB
PHP
376 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Grading;
|
|
|
|
use App\Models\ClassSection;
|
|
use App\Models\PlacementBatch;
|
|
use App\Models\PlacementLevel;
|
|
use App\Models\PlacementScore;
|
|
use App\Models\Student;
|
|
use Illuminate\Support\Facades\DB;
|
|
use RuntimeException;
|
|
|
|
class GradingPlacementService
|
|
{
|
|
public function placementContext(?int $classSectionId, string $schoolYear, ?string $placementTest, ?string $open): array
|
|
{
|
|
if (! $classSectionId) {
|
|
$showStudents = ($placementTest !== '' && $open === '1');
|
|
$students = $showStudents ? $this->fetchActiveStudentsWithSection($schoolYear) : [];
|
|
$batches = $this->fetchPlacementBatches($schoolYear);
|
|
$batchDetails = $this->fetchPlacementBatchDetails($batches, $schoolYear);
|
|
|
|
return [
|
|
'school_year' => $schoolYear,
|
|
'students' => $students,
|
|
'batches' => $batches,
|
|
'batch_details' => $batchDetails,
|
|
'placement_test' => $placementTest,
|
|
'show_students' => $showStudents,
|
|
];
|
|
}
|
|
|
|
$sectionName = ClassSection::query()->where('class_section_id', $classSectionId)->value('class_section_name');
|
|
$classId = ClassSection::query()->where('class_section_id', $classSectionId)->value('class_id');
|
|
|
|
$students = Student::getStudentInfoByClassSectionId($classSectionId, null, $schoolYear);
|
|
$studentIds = array_values(array_filter(array_map(
|
|
static fn ($row) => (int) ($row['student_id'] ?? 0),
|
|
$students
|
|
), static fn ($id) => $id > 0));
|
|
|
|
$levels = [];
|
|
if (! empty($studentIds)) {
|
|
$rows = PlacementLevel::query()
|
|
->whereIn('student_id', $studentIds)
|
|
->where('school_year', $schoolYear)
|
|
->get();
|
|
foreach ($rows as $row) {
|
|
$levels[(int) $row->student_id] = $row->level ?? null;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'class_section_id' => $classSectionId,
|
|
'class_section_name' => $sectionName ?? '',
|
|
'class_id' => $classId,
|
|
'school_year' => $schoolYear,
|
|
'students' => $students,
|
|
'levels' => $levels,
|
|
];
|
|
}
|
|
|
|
public function updatePlacementLevel(int $studentId, string $schoolYear, $level, ?int $userId): void
|
|
{
|
|
if ($studentId <= 0 || $schoolYear === '') {
|
|
throw new RuntimeException('Missing student or school year.');
|
|
}
|
|
|
|
$level = $level === '' || $level === null ? null : (int) $level;
|
|
if ($level !== null && ! in_array($level, [1, 2, 3], true)) {
|
|
throw new RuntimeException('Invalid placement level.');
|
|
}
|
|
|
|
$existing = PlacementLevel::query()
|
|
->where('student_id', $studentId)
|
|
->where('school_year', $schoolYear)
|
|
->first();
|
|
|
|
if ($level === null) {
|
|
if ($existing) {
|
|
$existing->delete();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
$payload = [
|
|
'student_id' => $studentId,
|
|
'school_year' => $schoolYear,
|
|
'level' => $level,
|
|
'updated_by' => $userId,
|
|
];
|
|
|
|
if ($existing) {
|
|
$existing->update($payload);
|
|
} else {
|
|
$payload['created_by'] = $userId;
|
|
PlacementLevel::query()->create($payload);
|
|
}
|
|
}
|
|
|
|
public function updatePlacementLevels(int $classSectionId, string $schoolYear, array $levels, ?int $userId): void
|
|
{
|
|
if ($classSectionId <= 0 || $schoolYear === '') {
|
|
throw new RuntimeException('Missing class section or school year.');
|
|
}
|
|
|
|
$students = Student::getStudentInfoByClassSectionId($classSectionId, null, $schoolYear);
|
|
$validIds = array_values(array_filter(array_map(
|
|
static fn ($row) => (int) ($row['student_id'] ?? 0),
|
|
$students
|
|
), static fn ($id) => $id > 0));
|
|
|
|
$validSet = array_flip($validIds);
|
|
$existingRows = [];
|
|
if (! empty($validIds)) {
|
|
$rows = PlacementLevel::query()
|
|
->whereIn('student_id', $validIds)
|
|
->where('school_year', $schoolYear)
|
|
->get();
|
|
foreach ($rows as $row) {
|
|
$existingRows[(int) $row->student_id] = $row;
|
|
}
|
|
}
|
|
|
|
foreach ($levels as $studentIdRaw => $levelRaw) {
|
|
$studentId = (int) $studentIdRaw;
|
|
if (! isset($validSet[$studentId])) {
|
|
continue;
|
|
}
|
|
$levelRaw = trim((string) $levelRaw);
|
|
$level = $levelRaw === '' ? null : (int) $levelRaw;
|
|
if ($level !== null && ! in_array($level, [1, 2, 3], true)) {
|
|
continue;
|
|
}
|
|
|
|
if ($level === null) {
|
|
if (isset($existingRows[$studentId])) {
|
|
$existingRows[$studentId]->delete();
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
$payload = [
|
|
'student_id' => $studentId,
|
|
'school_year' => $schoolYear,
|
|
'level' => $level,
|
|
'updated_by' => $userId,
|
|
];
|
|
|
|
if (isset($existingRows[$studentId])) {
|
|
$existingRows[$studentId]->update($payload);
|
|
} else {
|
|
$payload['created_by'] = $userId;
|
|
PlacementLevel::query()->create($payload);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function createPlacementBatch(string $schoolYear, string $placementTest, array $levels, ?int $userId): int
|
|
{
|
|
if ($schoolYear === '' || $placementTest === '') {
|
|
throw new RuntimeException('Missing placement test or school year.');
|
|
}
|
|
|
|
$students = $this->fetchActiveStudentsWithSection($schoolYear);
|
|
$validIds = array_values(array_filter(array_map(
|
|
static fn ($row) => (int) ($row['student_id'] ?? 0),
|
|
$students
|
|
), static fn ($id) => $id > 0));
|
|
$validSet = array_flip($validIds);
|
|
|
|
$batch = PlacementBatch::query()->create([
|
|
'placement_test' => $placementTest,
|
|
'school_year' => $schoolYear,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
]);
|
|
|
|
$savedCount = 0;
|
|
foreach ($levels as $studentIdRaw => $levelRaw) {
|
|
$studentId = (int) $studentIdRaw;
|
|
if (! isset($validSet[$studentId])) {
|
|
continue;
|
|
}
|
|
$levelRaw = trim((string) $levelRaw);
|
|
if ($levelRaw === '') {
|
|
continue;
|
|
}
|
|
$score = (int) $levelRaw;
|
|
if ($score < 0 || $score > 100) {
|
|
continue;
|
|
}
|
|
|
|
PlacementScore::query()->create([
|
|
'batch_id' => (int) $batch->id,
|
|
'student_id' => $studentId,
|
|
'score' => $score,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
]);
|
|
$savedCount++;
|
|
}
|
|
|
|
if ($savedCount === 0) {
|
|
$batch->delete();
|
|
throw new RuntimeException('No scores entered. Batch not saved.');
|
|
}
|
|
|
|
return (int) $batch->id;
|
|
}
|
|
|
|
public function getPlacementBatch(int $batchId): array
|
|
{
|
|
$batch = PlacementBatch::query()->find($batchId);
|
|
if (! $batch) {
|
|
throw new RuntimeException('Placement batch not found.');
|
|
}
|
|
|
|
$schoolYear = (string) ($batch->school_year ?? '');
|
|
$students = $this->fetchActiveStudentsWithSection($schoolYear);
|
|
$scores = $this->fetchPlacementScoresForBatch($batchId);
|
|
|
|
return [
|
|
'batch' => $batch,
|
|
'students' => $students,
|
|
'scores' => $scores,
|
|
];
|
|
}
|
|
|
|
public function updatePlacementBatch(int $batchId, string $schoolYear, array $levels, ?int $userId): void
|
|
{
|
|
$batch = PlacementBatch::query()->find($batchId);
|
|
if (! $batch) {
|
|
throw new RuntimeException('Placement batch not found.');
|
|
}
|
|
|
|
$students = $this->fetchActiveStudentsWithSection($schoolYear);
|
|
$validIds = array_values(array_filter(array_map(
|
|
static fn ($row) => (int) ($row['student_id'] ?? 0),
|
|
$students
|
|
), static fn ($id) => $id > 0));
|
|
|
|
$validSet = array_flip($validIds);
|
|
$existing = $this->fetchPlacementScoresForBatch($batchId);
|
|
|
|
foreach ($levels as $studentIdRaw => $scoreRaw) {
|
|
$studentId = (int) $studentIdRaw;
|
|
if (! isset($validSet[$studentId])) {
|
|
continue;
|
|
}
|
|
$scoreRaw = trim((string) $scoreRaw);
|
|
if ($scoreRaw === '') {
|
|
if (isset($existing[$studentId])) {
|
|
PlacementScore::query()->whereKey($existing[$studentId]['id'])->delete();
|
|
}
|
|
|
|
continue;
|
|
}
|
|
$score = (int) $scoreRaw;
|
|
if ($score < 0 || $score > 100) {
|
|
continue;
|
|
}
|
|
|
|
if (isset($existing[$studentId])) {
|
|
PlacementScore::query()->whereKey($existing[$studentId]['id'])->update([
|
|
'score' => $score,
|
|
'updated_by' => $userId,
|
|
]);
|
|
} else {
|
|
PlacementScore::query()->create([
|
|
'batch_id' => $batchId,
|
|
'student_id' => $studentId,
|
|
'score' => $score,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
]);
|
|
}
|
|
}
|
|
|
|
$batch->update([
|
|
'updated_by' => $userId,
|
|
]);
|
|
}
|
|
|
|
private function fetchActiveStudentsWithSection(string $schoolYear): array
|
|
{
|
|
return DB::table('students as s')
|
|
->select('s.id as student_id', 's.school_id', 's.firstname', 's.lastname', 'sc.class_section_id', 'cs.class_section_name', 'c.class_name')
|
|
->leftJoin('student_class as sc', function ($join) use ($schoolYear) {
|
|
$join->on('sc.student_id', '=', 's.id')
|
|
->where('sc.school_year', '=', $schoolYear);
|
|
})
|
|
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
|
|
->leftJoin('classes as c', 'c.id', '=', 'cs.class_id')
|
|
->where('s.is_active', 1)
|
|
->orderBy('s.lastname', 'ASC')
|
|
->orderBy('s.firstname', 'ASC')
|
|
->get()
|
|
->map(fn ($row) => (array) $row)
|
|
->all();
|
|
}
|
|
|
|
private function fetchPlacementBatches(string $schoolYear): array
|
|
{
|
|
return PlacementBatch::query()
|
|
->where('school_year', $schoolYear)
|
|
->orderBy('created_at', 'DESC')
|
|
->get()
|
|
->map(fn ($row) => (array) $row)
|
|
->all();
|
|
}
|
|
|
|
private function fetchPlacementBatchDetails(array $batches, string $schoolYear): array
|
|
{
|
|
if (empty($batches)) {
|
|
return [];
|
|
}
|
|
|
|
$batchIds = array_values(array_filter(array_map(
|
|
static fn ($row) => (int) ($row['id'] ?? 0),
|
|
$batches
|
|
), static fn ($id) => $id > 0));
|
|
|
|
if (empty($batchIds)) {
|
|
return [];
|
|
}
|
|
|
|
$rows = DB::table('placement_scores as ps')
|
|
->select('ps.batch_id', 'ps.score', 's.school_id', 's.firstname', 's.lastname', 'cs.class_section_name', 'c.class_name')
|
|
->join('students as s', 's.id', '=', 'ps.student_id')
|
|
->leftJoin('student_class as sc', function ($join) use ($schoolYear) {
|
|
$join->on('sc.student_id', '=', 's.id')
|
|
->where('sc.school_year', '=', $schoolYear);
|
|
})
|
|
->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
|
|
->leftJoin('classes as c', 'c.id', '=', 'cs.class_id')
|
|
->whereIn('ps.batch_id', $batchIds)
|
|
->where('s.is_active', 1)
|
|
->orderBy('ps.batch_id', 'ASC')
|
|
->orderBy('s.lastname', 'ASC')
|
|
->orderBy('s.firstname', 'ASC')
|
|
->get()
|
|
->map(fn ($row) => (array) $row)
|
|
->all();
|
|
|
|
$details = [];
|
|
foreach ($rows as $row) {
|
|
$bid = (int) ($row['batch_id'] ?? 0);
|
|
if ($bid <= 0) {
|
|
continue;
|
|
}
|
|
$details[$bid][] = $row;
|
|
}
|
|
|
|
return $details;
|
|
}
|
|
|
|
private function fetchPlacementScoresForBatch(int $batchId): array
|
|
{
|
|
$rows = PlacementScore::query()
|
|
->where('batch_id', $batchId)
|
|
->get()
|
|
->map(fn ($row) => (array) $row)
|
|
->all();
|
|
|
|
$scores = [];
|
|
foreach ($rows as $row) {
|
|
$scores[(int) $row['student_id']] = $row;
|
|
}
|
|
|
|
return $scores;
|
|
}
|
|
}
|