Files
alrahma_sunday_school_api/app/Services/Grading/GradingPlacementService.php
T
2026-06-09 01:03:53 -04:00

373 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;
}
}