add projet

This commit is contained in:
root
2026-03-05 12:29:37 -05:00
parent 8d1eef8ba8
commit 23b7db1107
9109 changed files with 1106501 additions and 73 deletions
+621
View File
@@ -0,0 +1,621 @@
<?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;
}
}