517 lines
19 KiB
PHP
Executable File
517 lines
19 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Models\Configuration;
|
|
use App\Models\Homework;
|
|
use App\Models\StudentClass;
|
|
use App\Models\Student;
|
|
use App\Models\TeacherClass;
|
|
use App\Models\User;
|
|
use RuntimeException;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class HomeworkController extends BaseApiController
|
|
{
|
|
protected Homework $homework;
|
|
protected Configuration $config;
|
|
protected StudentClass $studentClass;
|
|
protected Student $student;
|
|
protected TeacherClass $teacherClass;
|
|
protected User $user;
|
|
protected string $schoolYear;
|
|
protected string $semester;
|
|
protected $semesterScoreService;
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->homework = model(Homework::class);
|
|
$this->config = model(Configuration::class);
|
|
$this->studentClass = model(StudentClass::class);
|
|
$this->student = model(Student::class);
|
|
$this->teacherClass = model(TeacherClass::class);
|
|
$this->user = model(User::class);
|
|
$this->schoolYear = (string) ($this->config->getConfig('school_year') ?? '');
|
|
$this->semester = (string) ($this->config->getConfig('semester') ?? '');
|
|
$this->semesterScoreService = \Config\Services::semesterScoreService();
|
|
}
|
|
|
|
/**
|
|
* Get paginated homework list
|
|
*/
|
|
public function index()
|
|
{
|
|
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
|
$perPage = min(100, max(1, (int) ($this->request->getGet('per_page') ?? 20)));
|
|
$classSectionId = $this->request->getGet('class_section_id');
|
|
|
|
$query = $this->homework->newQuery()
|
|
->when($this->schoolYear !== '', fn($q) => $q->where('school_year', $this->schoolYear))
|
|
->when($this->semester !== '', fn($q) => $q->where('semester', $this->semester))
|
|
->when($classSectionId, fn($q) => $q->where('class_section_id', $classSectionId))
|
|
->orderBy('created_at', 'DESC');
|
|
|
|
$result = $this->paginate($query, $page, $perPage);
|
|
return $this->success($result, 'Homework retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* Get homework by student ID
|
|
*/
|
|
public function getByStudent($id = null)
|
|
{
|
|
$query = $this->homework->newQuery()
|
|
->when($id, fn($q) => $q->where('student_id', $id))
|
|
->when($this->schoolYear !== '', fn($q) => $q->where('school_year', $this->schoolYear))
|
|
->when($this->semester !== '', fn($q) => $q->where('semester', $this->semester));
|
|
|
|
$homework = $query->get()->toArray();
|
|
return $this->success($homework, 'Homework retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* Create a single homework entry
|
|
*/
|
|
public function create()
|
|
{
|
|
$user = $this->getCurrentUser();
|
|
$data = $this->payloadData();
|
|
if (empty($data)) {
|
|
return $this->respondError('Invalid request data', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$errors = $this->validateRequest($data, [
|
|
'student_id' => 'required|integer',
|
|
'class_section_id' => 'required|integer',
|
|
'homework_index' => 'required|integer',
|
|
]);
|
|
|
|
if (!empty($errors)) {
|
|
return $this->respondValidationError($errors);
|
|
}
|
|
|
|
$payload = [
|
|
'student_id' => $data['student_id'],
|
|
'class_section_id' => $data['class_section_id'],
|
|
'homework_index' => $data['homework_index'],
|
|
'score' => $data['score'] ?? null,
|
|
'comment' => $data['comment'] ?? null,
|
|
'school_year' => $this->schoolYear,
|
|
'semester' => $this->semester,
|
|
'updated_by' => $user->id ?? null,
|
|
];
|
|
|
|
$homework = $this->homework->create($payload);
|
|
return $this->success($homework, 'Homework created successfully', Response::HTTP_CREATED);
|
|
}
|
|
|
|
/**
|
|
* Update a single homework entry
|
|
*/
|
|
public function update($id = null)
|
|
{
|
|
$homework = $this->homework->find($id);
|
|
if (!$homework) {
|
|
return $this->respondError('Homework not found', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
$user = $this->getCurrentUser();
|
|
$data = $this->payloadData();
|
|
if (empty($data)) {
|
|
return $this->respondError('Invalid request data', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$allowed = ['score', 'comment'];
|
|
$update = [];
|
|
foreach ($allowed as $field) {
|
|
if (array_key_exists($field, $data)) {
|
|
$update[$field] = $data[$field];
|
|
}
|
|
}
|
|
|
|
if (empty($update)) {
|
|
return $this->respondError('No valid fields to update', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$update['updated_by'] = $user->id ?? null;
|
|
$update['updated_at'] = utc_now();
|
|
$this->homework->update($id, $update);
|
|
|
|
return $this->success($this->homework->find($id), 'Homework updated successfully');
|
|
}
|
|
|
|
/**
|
|
* Bulk update homework scores for multiple students
|
|
*/
|
|
public function updateScores()
|
|
{
|
|
$data = $this->payloadData();
|
|
$scores = $data['scores'] ?? null;
|
|
$classSectionId = (int) ($data['class_section_id'] ?? 0);
|
|
$updatedBy = $this->getCurrentUserId();
|
|
|
|
if (!$scores || !is_array($scores)) {
|
|
return $this->respondError('Scores data is required', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
if ($classSectionId <= 0) {
|
|
return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
try {
|
|
foreach ($scores as $studentId => $hwData) {
|
|
if (!is_numeric($studentId)) continue;
|
|
|
|
$student = $this->student->find($studentId);
|
|
if (!$student) continue;
|
|
|
|
foreach ($hwData as $index => $score) {
|
|
// STEP 1: Always fetch the existing record
|
|
$existing = $this->homework->where([
|
|
'student_id' => $studentId,
|
|
'homework_index' => $index,
|
|
'class_section_id' => $classSectionId,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
])->first();
|
|
|
|
// STEP 2: If blank, delete if exists
|
|
if ($score === '' || $score === null) {
|
|
if ($existing) {
|
|
$this->homework->delete($existing['id']);
|
|
log_message('debug', "Deleted blank score for student $studentId index $index");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// STEP 3: Save numeric score
|
|
if (is_numeric($score)) {
|
|
$updateData = [
|
|
'student_id' => $studentId,
|
|
'school_id' => $student['school_id'] ?? '',
|
|
'class_section_id' => $classSectionId,
|
|
'updated_by' => $updatedBy,
|
|
'homework_index' => $index,
|
|
'score' => (float)$score,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
'updated_at' => utc_now(),
|
|
];
|
|
|
|
if ($existing) {
|
|
$this->homework->update($existing['id'], $updateData);
|
|
log_message('debug', "Updated homework ID {$existing['id']} with score $score");
|
|
} else {
|
|
$updateData['created_at'] = utc_now();
|
|
$this->homework->insert($updateData);
|
|
log_message('debug', "Inserted score for student $studentId index $index");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update semester scores for students in the class section
|
|
$studentUserInfo = $this->student->getStudentInfoByClassSectionId(
|
|
$classSectionId,
|
|
$this->semester,
|
|
$this->schoolYear
|
|
);
|
|
|
|
try {
|
|
foreach ($studentUserInfo as $studentInfo) {
|
|
$this->semesterScoreService->updateStudentScores([
|
|
'student_id' => $studentInfo['student_id'],
|
|
'school_id' => $studentInfo['school_id'] ?? null,
|
|
'class_section_id' => $studentInfo['class_section_id'],
|
|
'updated_by' => $studentInfo['updated_by'] ?? $updatedBy,
|
|
], $this->semester, $this->schoolYear);
|
|
}
|
|
} catch (RuntimeException $e) {
|
|
log_message('error', 'Failed to update semester scores: ' . $e->getMessage());
|
|
}
|
|
|
|
return $this->success(null, 'Homework scores updated successfully');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Update homework scores error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to update homework scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a new homework column/index for all students in a class section
|
|
*/
|
|
public function addNextColumn()
|
|
{
|
|
$data = $this->payloadData();
|
|
$classSectionId = (int) ($data['class_section_id'] ?? 0);
|
|
$updatedBy = $this->getCurrentUserId();
|
|
|
|
if ($classSectionId <= 0) {
|
|
return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
try {
|
|
// Step 1: Get the highest existing homework_index
|
|
$existingIndexes = $this->homework
|
|
->select('homework_index')
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $this->semester)
|
|
->where('school_year', $this->schoolYear)
|
|
->groupBy('homework_index')
|
|
->orderBy('homework_index', 'DESC')
|
|
->findAll();
|
|
|
|
$maxIndex = 0;
|
|
foreach ($existingIndexes as $row) {
|
|
if (isset($row['homework_index']) && is_numeric($row['homework_index'])) {
|
|
$maxIndex = max($maxIndex, (int)$row['homework_index']);
|
|
}
|
|
}
|
|
|
|
$nextIndex = $maxIndex + 1;
|
|
|
|
// Step 2: Get all students in the class
|
|
$students = $this->studentClass
|
|
->where('class_section_id', $classSectionId)
|
|
->where('school_year', $this->schoolYear)
|
|
->findAll();
|
|
|
|
// Step 3: Insert a new homework row for each student if not already exists
|
|
$firstInsertedId = null;
|
|
$insertedCount = 0;
|
|
|
|
foreach ($students as $i => $student) {
|
|
$studentId = $student['student_id'];
|
|
|
|
// Check if record already exists
|
|
$existing = $this->homework
|
|
->where('student_id', $studentId)
|
|
->where('homework_index', $nextIndex)
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $this->semester)
|
|
->where('school_year', $this->schoolYear)
|
|
->first();
|
|
|
|
if ($existing) continue;
|
|
|
|
$studentRecord = $this->student->find($studentId);
|
|
$insertData = [
|
|
'student_id' => $studentId,
|
|
'school_id' => $studentRecord['school_id'] ?? '',
|
|
'class_section_id' => $classSectionId,
|
|
'updated_by' => $updatedBy,
|
|
'homework_index' => $nextIndex,
|
|
'score' => null,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
'created_at' => utc_now(),
|
|
'updated_at' => utc_now()
|
|
];
|
|
|
|
$id = $this->homework->insert($insertData);
|
|
if ($i === 0) {
|
|
$firstInsertedId = $id;
|
|
}
|
|
$insertedCount++;
|
|
}
|
|
|
|
return $this->success([
|
|
'homework_index' => $nextIndex,
|
|
'new_homework_id' => $firstInsertedId,
|
|
'students_processed' => $insertedCount
|
|
], 'Homework column added successfully');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Add homework column error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to add homework column: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get homework data for a class section (for management view)
|
|
*/
|
|
public function getByClassSection()
|
|
{
|
|
$classSectionId = (int) ($this->request->getGet('class_section_id') ?? 0);
|
|
|
|
if ($classSectionId <= 0) {
|
|
return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
try {
|
|
$homeworkScores = $this->getHomeworkScoresByStudent($classSectionId, $this->semester, $this->schoolYear);
|
|
$students = $this->getStudentsByClassSectionAndYear($classSectionId, $this->schoolYear);
|
|
$homeworkHeaders = $this->getHomeworkHeaders($classSectionId, $this->semester, $this->schoolYear);
|
|
|
|
return $this->success([
|
|
'homework_scores' => $homeworkScores,
|
|
'students' => $students,
|
|
'homework_headers' => $homeworkHeaders,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
'class_section_id' => $classSectionId,
|
|
'has_homework_cols' => !empty($homeworkHeaders),
|
|
], 'Homework data retrieved successfully');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Get homework by class section error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to retrieve homework data: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get students with their homework scores
|
|
*/
|
|
public function getStudentsWithScores()
|
|
{
|
|
$classSectionId = (int) ($this->request->getGet('class_section_id') ?? 0);
|
|
|
|
if ($classSectionId <= 0) {
|
|
return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
try {
|
|
$homeworkHeaders = $this->getHomeworkHeaders($classSectionId, $this->semester, $this->schoolYear);
|
|
$students = $this->getStudentsWithHomeworkScores($classSectionId, $homeworkHeaders);
|
|
|
|
return $this->success([
|
|
'students' => $students,
|
|
'homework_headers' => $homeworkHeaders,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
'class_section_id' => $classSectionId,
|
|
], 'Students with homework scores retrieved successfully');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Get students with scores error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to retrieve students with scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
// ──────────────── Helper Methods ────────────────
|
|
|
|
/**
|
|
* Get students by class section and year
|
|
*/
|
|
private function getStudentsByClassSectionAndYear($classSectionId, $schoolYear)
|
|
{
|
|
// Step 1: Get student IDs from student_class table
|
|
$studentClassRows = $this->studentClass
|
|
->select('student_id')
|
|
->where('class_section_id', $classSectionId)
|
|
->where('school_year', $schoolYear)
|
|
->findAll();
|
|
|
|
$studentIds = array_column($studentClassRows, 'student_id');
|
|
|
|
if (empty($studentIds)) {
|
|
return [];
|
|
}
|
|
|
|
// Step 2: Get student data from students table
|
|
$students = $this->student
|
|
->whereIn('id', $studentIds)
|
|
->orderBy('lastname', 'ASC')
|
|
->orderBy('firstname', 'ASC')
|
|
->findAll();
|
|
|
|
return $students;
|
|
}
|
|
|
|
/**
|
|
* Get homework scores grouped by student
|
|
*/
|
|
private function getHomeworkScoresByStudent($classSectionId, $semester, $schoolYear)
|
|
{
|
|
$rows = $this->homework
|
|
->select('student_id, homework_index, score')
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear)
|
|
->findAll();
|
|
|
|
$studentScores = [];
|
|
|
|
foreach ($rows as $row) {
|
|
$studentId = $row['student_id'];
|
|
$index = $row['homework_index'];
|
|
$score = $row['score'];
|
|
|
|
if (!isset($studentScores[$studentId])) {
|
|
$studentScores[$studentId] = ['scores' => []];
|
|
}
|
|
|
|
$studentScores[$studentId]['scores'][$index] = $score;
|
|
}
|
|
|
|
return $studentScores;
|
|
}
|
|
|
|
/**
|
|
* Get students with their homework scores
|
|
*/
|
|
private function getStudentsWithHomeworkScores($classSectionId, $homeworkHeaders)
|
|
{
|
|
$studentClasses = $this->studentClass
|
|
->where('class_section_id', $classSectionId)
|
|
->where('school_year', $this->schoolYear)
|
|
->findAll();
|
|
|
|
$students = [];
|
|
|
|
foreach ($studentClasses as $sc) {
|
|
$student = $this->student->find($sc['student_id']);
|
|
if (!$student) continue;
|
|
|
|
$scores = [];
|
|
|
|
foreach ($homeworkHeaders as $index) {
|
|
$entry = $this->homework
|
|
->where('homework_index', $index)
|
|
->where('student_id', $sc['student_id'])
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $this->semester)
|
|
->where('school_year', $this->schoolYear)
|
|
->first();
|
|
|
|
$scores[$index] = $entry['score'] ?? '';
|
|
}
|
|
|
|
$students[] = [
|
|
'student_id' => $sc['student_id'],
|
|
'firstname' => $student['firstname'],
|
|
'lastname' => $student['lastname'],
|
|
'scores' => $scores
|
|
];
|
|
}
|
|
|
|
usort($students, fn($a, $b) => strcmp($a['lastname'], $b['lastname']) ?: strcmp($a['firstname'], $b['firstname']));
|
|
|
|
return $students;
|
|
}
|
|
|
|
/**
|
|
* Get homework headers (unique homework_index values)
|
|
*/
|
|
private function getHomeworkHeaders($classSectionId, $semester, $schoolYear)
|
|
{
|
|
$rows = $this->homework
|
|
->select('homework_index')
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear)
|
|
->orderBy('homework_index', 'ASC')
|
|
->findAll();
|
|
|
|
$headers = [];
|
|
|
|
foreach ($rows as $row) {
|
|
$index = $row['homework_index'];
|
|
if (!is_array($index)) {
|
|
$headers[$index] = true;
|
|
}
|
|
}
|
|
|
|
ksort($headers);
|
|
return array_keys($headers);
|
|
}
|
|
}
|