authService = new AuthService(); $this->parentModel = new ParentModel(); $this->studentParentModel = new StudentParentModel(); } /** * GET /api/parent/children * List all children linked to the current parent user. */ public function children(): ResponseInterface { $user = $this->authService->currentUser(); if (!$user) { return $this->errorResponse('Unauthorized', null, null, ResponseInterface::HTTP_UNAUTHORIZED); } $roleCodes = array_column($user['roles'], 'role_code'); if (!in_array(Role::CODE_ORANG_TUA, $roleCodes, true)) { return $this->errorResponse('Forbidden: Parent access only', null, null, ResponseInterface::HTTP_FORBIDDEN); } // Find parent record linked to this user $parent = $this->parentModel->where('user_id', $user['id'])->first(); if (!$parent) { return $this->successResponse([], 'No children found'); } // Get all students linked to this parent $db = \Config\Database::connect(); $children = $db->table('student_parents AS sp') ->select('s.id, s.nisn, s.name, s.gender, s.class_id, c.grade AS class_grade, c.major AS class_major, c.name AS class_name, sp.relationship') ->join('students AS s', 's.id = sp.student_id', 'inner') ->join('classes AS c', 'c.id = s.class_id', 'left') ->where('sp.parent_id', $parent->id) ->where('s.is_active', 1) ->orderBy('c.grade', 'ASC') ->orderBy('c.major', 'ASC') ->orderBy('c.name', 'ASC') ->orderBy('s.name', 'ASC') ->get() ->getResultArray(); $data = array_map(function ($row) { $classLabel = null; if ($row['class_grade'] !== null || $row['class_major'] !== null || $row['class_name'] !== null) { $parts = array_filter([ trim((string) ($row['class_grade'] ?? '')), trim((string) ($row['class_major'] ?? '')), trim((string) ($row['class_name'] ?? '')), ]); $classLabel = implode(' ', $parts); } return [ 'id' => (int) $row['id'], 'nisn' => $row['nisn'], 'name' => $row['name'], 'gender' => $row['gender'], 'class_id' => $row['class_id'] !== null ? (int) $row['class_id'] : null, 'class_label' => $classLabel, 'relationship' => $row['relationship'], ]; }, $children); return $this->successResponse($data, 'Children list'); } /** * GET /api/parent/attendance?student_id=&from=&to= * Get attendance history for a specific child. */ public function attendance(): ResponseInterface { $user = $this->authService->currentUser(); if (!$user) { return $this->errorResponse('Unauthorized', null, null, ResponseInterface::HTTP_UNAUTHORIZED); } $roleCodes = array_column($user['roles'], 'role_code'); if (!in_array(Role::CODE_ORANG_TUA, $roleCodes, true)) { return $this->errorResponse('Forbidden: Parent access only', null, null, ResponseInterface::HTTP_FORBIDDEN); } $studentId = (int) $this->request->getGet('student_id'); $from = $this->request->getGet('from'); $to = $this->request->getGet('to'); if ($studentId <= 0) { return $this->errorResponse('student_id parameter is required', null, null, ResponseInterface::HTTP_BAD_REQUEST); } // Verify that this student belongs to the current parent $parent = $this->parentModel->where('user_id', $user['id'])->first(); if (!$parent) { return $this->errorResponse('Parent record not found', null, null, ResponseInterface::HTTP_NOT_FOUND); } $hasAccess = $this->studentParentModel ->where('parent_id', $parent->id) ->where('student_id', $studentId) ->first(); if (!$hasAccess) { return $this->errorResponse('Access denied: Student not linked to this parent', null, null, ResponseInterface::HTTP_FORBIDDEN); } // Query attendance sessions $db = \Config\Database::connect(); $builder = $db->table('attendance_sessions AS att') ->select('att.id, att.student_id, att.schedule_id, att.attendance_date, att.checkin_at, att.status, sch.day_of_week, sch.lesson_slot_id, sch.subject_id, sch.teacher_user_id, ls.start_time, ls.end_time, sub.name AS subject_name, u.name AS teacher_name') ->join('schedules AS sch', 'sch.id = att.schedule_id', 'left') ->join('lesson_slots AS ls', 'ls.id = sch.lesson_slot_id', 'left') ->join('subjects AS sub', 'sub.id = sch.subject_id', 'left') ->join('users AS u', 'u.id = sch.teacher_user_id', 'left') ->where('att.student_id', $studentId) ->orderBy('att.attendance_date', 'DESC') ->orderBy('att.checkin_at', 'DESC'); if ($from) { $builder->where('att.attendance_date >=', $from); } if ($to) { $builder->where('att.attendance_date <=', $to); } $rows = $builder->get()->getResultArray(); $data = array_map(function ($row) { return [ 'id' => (int) $row['id'], 'student_id' => (int) $row['student_id'], 'schedule_id' => $row['schedule_id'] !== null ? (int) $row['schedule_id'] : null, 'attendance_date' => $row['attendance_date'], 'checkin_at' => $row['checkin_at'], 'status' => $row['status'], 'subject_name' => $row['subject_name'], 'teacher_name' => $row['teacher_name'], 'start_time' => $row['start_time'], 'end_time' => $row['end_time'], ]; }, $rows); return $this->successResponse($data, 'Attendance history'); } /** * GET /api/parent/discipline?student_id= * Get discipline data (total points, level, violation history) for a specific child. */ public function discipline(): ResponseInterface { $user = $this->authService->currentUser(); if (!$user) { return $this->errorResponse('Unauthorized', null, null, ResponseInterface::HTTP_UNAUTHORIZED); } $roleCodes = array_column($user['roles'], 'role_code'); if (!in_array(Role::CODE_ORANG_TUA, $roleCodes, true)) { return $this->errorResponse('Forbidden: Parent access only', null, null, ResponseInterface::HTTP_FORBIDDEN); } $studentId = (int) $this->request->getGet('student_id'); if ($studentId <= 0) { return $this->errorResponse('student_id parameter is required', null, null, ResponseInterface::HTTP_BAD_REQUEST); } // Verify that this student belongs to the current parent $parent = $this->parentModel->where('user_id', $user['id'])->first(); if (!$parent) { return $this->errorResponse('Parent record not found', null, null, ResponseInterface::HTTP_NOT_FOUND); } $hasAccess = $this->studentParentModel ->where('parent_id', $parent->id) ->where('student_id', $studentId) ->first(); if (!$hasAccess) { return $this->errorResponse('Access denied: Student not linked to this parent', null, null, ResponseInterface::HTTP_FORBIDDEN); } // Get total points from violations $db = \Config\Database::connect(); $totalPointsResult = $db->table('student_violations AS sv') ->select('COALESCE(SUM(v.score), 0) AS total_points, COUNT(sv.id) AS violation_count') ->join('violations AS v', 'v.id = sv.violation_id', 'inner') ->where('sv.student_id', $studentId) ->where('v.is_active', 1) ->get() ->getRowArray(); $totalPoints = (int) ($totalPointsResult['total_points'] ?? 0); $violationCount = (int) ($totalPointsResult['violation_count'] ?? 0); // Get discipline level based on total points $levelModel = new DisciplineLevelModel(); $level = $levelModel ->where('is_active', 1) ->where('min_score <=', $totalPoints) ->where('max_score >=', $totalPoints) ->first(); $levelData = null; if ($level) { $levelData = [ 'title' => $level['title'], 'school_action' => $level['school_action'], 'executor' => $level['executor'], ]; } // Get violation history $violationModel = new StudentViolationModel(); $violations = $db->table('student_violations AS sv') ->select('sv.id, sv.violation_id, sv.occurred_at, sv.notes, v.title AS violation_title, v.score AS violation_score, vc.code AS category_code, vc.name AS category_name') ->join('violations AS v', 'v.id = sv.violation_id', 'inner') ->join('violation_categories AS vc', 'vc.id = v.category_id', 'left') ->where('sv.student_id', $studentId) ->orderBy('sv.occurred_at', 'DESC') ->get() ->getResultArray(); $violationHistory = array_map(function ($row) { return [ 'id' => (int) $row['id'], 'violation_id' => (int) $row['violation_id'], 'category_code' => $row['category_code'], 'category_name' => $row['category_name'], 'violation_title' => $row['violation_title'], 'score' => (int) $row['violation_score'], 'occurred_at' => $row['occurred_at'], 'notes' => $row['notes'], ]; }, $violations); $data = [ 'total_points' => $totalPoints, 'violation_count' => $violationCount, 'discipline_level' => $levelData, 'violations' => $violationHistory, ]; return $this->successResponse($data, 'Discipline data'); } }