format('N'); } /** * Get schedules for today filtered by user role. * Uses lesson_slots for start/end time and users for teacher_name; fallback to schedule columns when NULL. * * @param array|null $userContext { id, roles: [ { role_code } ] } * @return list */ public function getSchedulesToday(?array $userContext = null): array { $dayOfWeek = $this->getTodayDayOfWeek(); $db = \Config\Database::connect(); $builder = $db->table('schedules AS sch'); $builder->select(' sch.id AS schedule_id, COALESCE(sub.name, "-") AS subject_name, COALESCE(c.name, "-") AS class_name, COALESCE(u.name, sch.teacher_name) AS teacher_name, COALESCE(ls.start_time, sch.start_time) AS start_time, COALESCE(ls.end_time, sch.end_time) AS end_time '); $builder->join('lesson_slots AS ls', 'ls.id = sch.lesson_slot_id', 'left'); $builder->join('users AS u', 'u.id = sch.teacher_user_id', 'left'); $builder->join('subjects AS sub', 'sub.id = sch.subject_id', 'left'); $builder->join('classes AS c', 'c.id = sch.class_id', 'left'); $builder->where('sch.day_of_week', $dayOfWeek); $builder->orderBy('COALESCE(ls.start_time, sch.start_time)', 'ASC', false); $this->applyRoleFilter($builder, $userContext); $rows = $builder->get()->getResultArray(); $out = []; foreach ($rows as $row) { $out[] = [ 'schedule_id' => (int) $row['schedule_id'], 'subject_name' => (string) ($row['subject_name'] ?? '-'), 'class_name' => (string) ($row['class_name'] ?? '-'), 'teacher_name' => (string) ($row['teacher_name'] ?? '-'), 'start_time' => (string) $row['start_time'], 'end_time' => (string) $row['end_time'], ]; } return $out; } /** * Get schedules for a given date (day_of_week from date in Asia/Jakarta). Same shape as getSchedulesToday. * * @param string $dateYmd Y-m-d * @param array|null $userContext * @return list */ public function getSchedulesByDate(string $dateYmd, ?array $userContext = null): array { $tz = new \DateTimeZone('Asia/Jakarta'); $date = new \DateTimeImmutable($dateYmd . ' 12:00:00', $tz); $dayOfWeek = (int) $date->format('N'); $db = \Config\Database::connect(); $builder = $db->table('schedules AS sch'); $builder->select(' sch.id AS schedule_id, COALESCE(sub.name, "-") AS subject_name, COALESCE(c.name, "-") AS class_name, COALESCE(u.name, sch.teacher_name) AS teacher_name, COALESCE(ls.start_time, sch.start_time) AS start_time, COALESCE(ls.end_time, sch.end_time) AS end_time '); $builder->join('lesson_slots AS ls', 'ls.id = sch.lesson_slot_id', 'left'); $builder->join('users AS u', 'u.id = sch.teacher_user_id', 'left'); $builder->join('subjects AS sub', 'sub.id = sch.subject_id', 'left'); $builder->join('classes AS c', 'c.id = sch.class_id', 'left'); $builder->where('sch.day_of_week', $dayOfWeek); $builder->orderBy('COALESCE(ls.start_time, sch.start_time)', 'ASC', false); $this->applyRoleFilter($builder, $userContext); $rows = $builder->get()->getResultArray(); $out = []; foreach ($rows as $row) { $out[] = [ 'schedule_id' => (int) $row['schedule_id'], 'subject_name' => (string) ($row['subject_name'] ?? '-'), 'class_name' => (string) ($row['class_name'] ?? '-'), 'teacher_name' => (string) ($row['teacher_name'] ?? '-'), 'start_time' => (string) $row['start_time'], 'end_time' => (string) $row['end_time'], ]; } return $out; } /** * Get current lesson (schedule active right now) or next upcoming today. * Uses Asia/Jakarta. Same RBAC as getSchedulesToday. lesson_slots/users as source of truth with fallback. * * @param array|null $userContext * @return array{is_active_now: bool, schedule_id?: int, subject_name?: string, class_name?: string, teacher_name?: string, start_time?: string, end_time?: string, next_schedule?: array} */ public function getCurrentSchedule(?array $userContext = null): array { $tz = new \DateTimeZone('Asia/Jakarta'); $now = new \DateTimeImmutable('now', $tz); $dayOfWeek = (int) $now->format('N'); $currentTime = $now->format('H:i:s'); $db = \Config\Database::connect(); $base = 'sch.id AS schedule_id, sch.class_id AS class_id, COALESCE(sub.name, "-") AS subject_name, COALESCE(c.name, "-") AS class_name, ' . 'COALESCE(u.name, sch.teacher_name) AS teacher_name, ' . 'COALESCE(ls.start_time, sch.start_time) AS start_time, COALESCE(ls.end_time, sch.end_time) AS end_time'; $timeRangeWhere = '( COALESCE(ls.start_time, sch.start_time) <= ' . $db->escape($currentTime) . ' AND COALESCE(ls.end_time, sch.end_time) > ' . $db->escape($currentTime) . ' )'; $builder = $db->table('schedules AS sch'); $builder->select($base); $builder->join('lesson_slots AS ls', 'ls.id = sch.lesson_slot_id', 'left'); $builder->join('users AS u', 'u.id = sch.teacher_user_id', 'left'); $builder->join('subjects AS sub', 'sub.id = sch.subject_id', 'left'); $builder->join('classes AS c', 'c.id = sch.class_id', 'left'); $builder->where('sch.day_of_week', $dayOfWeek); $builder->where($timeRangeWhere); $builder->orderBy('COALESCE(ls.start_time, sch.start_time)', 'ASC', false); $builder->limit(1); $this->applyRoleFilter($builder, $userContext); $rows = $builder->get()->getResultArray(); if (!empty($rows)) { $row = $rows[0]; return [ 'is_active_now' => true, 'schedule_id' => (int) $row['schedule_id'], 'class_id' => (int) ($row['class_id'] ?? 0), 'subject_name' => (string) ($row['subject_name'] ?? '-'), 'class_name' => (string) ($row['class_name'] ?? '-'), 'teacher_name' => (string) ($row['teacher_name'] ?? '-'), 'start_time' => (string) $row['start_time'], 'end_time' => (string) $row['end_time'], ]; } $nextTimeWhere = '( COALESCE(ls.start_time, sch.start_time) > ' . $db->escape($currentTime) . ' )'; $nextBuilder = $db->table('schedules AS sch'); $nextBuilder->select($base); $nextBuilder->join('lesson_slots AS ls', 'ls.id = sch.lesson_slot_id', 'left'); $nextBuilder->join('users AS u', 'u.id = sch.teacher_user_id', 'left'); $nextBuilder->join('subjects AS sub', 'sub.id = sch.subject_id', 'left'); $nextBuilder->join('classes AS c', 'c.id = sch.class_id', 'left'); $nextBuilder->where('sch.day_of_week', $dayOfWeek); $nextBuilder->where($nextTimeWhere); $nextBuilder->orderBy('COALESCE(ls.start_time, sch.start_time)', 'ASC', false); $nextBuilder->limit(1); $this->applyRoleFilter($nextBuilder, $userContext); $nextRows = $nextBuilder->get()->getResultArray(); $nextSchedule = null; if (!empty($nextRows)) { $r = $nextRows[0]; $nextSchedule = [ 'schedule_id' => (int) $r['schedule_id'], 'subject_name' => (string) ($r['subject_name'] ?? '-'), 'class_name' => (string) ($r['class_name'] ?? '-'), 'teacher_name' => (string) ($r['teacher_name'] ?? '-'), 'start_time' => (string) $r['start_time'], 'end_time' => (string) $r['end_time'], ]; } $result = ['is_active_now' => false]; if ($nextSchedule !== null) { $result['next_schedule'] = $nextSchedule; } return $result; } protected function applyRoleFilter($builder, ?array $userContext): void { if ($userContext === null) { return; } $roles = $userContext['roles'] ?? []; $codes = array_column($roles, 'role_code'); $userId = (int) ($userContext['id'] ?? 0); if (in_array(Role::CODE_ADMIN, $codes, true) || in_array(Role::CODE_GURU_BK, $codes, true)) { return; } if (in_array(Role::CODE_WALI_KELAS, $codes, true)) { $classIds = $this->getClassIdsForWali($userId); if ($classIds === []) { $builder->where('1 =', 0); return; } $builder->whereIn('sch.class_id', $classIds); return; } if (in_array(Role::CODE_GURU_MAPEL, $codes, true)) { $builder->where('sch.teacher_user_id', $userId); return; } if (in_array(Role::CODE_ORANG_TUA, $codes, true)) { $studentIds = $this->getStudentIdsForParent($userId); if ($studentIds === []) { $builder->where('1 =', 0); return; } $db = \Config\Database::connect(); $subQuery = $db->table('students')->select('class_id')->whereIn('id', $studentIds)->get()->getResultArray(); $classIds = array_values(array_unique(array_map('intval', array_column($subQuery, 'class_id')))); if ($classIds === []) { $builder->where('1 =', 0); return; } $builder->whereIn('sch.class_id', $classIds); return; } } protected function getClassIdsForWali(int $userId): array { $db = \Config\Database::connect(); $rows = $db->table('classes')->select('id')->where('wali_user_id', $userId)->get()->getResultArray(); return array_map('intval', array_column($rows, 'id')); } protected function getStudentIdsForParent(int $userId): array { $db = \Config\Database::connect(); $rows = $db->table('student_parents AS sp') ->select('sp.student_id') ->join('parents AS p', 'p.id = sp.parent_id', 'inner') ->where('p.user_id', $userId) ->get() ->getResultArray(); return array_map('intval', array_column($rows, 'student_id')); } }