438 lines
15 KiB
PHP
438 lines
15 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\PlacementBatchModel;
|
|
use App\Models\PlacementLevelModel;
|
|
use App\Models\PlacementScoreModel;
|
|
use App\Models\StudentModel;
|
|
|
|
class PlacementGradingService
|
|
{
|
|
protected $db;
|
|
protected $studentModel;
|
|
protected $placementLevelModel;
|
|
protected $placementBatchModel;
|
|
protected $placementScoreModel;
|
|
protected string $schoolYear = '';
|
|
|
|
public function __construct(
|
|
\CodeIgniter\Database\BaseConnection $db,
|
|
StudentModel $studentModel,
|
|
PlacementLevelModel $placementLevelModel,
|
|
PlacementBatchModel $placementBatchModel,
|
|
PlacementScoreModel $placementScoreModel,
|
|
string $schoolYear = ''
|
|
) {
|
|
$this->db = $db;
|
|
$this->studentModel = $studentModel;
|
|
$this->placementLevelModel = $placementLevelModel;
|
|
$this->placementBatchModel = $placementBatchModel;
|
|
$this->placementScoreModel = $placementScoreModel;
|
|
$this->schoolYear = $schoolYear;
|
|
}
|
|
|
|
public function setSchoolYear(string $schoolYear): void
|
|
{
|
|
$this->schoolYear = $schoolYear;
|
|
}
|
|
|
|
public function updatePlacementLevel(array $params = [])
|
|
{
|
|
$get = $params['get'] ?? [];
|
|
$post = $params['post'] ?? [];
|
|
|
|
$studentId = (int) (($post['student_id'] ?? null) ?? 0);
|
|
$levelRaw = trim((string) (($post['placement_level'] ?? null) ?? ''));
|
|
$schoolYear = (string) (($post['school_year'] ?? null) ?? $this->schoolYear);
|
|
|
|
if ($studentId <= 0 || $schoolYear === '') {
|
|
return ['kind' => 'flash', 'redirect' => 'back', 'type' => 'error', 'message' => 'Missing student or school year.'];
|
|
}
|
|
|
|
$level = $levelRaw === '' ? null : (int) $levelRaw;
|
|
if ($level !== null && !in_array($level, [1, 2, 3], true)) {
|
|
return ['kind' => 'flash', 'redirect' => 'back', 'type' => 'error', 'message' => 'Invalid placement level.'];
|
|
}
|
|
|
|
$existing = $this->placementLevelModel
|
|
->where('student_id', $studentId)
|
|
->first();
|
|
|
|
if ($level === null) {
|
|
if ($existing) {
|
|
$this->placementLevelModel->delete($existing['id']);
|
|
}
|
|
return ['kind' => 'flash', 'redirect' => 'back', 'type' => 'status', 'message' => 'Placement level cleared.'];
|
|
}
|
|
|
|
$payload = [
|
|
'student_id' => $studentId,
|
|
'level' => $level,
|
|
'updated_by' => session()->get('user_id'),
|
|
];
|
|
|
|
if ($existing) {
|
|
$this->placementLevelModel->update($existing['id'], $payload);
|
|
} else {
|
|
$payload['created_by'] = session()->get('user_id');
|
|
$this->placementLevelModel->insert($payload);
|
|
}
|
|
|
|
return ['kind' => 'flash', 'redirect' => 'back', 'type' => 'status', 'message' => 'Placement level updated.'];
|
|
}
|
|
|
|
public function placementPage(array $params = [])
|
|
{
|
|
$get = $params['get'] ?? [];
|
|
$post = $params['post'] ?? [];
|
|
|
|
$classSectionId = (int) (($get['class_section_id'] ?? null) ?? 0);
|
|
$schoolYear = (string) (($get['school_year'] ?? null) ?? $this->schoolYear);
|
|
$placementTest = (string) (($get['placement_test'] ?? null) ?? '');
|
|
$openFlag = (string) (($get['open'] ?? null) ?? '');
|
|
|
|
if ($classSectionId <= 0 || $schoolYear === '') {
|
|
$showStudents = ($placementTest !== '' && $openFlag === '1');
|
|
$students = $showStudents ? $this->fetchActiveStudentsWithSection($schoolYear) : [];
|
|
$batches = $this->fetchPlacementBatches($schoolYear);
|
|
$batchDetails = $this->fetchPlacementBatchDetails($batches, $schoolYear);
|
|
return ['kind' => 'view', 'view' => 'grading/placement_index', 'data' => [
|
|
'schoolYear' => $schoolYear,
|
|
'students' => $students,
|
|
'batches' => $batches,
|
|
'batchDetails' => $batchDetails,
|
|
'placementTest' => $placementTest,
|
|
'showStudents' => $showStudents,
|
|
]];
|
|
}
|
|
|
|
$sectionName = $this->classSection->getClassSectionNameBySectionId($classSectionId) ?? '';
|
|
$classId = $this->classSection->getClassId($classSectionId);
|
|
|
|
$students = $this->studentModel->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 = $this->placementLevelModel
|
|
->whereIn('student_id', $studentIds)
|
|
->findAll();
|
|
foreach ($rows as $row) {
|
|
$levels[(int) $row['student_id']] = $row['level'] ?? null;
|
|
}
|
|
}
|
|
|
|
return ['kind' => 'view', 'view' => 'grading/placement', 'data' => [
|
|
'classSectionId' => $classSectionId,
|
|
'classSectionName' => $sectionName,
|
|
'classId' => $classId,
|
|
'schoolYear' => $schoolYear,
|
|
'students' => $students,
|
|
'levels' => $levels,
|
|
]];
|
|
}
|
|
|
|
public function updatePlacementLevels(array $params = [])
|
|
{
|
|
$get = $params['get'] ?? [];
|
|
$post = $params['post'] ?? [];
|
|
|
|
$classSectionId = (int) (($post['class_section_id'] ?? null) ?? 0);
|
|
$schoolYear = (string) (($post['school_year'] ?? null) ?? $this->schoolYear);
|
|
$levels = ($post['placement_level'] ?? null) ?? [];
|
|
|
|
if ($classSectionId <= 0 || $schoolYear === '') {
|
|
return ['kind' => 'flash', 'redirect' => 'back', 'type' => 'error', 'message' => 'Missing class section or school year.'];
|
|
}
|
|
|
|
$students = $this->studentModel->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 = $this->placementLevelModel
|
|
->whereIn('student_id', $validIds)
|
|
->findAll();
|
|
foreach ($rows as $row) {
|
|
$existingRows[(int) $row['student_id']] = $row;
|
|
}
|
|
}
|
|
|
|
$userId = session()->get('user_id');
|
|
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])) {
|
|
$this->placementLevelModel->delete($existingRows[$studentId]['id']);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$payload = [
|
|
'student_id' => $studentId,
|
|
'level' => $level,
|
|
'updated_by' => $userId,
|
|
];
|
|
|
|
if (isset($existingRows[$studentId])) {
|
|
$this->placementLevelModel->update($existingRows[$studentId]['id'], $payload);
|
|
} else {
|
|
$payload['created_by'] = $userId;
|
|
$this->placementLevelModel->insert($payload);
|
|
}
|
|
}
|
|
|
|
return ['kind' => 'flash', 'redirect' => 'back', 'type' => 'status', 'message' => 'Placement levels updated.'];
|
|
}
|
|
|
|
public function updatePlacementLevelsAll(array $params = [])
|
|
{
|
|
$get = $params['get'] ?? [];
|
|
$post = $params['post'] ?? [];
|
|
|
|
$schoolYear = (string) (($post['school_year'] ?? null) ?? $this->schoolYear);
|
|
$placementTest = (string) (($post['placement_test'] ?? null) ?? '');
|
|
$levels = ($post['placement_level'] ?? null) ?? [];
|
|
|
|
if ($schoolYear === '' || $placementTest === '') {
|
|
return ['kind' => 'flash', 'redirect' => 'back', 'type' => 'error', 'message' => '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);
|
|
$userId = session()->get('user_id');
|
|
|
|
$batchId = $this->placementBatchModel->insert([
|
|
'placement_test' => $placementTest,
|
|
'school_year' => $schoolYear,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
]);
|
|
|
|
if (!$batchId) {
|
|
return ['kind' => 'flash', 'redirect' => 'back', 'type' => 'error', 'message' => 'Unable to create placement batch.'];
|
|
}
|
|
|
|
$savedCount = 0;
|
|
foreach ($levels as $studentIdRaw => $levelRaw) {
|
|
$studentId = (int) $studentIdRaw;
|
|
if (!isset($validSet[$studentId])) {
|
|
continue;
|
|
}
|
|
$levelRaw = trim((string) $levelRaw);
|
|
if ($levelRaw === '') {
|
|
continue;
|
|
}
|
|
$level = (int) $levelRaw;
|
|
if ($level < 0 || $level > 100) {
|
|
continue;
|
|
}
|
|
|
|
$this->placementScoreModel->insert([
|
|
'batch_id' => (int) $batchId,
|
|
'student_id' => $studentId,
|
|
'score' => $level,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
]);
|
|
$savedCount++;
|
|
}
|
|
|
|
if ($savedCount === 0) {
|
|
$this->placementBatchModel->delete((int) $batchId);
|
|
return ['kind' => 'flash', 'redirect' => 'back', 'type' => 'error', 'message' => 'No scores entered. Batch not saved.'];
|
|
}
|
|
|
|
return ['kind' => 'flash', 'redirect' => base_url('grading/placement'), 'type' => 'status', 'message' => 'Placement batch saved.'];
|
|
}
|
|
|
|
public function editPlacementBatch(int $batchId, array $params = [])
|
|
{
|
|
$get = $params['get'] ?? [];
|
|
$post = $params['post'] ?? [];
|
|
|
|
$batch = $this->placementBatchModel->find($batchId);
|
|
if (!$batch) {
|
|
return ['kind' => 'flash', 'redirect' => base_url('grading/placement'), 'type' => 'error', 'message' => 'Placement batch not found.'];
|
|
}
|
|
|
|
$schoolYear = (string) ($batch['school_year'] ?? $this->schoolYear);
|
|
$students = $this->fetchActiveStudentsWithSection($schoolYear);
|
|
$scores = $this->fetchPlacementScoresForBatch($batchId);
|
|
|
|
return ['kind' => 'view', 'view' => 'grading/placement_batch', 'data' => [
|
|
'batch' => $batch,
|
|
'students' => $students,
|
|
'scores' => $scores,
|
|
]];
|
|
}
|
|
|
|
public function updatePlacementBatch(int $batchId, array $params = [])
|
|
{
|
|
$get = $params['get'] ?? [];
|
|
$post = $params['post'] ?? [];
|
|
|
|
$batch = $this->placementBatchModel->find($batchId);
|
|
if (!$batch) {
|
|
return ['kind' => 'flash', 'redirect' => base_url('grading/placement'), 'type' => 'error', 'message' => 'Placement batch not found.'];
|
|
}
|
|
|
|
$schoolYear = (string) ($batch['school_year'] ?? $this->schoolYear);
|
|
$levels = ($post['placement_level'] ?? null) ?? [];
|
|
|
|
$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);
|
|
|
|
$userId = session()->get('user_id');
|
|
foreach ($levels as $studentIdRaw => $scoreRaw) {
|
|
$studentId = (int) $studentIdRaw;
|
|
if (!isset($validSet[$studentId])) {
|
|
continue;
|
|
}
|
|
$scoreRaw = trim((string) $scoreRaw);
|
|
if ($scoreRaw === '') {
|
|
if (isset($existing[$studentId])) {
|
|
$this->placementScoreModel->delete($existing[$studentId]['id']);
|
|
}
|
|
continue;
|
|
}
|
|
$score = (int) $scoreRaw;
|
|
if ($score < 0 || $score > 100) {
|
|
continue;
|
|
}
|
|
|
|
if (isset($existing[$studentId])) {
|
|
$this->placementScoreModel->update($existing[$studentId]['id'], [
|
|
'score' => $score,
|
|
'updated_by' => $userId,
|
|
]);
|
|
} else {
|
|
$this->placementScoreModel->insert([
|
|
'batch_id' => $batchId,
|
|
'student_id' => $studentId,
|
|
'score' => $score,
|
|
'created_by' => $userId,
|
|
'updated_by' => $userId,
|
|
]);
|
|
}
|
|
}
|
|
|
|
$this->placementBatchModel->update($batchId, [
|
|
'updated_by' => $userId,
|
|
]);
|
|
|
|
return ['kind' => 'flash', 'redirect' => base_url('grading/placement'), 'type' => 'status', 'message' => 'Placement batch updated.'];
|
|
}
|
|
|
|
public function fetchActiveStudentsWithSection(string $schoolYear): array
|
|
{
|
|
return $this->db->table('students s')
|
|
->select('s.id AS student_id, s.school_id, s.firstname, s.lastname, s.is_active, sc.class_section_id, cs.class_section_name, c.class_name, e.enrollment_status, e.is_withdrawn')
|
|
->join('student_class sc', 'sc.student_id = s.id AND sc.school_year = ' . $this->db->escape($schoolYear), 'left')
|
|
->join('enrollments e', 'e.student_id = s.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left')
|
|
->join('`classSection` cs', 'cs.class_section_id = sc.class_section_id', 'left')
|
|
->join('classes c', 'c.id = cs.class_id', 'left')
|
|
->groupStart()
|
|
->where('s.is_active', 1)
|
|
->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','waitlist')", null, false)
|
|
->orWhere('e.is_withdrawn', 1)
|
|
->groupEnd()
|
|
->orderBy('s.lastname', 'ASC')
|
|
->orderBy('s.firstname', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
}
|
|
|
|
public function fetchPlacementBatches(string $schoolYear): array
|
|
{
|
|
return $this->placementBatchModel
|
|
->where('school_year', $schoolYear)
|
|
->orderBy('created_at', 'DESC')
|
|
->findAll();
|
|
}
|
|
|
|
public 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 = $this->db->table('placement_scores ps')
|
|
->select('ps.batch_id, ps.student_id, ps.score, s.school_id, s.firstname, s.lastname, s.is_active, e.enrollment_status, e.is_withdrawn, cs.class_section_name, c.class_name')
|
|
->join('students s', 's.id = ps.student_id', 'inner')
|
|
->join('student_class sc', 'sc.student_id = s.id AND sc.school_year = ' . $this->db->escape($schoolYear), 'left')
|
|
->join('enrollments e', 'e.student_id = s.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left')
|
|
->join('`classSection` cs', 'cs.class_section_id = sc.class_section_id', 'left')
|
|
->join('classes c', 'c.id = cs.class_id', 'left')
|
|
->whereIn('ps.batch_id', $batchIds)
|
|
->groupStart()
|
|
->where('s.is_active', 1)
|
|
->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','waitlist')", null, false)
|
|
->orWhere('e.is_withdrawn', 1)
|
|
->groupEnd()
|
|
->orderBy('ps.batch_id', 'ASC')
|
|
->orderBy('s.lastname', 'ASC')
|
|
->orderBy('s.firstname', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$details = [];
|
|
foreach ($rows as $row) {
|
|
$bid = (int) ($row['batch_id'] ?? 0);
|
|
if ($bid <= 0) continue;
|
|
$details[$bid][] = $row;
|
|
}
|
|
|
|
return $details;
|
|
}
|
|
|
|
public function fetchPlacementScoresForBatch(int $batchId): array
|
|
{
|
|
$rows = $this->placementScoreModel
|
|
->where('batch_id', $batchId)
|
|
->findAll();
|
|
$scores = [];
|
|
foreach ($rows as $row) {
|
|
$scores[(int) $row['student_id']] = $row;
|
|
}
|
|
return $scores;
|
|
}
|
|
}
|