622 lines
22 KiB
PHP
Executable File
622 lines
22 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Models\Configuration;
|
|
use App\Models\Project;
|
|
use App\Models\Student;
|
|
use App\Models\StudentClass;
|
|
use App\Models\TeacherClass;
|
|
use App\Services\SemesterScoreService;
|
|
use RuntimeException;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class ProjectController extends BaseApiController
|
|
{
|
|
protected Project $project;
|
|
protected Configuration $config;
|
|
protected Student $student;
|
|
protected StudentClass $studentClass;
|
|
protected TeacherClass $teacherClass;
|
|
protected SemesterScoreService $semesterScoreService;
|
|
protected string $schoolYear;
|
|
protected string $semester;
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->project = model(Project::class);
|
|
$this->config = model(Configuration::class);
|
|
$this->student = model(Student::class);
|
|
$this->studentClass = model(StudentClass::class);
|
|
$this->teacherClass = model(TeacherClass::class);
|
|
$this->semesterScoreService = app(SemesterScoreService::class);
|
|
|
|
$this->schoolYear = (string) ($this->config->getConfig('school_year') ?? '');
|
|
$this->semester = (string) ($this->config->getConfig('semester') ?? '');
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/projects
|
|
* List projects with optional filters
|
|
*/
|
|
public function index()
|
|
{
|
|
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
|
$perPage = min(100, max(1, (int) ($this->request->getGet('per_page') ?? 20)));
|
|
$studentId = $this->request->getGet('student_id');
|
|
$classSectionId = $this->request->getGet('class_section_id');
|
|
|
|
$query = $this->project->newQuery()
|
|
->where('school_year', $this->schoolYear)
|
|
->where('semester', $this->semester);
|
|
|
|
if ($studentId) {
|
|
$query->where('student_id', $studentId);
|
|
}
|
|
|
|
if ($classSectionId) {
|
|
$query->where('class_section_id', $classSectionId);
|
|
}
|
|
|
|
$result = $this->paginate($query, $page, $perPage);
|
|
|
|
return $this->success($result, 'Projects retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/projects/{id}
|
|
* Get a single project
|
|
*/
|
|
public function show($id = null)
|
|
{
|
|
$project = $this->project->find($id);
|
|
if (!$project) {
|
|
return $this->error('Project not found', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
return $this->success($project, 'Project retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/projects/student/{id}
|
|
* Get projects for a specific student
|
|
*/
|
|
public function getByStudent($id = null)
|
|
{
|
|
$projects = $this->project->newQuery()
|
|
->where('student_id', $id)
|
|
->where('school_year', $this->schoolYear)
|
|
->where('semester', $this->semester)
|
|
->orderBy('project_index', 'ASC')
|
|
->get()
|
|
->toArray();
|
|
|
|
return $this->success($projects, 'Projects retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/projects
|
|
* Create a new project entry
|
|
*/
|
|
public function store()
|
|
{
|
|
$user = $this->getCurrentUser();
|
|
if (!$user) {
|
|
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
$data = $this->payloadData();
|
|
if (empty($data)) {
|
|
return $this->error('Invalid request data', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$rules = [
|
|
'student_id' => 'required|integer',
|
|
'class_section_id' => 'required|integer',
|
|
'project_index' => 'required|integer',
|
|
];
|
|
|
|
$errors = $this->validateRequest($data, $rules);
|
|
if (!empty($errors)) {
|
|
return $this->respondValidationError($errors);
|
|
}
|
|
|
|
$projectData = [
|
|
'student_id' => $data['student_id'],
|
|
'class_section_id' => $data['class_section_id'],
|
|
'project_index' => $data['project_index'],
|
|
'score' => $data['score'] ?? null,
|
|
'comment' => $data['comment'] ?? null,
|
|
'school_year' => $this->schoolYear,
|
|
'semester' => $this->semester,
|
|
'updated_by' => $user->id,
|
|
];
|
|
|
|
try {
|
|
$project = $this->project->create($projectData);
|
|
return $this->success($project->toArray(), 'Project created successfully', Response::HTTP_CREATED);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Project creation error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to create project', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PATCH /api/v1/projects/{id}
|
|
* Update a project entry
|
|
*/
|
|
public function update($id = null)
|
|
{
|
|
$project = $this->project->find($id);
|
|
if (!$project) {
|
|
return $this->error('Project not found', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
$user = $this->getCurrentUser();
|
|
if (!$user) {
|
|
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
$data = $this->payloadData();
|
|
if (empty($data)) {
|
|
return $this->error('Invalid request data', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$allowedFields = ['score', 'comment', 'project_index'];
|
|
$updateData = [];
|
|
foreach ($allowedFields as $field) {
|
|
if (array_key_exists($field, $data)) {
|
|
$updateData[$field] = $data[$field];
|
|
}
|
|
}
|
|
|
|
if (empty($updateData)) {
|
|
return $this->error('No valid fields to update', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$updateData['updated_by'] = $user->id;
|
|
$updateData['updated_at'] = utc_now();
|
|
|
|
try {
|
|
$this->project->update($id, $updateData);
|
|
$updatedProject = $this->project->find($id);
|
|
return $this->success($updatedProject, 'Project updated successfully');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Project update error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to update project', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/projects/add-project
|
|
* Get project data for teacher's class section (equivalent to addProject view)
|
|
*/
|
|
public function addProject()
|
|
{
|
|
$updatedBy = $this->getCurrentUserId();
|
|
if (!$updatedBy) {
|
|
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
$teacherClass = $this->teacherClass->where('teacher_id', $updatedBy)->first();
|
|
if (!$teacherClass) {
|
|
return $this->respondError('No class section found for the current teacher.', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
$classSectionId = $teacherClass['class_section_id'];
|
|
session()->put('class_section_id', $classSectionId);
|
|
|
|
$projectRows = $this->project
|
|
->select('id, project_index')
|
|
->where('class_section_id', $classSectionId)
|
|
->where('updated_by', $updatedBy)
|
|
->where('semester', $this->semester)
|
|
->where('school_year', $this->schoolYear)
|
|
->orderBy('project_index', 'ASC')
|
|
->findAll();
|
|
|
|
$projectHeaders = [];
|
|
foreach ($projectRows as $row) {
|
|
$projectHeaders[$row['project_index']][] = $row['id'];
|
|
}
|
|
|
|
$headerLabels = array_keys($projectHeaders);
|
|
|
|
$studentsClasses = $this->studentClass->where('class_section_id', $classSectionId)->findAll();
|
|
$students = [];
|
|
|
|
foreach ($studentsClasses as $studentClass) {
|
|
$studentId = $studentClass['student_id'];
|
|
$student = $this->student->find($studentId);
|
|
if ($student) {
|
|
$scores = [];
|
|
foreach ($projectHeaders as $index => $ids) {
|
|
$entry = $this->project
|
|
->where('student_id', $studentId)
|
|
->where('project_index', $index)
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $this->semester)
|
|
->first();
|
|
|
|
$scores[$index] = $entry['score'] ?? '';
|
|
}
|
|
|
|
$students[] = [
|
|
'student_id' => $studentId,
|
|
'firstname' => $student['firstname'],
|
|
'lastname' => $student['lastname'],
|
|
'scores' => $scores
|
|
];
|
|
}
|
|
}
|
|
|
|
usort($students, fn($a, $b) => strcmp($a['lastname'], $b['lastname']) ?: strcmp($a['firstname'], $b['firstname']));
|
|
|
|
return $this->success([
|
|
'students' => $students,
|
|
'project_headers' => $headerLabels,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
'class_section_id' => $classSectionId
|
|
], 'Project data retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/projects/update-scores
|
|
* Update project scores for multiple students
|
|
*/
|
|
public function updateProjectScores()
|
|
{
|
|
$updatedBy = $this->getCurrentUserId();
|
|
if (!$updatedBy) {
|
|
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
$payload = $this->payloadData();
|
|
$scores = $payload['scores'] ?? null;
|
|
$classSectionId = (int) ($payload['class_section_id'] ?? session()->get('class_section_id') ?? 0);
|
|
|
|
if ($classSectionId <= 0) {
|
|
return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
if (!is_array($scores)) {
|
|
return $this->respondError('No project scores submitted.', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
try {
|
|
foreach ($scores as $studentId => $projects) {
|
|
if (!is_numeric($studentId)) continue;
|
|
|
|
$student = $this->student->find($studentId);
|
|
if (!$student) continue;
|
|
|
|
foreach ($projects as $index => $score) {
|
|
if (!is_numeric($score)) continue;
|
|
|
|
$existing = $this->project->where([
|
|
'student_id' => $studentId,
|
|
'project_index' => $index,
|
|
'class_section_id' => $classSectionId,
|
|
'semester' => $this->semester
|
|
])->first();
|
|
|
|
$data = [
|
|
'student_id' => $studentId,
|
|
'school_id' => $student['school_id'] ?? '',
|
|
'class_section_id' => $classSectionId,
|
|
'updated_by' => $updatedBy,
|
|
'project_index' => $index,
|
|
'score' => is_numeric($score) ? (float)$score : null,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
'updated_at' => utc_now(),
|
|
];
|
|
|
|
if ($existing) {
|
|
$this->project->update($existing['id'], $data);
|
|
} else {
|
|
$data['created_at'] = utc_now();
|
|
$this->project->insert($data);
|
|
}
|
|
}
|
|
}
|
|
|
|
$studentTeacherInfo = $this->student->getStudentInfoByClassSectionId($classSectionId);
|
|
|
|
// Call the updateScoresForStudents method
|
|
try {
|
|
$this->semesterScoreService->updateScoresForStudents($studentTeacherInfo);
|
|
} catch (RuntimeException $e) {
|
|
log_message('error', 'Failed to update semester scores: ' . $e->getMessage());
|
|
}
|
|
|
|
return $this->success(null, 'Project scores updated successfully.');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Update project scores error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to update project scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/projects/add-column
|
|
* Add a new project column for all students in a class section
|
|
*/
|
|
public function addNextProjectColumn()
|
|
{
|
|
$updatedBy = $this->getCurrentUserId();
|
|
if (!$updatedBy) {
|
|
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
$payload = $this->payloadData();
|
|
$classSectionId = (int) ($payload['class_section_id'] ?? session()->get('class_section_id') ?? 0);
|
|
|
|
if ($classSectionId <= 0) {
|
|
return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
try {
|
|
$existingIndexes = $this->project
|
|
->select('project_index')
|
|
->where('class_section_id', $classSectionId)
|
|
->where('updated_by', $updatedBy)
|
|
->where('semester', $this->semester)
|
|
->where('school_year', $this->schoolYear)
|
|
->groupBy('project_index')
|
|
->orderBy('project_index', 'DESC')
|
|
->findAll();
|
|
|
|
$maxIndex = 0;
|
|
foreach ($existingIndexes as $row) {
|
|
if (isset($row['project_index']) && is_numeric($row['project_index'])) {
|
|
$maxIndex = max($maxIndex, (int)$row['project_index']);
|
|
}
|
|
}
|
|
|
|
$nextIndex = $maxIndex + 1;
|
|
|
|
$students = $this->studentClass->where('class_section_id', $classSectionId)->findAll();
|
|
|
|
$insertedCount = 0;
|
|
foreach ($students as $student) {
|
|
$exists = $this->project->where([
|
|
'student_id' => $student['student_id'],
|
|
'project_index' => $nextIndex,
|
|
'class_section_id' => $classSectionId,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
])->first();
|
|
|
|
if (!$exists) {
|
|
$studentRecord = $this->student->find($student['student_id']);
|
|
$this->project->insert([
|
|
'student_id' => $student['student_id'],
|
|
'school_id' => $studentRecord['school_id'] ?? '',
|
|
'class_section_id' => $classSectionId,
|
|
'updated_by' => $updatedBy,
|
|
'project_index' => $nextIndex,
|
|
'score' => null,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
'created_at' => utc_now(),
|
|
'updated_at' => utc_now()
|
|
]);
|
|
$insertedCount++;
|
|
}
|
|
}
|
|
|
|
return $this->success([
|
|
'status' => 'success',
|
|
'project_index' => $nextIndex,
|
|
'students_processed' => $insertedCount
|
|
], 'Project column added successfully');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Add project column error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to add project column: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/projects/management
|
|
* Get project management data for a class section
|
|
*/
|
|
public function showProjectMngt()
|
|
{
|
|
// Accept POST first, then GET (query string), then session fallback
|
|
$incomingId = $this->request->getPost('class_section_id');
|
|
if (!$incomingId) {
|
|
$incomingId = $this->request->getGet('class_section_id');
|
|
}
|
|
|
|
$classSectionId = (int) ($incomingId ?: (session()->get('class_section_id') ?? 0));
|
|
|
|
if ($classSectionId <= 0) {
|
|
return $this->respondError('No class section found for the current teacher or selection.', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
session()->put('class_section_id', $classSectionId);
|
|
|
|
$projectScores = $this->getProjectScoresByStudent($classSectionId, $this->semester, $this->schoolYear);
|
|
$students = $this->getStudentsByClassSectionAndYear($classSectionId, $this->schoolYear);
|
|
$projectHeaders = $this->getProjectHeaders($classSectionId, $this->semester, $this->schoolYear);
|
|
|
|
return $this->success([
|
|
'project_scores' => $projectScores,
|
|
'students' => $students,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
'project_headers' => $projectHeaders,
|
|
'class_section_id' => $classSectionId,
|
|
], 'Project management data retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/projects/update
|
|
* Update project scores (management endpoint)
|
|
*/
|
|
public function updateProject()
|
|
{
|
|
$payload = $this->payloadData();
|
|
$scores = $payload['scores'] ?? null;
|
|
$studentIds = $payload['student_ids'] ?? null;
|
|
$semester = $payload['semester'] ?? $this->semester;
|
|
$schoolYear = $payload['school_year'] ?? $this->schoolYear;
|
|
$updatedBy = $payload['teacher_id'] ?? $this->getCurrentUserId();
|
|
$classSectionId = (int) ($payload['class_section_id'] ?? session()->get('class_section_id') ?? 0);
|
|
|
|
if (!$updatedBy) {
|
|
return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
if ($classSectionId <= 0) {
|
|
return $this->respondError('Class section ID is required', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
if ($scores && $studentIds && $semester && $schoolYear) {
|
|
try {
|
|
$this->saveProjectScores($scores, $studentIds, $semester, $schoolYear, $updatedBy, $classSectionId);
|
|
|
|
$studentTeacherInfo = $this->student->getStudentInfoByClassSectionId($classSectionId);
|
|
|
|
// Call the updateScoresForStudents method
|
|
try {
|
|
$this->semesterScoreService->updateScoresForStudents($studentTeacherInfo);
|
|
} catch (RuntimeException $e) {
|
|
log_message('error', 'Failed to update semester scores: ' . $e->getMessage());
|
|
}
|
|
|
|
return $this->success(null, 'Project scores updated successfully.');
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'Update project error: ' . $e->getMessage());
|
|
return $this->respondError('Failed to update scores: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
return $this->respondError('Failed to update scores. Missing required data.', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
/**
|
|
* Helper: Save project scores for multiple students
|
|
*/
|
|
private function saveProjectScores(array $scores, array $studentIds, string $semester, string $schoolYear, int $updatedBy, int $classSectionId)
|
|
{
|
|
$now = utc_now();
|
|
|
|
foreach ($studentIds as $studentId) {
|
|
if (!isset($scores[$studentId])) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($scores[$studentId] as $index => $score) {
|
|
// Check if the record exists
|
|
$existing = $this->project
|
|
->where('student_id', $studentId)
|
|
->where('project_index', $index)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear)
|
|
->where('class_section_id', $classSectionId)
|
|
->where('updated_by', $updatedBy)
|
|
->first();
|
|
|
|
$student = $this->student->find($studentId);
|
|
$data = [
|
|
'student_id' => $studentId,
|
|
'class_section_id' => $classSectionId,
|
|
'updated_by' => $updatedBy,
|
|
'project_index' => $index,
|
|
'score' => is_numeric($score) ? floatval($score) : null,
|
|
'semester' => $semester,
|
|
'school_year' => $schoolYear,
|
|
'updated_at' => $now,
|
|
];
|
|
|
|
if ($existing && isset($existing['id'])) {
|
|
$this->project->update($existing['id'], $data);
|
|
} else {
|
|
$data['created_at'] = $now;
|
|
$data['school_id'] = $student['school_id'] ?? '';
|
|
$this->project->insert($data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper: Get project headers for a class section
|
|
*/
|
|
private function getProjectHeaders($classSectionId, $semester, $schoolYear)
|
|
{
|
|
$rows = $this->project
|
|
->select('project_index')
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear)
|
|
->orderBy('project_index', 'ASC')
|
|
->findAll();
|
|
|
|
$headers = [];
|
|
foreach ($rows as $row) {
|
|
$index = $row['project_index'];
|
|
if (!is_array($index)) {
|
|
$headers[$index] = true;
|
|
}
|
|
}
|
|
|
|
ksort($headers);
|
|
return array_keys($headers);
|
|
}
|
|
|
|
/**
|
|
* Helper: 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 []; // No students found
|
|
}
|
|
|
|
// Step 2: Get student data from students table
|
|
$students = $this->student
|
|
->whereIn('id', $studentIds)
|
|
->orderBy('lastname', 'ASC')
|
|
->findAll();
|
|
|
|
return $students;
|
|
}
|
|
|
|
/**
|
|
* Helper: Get project scores by student
|
|
*/
|
|
private function getProjectScoresByStudent($classSectionId, $semester, $schoolYear)
|
|
{
|
|
$rows = $this->project
|
|
->select('student_id, project_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['project_index'];
|
|
$score = $row['score'];
|
|
|
|
$studentScores[$studentId]['scores'][$index] = $score;
|
|
}
|
|
|
|
return $studentScores;
|
|
}
|
|
}
|