init backend presensi

This commit is contained in:
mwpn
2026-03-05 14:37:36 +07:00
commit b4fda6b9c9
319 changed files with 27261 additions and 0 deletions

View File

@@ -0,0 +1,281 @@
<?php
namespace App\Modules\Parent\Controllers;
use App\Core\BaseApiController;
use App\Modules\Auth\Entities\Role;
use App\Modules\Auth\Services\AuthService;
use App\Modules\Attendance\Models\AttendanceSessionModel;
use App\Modules\Discipline\Models\DisciplineLevelModel;
use App\Modules\Discipline\Models\StudentViolationModel;
use App\Modules\Notification\Models\ParentModel;
use App\Modules\Notification\Models\StudentParentModel;
use CodeIgniter\HTTP\ResponseInterface;
/**
* Parent Portal API Controller
*
* Read-only APIs for parents to view their children's data.
* Requires ORANG_TUA role.
*/
class ParentController extends BaseApiController
{
protected AuthService $authService;
protected ParentModel $parentModel;
protected StudentParentModel $studentParentModel;
public function __construct()
{
$this->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');
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Parent Module Routes
*
* This file is automatically loaded by ModuleLoader.
* Define your parent portal routes here.
*
* @var \CodeIgniter\Router\RouteCollection $routes
*/
$routes->group('api/parent', [
'namespace' => 'App\Modules\Parent\Controllers',
'filter' => 'dashboard_auth',
], function ($routes) {
$routes->get('children', 'ParentController::children');
$routes->get('attendance', 'ParentController::attendance');
$routes->get('discipline', 'ParentController::discipline');
});