749 lines
29 KiB
PHP
Executable File
749 lines
29 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Models\Configuration;
|
|
use App\Models\TeacherClass;
|
|
use App\Models\User;
|
|
use App\Models\Teacher;
|
|
use App\Models\Student;
|
|
use App\Models\ClassSection;
|
|
use App\Models\StudentClass;
|
|
use App\Models\StudentMedicalCondition;
|
|
use App\Models\StudentAllergy;
|
|
use App\Models\StaffAttendance;
|
|
use App\Services\EmailService;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class TeacherController extends BaseApiController
|
|
{
|
|
protected User $user;
|
|
protected TeacherClass $teacherClass;
|
|
protected Configuration $config;
|
|
protected Teacher $teacher;
|
|
protected Student $student;
|
|
protected ClassSection $classSection;
|
|
protected StudentClass $studentClass;
|
|
protected StudentMedicalCondition $medical;
|
|
protected StudentAllergy $allergy;
|
|
protected StaffAttendance $staffAttendance;
|
|
protected EmailService $emailService;
|
|
protected string $semester;
|
|
protected string $schoolYear;
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->user = model(User::class);
|
|
$this->teacherClass = model(TeacherClass::class);
|
|
$this->config = model(Configuration::class);
|
|
$this->teacher = model(Teacher::class);
|
|
$this->student = model(Student::class);
|
|
$this->classSection = model(ClassSection::class);
|
|
$this->studentClass = model(StudentClass::class);
|
|
$this->medical = model(StudentMedicalCondition::class);
|
|
$this->allergy = model(StudentAllergy::class);
|
|
$this->staffAttendance = model(StaffAttendance::class);
|
|
$this->emailService = app(EmailService::class);
|
|
$this->semester = (string) ($this->config->getConfig('semester') ?? '');
|
|
$this->schoolYear = (string) ($this->config->getConfig('school_year') ?? '');
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
$page = max(1, (int) ($this->request->getGet('page') ?? 1));
|
|
$perPage = min(100, max(1, (int) ($this->request->getGet('per_page') ?? 20)));
|
|
|
|
$query = $this->user->newQuery()
|
|
->select('users.*')
|
|
->join('user_roles', 'user_roles.user_id', '=', 'users.id')
|
|
->join('roles', 'roles.id', '=', 'user_roles.role_id')
|
|
->whereIn('LOWER(roles.name)', ['teacher', 'teacher_assistant'])
|
|
->groupBy('users.id')
|
|
->orderBy('users.lastname', 'ASC');
|
|
|
|
$result = $this->paginate($query, $page, $perPage);
|
|
|
|
foreach ($result['data'] as &$teacher) {
|
|
unset($teacher['password'], $teacher['token']);
|
|
|
|
$assignments = $this->teacherClass->newQuery()
|
|
->where('teacher_id', $teacher['id'])
|
|
->where('school_year', $this->schoolYear)
|
|
->where('semester', $this->semester)
|
|
->get()
|
|
->toArray();
|
|
|
|
$teacher['class_assignments'] = $assignments;
|
|
}
|
|
|
|
return $this->success($result, 'Teachers retrieved successfully');
|
|
}
|
|
|
|
public function show($id = null)
|
|
{
|
|
$teacher = $this->user->newQuery()
|
|
->select('users.*')
|
|
->join('user_roles', 'user_roles.user_id', '=', 'users.id')
|
|
->join('roles', 'roles.id', '=', 'user_roles.role_id')
|
|
->whereIn('LOWER(roles.name)', ['teacher', 'teacher_assistant'])
|
|
->where('users.id', $id)
|
|
->first();
|
|
|
|
if (!$teacher) {
|
|
return $this->respondError('Teacher not found', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
$teacherArray = $teacher->toArray();
|
|
unset($teacherArray['password'], $teacherArray['token']);
|
|
|
|
$assignments = $this->teacherClass->newQuery()
|
|
->where('teacher_id', $id)
|
|
->where('school_year', $this->schoolYear)
|
|
->where('semester', $this->semester)
|
|
->get()
|
|
->toArray();
|
|
|
|
$teacherArray['class_assignments'] = $assignments;
|
|
|
|
return $this->success($teacherArray, 'Teacher retrieved successfully');
|
|
}
|
|
|
|
public function getClasses($id = null)
|
|
{
|
|
$assignments = $this->teacherClass->newQuery()
|
|
->select([
|
|
'tc.*',
|
|
'cs.class_section_name',
|
|
'c.name as class_name',
|
|
])
|
|
->from('teacher_class as tc')
|
|
->leftJoin('class_sections as cs', 'cs.class_section_id', '=', 'tc.class_section_id')
|
|
->leftJoin('classes as c', 'c.id', '=', 'cs.class_id')
|
|
->where('tc.teacher_id', $id)
|
|
->where('tc.school_year', $this->schoolYear)
|
|
->where('tc.semester', $this->semester)
|
|
->orderBy('cs.class_section_name', 'ASC')
|
|
->get()
|
|
->toArray();
|
|
|
|
return $this->success($assignments, 'Teacher classes retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/teachers/class-view
|
|
* Get class view data for the logged-in teacher
|
|
*/
|
|
public function getClassView()
|
|
{
|
|
try {
|
|
$userId = $this->getCurrentUserId();
|
|
if (!$userId) {
|
|
return $this->respondError('User not logged in', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
// Fetch all assignments (as main or TA)
|
|
$classes = $this->teacherClass->getClassAssignmentsByUserId($userId, $this->schoolYear);
|
|
|
|
if (empty($classes)) {
|
|
return $this->success([
|
|
'has_classes' => false,
|
|
'message' => 'You do not have an assigned class yet. Please contact the administration.',
|
|
], 'No classes found');
|
|
}
|
|
|
|
// Identify user and their role
|
|
$user = $this->user->find($userId);
|
|
if (!$user) {
|
|
return $this->respondError('User not found', Response::HTTP_NOT_FOUND);
|
|
}
|
|
|
|
$roles = DB::table('user_roles ur')
|
|
->select('r.name')
|
|
->join('roles r', 'r.id', '=', 'ur.role_id')
|
|
->where('ur.user_id', $userId)
|
|
->get()
|
|
->map(fn($r) => strtolower($r->name))
|
|
->toArray();
|
|
|
|
$roleName = 'teacher';
|
|
if (in_array('teacher', $roles)) {
|
|
$roleName = 'teacher';
|
|
} elseif (in_array('teacher_assistant', $roles)) {
|
|
$roleName = 'teacher_assistant';
|
|
} else {
|
|
return $this->respondError('Access denied', Response::HTTP_FORBIDDEN);
|
|
}
|
|
|
|
// Collect class section IDs
|
|
$classSectionIds = array_column($classes, 'class_section_id');
|
|
$studentsData = [];
|
|
|
|
if (!empty($classSectionIds)) {
|
|
// Fetch all students for the selected class sections
|
|
$students = $this->studentClass->getStudentsByClassSectionIds($classSectionIds);
|
|
|
|
if (!empty($students)) {
|
|
// Collect unique student IDs
|
|
$studentIds = array_values(array_unique(array_map(
|
|
static fn(array $row) => (int) ($row['student_id'] ?? 0),
|
|
$students
|
|
)));
|
|
$studentIds = array_values(array_filter($studentIds));
|
|
|
|
// Bulk fetch medical conditions & allergies grouped by student_id
|
|
$conditionsByStudent = !empty($studentIds)
|
|
? $this->medical->getMedicalByStudentIds($studentIds)
|
|
: [];
|
|
|
|
$allergiesByStudent = !empty($studentIds)
|
|
? $this->allergy->getAllergiesByStudentIds($studentIds)
|
|
: [];
|
|
|
|
// Attach medical/allergy data to each student
|
|
foreach ($students as $student) {
|
|
$sid = (int) ($student['student_id'] ?? 0);
|
|
$cid = (int) ($student['class_section_id'] ?? 0);
|
|
|
|
$medList = array_column($conditionsByStudent[$sid] ?? [], 'condition_name');
|
|
$algList = array_column($allergiesByStudent[$sid] ?? [], 'allergy');
|
|
|
|
$student['medical_conditions'] = $medList;
|
|
$student['allergies'] = $algList;
|
|
$student['medical_conditions_text'] = implode(', ', $medList);
|
|
$student['allergies_text'] = implode(', ', $algList);
|
|
|
|
$studentsData[$cid][] = $student;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build assigned staff lists (all Main teachers and all TAs) per class section
|
|
$assignedNames = [];
|
|
if (!empty($classSectionIds)) {
|
|
$rows = DB::table('teacher_class tc')
|
|
->select('tc.class_section_id', 'tc.position', 'u.firstname', 'u.lastname')
|
|
->join('users u', 'u.id', '=', 'tc.teacher_id')
|
|
->whereIn('tc.class_section_id', $classSectionIds)
|
|
->where('tc.school_year', $this->schoolYear)
|
|
->where('tc.semester', $this->semester)
|
|
->orderBy('tc.class_section_id', 'ASC')
|
|
->orderByRaw("FIELD(tc.position,'main','ta')")
|
|
->orderBy('u.firstname', 'ASC')
|
|
->get();
|
|
|
|
foreach ($rows as $r) {
|
|
$sid = (int) ($r->class_section_id ?? 0);
|
|
$pos = strtolower((string) ($r->position ?? ''));
|
|
$name = trim(($r->firstname ?? '') . ' ' . ($r->lastname ?? ''));
|
|
|
|
if ($sid === 0 || $name === '') continue;
|
|
|
|
if (in_array($pos, ['main', 'teacher'], true)) {
|
|
$assignedNames[$sid]['mains'][] = $name;
|
|
} elseif (in_array($pos, ['ta', 'assistant', 'teacher_assistant'], true)) {
|
|
$assignedNames[$sid]['tas'][] = $name;
|
|
} else {
|
|
$assignedNames[$sid]['others'][] = $name;
|
|
}
|
|
}
|
|
}
|
|
|
|
$classSectionId = $classSectionIds[0] ?? null;
|
|
|
|
return $this->success([
|
|
'teacher_name' => trim(($user['firstname'] ?? '') . ' ' . ($user['lastname'] ?? '')),
|
|
'role_name' => $roleName,
|
|
'classes' => $classes,
|
|
'students' => $studentsData[$classSectionId] ?? [],
|
|
'studentsData' => $studentsData,
|
|
'class_section_id' => $classSectionId,
|
|
'assignedNames' => $assignedNames,
|
|
], 'Class view data retrieved successfully');
|
|
} catch (\Exception $e) {
|
|
Log::error('Error in TeacherController::getClassView(): ' . $e->getMessage());
|
|
return $this->respondError('An error occurred. Please try again later.', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/teachers/class-assignments
|
|
* Get teacher class assignment data for administration
|
|
*/
|
|
public function getTeacherClassAssignments()
|
|
{
|
|
$forYear = trim((string) ($this->request->getGet('schoolYear') ?? $this->request->getGet('school_year') ?? ''));
|
|
$payload = $this->buildTeacherClassAssignmentPayload($forYear !== '' ? $forYear : null);
|
|
$payload['ok'] = true;
|
|
return $this->success($payload, 'Teacher class assignments retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/teachers/class-assignments/assign
|
|
* Assign a teacher to a class
|
|
*/
|
|
public function assignClassTeacher()
|
|
{
|
|
$data = $this->payloadData();
|
|
$result = $this->handleAssignClassTeacher($data);
|
|
$statusCode = $result['ok'] ? Response::HTTP_OK : Response::HTTP_UNPROCESSABLE_ENTITY;
|
|
return response()->json($result, $statusCode);
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/v1/teachers/class-assignments
|
|
* Delete a teacher class assignment
|
|
*/
|
|
public function deleteAssignment()
|
|
{
|
|
$data = $this->payloadData();
|
|
$result = $this->handleDeleteAssignment($data);
|
|
$statusCode = $result['ok'] ? Response::HTTP_OK : Response::HTTP_UNPROCESSABLE_ENTITY;
|
|
return response()->json($result, $statusCode);
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/teachers/absence-form
|
|
* Get absence form data for the logged-in teacher
|
|
*/
|
|
public function getAbsenceForm()
|
|
{
|
|
$userId = $this->getCurrentUserId();
|
|
if (!$userId) {
|
|
return $this->respondError('Please log in first.', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
$teacher = $this->user->find($userId);
|
|
$displayName = $teacher ? trim(($teacher['firstname'] ?? '') . ' ' . ($teacher['lastname'] ?? '')) : 'Unknown';
|
|
|
|
// Fetch existing self-reported staff attendance rows for this user (current term)
|
|
$existing = $this->staffAttendance
|
|
->where('user_id', $userId)
|
|
->where('semester', $this->semester)
|
|
->where('school_year', $this->schoolYear)
|
|
->orderBy('date', 'DESC')
|
|
->findAll();
|
|
|
|
return $this->success([
|
|
'teacher_name' => $displayName,
|
|
'semester' => $this->semester,
|
|
'schoolYear' => $this->schoolYear,
|
|
'existing' => $existing,
|
|
'availableDates' => $this->allowedAbsenceDates(),
|
|
], 'Absence form data retrieved successfully');
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/teachers/absence
|
|
* Submit absence/vacation request
|
|
*/
|
|
public function submitAbsence()
|
|
{
|
|
$userId = $this->getCurrentUserId();
|
|
if (!$userId) {
|
|
return $this->respondError('Please log in first.', Response::HTTP_UNAUTHORIZED);
|
|
}
|
|
|
|
if ($this->semester === '' || $this->schoolYear === '') {
|
|
return $this->respondError('Semester or school year not configured.', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$data = $this->payloadData();
|
|
$dates = (array) ($data['dates'] ?? []);
|
|
$reasonType = trim((string) ($data['reason_type'] ?? ''));
|
|
$reasonText = trim((string) ($data['reason'] ?? ''));
|
|
|
|
if ($reasonText === '') {
|
|
return $this->respondValidationError(['reason' => ['Reason is required.']]);
|
|
}
|
|
|
|
// Build reason string
|
|
$reasonBase = ($reasonType !== '') ? strtolower($reasonType) : '';
|
|
$reason = $reasonBase !== '' ? ($reasonBase . ': ' . $reasonText) : $reasonText;
|
|
|
|
// Enforce allowed Sundays list
|
|
$allowedDates = $this->allowedAbsenceDates();
|
|
$allowedSet = array_fill_keys($allowedDates, true);
|
|
|
|
$roleName = $this->user->getUserRole($userId) ?: null;
|
|
|
|
// Validate dates and save
|
|
$saved = 0;
|
|
$invalid = [];
|
|
$savedDates = [];
|
|
|
|
$dates = array_values(array_unique(array_map('strval', $dates)));
|
|
|
|
foreach ($dates as $d) {
|
|
$d = trim((string) $d);
|
|
if ($d === '') continue;
|
|
|
|
$dt = \DateTimeImmutable::createFromFormat('Y-m-d', $d);
|
|
if (!$dt || $dt->format('Y-m-d') !== $d || empty($allowedSet[$d])) {
|
|
$invalid[] = $d;
|
|
continue;
|
|
}
|
|
|
|
$ok = $this->staffAttendance->upsertOne(
|
|
userId: $userId,
|
|
roleName: $roleName,
|
|
date: $d,
|
|
semester: $this->semester,
|
|
schoolYear: $this->schoolYear,
|
|
status: StaffAttendance::STATUS_ABSENT,
|
|
reason: $reason,
|
|
editorId: $userId
|
|
);
|
|
|
|
if ($ok) {
|
|
$saved++;
|
|
$savedDates[] = $d;
|
|
}
|
|
}
|
|
|
|
if (!empty($invalid)) {
|
|
return $this->respondError('Invalid dates: ' . implode(', ', $invalid), Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
// Send notification email to principal
|
|
try {
|
|
$user = $this->user->find($userId) ?: [];
|
|
$fullName = trim(($user['firstname'] ?? '') . ' ' . ($user['lastname'] ?? '')) ?: 'Unknown';
|
|
$userEmail = $user['email'] ?? '';
|
|
$role = $roleName ?: 'staff';
|
|
$dateList = !empty($savedDates) ? implode(', ', $savedDates) : implode(', ', $dates);
|
|
|
|
// If teacher/TA, include assigned class(es) for current term
|
|
$assignedText = '-';
|
|
if (in_array(strtolower((string) $roleName), ['teacher', 'teacher_assistant'], true)) {
|
|
try {
|
|
$assignments = $this->teacherClass->getClassAssignmentsByUserId($userId, $this->schoolYear, $this->semester);
|
|
if (!empty($assignments)) {
|
|
$parts = [];
|
|
foreach ($assignments as $as) {
|
|
$name = (string) ($as['class_section_name'] ?? '');
|
|
$r = (string) ($as['teacher_role'] ?? '');
|
|
if ($name !== '') {
|
|
$parts[] = $r ? ($name . ' (' . $r . ')') : $name;
|
|
}
|
|
}
|
|
if (!empty($parts)) {
|
|
$assignedText = implode(', ', $parts);
|
|
} else {
|
|
$assignedText = 'No assigned class';
|
|
}
|
|
} else {
|
|
$assignedText = 'No assigned class';
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$assignedText = 'N/A';
|
|
}
|
|
}
|
|
|
|
$subject = sprintf('TimeOff Request: %s (%s) — %s', $fullName, ucfirst((string) $role), $dateList ?: 'No dates');
|
|
$submittedAt = utc_now();
|
|
|
|
$body = '<div style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:1.5;">'
|
|
. '<h3 style="margin:0 0 8px;">New TimeOff Request</h3>'
|
|
. '<p style="margin:0 0 12px;">A staff time-off request was submitted from the teacher portal.</p>'
|
|
. '<table cellpadding="6" cellspacing="0" style="border-collapse:collapse;">'
|
|
. '<tr><td><strong>Name</strong></td><td>' . esc($fullName) . '</td></tr>'
|
|
. '<tr><td><strong>Email</strong></td><td>' . esc($userEmail) . '</td></tr>'
|
|
. '<tr><td><strong>Role</strong></td><td>' . esc((string) $role) . '</td></tr>'
|
|
. '<tr><td><strong>Semester</strong></td><td>' . esc($this->semester) . '</td></tr>'
|
|
. '<tr><td><strong>School Year</strong></td><td>' . esc($this->schoolYear) . '</td></tr>'
|
|
. '<tr><td><strong>Reason Type</strong></td><td>' . esc($reasonType ?: '-') . '</td></tr>'
|
|
. '<tr><td><strong>Reason</strong></td><td>' . esc($reasonText) . '</td></tr>'
|
|
. '<tr><td><strong>Dates</strong></td><td>' . esc($dateList ?: '-') . '</td></tr>'
|
|
. (in_array(strtolower((string) $roleName), ['teacher', 'teacher_assistant'], true)
|
|
? '<tr><td><strong>Assigned Class(es)</strong></td><td>' . esc($assignedText) . '</td></tr>'
|
|
: '')
|
|
. '<tr><td><strong>Submitted At</strong></td><td>' . esc($submittedAt) . '</td></tr>'
|
|
. '</table>'
|
|
. '</div>';
|
|
|
|
// Note: StaffTimeOffLinkService not found, skipping link generation
|
|
$principalEmail = env('PRINCIPAL_EMAIL') ?: 'principal@alrahmaisgl.org';
|
|
$this->emailService->send($principalEmail, $subject, $body);
|
|
} catch (\Throwable $e) {
|
|
Log::error('Failed to send TimeOff email: ' . $e->getMessage());
|
|
}
|
|
|
|
return $this->success([
|
|
'saved' => $saved,
|
|
'savedDates' => $savedDates,
|
|
], $saved . ' day(s) saved as absent.');
|
|
}
|
|
|
|
/**
|
|
* Build teacher class assignment payload
|
|
*/
|
|
private function buildTeacherClassAssignmentPayload(?string $forYear = null): array
|
|
{
|
|
$schoolYear = $forYear ?: $this->schoolYear;
|
|
|
|
$teachers = $this->teacher->getTeachersAndTAs();
|
|
|
|
// Prefer class sections for the selected year, fallback to all if none
|
|
$classSections = $this->classSection
|
|
->where('school_year', $schoolYear)
|
|
->orderBy('class_section_name', 'ASC')
|
|
->findAll();
|
|
|
|
if (empty($classSections)) {
|
|
$classSections = $this->classSection->orderBy('class_section_name', 'ASC')->findAll();
|
|
}
|
|
|
|
$classNames = [];
|
|
foreach ($classSections as $section) {
|
|
$sectionId = (int) ($section['class_section_id'] ?? $section['id'] ?? 0);
|
|
if ($sectionId <= 0) continue;
|
|
$classNames[$sectionId] = $section['class_section_name'] ?? ($section['name'] ?? 'N/A');
|
|
}
|
|
|
|
$assignments = $this->teacherClass
|
|
->where('school_year', $schoolYear)
|
|
->findAll();
|
|
|
|
$assignmentMap = [];
|
|
foreach ($assignments as $assign) {
|
|
$teacherId = (int) ($assign['teacher_id'] ?? 0);
|
|
$sectionId = (int) ($assign['class_section_id'] ?? 0);
|
|
if ($teacherId <= 0 || $sectionId <= 0) continue;
|
|
|
|
$role = strtolower((string) ($assign['position'] ?? ''));
|
|
if (!in_array($role, ['main', 'ta'], true)) continue;
|
|
|
|
if (!isset($assignmentMap[$teacherId])) {
|
|
$assignmentMap[$teacherId] = ['main' => [], 'ta' => []];
|
|
}
|
|
|
|
$assignmentMap[$teacherId][$role][] = [
|
|
'section_id' => $sectionId,
|
|
'class_section_name' => $classNames[$sectionId] ?? 'N/A',
|
|
'role' => $role,
|
|
'semester' => (string) ($assign['semester'] ?? $this->semester ?? ''),
|
|
'school_year' => (string) ($assign['school_year'] ?? $schoolYear),
|
|
];
|
|
}
|
|
|
|
$teacherData = [];
|
|
foreach ($teachers as $teacher) {
|
|
$tid = (int) ($teacher['id'] ?? 0);
|
|
if ($tid <= 0) continue;
|
|
|
|
$assigned = $assignmentMap[$tid] ?? ['main' => [], 'ta' => []];
|
|
$firstName = $teacher['firstname'] ?? '';
|
|
$lastName = $teacher['lastname'] ?? '';
|
|
$name = trim($firstName . ' ' . $lastName);
|
|
if ($name === '') {
|
|
$name = 'User#' . $tid;
|
|
}
|
|
|
|
$teacherData[] = [
|
|
'teacher_id' => $tid,
|
|
'firstname' => $firstName,
|
|
'lastname' => $lastName,
|
|
'name' => $name,
|
|
'email' => $teacher['email'] ?? '',
|
|
'cellphone' => $teacher['cellphone'] ?? '',
|
|
'role' => $teacher['role'] ?? '',
|
|
'school_year' => $this->schoolYear,
|
|
'main_assignments' => $assigned['main'],
|
|
'ta_assignments' => $assigned['ta'],
|
|
];
|
|
}
|
|
|
|
$classesPayload = [];
|
|
foreach ($classSections as $section) {
|
|
$sectionId = (int) ($section['class_section_id'] ?? $section['id'] ?? 0);
|
|
if ($sectionId <= 0) continue;
|
|
|
|
$classesPayload[] = [
|
|
'class_section_id' => $sectionId,
|
|
'class_section_name' => $section['class_section_name'] ?? ($section['name'] ?? 'N/A'),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'school_year' => $schoolYear,
|
|
'teachers' => $teacherData,
|
|
'classes' => $classesPayload,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Handle assign class teacher logic
|
|
*/
|
|
private function handleAssignClassTeacher(array $input): array
|
|
{
|
|
$teacherId = (int) ($input['teacher_id'] ?? 0);
|
|
$classSectionId = (int) ($input['class_section_id'] ?? 0);
|
|
$teacherRole = $input['teacher_role'] ?? null;
|
|
$loggedInUserId = $this->getCurrentUserId() ?? 0;
|
|
$currentDateTime = utc_now();
|
|
|
|
if ($teacherId <= 0 || $classSectionId <= 0) {
|
|
return ['ok' => false, 'status' => 'error', 'message' => 'Missing required parameters for assignment.'];
|
|
}
|
|
|
|
if (empty($this->semester) || empty($this->schoolYear)) {
|
|
return ['ok' => false, 'status' => 'error', 'message' => 'Semester or school year not configured.'];
|
|
}
|
|
|
|
if ($teacherRole === null || $teacherRole === '') {
|
|
$teacherRole = $this->user->getUserRole($teacherId);
|
|
}
|
|
|
|
$isAssistant = strtolower((string) $teacherRole) === 'teacher_assistant';
|
|
$position = $isAssistant ? 'ta' : 'main';
|
|
|
|
$alreadyAssigned = $this->teacherClass->where([
|
|
'teacher_id' => $teacherId,
|
|
'class_section_id' => $classSectionId,
|
|
'position' => $position,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
])->first();
|
|
|
|
if ($alreadyAssigned) {
|
|
return ['ok' => false, 'status' => 'info', 'message' => 'This teacher is already assigned to this class with the same role.'];
|
|
}
|
|
|
|
$insertData = [
|
|
'teacher_id' => $teacherId,
|
|
'class_section_id' => $classSectionId,
|
|
'position' => $position,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
'created_at' => $currentDateTime,
|
|
'updated_at' => $currentDateTime,
|
|
'updated_by' => $loggedInUserId,
|
|
];
|
|
|
|
try {
|
|
$this->teacherClass->insert($insertData);
|
|
} catch (\Throwable $e) {
|
|
Log::error('assignClassTeacher failed: ' . $e->getMessage());
|
|
return ['ok' => false, 'status' => 'error', 'message' => 'Unable to assign class. Please try again.'];
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 'success',
|
|
'message' => 'Class assigned to ' . ($isAssistant ? 'Teacher Assistant' : 'Main Teacher') . ' successfully.',
|
|
'position' => $position,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Handle delete assignment logic
|
|
*/
|
|
private function handleDeleteAssignment(array $input): array
|
|
{
|
|
$teacherId = (int) ($input['teacher_id'] ?? 0);
|
|
$classSectionId = (int) ($input['class_section_id'] ?? 0);
|
|
$position = strtolower((string) ($input['position'] ?? ''));
|
|
|
|
if ($teacherId <= 0 || $classSectionId <= 0 || !in_array($position, ['main', 'ta'], true)) {
|
|
return ['ok' => false, 'status' => 'error', 'message' => 'Missing or invalid data for deletion.'];
|
|
}
|
|
|
|
if (empty($this->semester) || empty($this->schoolYear)) {
|
|
return ['ok' => false, 'status' => 'error', 'message' => 'Semester or school year not configured.'];
|
|
}
|
|
|
|
$requestedSemester = trim((string) ($input['semester'] ?? ''));
|
|
$requestedSchoolYear = trim((string) ($input['school_year'] ?? ''));
|
|
|
|
$semester = $requestedSemester !== '' ? $requestedSemester : $this->semester;
|
|
$schoolYear = $requestedSchoolYear !== '' ? $requestedSchoolYear : $this->schoolYear;
|
|
|
|
if ($semester === '' || $schoolYear === '') {
|
|
return ['ok' => false, 'status' => 'error', 'message' => 'Semester or school year not configured.'];
|
|
}
|
|
|
|
$assignment = $this->teacherClass
|
|
->where('teacher_id', $teacherId)
|
|
->where('class_section_id', $classSectionId)
|
|
->where('semester', $semester)
|
|
->where('school_year', $schoolYear)
|
|
->where('position', $position)
|
|
->first();
|
|
|
|
if (!$assignment) {
|
|
return ['ok' => false, 'status' => 'error', 'message' => 'Assignment not found.'];
|
|
}
|
|
|
|
try {
|
|
$this->teacherClass->delete($assignment['id']);
|
|
} catch (\Throwable $e) {
|
|
Log::error('deleteAssignment failed: ' . $e->getMessage());
|
|
return ['ok' => false, 'status' => 'error', 'message' => 'Unable to remove assignment. Please try again.'];
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => 'success',
|
|
'message' => ucfirst($position) . ' assignment removed successfully.',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Compute all allowed absence dates (Sundays) for the configured school year
|
|
*/
|
|
private function allowedAbsenceDates(): array
|
|
{
|
|
$todayStr = local_date(utc_now(), 'Y-m-d');
|
|
try {
|
|
$today = new \DateTimeImmutable($todayStr);
|
|
} catch (\Throwable $e) {
|
|
$today = new \DateTimeImmutable();
|
|
}
|
|
|
|
$sy = $this->schoolYear;
|
|
$startYear = null;
|
|
$endYear = null;
|
|
|
|
if (preg_match('/^(\d{4})\D+(\d{4})$/', $sy, $m)) {
|
|
$startYear = (int) $m[1];
|
|
$endYear = (int) $m[2];
|
|
} else {
|
|
// Fallback based on current date
|
|
$cy = (int) date('Y');
|
|
$cm = (int) date('n');
|
|
if ($cm >= 9) { // Sep-Dec
|
|
$startYear = $cy;
|
|
$endYear = $cy + 1;
|
|
} else { // Jan-Aug
|
|
$startYear = $cy - 1;
|
|
$endYear = $cy;
|
|
}
|
|
}
|
|
|
|
try {
|
|
$start = new \DateTimeImmutable(sprintf('%04d-09-01', $startYear));
|
|
$end = new \DateTimeImmutable(sprintf('%04d-05-31', $endYear));
|
|
} catch (\Throwable $e) {
|
|
return [];
|
|
}
|
|
|
|
// Clamp start to today if in range
|
|
if ($start < $today) {
|
|
$start = $today;
|
|
}
|
|
|
|
// Iterate and collect Sundays in range [start..end]
|
|
$dates = [];
|
|
for ($cursor = $start; $cursor <= $end; $cursor = $cursor->modify('+1 day')) {
|
|
if ((int) $cursor->format('w') === 0) { // Sunday
|
|
$dates[] = $cursor->format('Y-m-d');
|
|
}
|
|
}
|
|
|
|
return $dates;
|
|
}
|
|
}
|