scheduleModel = new ScheduleModel(); $this->lessonSlotModel = new LessonSlotModel(); } /** * GET /api/academic/schedules/class/{classId} * Returns weekly schedule grid: slots with days 1–5, each cell schedule or null. */ public function getByClass($classId): ResponseInterface { $classId = (int) $classId; $slots = $this->lessonSlotModel->where('is_active', 1)->orderBy('slot_number', 'ASC')->findAll(); $rows = $this->scheduleModel->where('class_id', $classId)->findAll(); $bySlotDay = []; foreach ($rows as $row) { $slotId = $row->lesson_slot_id ?? 0; $day = (int) $row->day_of_week; if ($slotId && $day >= 1 && $day <= 7) { $bySlotDay[$slotId][$day] = $this->scheduleToArray($row); } } $grid = []; foreach ($slots as $slot) { $slotId = (int) $slot->id; $days = []; for ($d = 1; $d <= 5; $d++) { $days[$d] = $bySlotDay[$slotId][$d] ?? null; } $grid[] = [ 'lesson_slot_id' => $slotId, 'slot_number' => (int) $slot->slot_number, 'start_time' => (string) $slot->start_time, 'end_time' => (string) $slot->end_time, 'days' => $days, ]; } return $this->successResponse($grid, 'Weekly schedule'); } /** * POST /api/academic/schedules/bulk-save * Body: { class_id, schedules: [ { day_of_week, lesson_slot_id, subject_id, teacher_user_id [, room] }, ... ] } * Deletes existing schedules for class then inserts new ones in a transaction. */ public function bulkSave(): ResponseInterface { $input = $this->request->getJSON(true) ?? []; $classId = (int) ($input['class_id'] ?? 0); $items = $input['schedules'] ?? []; if ($classId <= 0) { return $this->errorResponse('class_id is required and must be positive', null, null, 422); } if (!is_array($items)) { return $this->errorResponse('schedules must be an array', null, null, 422); } $db = \Config\Database::connect(); $db->transStart(); try { $this->scheduleModel->where('class_id', $classId)->delete(); $this->scheduleModel->skipValidation(true); foreach ($items as $item) { $day = (int) ($item['day_of_week'] ?? 0); $slot = (int) ($item['lesson_slot_id'] ?? 0); $subj = (int) ($item['subject_id'] ?? 0); $teacher = (int) ($item['teacher_user_id'] ?? 0); if ($day < 1 || $day > 7 || $slot <= 0 || $subj <= 0) { $db->transRollback(); return $this->errorResponse('Each schedule must have day_of_week (1-7), lesson_slot_id, subject_id', null, null, 422); } $data = [ 'class_id' => $classId, 'subject_id' => $subj, 'teacher_user_id' => $teacher ?: null, 'lesson_slot_id' => $slot, 'day_of_week' => $day, 'room' => isset($item['room']) ? (string) $item['room'] : null, 'is_active' => 1, ]; if ($this->scheduleModel->insert($data) === false) { $db->transRollback(); return $this->errorResponse('Failed to insert schedule', null, null, 422); } } $this->scheduleModel->skipValidation(false); $db->transComplete(); } catch (\Throwable $e) { if ($db->transStatus() === false) { $db->transRollback(); } return $this->errorResponse($e->getMessage(), null, null, 500); } if ($db->transStatus() === false) { return $this->errorResponse('Database transaction failed', null, null, 500); } return $this->successResponse(null, 'Schedules saved'); } protected function scheduleToArray($schedule): array { if (is_array($schedule)) { return [ 'id' => (int) ($schedule['id'] ?? 0), 'class_id' => (int) ($schedule['class_id'] ?? 0), 'subject_id' => (int) ($schedule['subject_id'] ?? 0), 'teacher_user_id' => isset($schedule['teacher_user_id']) ? (int) $schedule['teacher_user_id'] : null, 'lesson_slot_id' => (int) ($schedule['lesson_slot_id'] ?? 0), 'day_of_week' => (int) ($schedule['day_of_week'] ?? 0), 'room' => isset($schedule['room']) ? (string) $schedule['room'] : null, ]; } return [ 'id' => (int) $schedule->id, 'class_id' => (int) $schedule->class_id, 'subject_id' => (int) $schedule->subject_id, 'teacher_user_id' => $schedule->teacher_user_id !== null ? (int) $schedule->teacher_user_id : null, 'lesson_slot_id' => (int) $schedule->lesson_slot_id, 'day_of_week' => (int) $schedule->day_of_week, 'room' => $schedule->room !== null ? (string) $schedule->room : null, ]; } }